SurfaceView 教程 GIF 制作
2011-06-20 16:47
218 查看
在本教程中,介绍OPhone中提供的SurfaceView以及如何通过SurfaceView来编写一个显示GIF动画的View 。
SurfaceView介绍
通常情况程序的View和用户响应都是在同一个线程中处理的,这也是为什么处理长时间事件(例如访问网络)需要放到另外的线程中去(防止阻塞当前UI线程的操作和绘制)。但是在其他线程中却不能修改UI元素,例如用后台线程更新自定义View(调用View的在自定义View中的onDraw函数)是不允许的。
如果需要在另外的线程绘制界面、需要迅速的更新界面或则渲染UI界面需要较长的时间,这种情况就要使用SurfaceView了。SurfaceView中包含一个Surface对象,而Surface是可以在后台线程中绘制的。Surface属于
OPhone底层显示系统,关于这方面的介绍请参考附录中的资料[1]。SurfaceView的性质决定了其比较适合一些场景:需要界面迅速更新、对帧率要求较高的情况。使用SurfaceView需要注意以下几点情况:
SurfaceView和SurfaceHolder.Callback函数都从当前SurfaceView窗口线程中调用(一般而言就是程序的主线程)。有关资源状态要注意和绘制线程之间的同步。
在绘制线程中必须先合法的获取Surface才能开始绘制内容,在SurfaceHolder.Callback.surfaceCreated() 和SurfaceHolder.Callback.surfaceDestroyed()之间的状态为合法的,另外在Surface类型为SURFACE_TYPE_PUSH_BUFFERS时候是不合法的。
额外的绘制线程会消耗系统的资源,在使用SurfaceView的时候要注意这点。
使用SurfaceView
只要继承SurfaceView类并实现SurfaceHolder.Callback接口就可以实现一个自定义的SurfaceView了,SurfaceHolder.Callback在底层的Surface状态发生变化的时候通知View,SurfaceHolder.Callback具有如下的接口:
surfaceCreated(SurfaceHolder holder):当Surface第一次创建后会立即调用该函数。程序可以在该函数中做些和绘制界面相关的初始化工作,一般情况下都是在另外的线程来绘制界面,所以不要在这个函数中绘制Surface。
surfaceChanged(SurfaceHolder holder, int format, int width,int height):当Surface的状态(大小和格式)发生变化的时候会调用该函数,在surfaceCreated调用后该函数至少会被调用一次。
surfaceDestroyed(SurfaceHolder holder):当Surface被摧毁前会调用该函数,该函数被调用后就不能继续使用Surface了,一般在该函数中来清理使用的资源。
通过SurfaceView的getHolder()函数可以获取SurfaceHolder对象,Surface 就在SurfaceHolder对象内。虽然Surface保存了当前窗口的像素数据,但是在使用过程中是不直接和Surface打交道的,由SurfaceHolder的Canvas lockCanvas()或则Canvas lockCanvas(Rect dirty)函数来获取Canvas对象,通过在Canvas上绘制内容来修改Surface中的数据。如果Surface不可编辑或则尚未创建调用该函数会返回null,在 unlockCanvas() 和 lockCanvas()中Surface的内容是不缓存的,所以需要完全重绘Surface的内容,为了提高效率只重绘变化的部分则可以调用lockCanvas(Rect dirty)函数来指定一个dirty区域,这样该区域外的内容会缓存起来。在调用lockCanvas函数获取Canvas后,SurfaceView会获取Surface的一个同步锁直到调用unlockCanvasAndPost(Canvas canvas)函数才释放该锁,这里的同步机制保证在Surface绘制过程中不会被改变(被摧毁、修改)。
当在Canvas中绘制完成后,调用函数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对象了。
目前OPhone还不支持GIF动画图片的显示,这里就通过一个SurfaceView来展示如何定制一个支持GIF动画的View,同时从该示例(注释)中也可以看出如何使用SurfaceView。
首先创建一个GifView继承在SurfaceView,代码如下:
view plaincopy to clipboardprint?
/**
* 通过继承SurfaceView并实现Callback接口来实现一个GifView。
*
*/
class GifView extends SurfaceView implements Callback {
//GifThread是用来绘制的后台线程,一般使用SurfaceView都会使用一个
//后台线程来做绘制的工作
private GifThread mGifThread;
//GifDecoder是一个Gif图片格式的解析器,用来解析Gif图片的每帧数据和显示时间。
private GifDecoder mGifDecoder;
/**
* 构造函数,读者可以自己实现另外的构造函数以方便在xml layout中使用GifView。
* @param context
* @param gifId 需要显示的Gif图片id,放置在Raw目录下的资源文件
*/
public GifView(Context context,int gifId) {
super(context);
//获取Gif图片数据
InputStream is = context.getResources().openRawResource(gifId);
//解析Gif图片数据
mGifDecoder = new GifDecoder();
mGifDecoder.read(is);
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
is = null;
//获取SurfaceHolder
SurfaceHolder holder = getHolder();
//如果有必要可以设置合适的SurfaceType
//holder.setType(SurfaceHolder.SURFACE_TYPE_HARDWARE);
//设置回调函数
holder.addCallback(this);
//创建Gif绘制线程
mGifThread = new GifThread(holder,this);
}
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
//Do nothing
}
public void surfaceCreated(SurfaceHolder holder) {
//当Surface创建成功后,启动绘制线程
mGifThread.setRunning(true);
mGifThread.start();
}
public void surfaceDestroyed(SurfaceHolder holder) {
//当Surface即将摧毁的时候,停止绘制线程
boolean retry = true;
mGifThread.setRunning(false);
while (retry) {
try {
mGifThread.join();
retry = false;
} catch (InterruptedException e) {
// we will try it again and again...
}
}
}
}
在代码中都有详细的注释,这里就不再解释,下面是GifThread代码:
/**
* 负责绘制Surface的线程
*/
class GifThread extends Thread {
private SurfaceHolder mSurfaceHolder;
private boolean mRunning;
private int mCurrentFrame;//当前绘制的帧数
private long mLastTime;//上一帧绘制的时间
private int mFrameCount;//总帧数
private Bitmap[] mBitmap;//每帧的图片数据
private int[] mDelay;//每帧的显示时间
/**
* 构造函数,初始化相关的数据
* @param surfaceHolder
* @param gifView
*/
public GifThread(SurfaceHolder surfaceHolder, GifView gifView) {
mSurfaceHolder = surfaceHolder;
GifDecoder mGifDecoder = gifView.mGifDecoder;
mLastTime = System.currentTimeMillis();
mFrameCount = mGifDecoder.getFrameCount();
mBitmap = new Bitmap[mFrameCount];
mDelay = new int[mFrameCount];
for (int i = 0; i < mFrameCount; i++) {
mBitmap[i] = mGifDecoder.getFrame(i);
mDelay[i] = mGifDecoder.getDelay(i);
}
mGifDecoder = null;
}
/**
* 设置运行状态,如果设置为false,则线程退出绘制。
*/
public void setRunning(boolean running) {
mRunning = running;
}
/**
* 绘制当前帧
*/
private void doDraw(Canvas canvas) {
mLastTime = System.currentTimeMillis();;
canvas.drawBitmap(mBitmap[mCurrentFrame], new Matrix(), null);
mCurrentFrame ++;
}
@Override
public void run() {
Canvas c;
int delay = 0;
while (mRunning) {
c = null;
if(mCurrentFrame != 0) {
delay = mDelay[mCurrentFrame-1];
}
if(mCurrentFrame == mFrameCount) {
mCurrentFrame = 0;
}
long currentTime = System.currentTimeMillis();
long t = currentTime - mLastTime;
//如果到下一帧绘制时间开始绘制下一帧
if(t >= delay) {
try {
//获取Canvas来绘制界面
c = mSurfaceHolder.lockCanvas(null);
//通过mSurfaceHolder来同步绘制操作
synchronized (mSurfaceHolder) {
doDraw(c);
}
} finally {
// 在finally中执行该操作,这样当上面的代码抛出异常的时候
//不会导致Surface出去不一致的状态。
if (c != null) {
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
}
最后创建一个Activity来测试下GifView:
view plaincopy to clipboardprint?
public class SurfaceActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new GifView(this,R.raw.pic));
}
}
程序截图:
GifView运行过程中的两幅截图
SurfaceView介绍
通常情况程序的View和用户响应都是在同一个线程中处理的,这也是为什么处理长时间事件(例如访问网络)需要放到另外的线程中去(防止阻塞当前UI线程的操作和绘制)。但是在其他线程中却不能修改UI元素,例如用后台线程更新自定义View(调用View的在自定义View中的onDraw函数)是不允许的。
如果需要在另外的线程绘制界面、需要迅速的更新界面或则渲染UI界面需要较长的时间,这种情况就要使用SurfaceView了。SurfaceView中包含一个Surface对象,而Surface是可以在后台线程中绘制的。Surface属于
OPhone底层显示系统,关于这方面的介绍请参考附录中的资料[1]。SurfaceView的性质决定了其比较适合一些场景:需要界面迅速更新、对帧率要求较高的情况。使用SurfaceView需要注意以下几点情况:
SurfaceView和SurfaceHolder.Callback函数都从当前SurfaceView窗口线程中调用(一般而言就是程序的主线程)。有关资源状态要注意和绘制线程之间的同步。
在绘制线程中必须先合法的获取Surface才能开始绘制内容,在SurfaceHolder.Callback.surfaceCreated() 和SurfaceHolder.Callback.surfaceDestroyed()之间的状态为合法的,另外在Surface类型为SURFACE_TYPE_PUSH_BUFFERS时候是不合法的。
额外的绘制线程会消耗系统的资源,在使用SurfaceView的时候要注意这点。
使用SurfaceView
只要继承SurfaceView类并实现SurfaceHolder.Callback接口就可以实现一个自定义的SurfaceView了,SurfaceHolder.Callback在底层的Surface状态发生变化的时候通知View,SurfaceHolder.Callback具有如下的接口:
surfaceCreated(SurfaceHolder holder):当Surface第一次创建后会立即调用该函数。程序可以在该函数中做些和绘制界面相关的初始化工作,一般情况下都是在另外的线程来绘制界面,所以不要在这个函数中绘制Surface。
surfaceChanged(SurfaceHolder holder, int format, int width,int height):当Surface的状态(大小和格式)发生变化的时候会调用该函数,在surfaceCreated调用后该函数至少会被调用一次。
surfaceDestroyed(SurfaceHolder holder):当Surface被摧毁前会调用该函数,该函数被调用后就不能继续使用Surface了,一般在该函数中来清理使用的资源。
通过SurfaceView的getHolder()函数可以获取SurfaceHolder对象,Surface 就在SurfaceHolder对象内。虽然Surface保存了当前窗口的像素数据,但是在使用过程中是不直接和Surface打交道的,由SurfaceHolder的Canvas lockCanvas()或则Canvas lockCanvas(Rect dirty)函数来获取Canvas对象,通过在Canvas上绘制内容来修改Surface中的数据。如果Surface不可编辑或则尚未创建调用该函数会返回null,在 unlockCanvas() 和 lockCanvas()中Surface的内容是不缓存的,所以需要完全重绘Surface的内容,为了提高效率只重绘变化的部分则可以调用lockCanvas(Rect dirty)函数来指定一个dirty区域,这样该区域外的内容会缓存起来。在调用lockCanvas函数获取Canvas后,SurfaceView会获取Surface的一个同步锁直到调用unlockCanvasAndPost(Canvas canvas)函数才释放该锁,这里的同步机制保证在Surface绘制过程中不会被改变(被摧毁、修改)。
当在Canvas中绘制完成后,调用函数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对象了。
目前OPhone还不支持GIF动画图片的显示,这里就通过一个SurfaceView来展示如何定制一个支持GIF动画的View,同时从该示例(注释)中也可以看出如何使用SurfaceView。
首先创建一个GifView继承在SurfaceView,代码如下:
view plaincopy to clipboardprint?
/**
* 通过继承SurfaceView并实现Callback接口来实现一个GifView。
*
*/
class GifView extends SurfaceView implements Callback {
//GifThread是用来绘制的后台线程,一般使用SurfaceView都会使用一个
//后台线程来做绘制的工作
private GifThread mGifThread;
//GifDecoder是一个Gif图片格式的解析器,用来解析Gif图片的每帧数据和显示时间。
private GifDecoder mGifDecoder;
/**
* 构造函数,读者可以自己实现另外的构造函数以方便在xml layout中使用GifView。
* @param context
* @param gifId 需要显示的Gif图片id,放置在Raw目录下的资源文件
*/
public GifView(Context context,int gifId) {
super(context);
//获取Gif图片数据
InputStream is = context.getResources().openRawResource(gifId);
//解析Gif图片数据
mGifDecoder = new GifDecoder();
mGifDecoder.read(is);
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
is = null;
//获取SurfaceHolder
SurfaceHolder holder = getHolder();
//如果有必要可以设置合适的SurfaceType
//holder.setType(SurfaceHolder.SURFACE_TYPE_HARDWARE);
//设置回调函数
holder.addCallback(this);
//创建Gif绘制线程
mGifThread = new GifThread(holder,this);
}
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
//Do nothing
}
public void surfaceCreated(SurfaceHolder holder) {
//当Surface创建成功后,启动绘制线程
mGifThread.setRunning(true);
mGifThread.start();
}
public void surfaceDestroyed(SurfaceHolder holder) {
//当Surface即将摧毁的时候,停止绘制线程
boolean retry = true;
mGifThread.setRunning(false);
while (retry) {
try {
mGifThread.join();
retry = false;
} catch (InterruptedException e) {
// we will try it again and again...
}
}
}
}
在代码中都有详细的注释,这里就不再解释,下面是GifThread代码:
/**
* 负责绘制Surface的线程
*/
class GifThread extends Thread {
private SurfaceHolder mSurfaceHolder;
private boolean mRunning;
private int mCurrentFrame;//当前绘制的帧数
private long mLastTime;//上一帧绘制的时间
private int mFrameCount;//总帧数
private Bitmap[] mBitmap;//每帧的图片数据
private int[] mDelay;//每帧的显示时间
/**
* 构造函数,初始化相关的数据
* @param surfaceHolder
* @param gifView
*/
public GifThread(SurfaceHolder surfaceHolder, GifView gifView) {
mSurfaceHolder = surfaceHolder;
GifDecoder mGifDecoder = gifView.mGifDecoder;
mLastTime = System.currentTimeMillis();
mFrameCount = mGifDecoder.getFrameCount();
mBitmap = new Bitmap[mFrameCount];
mDelay = new int[mFrameCount];
for (int i = 0; i < mFrameCount; i++) {
mBitmap[i] = mGifDecoder.getFrame(i);
mDelay[i] = mGifDecoder.getDelay(i);
}
mGifDecoder = null;
}
/**
* 设置运行状态,如果设置为false,则线程退出绘制。
*/
public void setRunning(boolean running) {
mRunning = running;
}
/**
* 绘制当前帧
*/
private void doDraw(Canvas canvas) {
mLastTime = System.currentTimeMillis();;
canvas.drawBitmap(mBitmap[mCurrentFrame], new Matrix(), null);
mCurrentFrame ++;
}
@Override
public void run() {
Canvas c;
int delay = 0;
while (mRunning) {
c = null;
if(mCurrentFrame != 0) {
delay = mDelay[mCurrentFrame-1];
}
if(mCurrentFrame == mFrameCount) {
mCurrentFrame = 0;
}
long currentTime = System.currentTimeMillis();
long t = currentTime - mLastTime;
//如果到下一帧绘制时间开始绘制下一帧
if(t >= delay) {
try {
//获取Canvas来绘制界面
c = mSurfaceHolder.lockCanvas(null);
//通过mSurfaceHolder来同步绘制操作
synchronized (mSurfaceHolder) {
doDraw(c);
}
} finally {
// 在finally中执行该操作,这样当上面的代码抛出异常的时候
//不会导致Surface出去不一致的状态。
if (c != null) {
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
}
/** * 通过继承SurfaceView并实现Callback接口来实现一个GifView。 * */ class GifView extends SurfaceView implements Callback { //GifThread是用来绘制的后台线程,一般使用SurfaceView都会使用一个 //后台线程来做绘制的工作 private GifThread mGifThread; //GifDecoder是一个Gif图片格式的解析器,用来解析Gif图片的每帧数据和显示时间。 private GifDecoder mGifDecoder; /** * 构造函数,读者可以自己实现另外的构造函数以方便在xml layout中使用GifView。 * @param context * @param gifId 需要显示的Gif图片id,放置在Raw目录下的资源文件 */ public GifView(Context context,int gifId) { super(context); //获取Gif图片数据 InputStream is = context.getResources().openRawResource(gifId); //解析Gif图片数据 mGifDecoder = new GifDecoder(); mGifDecoder.read(is); try { is.close(); } catch (IOException e) { e.printStackTrace(); } is = null; //获取SurfaceHolder SurfaceHolder holder = getHolder(); //如果有必要可以设置合适的SurfaceType //holder.setType(SurfaceHolder.SURFACE_TYPE_HARDWARE); //设置回调函数 holder.addCallback(this); //创建Gif绘制线程 mGifThread = new GifThread(holder,this); } public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { //Do nothing } public void surfaceCreated(SurfaceHolder holder) { //当Surface创建成功后,启动绘制线程 mGifThread.setRunning(true); mGifThread.start(); } public void surfaceDestroyed(SurfaceHolder holder) { //当Surface即将摧毁的时候,停止绘制线程 boolean retry = true; mGifThread.setRunning(false); while (retry) { try { mGifThread.join(); retry = false; } catch (InterruptedException e) { // we will try it again and again... } } } } 在代码中都有详细的注释,这里就不再解释,下面是GifThread代码: /** * 负责绘制Surface的线程 */ class GifThread extends Thread { private SurfaceHolder mSurfaceHolder; private boolean mRunning; private int mCurrentFrame;//当前绘制的帧数 private long mLastTime;//上一帧绘制的时间 private int mFrameCount;//总帧数 private Bitmap[] mBitmap;//每帧的图片数据 private int[] mDelay;//每帧的显示时间 /** * 构造函数,初始化相关的数据 * @param surfaceHolder * @param gifView */ public GifThread(SurfaceHolder surfaceHolder, GifView gifView) { mSurfaceHolder = surfaceHolder; GifDecoder mGifDecoder = gifView.mGifDecoder; mLastTime = System.currentTimeMillis(); mFrameCount = mGifDecoder.getFrameCount(); mBitmap = new Bitmap[mFrameCount]; mDelay = new int[mFrameCount]; for (int i = 0; i < mFrameCount; i++) { mBitmap[i] = mGifDecoder.getFrame(i); mDelay[i] = mGifDecoder.getDelay(i); } mGifDecoder = null; } /** * 设置运行状态,如果设置为false,则线程退出绘制。 */ public void setRunning(boolean running) { mRunning = running; } /** * 绘制当前帧 */ private void doDraw(Canvas canvas) { mLastTime = System.currentTimeMillis();; canvas.drawBitmap(mBitmap[mCurrentFrame], new Matrix(), null); mCurrentFrame ++; } @Override public void run() { Canvas c; int delay = 0; while (mRunning) { c = null; if(mCurrentFrame != 0) { delay = mDelay[mCurrentFrame-1]; } if(mCurrentFrame == mFrameCount) { mCurrentFrame = 0; } long currentTime = System.currentTimeMillis(); long t = currentTime - mLastTime; //如果到下一帧绘制时间开始绘制下一帧 if(t >= delay) { try { //获取Canvas来绘制界面 c = mSurfaceHolder.lockCanvas(null); //通过mSurfaceHolder来同步绘制操作 synchronized (mSurfaceHolder) { doDraw(c); } } finally { // 在finally中执行该操作,这样当上面的代码抛出异常的时候 //不会导致Surface出去不一致的状态。 if (c != null) { mSurfaceHolder.unlockCanvasAndPost(c); } } } } } }
最后创建一个Activity来测试下GifView:
view plaincopy to clipboardprint?
public class SurfaceActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new GifView(this,R.raw.pic));
}
}
public class SurfaceActivity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(new GifView(this,R.raw.pic)); } }
程序截图:
GifView运行过程中的两幅截图
相关文章推荐
- SurfaceView与Camera制作照相机
- 03-photoshop制作gif动画教程
- SurfaceView与Camera制作照相机
- Android高级控件(二)——SurfaceView实现GIF动画架包,播放GIF动画,自己实现功能的初体现
- Mac屏幕录制与gif图片制作教程
- Android View与SurfaceView的手绘板制作
- 21天学习android开发教程之SurfaceView
- gif图片制作教程
- Android高级控件(二)——SurfaceView实现GIF动画架包,播放GIF动画,自己实现功能的初体现
- 传感器的应用/SurfaceView/制作简单的指南针
- cocos2dx 3D战斗类游戏制作:【四】——一些零散笔记,3D小地图,android surfaceview等
- Android使用SurfaceView制作卷轴打开效果
- Android学习笔记:使用SurfaceView制作简单游戏(1)
- 【android-tips】SurfaceView的制作android游戏框架介绍
- Android用surfaceView制作动画效果
- 使用surfaceView制作的拍照demo
- 21天学习android开发教程之SurfaceView与多线程的混搭
- 使用surfaceView制作视频播放器
- 用SurfaceView制作简单的android游戏 : 重力小球(1)--------创建游戏整体框架
- PS制作gif动图教程