最近有一个项目,有很多组帧动画,每一组大概60帧左右,再不同的状态下回调不同的接口,动态的切换到不同的帧动画,但是不管每一帧只有10K左右,使用软引用,提前加载帧动画,这些操作都试过了,但是再切换动画的时候还是有不时的有卡顿。由于图片过多,效果异常卡顿,所以在这篇文章中就说一下帧动画的优化问题。 首先还是先来看一下android原生的帧动画的实现,代码如下:
(1)帧动画的资源文件 放入res/drawable下
(2)布局文件
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.fareast.dealframeanimation.MainActivity">
(3)Java代码实现开启帧动画//animationImg为ImageView的控件
AnimationDrawable animationDrawable= (AnimationDrawable) animationImg.getDrawable();
animationDrawable.start();
以上是android原生实现帧动画的,如果图片比较少可以这样使用,但是图片过多的话在低端机上就会造成卡顿,甚至会出现OOM异常,为了解决这个问题,可以把帧动画的图片一张张的分开,读到哪一张图就加载哪一张图,加载后把上一张图片释放。具体代码如下:(1)帧动画的资源文件 放入res/values/arrays.xml文件中 如果没有arrays.xml文件就新建一个
- @drawable/animation2
- @drawable/animation3
- @drawable/animation4
- @drawable/animation5
- @drawable/animation6
- @drawable/animation7
- @drawable/animation8
- @drawable/animation9
- @drawable/animation10
- @drawable/animation11
- @drawable/animation12
- @drawable/animation13
- @drawable/animation14
- @drawable/animation15
- @drawable/animation16
- @drawable/animation17
- @drawable/animation18
- @drawable/animation19
- @drawable/animation20
- @drawable/animation21
- @drawable/animation22
- @drawable/animation23
- @drawable/animation24
- @drawable/animation25
- @drawable/animation26
- @drawable/animation27
- @drawable/animation28
- @drawable/animation29
- @drawable/animation30
- @drawable/animation31
- @drawable/animation32
- @drawable/animation33
- @drawable/animation34
- @drawable/animation35
- @drawable/animation36
- @drawable/animation37
- @drawable/animation38
- @drawable/animation39
- @drawable/animation40
- @drawable/animation41
- @drawable/animation42
- @drawable/animation43
- @drawable/animation44
- @drawable/animation45
- @drawable/animation46
- @drawable/animation47
- @drawable/animation48
- @drawable/animation49
- @drawable/animation50
- @drawable/animation51
- @drawable/animation52
- @drawable/animation53
- @drawable/animation54
- @drawable/animation55
- @drawable/animation56
- @drawable/animation57
- @drawable/animation58
- @drawable/animation59
- @drawable/animation60
- @drawable/animation61
- @drawable/animation62
- @drawable/animation63
- @drawable/animation64
- @drawable/animation65
- @drawable/animation66
- @drawable/animation67
- @drawable/animation68
- @drawable/animation69
- @drawable/animation70
- @drawable/animation71
(2)布局文件同上面的原生帧动画的布局文件一样,这里不再多做声明。创建一个类继承自Application 在这个类中获取全局上下文,构建单例,别忘记在AndroidManifest.xml中注册Application否则无效,代码如下:
private static Context mContext;
@Override public void onCreate() {
super.onCreate();
// Fresco.initialize(this);//Fresco初始化
MyApplication.mContext = getApplicationContext();
}
public static Context getAppContext() {
return MyApplication.mContext;
}
}(3)功能类 在此处理帧动画的加载的问题 播放 暂停帧动画的功能,具体情况如下代码:public class AnimationsContainer {
public int FPS = 40; // 每秒播放帧数,fps = 1/t,t-动画两帧时间间隔
private int resId = R.array.loading_anim; //图片资源
private Context mContext = MyApplication.getAppContext();
// 单例
private static AnimationsContainer mInstance;
public AnimationsContainer(){
}
//获取单例
public static AnimationsContainer getInstance(int resId, int fps) {
if (mInstance == null){
mInstance = new AnimationsContainer();
}
mInstance.setResId(resId, fps);
return mInstance;
}
public void setResId(int resId, int fps){
this.resId = resId;
this.FPS = fps;
}
// // 从xml中读取资源ID数组
// private int[] mProgressAnimFrames = getData(resId);
/**
* @param imageView
* @return progress dialog animation
*/
public FramesSequenceAnimation createProgressDialogAnim(ImageView imageView) {
return new FramesSequenceAnimation(imageView, getData(resId), FPS);
}
/**
* 循环读取帧---循环播放帧
*/
public class FramesSequenceAnimation {
private int[] mFrames; // 帧数组
private int mIndex; // 当前帧
private boolean mShouldRun; // 开始/停止播放用
private boolean mIsRunning; // 动画是否正在播放,防止重复播放
private SoftReference mSoftReferenceImageView; // 软引用ImageView,以便及时释放掉
private Handler mHandler;
private int mDelayMillis;
private OnAnimationStoppedListener mOnAnimationStoppedListener; //播放停止监听
private Bitmap mBitmap = null;
private BitmapFactory.Options mBitmapOptions;//Bitmap管理类,可有效减少Bitmap的OOM问题
public FramesSequenceAnimation(ImageView imageView, int[] frames, int fps) {
mHandler = new Handler();
mFrames = frames;
mIndex = -1;
mSoftReferenceImageView = new SoftReference(imageView);
mShouldRun = false;
mIsRunning = false;
mDelayMillis = 1000 / fps;//帧动画时间间隔,毫秒
imageView.setImageResource(mFrames[0]);
// 当图片大小类型相同时进行复用,避免频繁GC
if (Build.VERSION.SDK_INT >= 11) {
Bitmap bmp = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
int width = bmp.getWidth();
int height = bmp.getHeight();
Bitmap.Config config = bmp.getConfig();
mBitmap = Bitmap.createBitmap(width, height, config);
mBitmapOptions = new BitmapFactory.Options();
//设置Bitmap内存复用
mBitmapOptions.inBitmap = mBitmap;//Bitmap复用内存块,类似对象池,避免不必要的内存分配和回收
mBitmapOptions.inMutable = true;//解码时返回可变Bitmap
mBitmapOptions.inSampleSize = 1;//缩放比例
}
}
//循环读取下一帧
private int getNext() {
mIndex++;
//设置只播放一次动画
// if(mIndex==mFrames.length-1){
// stop();
// }
if (mIndex >= mFrames.length)
mIndex = 0;
return mFrames[mIndex];
}
/**
* 播放动画,同步锁防止多线程读帧时,数据安全问题
*/
public synchronized void start() {
mShouldRun = true;
if (mIsRunning)
return;
Runnable runnable = new Runnable() {
@Override
public void run() {
ImageView imageView = mSoftReferenceImageView.get();
if (!mShouldRun || imageView == null) {
mIsRunning = false;
if (mOnAnimationStoppedListener != null) {
mOnAnimationStoppedListener.AnimationStopped();
}
return;
}
mIsRunning = true;
//新开线程去读下一帧
mHandler.postDelayed(this, mDelayMillis);
if (imageView.isShown()) {
int imageRes = getNext();
if (mBitmap != null) { // so Build.VERSION.SDK_INT >= 11
Bitmap bitmap = null;
try {
bitmap = BitmapFactory.decodeResource(imageView.getResources(), imageRes, mBitmapOptions);
} catch (Exception e) {
e.printStackTrace();
}
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
} else {
imageView.setImageResource(imageRes);
mBitmap.recycle();
mBitmap = null;
}
} else {
imageView.setImageResource(imageRes);
}
}
}
};
mHandler.post(runnable);
}
/**
* 停止播放
*/
public synchronized void stop() {
mShouldRun = false;
}
/**
* 设置停止播放监听
* @param listener
*/
public void setOnAnimStopListener(OnAnimationStoppedListener listener){
this.mOnAnimationStoppedListener = listener;
}
}
/**
* 从xml中读取帧数组
* @param resId
* @return
*/
private int[] getData(int resId){
TypedArray array = mContext.getResources().obtainTypedArray(resId);
int len = array.length();
int[] intArray = new int[array.length()];
for(int i = 0; i < len; i++){
intArray[i] = array.getResourceId(i, 0);
}
array.recycle();
return intArray;
}
/**
* 停止播放监听
*/
public interface OnAnimationStoppedListener{
void AnimationStopped();
}
}(4)调用优化后的帧动画:public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button startBtn;
private ImageView animationImg;
private AnimationsContainer.FramesSequenceAnimation animation;
private boolean start = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startBtn=findViewById(R.id.start_btn);
animationImg=findViewById(R.id.animation_img);
startBtn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if(animation == null){
//优化后的帧动画
animation = AnimationsContainer.getInstance(R.array.loading_anim, 40).createProgressDialogAnim(animationImg);
}
if(!switchBtn()){
animation.start();
}else {
animation.stop();
}
}
//控制开关
private boolean switchBtn(){
boolean returnV = start;
start = !start;
return returnV;
}
}
此方案亲测可行,流畅,需注意的是fps为帧数而不是时间间隔噢。