最近公司有一个项目需求,需要在一个特定的android设备上做一组帧动画的效果,当时感觉这个功能很容易,相信大家都知道,android的原生帧动画实现方法,但是问题就出现在这,如果使用原生的帧动画,在手机上是没有问题的,但是在公司的设备上就非常卡顿,也就是低端机,由于图片过多,效果异常卡顿,所以在这篇文章中就说一下帧动画的优化问题。
首先还是先来看一下android原生的帧动画的实现,代码如下:
(1)帧动画的资源文件 放入res/drawable下
<?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@drawable/animation2" android:duration="30"/> <item android:drawable="@drawable/animation3" android:duration="30"/> <item android:drawable="@drawable/animation4" android:duration="30"/> <item android:drawable="@drawable/animation5" android:duration="30"/> <item android:drawable="@drawable/animation6" android:duration="30"/> <item android:drawable="@drawable/animation7" android:duration="30"/> <item android:drawable="@drawable/animation8" android:duration="30"/> <item android:drawable="@drawable/animation9" android:duration="30"/> <item android:drawable="@drawable/animation10" android:duration="30"/> <item android:drawable="@drawable/animation11" android:duration="30"/> <item android:drawable="@drawable/animation12" android:duration="30"/> <item android:drawable="@drawable/animation13" android:duration="30"/> <item android:drawable="@drawable/animation14" android:duration="30"/> <item android:drawable="@drawable/animation15" android:duration="30"/> <item android:drawable="@drawable/animation16" android:duration="30"/> <item android:drawable="@drawable/animation17" android:duration="30"/> <item android:drawable="@drawable/animation18" android:duration="30"/> <item android:drawable="@drawable/animation19" android:duration="30"/> <item android:drawable="@drawable/animation20" android:duration="30"/> <item android:drawable="@drawable/animation21" android:duration="30"/> <item android:drawable="@drawable/animation22" android:duration="30"/> <item android:drawable="@drawable/animation23" android:duration="30"/> <item android:drawable="@drawable/animation24" android:duration="30"/> <item android:drawable="@drawable/animation25" android:duration="30"/> <item android:drawable="@drawable/animation26" android:duration="30"/> <item android:drawable="@drawable/animation27" android:duration="30"/> <item android:drawable="@drawable/animation28" android:duration="30"/> <item android:drawable="@drawable/animation29" android:duration="30"/> <item android:drawable="@drawable/animation30" android:duration="30"/> <item android:drawable="@drawable/animation31" android:duration="30"/> <item android:drawable="@drawable/animation32" android:duration="30"/> <item android:drawable="@drawable/animation33" android:duration="30"/> <item android:drawable="@drawable/animation34" android:duration="30"/> <item android:drawable="@drawable/animation35" android:duration="30"/> <item android:drawable="@drawable/animation36" android:duration="30"/> <item android:drawable="@drawable/animation37" android:duration="30"/> <item android:drawable="@drawable/animation38" android:duration="30"/> <item android:drawable="@drawable/animation39" android:duration="30"/> <item android:drawable="@drawable/animation40" android:duration="30"/> <item android:drawable="@drawable/animation41" android:duration="30"/> <item android:drawable="@drawable/animation42" android:duration="30"/> <item android:drawable="@drawable/animation43" android:duration="30"/> <item android:drawable="@drawable/animation44" android:duration="30"/> <item android:drawable="@drawable/animation45" android:duration="30"/> <item android:drawable="@drawable/animation46" android:duration="30"/> <item android:drawable="@drawable/animation47" android:duration="30"/> <item android:drawable="@drawable/animation48" android:duration="30"/> <item android:drawable="@drawable/animation49" android:duration="30"/> <item android:drawable="@drawable/animation50" android:duration="30"/> <item android:drawable="@drawable/animation51" android:duration="30"/> <item android:drawable="@drawable/animation52" android:duration="30"/> <item android:drawable="@drawable/animation53" android:duration="30"/> <item android:drawable="@drawable/animation54" android:duration="30"/> <item android:drawable="@drawable/animation55" android:duration="30"/> <item android:drawable="@drawable/animation56" android:duration="30"/> <item android:drawable="@drawable/animation57" android:duration="30"/> <item android:drawable="@drawable/animation58" android:duration="30"/> <item android:drawable="@drawable/animation59" android:duration="30"/> <item android:drawable="@drawable/animation60" android:duration="30"/> <item android:drawable="@drawable/animation61" android:duration="30"/> <item android:drawable="@drawable/animation62" android:duration="30"/> <item android:drawable="@drawable/animation63" android:duration="30"/> <item android:drawable="@drawable/animation64" android:duration="30"/> <item android:drawable="@drawable/animation65" android:duration="30"/> <item android:drawable="@drawable/animation66" android:duration="30"/> <item android:drawable="@drawable/animation67" android:duration="30"/> <item android:drawable="@drawable/animation68" android:duration="30"/> <item android:drawable="@drawable/animation69" android:duration="30"/> <item android:drawable="@drawable/animation70" android:duration="30"/> <item android:drawable="@drawable/animation71" android:duration="30"/> </animation-list>
(2)布局文件
<?xml version="1.0" encoding="utf-8"?> <LinearLayout 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"> <Button android:id="@+id/start_btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="开始动画"/> <ImageView android:id="@+id/animation_img" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" /> </LinearLayout>
(3)Java代码实现开启帧动画
//animationImg为ImageView的控件 animationImg.setImageResource(R.drawable.animation_list); AnimationDrawable animationDrawable= (AnimationDrawable) animationImg.getDrawable(); animationDrawable.start();
以上是android原生实现帧动画的,如果图片比较少可以这样使用,但是图片过多的话在低端机上就会造成卡顿,甚至会出现OOM异常,为了解决这个问题,可以把帧动画的图片一张张的分开,读到哪一张图就加载哪一张图,加载后把上一张图片释放。具体代码如下:
(1)帧动画的资源文件 放入res/values/arrays.xml文件中 如果没有arrays.xml文件就新建一个
<?xml version="1.0" encoding="utf-8"?> <resources> <string-array name="loading_anim"> <item>@drawable/animation2</item> <item>@drawable/animation3</item> <item>@drawable/animation4</item> <item>@drawable/animation5</item> <item>@drawable/animation6</item> <item>@drawable/animation7</item> <item>@drawable/animation8</item> <item>@drawable/animation9</item> <item>@drawable/animation10</item> <item>@drawable/animation11</item> <item>@drawable/animation12</item> <item>@drawable/animation13</item> <item>@drawable/animation14</item> <item>@drawable/animation15</item> <item>@drawable/animation16</item> <item>@drawable/animation17</item> <item>@drawable/animation18</item> <item>@drawable/animation19</item> <item>@drawable/animation20</item> <item>@drawable/animation21</item> <item>@drawable/animation22</item> <item>@drawable/animation23</item> <item>@drawable/animation24</item> <item>@drawable/animation25</item> <item>@drawable/animation26</item> <item>@drawable/animation27</item> <item>@drawable/animation28</item> <item>@drawable/animation29</item> <item>@drawable/animation30</item> <item>@drawable/animation31</item> <item>@drawable/animation32</item> <item>@drawable/animation33</item> <item>@drawable/animation34</item> <item>@drawable/animation35</item> <item>@drawable/animation36</item> <item>@drawable/animation37</item> <item>@drawable/animation38</item> <item>@drawable/animation39</item> <item>@drawable/animation40</item> <item>@drawable/animation41</item> <item>@drawable/animation42</item> <item>@drawable/animation43</item> <item>@drawable/animation44</item> <item>@drawable/animation45</item> <item>@drawable/animation46</item> <item>@drawable/animation47</item> <item>@drawable/animation48</item> <item>@drawable/animation49</item> <item>@drawable/animation50</item> <item>@drawable/animation51</item> <item>@drawable/animation52</item> <item>@drawable/animation53</item> <item>@drawable/animation54</item> <item>@drawable/animation55</item> <item>@drawable/animation56</item> <item>@drawable/animation57</item> <item>@drawable/animation58</item> <item>@drawable/animation59</item> <item>@drawable/animation60</item> <item>@drawable/animation61</item> <item>@drawable/animation62</item> <item>@drawable/animation63</item> <item>@drawable/animation64</item> <item>@drawable/animation65</item> <item>@drawable/animation66</item> <item>@drawable/animation67</item> <item>@drawable/animation68</item> <item>@drawable/animation69</item> <item>@drawable/animation70</item> <item>@drawable/animation71</item> </string-array> </resources>
(2)布局文件同上面的原生帧动画的布局文件一样,这里不再多做声明。创建一个类继承自Application 在这个类中获取全局上下文,构建单例,别忘记在AndroidManifest.xml中注册Application否则无效,代码如下:
public class MyApplication extends 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<ImageView> 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>(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; } }以上为解决帧动画卡顿的总结和解决方案,如有问题请留言,谢谢各位。