转载请标明出处:漆可的博客http://blog.csdn.net/q649381130/article/details/49407129
在安卓开发中,帧动画是通过逐帧显示配置在动画资源文件中的图片来实现。然而,这存在一个巨大的风险,由于安卓是一次性把动画资源文件中的所有图片资源全部加载,这就意味着如果图片数目过多,极其容易造成内存溢出。
那么,如何避免这种情况的发生呢。下面我们推出本文的主角SurfaceView。
SurfaceView是一个专门用户绘制的view的子类,由于内部已做处理,并且实现了双缓冲,它可以在非UI线程中进行UI操作(谷歌仅为我们提供俩个有该特异功能的控件,另外一个是ProgressBar)。这样可以避免画图任务繁重的时候造成主线程阻塞,从而提高了程序的反应速度。广泛用于游戏开发中的绘制。
SurfaceView的使用非常简单,一般与SurfaceHolder结合使用:
创建一个集成了SurfaceView的类,并实现SurfaceHolder.Callback实现后SurfaceHolder.Callback必须实现了三个方法,意思也很明白,分别是:
surfaceCreated:
surfaceChanged:
surfaceDestroyed:
本文就是通过SurfaceView内部开启线程动态的绘制图片。
先介绍下安卓原生的帧动画使用方法,废话少说,直接上代码,完整源码在文章最后提供下载地址。
在res目录下新建anim文件,该文件下创建frame_demo.xml文件:
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="true" >
<item android:drawable="@drawable/penguin_happy_3_1" android:duration="150"/>
<item android:drawable="@drawable/penguin_happy_3_2" android:duration="150"/>
<item android:drawable="@drawable/penguin_happy_3_3" android:duration="150"/>
<item android:drawable="@drawable/penguin_happy_3_4" android:duration="150"/>
<item android:drawable="@drawable/penguin_happy_3_5" android:duration="150"/>
<item android:drawable="@drawable/penguin_happy_3_6" android:duration="150"/>
<item android:drawable="@drawable/penguin_happy_3_7" android:duration="150"/>
<item android:drawable="@drawable/penguin_happy_3_8" android:duration="150"/>
<item android:drawable="@drawable/penguin_happy_3_9" android:duration="150"/>
<item android:drawable="@drawable/penguin_happy_3_10" android:duration="150"/>
<item android:drawable="@drawable/penguin_happy_3_11" android:duration="150"/>
<item android:drawable="@drawable/penguin_happy_3_12" android:duration="150"/>
<item android:drawable="@drawable/penguin_happy_3_13" android:duration="150"/>
</animation-list>
布局文件activity_original.xml
很简单,就是一个ImageView,scr属性为该动画资源:
<RelativeLayout 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"
tools:context="${relativePackage}.${activityClass}" >
<ImageView
android:id="@+id/iv_orginal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@anim/frame_demo" />
</RelativeLayout>
然后在activity实现动画播放:
ImageView iv_original = (ImageView) findViewById(R.id.iv_orginal);
AnimationDrawable ani = (AnimationDrawable) iv_original.getDrawable();
ani.start();//播放动画
//打印出程序占用的内存
Log.e(TAG, Runtime.getRuntime().totalMemory()/1024 + "k");
以上就是安卓开发中利用普通方法实现帧动画的方式,代码很简单,我们输出该方式播放时整个应用占用的内存大小为6.08M。
首先是我们自定义的动画框架,继承自SurfaceView,
完整代码如下:
public class FrameAnimation extends SurfaceView implements SurfaceHolder.Callback, Runnable {
private SurfaceHolder mSurfaceHolder;
private boolean mIsThreadRunning = true; // 线程运行开关
private boolean mIsDestroy = false;// 是否已经销毁
private int[] mBitmapResourceIds;// 用于播放动画的图片资源数组
private Canvas mCanvas;
private Bitmap mBitmap;// 显示的图片
private int mCurrentIndext;// 当前动画播放的位置
private int mGapTime = 150;// 每帧动画持续存在的时间
private OnFrameFinishedListener mOnFrameFinishedListener;// 动画监听事件
public FrameAnimation(Context context)
{
this(context, null);
}
public FrameAnimation(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
mSurfaceHolder = this.getHolder();
mSurfaceHolder.addCallback(this);// 注册回调方法
// 白色背景
setZOrderOnTop(true);
mSurfaceHolder.setFormat(PixelFormat.TRANSLUCENT);
}
public FrameAnimation(Context context, AttributeSet attrs)
{
this(context, attrs, 0);
}
@Override
public void surfaceCreated(SurfaceHolder holder)
{
// 创建surfaceView时启动线程
// new Thread(this).start();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
}
@Override
public void surfaceDestroyed(SurfaceHolder holder)
{
// 当surfaceView销毁时, 停止线程的运行. 避免surfaceView销毁了线程还在运行而报错.
mIsThreadRunning = false;
try
{
Thread.sleep(mGapTime);
} catch (InterruptedException e)
{
e.printStackTrace();
}
mIsDestroy = true;
}
/**
* 制图方法
*/
private void drawView()
{
// 无资源文件退出
if (mBitmapResourceIds == null)
{
Log.e("frameview", "the bitmapsrcIDs is null");
mIsThreadRunning = false;
return;
}
// 锁定画布
mCanvas = mSurfaceHolder.lockCanvas();
try
{
if (mSurfaceHolder != null && mCanvas != null)
{
mCanvas.drawColor(Color.WHITE);
// 如果图片过大可以再次对图片进行二次采样缩放处理
mBitmap = BitmapFactory.decodeResource(getResources(), mBitmapResourceIds[mCurrentIndext]);
mCanvas.drawBitmap(mBitmap, (getWidth() - mBitmap.getWidth()) / 2,
(getHeight() - mBitmap.getHeight()) / 2, null);
// 播放到最后一张图片,停止线程
if (mCurrentIndext == mBitmapResourceIds.length - 1)
{
mIsThreadRunning = false;
}
}
} catch (Exception e)
{
e.printStackTrace();
} finally
{
mCurrentIndext++;
if (mCanvas != null)
{
// 将画布解锁并显示在屏幕上
mSurfaceHolder.unlockCanvasAndPost(mCanvas);
}
if (mBitmap != null)
{
// 收回图片
mBitmap.recycle();
}
}
}
@Override
public void run()
{
if (mOnFrameFinishedListener != null)
{
mOnFrameFinishedListener.onStart();
}
// 每隔100ms刷新屏幕
while (mIsThreadRunning)
{
drawView();
try
{
Thread.sleep(mGapTime);
} catch (Exception e)
{
e.printStackTrace();
}
}
if (mOnFrameFinishedListener != null)
{
mOnFrameFinishedListener.onStop();
}
}
/**
* 开始动画
*/
public void start()
{
if (!mIsDestroy)
{
mCurrentIndext = 0;
mIsThreadRunning = true;
new Thread(this).start();
} else
{
// 如果SurfaceHolder已经销毁抛出该异常
try
{
throw new Exception("IllegalArgumentException:Are you sure the SurfaceHolder is not destroyed");
} catch (Exception e)
{
e.printStackTrace();
}
}
}
/**
* 设置动画播放素材
* @param bitmapResoursIds 图片资源id
*/
public void setBitmapResoursID(int[] bitmapResourceIds)
{
this.mBitmapResourceIds = bitmapResourceIds;
}
/**
* 设置每帧时间
*/
public void setGapTime(int gapTime)
{
this.mGapTime = gapTime;
}
/**
* 结束动画
*/
public void stop()
{
mIsThreadRunning = false;
}
/**
* 继续动画
*/
public void reStart()
{
mIsThreadRunning = false;
}
/**
* 设置动画监听器
*/
public void setOnFrameFinisedListener(OnFrameFinishedListener onFrameFinishedListener)
{
this.mOnFrameFinishedListener = onFrameFinishedListener;
}
/**
* 动画监听器
* @author qike
*
*/
public interface OnFrameFinishedListener {
/**
* 动画开始
*/
void onStart();
/**
* 动画结束
*/
void onStop();
}
/**
* 当用户点击返回按钮时,停止线程,反转内存溢出
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
// 当按返回键时,将线程停止,避免surfaceView销毁了,而线程还在运行而报错
if (keyCode == KeyEvent.KEYCODE_BACK)
{
mIsThreadRunning = false;
}
return super.onKeyDown(keyCode, event);
}
}
布局文件activity_main.xml,同样很简单:
<RelativeLayout 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:background="@android:color/white"
tools:context="${relativePackage}.${activityClass}" >
<com.example.frameanidemo.view.FrameAnimation
android:id="@+id/ani_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
最后是activity:
public class CustomFrameActivity extends Activity {
protected static final String TAG = "Custom";
private FrameAnimation frameView;
//动画资源文件
int[] srcId =
{ R.drawable.penguin_happy_3_1, R.drawable.penguin_happy_3_2, R.drawable.penguin_happy_3_3,
R.drawable.penguin_happy_3_4, R.drawable.penguin_happy_3_5, R.drawable.penguin_happy_3_6,
R.drawable.penguin_happy_3_7, R.drawable.penguin_happy_3_8, R.drawable.penguin_happy_3_9,
R.drawable.penguin_happy_3_10, R.drawable.penguin_happy_3_11, R.drawable.penguin_happy_3_12,
R.drawable.penguin_happy_3_13 };
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
frameView = (FrameAnimation) findViewById(R.id.ani_view);
frameView.setBitmapResoursID(srcId);
//设置监听事件
frameView.setOnFrameFinisedListener(new FrameAnimation.OnFrameFinishedListener() {
@Override
public void onStop()
{
Log.e(TAG, "stop");
}
@Override
public void onStart()
{
Log.e(TAG, "start");
Log.e(TAG, Runtime.getRuntime().totalMemory() / 1024 + "k");
}
});
frameView.start();
}
}
代码已经附上,看下这种方式所占的内存只有2.8M,比原生的6.08M节省了不是一丁点,这种差距尤其是在大量图片的播放中更是明显,经测试原生帧动画播放超过20张图片就非常危险,而我们自定义的与图片的多寡并无区别:
源码下载:http://download.csdn.net/detail/q649381130/9212135