android 动画有几种不同的类型,其中有一种是帧动画。实现它的方式也有几种,一种是直接作出 gif 或 webP 格式的图片,直接播放;一种则是 android 系统提供封装好的方法,将动画定义在 xml 中,用 animation-list 标签来实现它,<animation-list> 元素是必要的,可以包含n个 <item> 元素,每个 item 代表一帧动画。 以上两种都能实现,但比较耗费内存,想想看,假如说帧动画有 100 张图片,每张图片都比较大的情况下,此时执行动画,瞬间加载这么多图片,内存会出现什么问题,很大的几率会 OOM。
有没有节省内存的方法?帧动画说白了就是固定时间的刷新一个ImageView,显示不同的图片,既然知道了这些,那么我们可以使用 Handler ,每隔100毫秒就发送一个通知,接收到通知后获取一个指定的图片资源,刷新到ImageView上面,这样就避免了瞬间加载多张图片资源的问题了;如果再进一步优化,我们可以获取图片资源时,使用缓存把获取到的图片存起来,这样就不必每次都从资源里面获取,先判断缓存中有没有;再进一步,我们可以把中封装起来,熟悉自定义控件的同学,我们可以直接继承 View,来实现这个逻辑。图片绘制说白了就是对 Drawable 的绘制,之前文章中也提到过,这里就不多说了,直接上代码,主要是提供一个思路。
public class AnimationView extends View {
private final static String KEY_MARK = "0";
private int[] mFrameRess;
private int mDuration;
private int mLastFrame;
private int mCurrentFrame;
private boolean mPause;
private Rect mDst;
private BitmapLRU mLruCache;
public AnimationView(Context context) {
this(context, null);
}
public AnimationView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AnimationView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
TypedArray typedArray = getResources().obtainTypedArray(R.array.animation_1);
int len = typedArray.length();
int[] resId = new int[len];
for (int i = 0; i < len; i++) {
resId[i] = typedArray.getResourceId(i, -1);
}
typedArray.recycle();
this.mFrameRess = resId;
this.mDuration = 50;
this.mLastFrame = resId.length - 1;
mLruCache = BitmapLRU.getInstance();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
pauseAnimation();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mDst = new Rect(0, 0, w, h);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
Drawable d = takeDrawable();
d.setBounds(mDst);
d.draw(canvas);
canvas.restore();
}
private Drawable takeDrawable(){
int resid = mFrameRess[mCurrentFrame];
String key = KEY_MARK + resid;
Drawable drawable = mLruCache.get(key);
if(drawable == null){
drawable = getResources().getDrawable(resid);
mLruCache.put(key, drawable);
}
return drawable;
}
public void play(final int i) {
postDelayed(new Runnable() {
@Override
public void run() {
mCurrentFrame = i;
if (mPause) {
return;
}
invalidate();
if (i == mLastFrame) {
play(0);
} else {
play(i + 1);
}
}
}, mDuration);
}
public void pauseAnimation() {
this.mPause = true;
}
}
public class BitmapLRU {
private int maxMemory = (int) Runtime.getRuntime().maxMemory();
private int cacheSize = maxMemory / 18;
private LruCache<String, Drawable> mCache;
private BitmapLRU(){
mCache = new LruCache<String, Drawable>(cacheSize){
@Override
protected int sizeOf(String key, Drawable value) {
return value.getIntrinsicWidth() * value.getIntrinsicHeight();
}
};
}
private static BitmapLRU single = new BitmapLRU();
public static BitmapLRU getInstance(){
return single;
}
public void put(String key, Drawable value){
mCache.put(key, value);
}
public Drawable get(String key){
return mCache.get(key);
}
}
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
<array name="animation_1">
<item>@drawable/da_00</item>
<item>@drawable/da_01</item>
<item>@drawable/da_02</item>
<item>@drawable/da_03</item>
<item>@drawable/da_04</item>
<item>@drawable/da_05</item>
<item>@drawable/da_06</item>
<item>@drawable/da_07</item>
<item>@drawable/da_08</item>
<item>@drawable/da_09</item>
<item>@drawable/da_10</item>
</array>
</resources>
在外面使用 animationView.play(0); 即可。