天天看点

Android视图SurfaceView的使用

SurfaceView看名字应该是View的子类:

Android视图SurfaceView的使用

关于SurfaceView的详细理解,请参考老罗的博客:

http://blog.csdn.net/luoshengyang/article/details/8661317/

这里只讲一下SurfaceView的简单使用。

SurfaceView是个特殊的View,它与一般View不同的是它的UI实现可以不在主线程中执行,这是因为它绘制UI元素的surface跟其他的View不同,其他的View都是使用同一个surface,而它有自己独有的,surface可以简单理解绘制的区域。这样以来如果要绘制一些复杂耗时的UI就可以使用SurfaceView来实现了,因为程序的主线程如果出现耗时的情况就出现ANR异常,比如摄像头预览UI,游戏画面一般用SurfaceView实现...下面就简单介绍一下使用SurfaceView来绘制界面。

SurfaceView 的源码在/frameworks/base/core/java/android/view/SurfaceView.java路径

与SurfaceView相关的几个重要的类:

SurfaceHolder:

/frameworks/base/core/java/android/view/SurfaceHolder.java

Callback:是SurfaceHolder的内部接口

public interface Callback {
        /**
         * This is called immediately after the surface is first created.
         * Implementations of this should start up whatever rendering code
         * they desire.  Note that only one thread can ever draw into
         * a {@link Surface}, so you should not draw into the Surface here
         * if your normal rendering will be in another thread.
         * 
         * @param holder The SurfaceHolder whose surface is being created.
         */
        public void surfaceCreated(SurfaceHolder holder);

        /**
         * This is called immediately after any structural changes (format or
         * size) have been made to the surface.  You should at this point update
         * the imagery in the surface.  This method is always called at least
         * once, after {@link #surfaceCreated}.
         * 
         * @param holder The SurfaceHolder whose surface has changed.
         * @param format The new PixelFormat of the surface.
         * @param width The new width of the surface.
         * @param height The new height of the surface.
         */
        public void surfaceChanged(SurfaceHolder holder, int format, int width,
                int height);

        /**
         * This is called immediately before a surface is being destroyed. After
         * returning from this call, you should no longer try to access this
         * surface.  If you have a rendering thread that directly accesses
         * the surface, you must ensure that thread is no longer touching the 
         * Surface before returning from this function.
         * 
         * @param holder The SurfaceHolder whose surface is being destroyed.
         */
        public void surfaceDestroyed(SurfaceHolder holder);
    }
           

SurfaceView有一个成员变量mSurfaceHolder,是SurfaceHolder类型。

在/frameworks/base/core/java/android/view/SurfaceView.java中

private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
        
        private static final String LOG_TAG = "SurfaceHolder";
        
        public boolean isCreating() {
            return mIsCreating;
        }

        public void addCallback(Callback callback) {
            synchronized (mCallbacks) {
                // This is a linear search, but in practice we'll 
                // have only a couple callbacks, so it doesn't matter.
                if (mCallbacks.contains(callback) == false) {      
                    mCallbacks.add(callback);
                }
            }
        }

        public void removeCallback(Callback callback) {
            synchronized (mCallbacks) {
                mCallbacks.remove(callback);
            }
        }
        
        public void setFixedSize(int width, int height) {
            if (mRequestedWidth != width || mRequestedHeight != height) {
                mRequestedWidth = width;
                mRequestedHeight = height;
                requestLayout();
            }
        }

        public void setSizeFromLayout() {
            if (mRequestedWidth != -1 || mRequestedHeight != -1) {
                mRequestedWidth = mRequestedHeight = -1;
                requestLayout();
            }
        }

        public void setFormat(int format) {

            // for backward compatibility reason, OPAQUE always
            // means 565 for SurfaceView
            if (format == PixelFormat.OPAQUE)
                format = PixelFormat.RGB_565;

            mRequestedFormat = format;
            if (mWindow != null) {
                updateWindow(false, false);
            }
        }

        /**
         * @deprecated setType is now ignored.
         */
        @Deprecated
        public void setType(int type) { }

        public void setKeepScreenOn(boolean screenOn) {
            Message msg = mHandler.obtainMessage(KEEP_SCREEN_ON_MSG);
            msg.arg1 = screenOn ? 1 : 0;
            mHandler.sendMessage(msg);
        }
        
        public Canvas lockCanvas() {
            return internalLockCanvas(null);
        }

        public Canvas lockCanvas(Rect dirty) {
            return internalLockCanvas(dirty);
        }

        private final Canvas internalLockCanvas(Rect dirty) {
            mSurfaceLock.lock();

            if (DEBUG) Log.i(TAG, "Locking canvas... stopped="
                    + mDrawingStopped + ", win=" + mWindow);

            Canvas c = null;
            if (!mDrawingStopped && mWindow != null) {
                if (dirty == null) {
                    if (mTmpDirty == null) {
                        mTmpDirty = new Rect();
                    }
                    mTmpDirty.set(mSurfaceFrame);
                    dirty = mTmpDirty;
                }

                try {
                    c = mSurface.lockCanvas(dirty);
                } catch (Exception e) {
                    Log.e(LOG_TAG, "Exception locking surface", e);
                }
            }

            if (DEBUG) Log.i(TAG, "Returned canvas: " + c);
            if (c != null) {
                mLastLockTime = SystemClock.uptimeMillis();
                return c;
            }
            
            // If the Surface is not ready to be drawn, then return null,
            // but throttle calls to this function so it isn't called more
            // than every 100ms.
            long now = SystemClock.uptimeMillis();
            long nextTime = mLastLockTime + 100;
            if (nextTime > now) {
                try {
                    Thread.sleep(nextTime-now);
                } catch (InterruptedException e) {
                }
                now = SystemClock.uptimeMillis();
            }
            mLastLockTime = now;
            mSurfaceLock.unlock();
            
            return null;
        }

        public void unlockCanvasAndPost(Canvas canvas) {
            mSurface.unlockCanvasAndPost(canvas);
            mSurfaceLock.unlock();
        }

        public Surface getSurface() {
            return mSurface;
        }

        public Rect getSurfaceFrame() {
            return mSurfaceFrame;
        }
    };
           

通过的SurfaceView的getHolder()函数就可以得到这个holder

public SurfaceHolder getHolder() {
        return mSurfaceHolder;
    }
           

通过这个holder的lockCanvas()函数就可以得到一个canvas,相当于就得到了SurfaceView的绘制区域,然后就可以开始绘制了,绘制完成之后,需要调用holder的unlockCanvasAndPost()函数通知WMS绘制完成可以显示了。

通过这个holder的addCallback()函数添加一个callback,该对象是SurfaceHolder的内部定义的接口。

SurfaceView虽然绘制UI在自己的区域绘制的,但是它依然是整个View树的一部分,所以它还是遵守View树的测量,布局,绘制这套流程。当它的自己的绘制区域(surface)创建的时候,就会回调之前通过holder 添加的callback的回调函数,这时就可以在该回调里去绘制你想要的UI了。

下面的demo,演示了使用SurfaceView绘制圆形进度条:

首先看一下效果。

Android视图SurfaceView的使用

在SurfaceView什么都没有绘制的时候,默认是黑色的界面,这个老罗老师已经分析过了:“SurfaceView在其宿主窗口的绘图表面上面所做的操作就是将自己所占据的区域绘为黑色,除此之外,就没有其它更多的操作了,这是因为SurfaceView的UI是要展现在它自己的绘图表面上面的”。

看一下代码,代码很少

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="cj.com.surfaceviewdemo.MainActivity">

    <SurfaceView
        android:layout_gravity="center_horizontal"
        android:id="@+id/surface_view"
        android:layout_width="300dp"
        android:layout_height="300dp" />
    <Button
        android:onClick="click"
        android:textSize="25sp"
        android:text="绘制"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>
           

初始化SurfaceView:

surfaceView = (SurfaceView) findViewById(R.id.surface_view);
     holder = surfaceView.getHolder();
     holder.addCallback(this);
           
MainActivity extends AppCompatActivity implements SurfaceHolder.Callback
           

点击button开始绘制:

public void click(View view){
        Log.d(TAG,"click");
        if(isSurfaceCreated){
            //开启子线程去绘制复杂的UI
           new MyThread().start();

        }else{
            Toast.makeText(this,"surface has not created ",Toast.LENGTH_SHORT).show();
        }
    }
           

在callBack的回调里:

@Override
    public void surfaceCreated(SurfaceHolder holder) {
        isSurfaceCreated = true;
        left = surfaceView.getLeft();
        top = surfaceView.getTop();
        right = surfaceView.getRight();
        bootom = surfaceView.getBottom();
        width = surfaceView.getWidth();
        height = surfaceView.getHeight();
        Log.d(TAG,"left = "+left+" top = "+top+" right = "+right+" bootom = "+bootom);
        Log.d(TAG,"width = "+width+" height = "+height);
    }
           

绘制的具体操作:

private class MyThread extends Thread{
        private boolean isDrawing = true;
        private Paint paint = new Paint();
        RectF rectF = new RectF(20,20,right-left-20,bootom-top-20);
        int num = 360;
        int current =0;
        @Override
        public void run() {
            Log.d(TAG,"run");
            super.run();
                Canvas canvas = null;
            while (isDrawing){
                Log.d(TAG,"isDrawing");
                canvas = holder.lockCanvas();//
                canvas.drawColor(Color.WHITE);
                paint.setAntiAlias(true);
                paint.setStrokeWidth(12.0f);
                paint.setStyle(Paint.Style.STROKE);
                paint.setColor(Color.rgb(0xe9, 0xe9, 0xe9));

                canvas.drawArc(rectF,-90,360,false,paint);//-90表示从竖直上的方向开始顺时针绘制 第三个参数false 画弧度的时候 边框不显示

                paint.setColor(Color.rgb(0xf8, 0x60, 0x30));
                canvas.drawArc(rectF,-90,current,false,paint);//

                paint.setStyle(Paint.Style.FILL);
                paint.setStrokeWidth(1.0f);
                paint.setTextSize(50);
                paint.setColor(Color.BLUE);
                paint.setTextAlign(Paint.Align.CENTER);
                float baseX = width/2;
                float baseY = height/2;
                canvas.drawText((current*100)/360+"%",baseX,baseY,paint);
                try {
                    if(current >=360){
                        isDrawing =false;
                        current = 360;
                    }else{
                        current = current+10;
                    }
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                holder.unlockCanvasAndPost(canvas);
            }
            isDrawing = true;
        }
    }
           

绘制的操作主要是用canvas的相关函数了,画弧形,画文本...

这里注意holder的lockCanvas()函数和unlockCanvasAndPost()要成对使用。

继续阅读