Android SurfaceView初步

/ 0评 / 0

SurfaceView定义

SurfaceView是View的子类,与View不同的是,SurfaceView可以在其他线程进行界面的绘制,而不需要在主线程中。所以SurfaceView适合用作需要快速绘制的情景,比如作为摄像头的预览界面或者播放视频。View的刷新在主线程,主要绘制在onDraw方法中。SurfaceView主要在SurfaceHolder中,由于SurfaceView直接继承于View,所以自定义SurfaceView类似于自定义View,不同的只是将绘制放在了新线程而已。

SurfaceView的使用

SurfaceView的使用和自定义View类似,首先继承SurfaceView,然后通过SurfaceView的getHolder方法即可获取到SurfaceHolder,通过给SurfaceHolder添加设置回调监听,即可在SurfaceView的创建/尺寸改变/销毁的时候做自己的操作。

要想在SurfaceView上面绘制画面,需要使用SurfaceHolder的lockCanvas()方法和unlockCanvasAndPost()方法。分别对应获取一个Canvas方法和将Canvas绘制到屏幕的操作,注意:同一时刻只能有一个线程获取到Canvas,这点从API名称上就可以看出来了。

大致流程为:继承SurfaceView->根据SurfaceView实例通过getHolder方法获取到SurfaceHolder->给SurfaceHolder通过addCallback方法添加事件监听->在新线程中通过lockCanvas()方法和unlockCanvasAndPost()方法绘制画面->在布局文件中使用自定义的SurfaceView。

当然,你也可以在布局文件中直接使用SurfaceView标签,然后在Activity中通过findViewById方法获取其实例,然后和上面一样的操作,不过这样可能将逻辑全部放在Activity中,不利于代码解耦。

SurfaceView的Demo

下面贴出一个使用SurfaceView进行颜色渐变的Demo。

Demo下载地址:TestSurfaceView

首先CustomSurfaceView直接继承自SurfaceView并实现了SurfaceHolder.Callback接口,然后在CustomSurfaceView的构造函数中获取到SurfaceHolder,并设置为透明以及添加回调,当surfaceCreated被调用的时候启动了一个新线程来绘制界面,当surfaceDestroyed被调用的时候结束了这个线程。

DrawThread主要是一个while(isAlive)循环,循环通过Canvas canvas = mSurfaceHolder.lockCanvas();获取到Canvas ,然后在其上面画上颜色,然后通过mSurfaceHolder.unlockCanvasAndPost(canvas);提交绘制结果,这样我们就实现了SurfaceView的使用。

/**
 * 使用SurfaceView实现变色效果
 * 
 * @author Alias {@link #www.27house.cn}
 */
public class CustomSurfaceView extends SurfaceView implements SurfaceHolder.Callback {

    private static final String TAG = "CustomSurfaceView";

    private SurfaceHolder mSurfaceHolder = null;

    private DrawThread mDrawThread = null;

    public CustomSurfaceView(Context context) {
        this(context, null);
    }

    public CustomSurfaceView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CustomSurfaceView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mSurfaceHolder = getHolder();
        mSurfaceHolder.setFormat(PixelFormat.TRANSLUCENT);
        mSurfaceHolder.addCallback(this);
    }

    /**
     * 这个回调在surface第一次被创建以后会立即调用,可以在这个方法里面启动渲染线程,注意只有一个线程可以绘制Surface
     * <p>
     * 所以如果你的绘制操作主要在另一个线程,那么你不应该在这里进行绘图操作
     */
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        Log.i(TAG, "surfaceCreated");
        mDrawThread = new DrawThread(holder);
        mDrawThread.start();
        // 修改SurfaceView大小,会让surfaceChanged会被调用
        // mSurfaceHolder.setFixedSize(500, 500);
    }

    /**
     * 当SurfaceView的尺寸发成变化的时候,这个方法会被立即调用
     * <p>
     * 在{@link #surfaceCreated}方法被调用以后,此方法至少会被调用一次
     */
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        Log.i(TAG, "surfaceChanged");
    }

    /**
     * SurfaceView被销毁的时候调用
     */
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        Log.i(TAG, "surfaceDestroyed");
        mDrawThread.stopDrawThread();
    }

    /**
     * 后台绘制线程
     */
    private static class DrawThread extends Thread {
        private SurfaceHolder mSurfaceHolder = null;

        private boolean mIsAlive = true;

        public DrawThread(SurfaceHolder holder) {
            mSurfaceHolder = holder;
        }

        @Override
        public void run() {
            while (mIsAlive) {
                if (beginTime == 0) {
                    beginTime = System.currentTimeMillis();
                }
                nowTime = System.currentTimeMillis();
                Canvas canvas = mSurfaceHolder.lockCanvas();
                if (canvas == null) {
                    continue;
                }
                drawBackgroundColor(canvas);
                mSurfaceHolder.unlockCanvasAndPost(canvas);
                try {
                    Thread.sleep(15);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    mIsAlive = false;
                    Log.i(TAG, "InterruptedException");
                }
            }
        }

        public void stopDrawThread() {
            mIsAlive = false;
        }

        private long during = 3000;

        private long beginTime = 0;

        private long nowTime = 0;

        private void drawBackgroundColor(Canvas canvas) {
            float fraction = (float) ((nowTime - beginTime + 0.0) / during);
            if (fraction > 1.0) {
                fraction = 0;
                beginTime = nowTime;
            }
            canvas.drawColor(evaluateColor(fraction, 0xff00A2E8, 0xffA349A4));
        }

        /**
         * 计算当前的颜色
         */
        public int evaluateColor(float fraction, int startValue, int endValue) {
            int startInt = startValue;
            int startA = (startInt >> 24);
            int startR = (startInt >> 16) & 0xff;
            int startG = (startInt >> 8) & 0xff;
            int startB = startInt & 0xff;

            int endInt = endValue;
            int endA = (endInt >> 24);
            int endR = (endInt >> 16) & 0xff;
            int endG = (endInt >> 8) & 0xff;
            int endB = endInt & 0xff;

            return (int) ((startA + (int) (fraction * (endA - startA))) << 24) |
                    (int) ((startR + (int) (fraction * (endR - startR))) << 16) |
                    (int) ((startG + (int) (fraction * (endG - startG))) << 8) |
                    (int) ((startB + (int) (fraction * (endB - startB))));
        }
    }
}

自定义SurfaceView的使用类似于自定义View,直接在布局文件中使用:

<com.testsurfaceview.CustomSurfaceView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

SurfaceView默认大小为填充父布局,你可以通过 mSurfaceHolder.setFixedSize(500, 500);方法来设置其大小。这个方法会导致surfaceChanged被立即调用。

其他

unlockCanvas()lockCanvas()中Surface的内容是不缓存的,所以会完全重绘Surface的内容,为了提高效率只重绘变化的部分则可以调用lockCanvas(Rect dirty)函数来指定一个dirty区域,这样该区域外的内容会缓存起来。在调用lockCanvas函数获取Canvas后,SurfaceView会获取Surface的一个同步锁直到调用unlockCanvasAndPost(Canvas canvas)函数才释放该锁,这里的同步机制保证在Surface绘制过程中不会被改变(被摧毁、修改)。

为了充分利用不同平台的资源,发挥平台的最优效果可以通过SurfaceHolder的setType函数来设置绘制的类型,目前接收如下的参数:

SURFACE_TYPE_NORMAL:用RAM缓存原生数据的普通Surface

SURFACE_TYPE_HARDWARE:适用于DMA(Direct memory access )引擎和硬件加速的Surface

SURFACE_TYPE_GPU:适用于GPU加速的Surface

SURFACE_TYPE_PUSH_BUFFERS:表明该Surface不包含原生数据,Surface用到的数据由其他对象提供,在Camera图像预览中就使用该类型的Surface,有Camera负责提供给预览Surface数据,这样图像预览会比较流畅。如果设置这种类型则就不能调用lockCanvas来获取Canvas对象了。

上面提到的四个值均在3.0被弃用了,3.0以后是如果当需要的时候,Android会自动设置这些值。

Demo下载地址:https://github.com/CB2Git/BlogDemoRepository/tree/master/TestSurfaceView

发表评论

您的电子邮箱地址不会被公开。