SurfaceView 基础用法

来源:互联网 发布:手机浏览器知乎 编辑:程序博客网 时间:2024/06/06 11:40

  • 概要
  • SurfaceView类粗略认识
  • SurfaceView实战
  • 总结

概要

刚接触Android开发的同学都被灌输过一个理念,一般情况下View的更新必须要在主线程操作。这也符合我们平时的操作习惯,先有输入事件,然后view响应了输入事件再去更新。但总会遇到需要在子线程更新View的情景,比如玩《节奏大师》这种手游时,View一直在不断的更新,并且更新的频率很高,这种情况如果再放到主线程去处理View的更新事件,就不合适时宜了。Android 为解决该类应用场景,给View家族增加了一个异类—SurfaceView,它能在子线程中去刷新View。本篇博文介绍其基础用法。

SurfaceView类粗略认识

分析一个陌生类之前,最好避免开篇就进入其类部,追查各个方法,而是要从宏观的角度查看其继承关系,实现接口,类的注释等,这样避免陷入细节的泥潭走不出来。回到主题,先从api文档看SurfaceView的继承结构,具体如下图:
这里写图片描述
在看具体代码

public class SurfaceView extends View {

这里先明确SurfaceView就是一个View的概念,读代码跟认识人都差不多,我们在和陌生人接触时如果发现双方都认识同一个人,立马会拉近彼此的距离。这里我们也算是找到了一个亲戚,看到SurfaceView是个View,是不是心里对代码亲近了许多。
在看其类层的注释,由于注释很长就不贴出来了,直接说下我从注释里获取的信息。
1. SurfaceView的作用:提供一个可嵌入View的Surface,通过它能控制surface的格式、大小等。类比TextView和text的关系,可以把Surface当成一个text。
2.SurfaceView的特点:Surface是Z方向排列,它在window的下面。SurfaceView会在window上挖个洞将Surface暴露出来。在SurfaceView可见的时候就会创建出Surface,子线程可以将Surface渲染到屏幕上。SurfaceView所属的window在哪个线程运行,SurfaceView以及SurfaceHolder.Callback所属的方法就会在该线程调用。
3. SurfaceView怎么用:通过SurfaceHolder来管理Surface,实现SurfaceHolder.Callback.surfaceCreated和SurfaceHolder.Callback.surfaceDestroyed方法可以跟踪Surface被创建和销毁的事件。

SurfaceView实战

上面扯了点闲蛋,下面进入实战。用一个实例来演示SurfaceView 基础用法。先放一张效果图,美女引狼,知道大家好这口,不解释。
这里写图片描述
暂时没有在Ubuntu环境下找到好的制作gif图片的工具,静态图将就看,运行效果脑补下。我们要实现的效果是,通过SurfaceView将美女加载显示出来,如果不满意,点击button切换下一位美女。
先放布局文件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical" >    <Button        android:id="@+id/switch_img"        android:layout_width="wrap_content"        android:layout_height="50dp"        android:layout_gravity="center"        android:paddingBottom="5dp"        android:text="点击切换图片" />    <com.azhengye.demosurfaceview.CustomSurfaceView        android:id="@+id/surfaceview"        android:layout_width="wrap_content"        android:layout_height="wrap_content" /></LinearLayout>

Layout 很简单,主要看CustomSurfaceView,它继承自SurfaceView也是本篇的主角。代码不多,直接给出。

package com.azhengye.demosurfaceview;import android.content.Context;import android.graphics.Bitmap;import android.graphics.Canvas;import android.util.AttributeSet;import android.view.SurfaceHolder;import android.view.SurfaceView;public class CustomSurfaceView extends SurfaceView implements        SurfaceHolder.Callback {    private SurfaceHolder holder = null;    private RenderThread renderThread = null;;    private boolean isRunning = false;    private Bitmap mBitmap = null;    public CustomSurfaceView(Context context,AttributeSet att) {        super(context,att);        holder = getHolder();        holder.addCallback(this);        renderThread = new RenderThread();    }    public void setBitmap(Bitmap bitmap){        this.mBitmap = bitmap;    }    @Override    public void surfaceCreated(SurfaceHolder holder) {        isRunning = true;        renderThread.start();    }    @Override    public void surfaceChanged(SurfaceHolder holder, int format, int width,            int height) {    }    @Override    public void surfaceDestroyed(SurfaceHolder holder) {        isRunning = false;    }    class RenderThread extends Thread {        @Override        public void run() {            while (isRunning) {                drawPicture();            }            super.run();        }        private void drawPicture() {            Canvas canvas = holder.lockCanvas();            try {                drawCanvas(canvas);            } catch (Exception e) {                e.printStackTrace();            } finally {                //if (canvas != null) {//坑啊                    holder.unlockCanvasAndPost(canvas);                //}            }        }        private void drawCanvas(Canvas canvas) {            canvas.drawBitmap(mBitmap, 0f, 0f, null);        }    }}

这里有个小坑,自定义View的构造方法,如果直接写
public CustomSurfaceView(Context context)
就会报出下面的错误:

E/AndroidRuntime( 7294): Caused by: java.lang.NoSuchMethodException: <init> [class android.content.Context, interface android.util.AttributeSet]

最后放出演示用的Activity.

package com.azhengye.demosurfaceview;import com.azhengye.demosurfaceview.R;import android.app.Activity;import android.content.res.Configuration;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Matrix;import android.os.Bundle;import android.view.View;import android.view.View.OnClickListener;import android.widget.LinearLayout;public class MainActivity extends Activity implements OnClickListener {    private int sampleIdx = -1;    private int samples[];    private CustomSurfaceView mCustomSurfaceView = null;    private static final int NUMSAPLES = 9;    private Bitmap mBitmap = null;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        findViewById(R.id.switch_img).setOnClickListener(this);        mCustomSurfaceView = (CustomSurfaceView) findViewById(R.id.surfaceview);        initData();        initView();    }    private void initData() {        samples = new int[NUMSAPLES];        samples[0] = R.drawable.p1;        samples[1] = R.drawable.p2;        samples[2] = R.drawable.p3;        samples[3] = R.drawable.p4;        samples[4] = R.drawable.p5;        samples[5] = R.drawable.p6;        samples[6] = R.drawable.p7;        samples[7] = R.drawable.p8;        samples[8] = R.drawable.p9;    }    private void initView() {        mBitmap = BitmapFactory.decodeResource(getResources(), samples[0]);        mCustomSurfaceView.setBitmap(mBitmap);    }    @Override    public void onClick(View v) {        showPicture();    }    @Override    protected void onRestoreInstanceState(Bundle savedInstanceState) {        mBitmap = (Bitmap) savedInstanceState.get("bitmap");        super.onRestoreInstanceState(savedInstanceState);    }    private void showPicture() {        sampleIdx = (sampleIdx + 1) % samples.length;        int imageSample = samples[sampleIdx];        mBitmap = BitmapFactory.decodeResource(getResources(), imageSample);        mCustomSurfaceView.setBitmap(mBitmap);    }}

自此就能满心欢喜的看美女咯,但是如果拿错手机的姿势,那么会遇到下面的错误log:

E/AndroidRuntime( 6950): Process: com.azhengye.demosurfaceview, PID: 6950E/AndroidRuntime( 6950): java.lang.IllegalArgumentException: canvas object must be the same instance that was previously returned by lockCanvasE/AndroidRuntime( 6950):    at android.view.Surface.unlockCanvasAndPost(Surface.java:279)E/AndroidRuntime( 6950):    at android.view.SurfaceView$4.unlockCanvasAndPost(SurfaceView.java:860)E/AndroidRuntime( 6950):    at com.azhengye.demosurfaceview.CustomSurfaceView$RenderThread.drawPicture(CustomSurfaceView.java:58)E/AndroidRuntime( 6950):    at com.azhengye.demosurfaceview.CustomSurfaceView$RenderThread.run(CustomSurfaceView.java:46)

这是因为横竖屏切换canvas对象被重建了,lockCanvas和unlockCanvasAndPost必须被同一个实例对象所调用。打开CustomSurfaceView里面的这段注释可以避免该错误。

//if (canvas != null) {//坑啊                    holder.unlockCanvasAndPost(canvas);                //}

另外加载图片的大小有可能会不同,这样当第二次切换的图片比第一张小时,会导致前一次的绘制图像还会露出来,为了让CustomSurfaceView的大小动态的匹配加载的图片。可以修改Activity中的showPicture如下

    private void showPicture() {        sampleIdx = (sampleIdx + 1) % samples.length;        int imageSample = samples[sampleIdx];        mBitmap = BitmapFactory.decodeResource(getResources(), imageSample);        LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(                mBitmap.getWidth(), mBitmap.getHeight());        mCustomSurfaceView.setLayoutParams(layoutParams);        mCustomSurfaceView.setBitmap(mBitmap);    }

这里就运用了View中的方法,SurfaceView既然是一个View,那么就可以通过setLayoutParams设置其layout参数。

总结

本文总结了对SurfaceView的粗浅运用方法,通过它可以看到子线程也可去渲染一个View对象,同时需要注意线程是在surfaceCreated方法中启动的,SurfaceView可见的时候就会创建出Surface,同时系统通过调用surfaceCreated告知Surface被创建了,然后子线程通过SurfaceHolder拿到canvas填充Surface。
该篇作为Open GL学习的前瞻篇,后续对这块有了新的认知还会更新。

0 0
原创粉丝点击