您的位置:首页 > 移动开发 > Android开发

SurfaceView 基础用法

2016-01-14 00:28 393 查看
概要

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: 6950
E/AndroidRuntime( 6950): java.lang.IllegalArgumentException: canvas object must be the same instance that was previously returned by lockCanvas
E/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学习的前瞻篇,后续对这块有了新的认知还会更新。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android android开发