Android UI How to - Draw to SurfaceView with thread








The following code shows how to Draw to SurfaceView with thread.

Code revised from
Android Recipes:A Problem-Solution Approach
http://www.apress.com/9781430234135
ISBN13: 978-1-4302-3413-5

Example

Main layout xml file

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <Button
        android:id="@+id/button_erase"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Erase" />
    <SurfaceView 
        android:id="@+id/surface"
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:layout_gravity="center" />

</FrameLayout>

Main Activity Java code

import java.util.ArrayList;
//from w  ww . ja v a 2 s . com
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;

public class MainActivity extends Activity implements View.OnClickListener, View.OnTouchListener, SurfaceHolder.Callback {

    private SurfaceView mSurface;
    private DrawingThread mThread;
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        //Attach listener to button
        findViewById(R.id.button_erase).setOnClickListener(this);
        
        //Set up the surface with a touch listener and callback
        mSurface = (SurfaceView) findViewById(R.id.surface);
        mSurface.setOnTouchListener(this);
        mSurface.getHolder().addCallback(this);
    }
    
    @Override
    public void onClick(View v) {
        mThread.clearItems();
    }
    
    public boolean onTouch(View v, MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            mThread.addItem((int) event.getX(), (int) event.getY());
        }
        return true;
    }
    
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        mThread = new DrawingThread(holder, BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher));
        mThread.start();
    }
    
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width,
            int height) {
        mThread.updateSize(width, height);
    }
    
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        mThread.quit();
        mThread = null;
    }
    
    private static class DrawingThread extends HandlerThread implements Handler.Callback {
        private static final int MSG_ADD = 100;
        private static final int MSG_MOVE = 101;
        private static final int MSG_CLEAR = 102;
        
        private int mDrawingWidth, mDrawingHeight;
        
        private SurfaceHolder mDrawingSurface;
        private Paint mPaint;
        private Handler mReceiver;
        private Bitmap mIcon;
        private ArrayList<DrawingItem> mLocations;
        
        private class DrawingItem {
            //Current location marker
            int x, y;
            //Direction markers for motion
            boolean horizontal, vertical;
            
            public DrawingItem(int x, int y, boolean horizontal, boolean vertical) {
                this.x = x;
                this.y = y;
                this.horizontal = horizontal;
                this.vertical = vertical;
            }
        }
        
        public DrawingThread(SurfaceHolder holder, Bitmap icon) {
            super("DrawingThread");
            mDrawingSurface = holder;
            mLocations = new ArrayList<DrawingItem>();
            mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            mIcon = icon;
        }
        
        @Override
        protected void onLooperPrepared() {
            mReceiver = new Handler(getLooper(), this);
            //Start the rendering
            mReceiver.sendEmptyMessage(MSG_MOVE);
        }
        
        @Override
        public boolean quit() {
            // Clear all messages before dying
            mReceiver.removeCallbacksAndMessages(null);
            return super.quit();
        }
        
        @Override
        public boolean handleMessage(Message msg) {
            switch (msg.what) {
            case MSG_ADD:
                //Create a new item at the touch location, with a randomized start direction
                DrawingItem newItem = new DrawingItem(msg.arg1, msg.arg2,
                        Math.round(Math.random()) == 0,
                        Math.round(Math.random()) == 0);
                mLocations.add(newItem);
                break;
            case MSG_CLEAR:
                //Remove all objects
                mLocations.clear();
                break;
            case MSG_MOVE:
                //Render a frame
                Canvas c = mDrawingSurface.lockCanvas();
                if (c == null) {
                    break;
                }
                //Clear canvas first
                c.drawColor(Color.BLACK);
                //Draw each item
                for (DrawingItem item : mLocations) {
                    //Update location
                    item.x += (item.horizontal ? 5 : -5);
                    if (item.x >= (mDrawingWidth - mIcon.getWidth()) ) item.horizontal = false;
                    if (item.x <= 0) item.horizontal = true;
                    item.y += (item.vertical ? 5 : -5);
                    if (item.y >= (mDrawingHeight - mIcon.getHeight()) ) item.vertical = false;
                    if (item.y <= 0) item.vertical = true;
                    
                    c.drawBitmap(mIcon, item.x, item.y, mPaint);
                }
                mDrawingSurface.unlockCanvasAndPost(c);
                break;
            }
            //Post the next frame
            mReceiver.sendEmptyMessage(MSG_MOVE);
            return true;
        }
        
        public void updateSize(int width, int height) {
            mDrawingWidth = width;
            mDrawingHeight = height;
        }
        
        public void addItem(int x, int y) {
            //Pass the location into the Handler using Message arguments
            Message msg = Message.obtain(mReceiver, MSG_ADD, x, y);
            mReceiver.sendMessage(msg);
        }
        
        public void clearItems() {
            mReceiver.sendEmptyMessage(MSG_CLEAR);
        }
    }
}
null