Android Draw SurfaceView In Thread Example

SurfaceView is a view with a surface. It is a sub-class of the class android.view.View. So it is similar to other views, it can receive user input on the screen, it also inherits all view life cycle callback functions.

1. SurfaceView Overview.

  1. SurfaceView has an independent surface from the Window object. So you can draw on the surface in a child thread which is a so-called rendering thread.
  2. After rendering, you need to post a message to the main thread to let the surface canvas drawing be drawn on the main thread UI view canvas again.
  3. Below is the android Activity, SurfaceView, Window, View‘s relation diagram.
    surfaceview-and-view
  4. The Surface can be destroyed. It only works in between SurfaceHolder.Callback.surfaceCreated() and SurfaceHolder.Callback.surfaceDestroyed() method. The two methods will be invoked when the surface is created and destroyed.
  5. The SurfaceHolder.Callback instance is added to SurfaceHolder by using SurfaceHolder‘s addCallback method. And you can call SurfaceView‘s getHolder() method to get it’s SurfaceHolder.
  6. Generally, we let’s the custom SurfaceView class implement SurfaceHolder.Callback interface and add the SurfaceView class instance to the SurfaceHolder, the SurfaceView can then listen to the creation and destruction of this Surface.

2. SurfaceHolder Overview.

  1. SurfaceHolder is the wrapper for SurfaceView‘s Surface.
  2. SurfaceHolder is not only responsible for Surface creation and destruction callbacks in the SurfaceHolder.Callback interface, but also for thread-safe wrapping of key methods of Surface such as LockCanvas() and unLockCanvasAndPost().
  3. So SurfaceHolder is the owner of the Surface object and is responsible for calling methods that operate on the Surface during the Surface‘s life cycle. For example, the SurfaceHolder‘s lockCanvas(Rect Rect) method can specify a rectangle area as invalidate data in the rectangle and redraw some data on it.
    surfaceview-surfaceholder-surface-relation

3. SurfaceView Usage Steps.

  1. Get the SurfaceView related SurfaceHolder object and add a SurfaceHolder.Callback object to the SurfaceHolder.
  2. Create the rendering thread object.
  3. Start drawing on the Surface in the child thread.
  4. Because we can not get the Surface object directly, so we should use the SurfaceHolder‘s lockCanvas () to get the Canvas of the specified area above the Surface and draw graphics on the canvas.
  5. When the painting is finished, use the SurfaceHolder‘s unlockCanvasAndPost() method to unlock the Canvas and let the UI thread draw the Surface canvas drawing to the view’s canvas.

4. Android SurfaceView Use Thread Drawing Example.

If you can not watch the above video, you can see it on the youtube URL https://youtu.be/lKGmhdH-zoc

  1. When you input a text in the input text box and click enter key, the text will be drawn with move animation from top left to bottom right in the blue rectangle at the bottom.

5. Draw In SurfaceView Use Thread Example Source Code.

  1. Below is the example project’s files list.
    C:\WORKSPACE\WORK\DEV2QA.COM-EXAMPLE-CODE\ANDROIDEXAMPLEPROJECT\SURFACEVIEWTHREAD
    │   .gitignore
    │   build.gradle
    │   gradle.properties
    │   gradlew
    │   gradlew.bat
    │   settings.gradle
    │
    ├───.idea
    │       gradle.xml
    │       misc.xml
    │       modules.xml
    │       runConfigurations.xml
    │
    ├───app
    │   │   .gitignore
    │   │   build.gradle
    │   │   proguard-rules.pro
    │   │
    │   └───src
    │       ├───androidTest
    │       │   └───java
    │       │       └───com
    │       │           └───dev2qa
    │       │               └───surfaceviewthread
    │       │                       ExampleInstrumentedTest.java
    │       │
    │       ├───main
    │       │   │   AndroidManifest.xml
    │       │   │
    │       │   ├───java
    │       │   │   └───com
    │       │   │       └───dev2qa
    │       │   │           └───surfaceviewthread
    │       │   │                   MainActivity.java
    │       │   │                   SurfaceViewThread.java
    │       │   │
    │       │   └───res
    │       │       ├───drawable
    │       │       │       ic_launcher_background.xml
    │       │       │
    │       │       ├───drawable-v24
    │       │       │       ic_launcher_foreground.xml
    │       │       │
    │       │       ├───layout
    │       │       │       activity_main.xml
    │       │       │
    │       │       ├───mipmap-anydpi-v26
    │       │       │       ic_launcher.xml
    │       │       │       ic_launcher_round.xml
    │       │       │
    │       │       ├───mipmap-hdpi
    │       │       │       ic_launcher.png
    │       │       │       ic_launcher_round.png
    │       │       │
    │       │       ├───mipmap-mdpi
    │       │       │       ic_launcher.png
    │       │       │       ic_launcher_round.png
    │       │       │
    │       │       ├───mipmap-xhdpi
    │       │       │       ic_launcher.png
    │       │       │       ic_launcher_round.png
    │       │       │
    │       │       ├───mipmap-xxhdpi
    │       │       │       ic_launcher.png
    │       │       │       ic_launcher_round.png
    │       │       │
    │       │       ├───mipmap-xxxhdpi
    │       │       │       ic_launcher.png
    │       │       │       ic_launcher_round.png
    │       │       │
    │       │       └───values
    │       │               colors.xml
    │       │               strings.xml
    │       │               styles.xml
    │       │
    │       └───test
    │           └───java
    │               └───com
    │                   └───dev2qa
    │                       └───surfaceviewthread
    │                               ExampleUnitTest.java
    │
    └───gradle
        └───wrapper
                gradle-wrapper.jar
                gradle-wrapper.properties

5.1 Main Activity.

  1. MainActivity.java
    package com.dev2qa.surfaceviewthread;
    
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.view.KeyEvent;
    import android.view.View;
    import android.view.WindowManager;
    import android.widget.EditText;
    import android.widget.LinearLayout;
    
    import java.security.Key;
    
    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            setTitle("dev2qa.com - Android Draw SurfaceView In Thread Example.");
    
            // Hide app title bar.
            getSupportActionBar().hide();
    
            // Make app full screen to hide top status bar.
            this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
    
            // Create the SurfaceViewThread object.
            final SurfaceViewThread surfaceViewThread = new SurfaceViewThread(getApplicationContext());
    
            // Get text drawing LinearLayout canvas.
            LinearLayout drawTextCanvas = (LinearLayout)findViewById(R.id.drawTextCanvas);
    
            // Add surfaceview object to the LinearLayout object.
            drawTextCanvas.addView(surfaceViewThread);
    
            // Get the edittext input text box.
            final EditText inputText = (EditText)findViewById(R.id.inputText);
    
            // Add key listener to listen to key type event.
            inputText.setOnKeyListener(new View.OnKeyListener() {
                @Override
                public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
    
                    // If user input enter key.
                    if(keyCode == KeyEvent.KEYCODE_ENTER) {
                        // Get user input text.
                        String userInputText = inputText.getText().toString();
                        // Set the text to custom SurfaceView object.
                        surfaceViewThread.setText(userInputText);
                        // Means the key event has been processed.
                        return true;
                    }else
                    {
                        return false;
                    }
                }
            });
        }
    }

5.2 SurfaceView Use Thread Java Class.

  1. SurfaceViewThread.java
    package com.dev2qa.surfaceviewthread;
    
    import android.content.Context;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.graphics.PixelFormat;
    import android.graphics.Rect;
    import android.text.TextUtils;
    import android.util.Log;
    import android.view.SurfaceView;
    import android.view.SurfaceHolder;
    
    import java.util.logging.Logger;
    
    public class SurfaceViewThread extends SurfaceView implements SurfaceHolder.Callback, Runnable {
    
        private SurfaceHolder surfaceHolder = null;
    
        private Paint paint = null;
    
        private Thread thread = null;
    
        // Record whether the child thread is running or not.
        private boolean threadRunning = false;
    
        private Canvas canvas = null;
    
        private float textX = 0;
    
        private float textY = 0;
    
        private String text = "";
    
        private int screenWidth = 0;
    
        private int screenHeight = 0;
    
        private static String LOG_TAG = "SURFACE_VIEW_THREAD";
    
        public SurfaceViewThread(Context context) {
            super(context);
    
            setFocusable(true);
    
            // Get SurfaceHolder object.
            surfaceHolder = this.getHolder();
            // Add current object as the callback listener.
            surfaceHolder.addCallback(this);
    
            // Create the paint object which will draw the text.
            paint = new Paint();
            paint.setTextSize(100);
            paint.setColor(Color.GREEN);
    
            // Set the SurfaceView object at the top of View object.
            setZOrderOnTop(true);
    
            //setBackgroundColor(Color.RED);
        }
    
        @Override
        public void surfaceCreated(SurfaceHolder surfaceHolder) {
    
            // Create the child thread when SurfaceView is created.
            thread = new Thread(this);
            // Start to run the child thread.
            thread.start();
            // Set thread running flag to true.
            threadRunning = true;
    
            // Get screen width and height.
            screenHeight = getHeight();
            screenWidth = getWidth();
    
        }
    
        @Override
        public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
    
        }
    
        @Override
        public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
            // Set thread running flag to false when Surface is destroyed.
            // Then the thread will jump out the while loop and complete.
            threadRunning = false;
        }
    
        @Override
        public void run() {
            while(threadRunning)
            {
                if(TextUtils.isEmpty(text))
                {
                    text = "Input text in above text box.";
                }
    
                long startTime = System.currentTimeMillis();
    
                textX += 100;
    
                textY += 100;
    
                if(textX > screenWidth)
                {
                    textX = 0;
                }
    
                if(textY > screenHeight)
                {
                    textY = 0;
                }
    
                drawText();
    
                long endTime = System.currentTimeMillis();
    
                long deltaTime = endTime - startTime;
    
                if(deltaTime < 200)
                {
                    try {
                        Thread.sleep(200 - deltaTime);
                    }catch (InterruptedException ex)
                    {
                        Log.e(LOG_TAG, ex.getMessage());
                    }
    
                }
            }
        }
    
        private void drawText()
        {
            int margin = 100;
    
            int left = margin;
    
            int top = margin;
    
            int right = screenWidth - margin;
    
            int bottom = screenHeight - margin;
    
            Rect rect = new Rect(left, top, right, bottom);
    
            // Only draw text on the specified rectangle area.
            canvas = surfaceHolder.lockCanvas(rect);
    
            // Draw the specify canvas background color.
            Paint backgroundPaint = new Paint();
            backgroundPaint.setColor(Color.BLUE);
            canvas.drawRect(rect, backgroundPaint);
    
            // Draw text in the canvas.
            canvas.drawText(text, textX, textY, paint);
    
            // Send message to main UI thread to update the drawing to the main view special area.
            surfaceHolder.unlockCanvasAndPost(canvas);
        }
    
        public String getText() {
            return text;
        }
    
        public void setText(String text) {
            this.text = text;
        }
    }

5.3 Main Activity Layout Xml File.

  1. app / res / layout / activity_main.xml
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    
        <EditText
            android:id="@+id/inputText"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="Input a text here which will be drawn when click enter."/>
    
        <LinearLayout
            android:id="@+id/drawTextCanvas"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">
    
        </LinearLayout>
    
    </LinearLayout>

Reference

  1. Android SurfaceView Drawing Example

1 thought on “Android Draw SurfaceView In Thread Example”

  1. Rick Oldenburger

    I could not get the event onKey to work…

    I used: setOnEditorActionListener

    // Add key listener to listen to key type event.
    inputText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
    @Override
    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
    if ( (actionId == EditorInfo.IME_ACTION_DONE) || ((event.getKeyCode() == KeyEvent.KEYCODE_ENTER) &&
    (event.getAction() == KeyEvent.ACTION_DOWN ))) {

    // If user input enter key.
    String userInputText = inputText.getText().toString();
    // Set the text to custom SurfaceView object.
    surfaceViewThread.setText(userInputText);
    // Means the key event has been processed.
    return true;
    }else
    {
    return false;
    }
    }
    });

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.