Android体系自身提供了帧动画的完成:animation-list; 比方下面的:

<?xml version="1.0" encoding="utf-8"?>
<!--FrameAnimator-->
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item
        android:drawable="@drawable/z1"
        android:duration="100" />
    <item
        android:drawable="@drawable/z2"
        android:duration="100" />
    <item
        android:drawable="@drawable/z3"
        android:duration="100" />
    <!--...-->
</animation-list

这个动画使用场景在图片较小,帧数较小的场景下。 不适用于,帧数多,图片大的场景下使用,简单触发内存溢出(OOM)问题。

因此在大图片的帧动画完成必须自定义完成!

帧动画 实际是次序图片调集按次序播映,完成的放电影作用。

  1. 显现载体选用ImageView,并采用 SetBitmap的方法(实际使用发现在高频率设置ImageResource方法会导致UI界面不刷新的bug),并且经过Bitmap,也便于自己操控内存的消耗,解决OOM危险;
  2. 帧加载战略:注意不是完好加载一切帧,只创立待显现的帧和行将显现的帧。图片加载成Bitmap的格局待显现,并创立一个Bitmap的缓存调集,操控Bitmap的创立收回战略,操控内存不盲目扩张,导致APP运行OOM。
  3. 图片资源转Bitmap放在子线程处理,需求提早创立“用于生产Bitmap”的线程,且是单线程,防止动画过程中因线程动态创立耗时卡冻,影响动画流畅性;同时把创立Bitmap任务设置在子线程中,防止了主线程因超负荷加载图片导致的卡死危险。 提高了动画的容错率。
  4. 设置播映进展 完成动画进展自在操控。

完好流程编码如下:

Bitmap创立线程完成:

package com.cw.widget.fanim;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
 * 作用:提早创立子线程 用来创立Bitmap,节省动态创立线程的耗时。
 *
 * @author liuwenbo
 */
public class BitmapCreator {
    private static final String TAG = "BitmapCreator";
    private static final Boolean isDebug = false;
    private final ScheduledExecutorService executorService;
    private static BitmapCreator instance = new BitmapCreator();
    public static BitmapCreator getInstance() {
        return instance;
    }
    private BitmapCreator() {
        executorService = new ScheduledThreadPoolExecutor(1,
                new BasicThreadFactory.Builder()
                        .namingPattern("createBitmap-schedule-pool-%d").daemon(true).build());
    }
    /**
     * 子线程创立Bitmap。  最大耗时50ms, 不阻塞主线程
     *
     * @param context
     * @param resId
     * @return
     */
    public Bitmap createBitmap(Context context, int resId) {
        Bitmap bitmap = null;
        long startTime = System.currentTimeMillis();
        try {
            Future<Bitmap> bitmapFuture = executorService.submit(() -> BitmapFactory.decodeResource(context.getResources(), resId));
            bitmap = bitmapFuture.get(50, TimeUnit.MILLISECONDS);
            Log.i(TAG, "isCancelled == " + bitmapFuture.isCancelled() + " -- isDone == " + bitmapFuture.isDone());
            if (!bitmapFuture.isCancelled() && !bitmapFuture.isDone()) {
                bitmapFuture.cancel(true);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        long endTime = System.currentTimeMillis();
        long dTime = endTime - startTime;
        if (isDebug) {
            Log.e(TAG, "createBitmap use time == " + dTime);
        }
        return bitmap;
    }
}

Bitmap帧管理类:

package com.cw.widget.fanim;
import android.graphics.Bitmap;
import android.util.Log;
import android.widget.ImageView;
import com.cw.widget.R;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
 * 帧专成Bitmap的方法设置图像。 防止SetImageRes不显现图像
 *
 * @author liuwenbo
 */
public class FrameBitmapManager {
    private static final boolean isDebug = false;
    private static final String TAG = "FrameBitmap";
    private static final int CHECK_SIZE = 5;
    private final List<Integer> sourceList;
    private final ImageView frameImageView;
    private HashMap<Integer, Bitmap> cacheMap = new HashMap<>();
    public FrameBitmapManager(ImageView frameImageView, List<Integer> sourceList) {
        this.sourceList = sourceList;
        this.frameImageView = frameImageView;
        BitmapCreator.getInstance();
    }
    public void setProgress(float progress) {
        long startTime = System.currentTimeMillis();
        int index = progressToIndex(progress);
        int resId = sourceList.get(index);
        Bitmap bitmap = getResourceBitmap(resId);
        if (bitmap == null) {
            Log.e(TAG, "bitmap is null, No set frame image");
            return;
        }
        Object tagId = frameImageView.getTag(R.id.view_tag);
        Object oldTagBitmap = frameImageView.getTag(R.id.cb_item_tag);
        frameImageView.setImageBitmap(bitmap);
        frameImageView.setTag(R.id.view_tag, resId);
        frameImageView.setTag(R.id.cb_item_tag, bitmap);
        if (tagId != null && tagId instanceof Integer && oldTagBitmap != null &&
                oldTagBitmap instanceof Bitmap) {
            if (!((Bitmap) oldTagBitmap).isRecycled()) {
                setCacheData((Integer) tagId, (Bitmap) oldTagBitmap);
            }
        }
        long endTime = System.currentTimeMillis();
        if (isDebug) {
            Log.e(TAG, "setProgress  time == " + (endTime - startTime));
        }
    }
    private void setCacheData(Integer key, Bitmap bitmap) {
        if (cacheMap.size() > CHECK_SIZE) {
            for (Iterator<Map.Entry<Integer, Bitmap>> it = cacheMap.entrySet().iterator(); it.hasNext(); ) {
                Map.Entry<Integer, Bitmap> item = it.next();
                Bitmap map = item.getValue();
                if (map != null && !map.isRecycled()) {
                    map.recycle();
                }
                it.remove();
                break;
            }
        }
        cacheMap.put(key, bitmap);
    }
    private int progressToIndex(float progress) {
        int size = sourceList == null ? 0 : sourceList.size();
        int index = (int) (progress * size);
        if (index <= 0) {
            return 0;
        } else if (index > size - 1) {
            return size - 1;
        } else {
            return index;
        }
    }
    private Bitmap getResourceBitmap(int resId) {
        Object value = cacheMap.remove(resId);
        if (value != null) {
            if (isDebug) {
                Log.e(TAG, "use cache");
            }
            return (Bitmap) value;
        } else {
            return BitmapCreator.getInstance().createBitmap(frameImageView.getContext(), resId);
        }
    }
    public void release() {
        for (Iterator<Map.Entry<Integer, Bitmap>> it = cacheMap.entrySet().iterator(); it.hasNext(); ) {
            Map.Entry<Integer, Bitmap> item = it.next();
            Bitmap map = item.getValue();
            if (map != null && !map.isRecycled()) {
                map.recycle();
            }
            it.remove();
        }
    }
}

应用在ImageView的实例:

package com.cw.widget.fanim
import android.content.Context
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.util.AttributeSet
import android.view.View
import androidx.appcompat.widget.AppCompatImageView
/**
 * 帧动画View
 */
class FrameAnimImageView : AppCompatImageView {
    constructor(context: Context) : super(context) {}
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {}
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    ) {
    }
    private val frameRatio = 50;
    private val msgGoNext = 1;
    var isLooping = false;
    private var isPlaying = false;
    private var userPlayControl = false;
    private var currentProcess: Int = 0;
    private val frameList = ArrayList<Int>();
    private val aminHelper = FrameBitmapManager(this, frameList);
    private val playHandler = object : Handler(Looper.getMainLooper()) {
        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
            if (msg.what == msgGoNext) {
                var p = currentProcess + 1;
                var isEnd = false
                if (p > 100) {
                    if (isLooping) {
                        p = 0;
                    } else {
                        p = 100;
                        isEnd = true;
                    }
                }
                setProgress(p)
                if (isPlaying && !isEnd) {
                    sendEmptyMessageDelayed(msgGoNext, 1000L / frameRatio)
                }
            }
        }
    }
    fun setFrameResource(res: List<Int>) {
        frameList.clear()
        if (res.isNotEmpty()) {
            frameList.addAll(res)
        }
    }
    /**
     * 0-100;
     */
    fun setProgress(process: Int) {
        currentProcess = process
        aminHelper.setProgress(process / 100f)
    }
    fun start() {
        userPlayControl = true;
        play()
    }
    fun stop() {
        userPlayControl = false
        pause()
    }
    private fun play() {
        isPlaying = true;
        playHandler.sendEmptyMessage(msgGoNext)
    }
    private fun pause() {
        isPlaying = false;
        playHandler.removeMessages(msgGoNext)
    }
    private fun release() {
        pause()
        playHandler.removeCallbacksAndMessages(null)
        aminHelper.release()
    }
    override fun onWindowVisibilityChanged(visibility: Int) {
        super.onWindowVisibilityChanged(visibility)
        if (visibility == View.VISIBLE && userPlayControl) {
            play()
        } else {
            release()
        }
    }
}

此设计适用范围:帧图较多,较大,动画随用户可控,页面切换动画作用 实际作用展现:

Android-图片帧实现帧动画