这是我发表的第一篇文章,如有不对之处,请大家多多指教。
最近项目有个需求,做一个序列帧动画的播放(3600张图片的播放,这么多图片,对内存,对cpu是一个考验)。我在项目的开发过程中,探索了很多方法,思路,下面我会一一介绍。

1.Android 原生方法

Android 原生方法适用于:图片小(分辨率小) 、内存小。这种方法会一次加载所有图片,对于序列帧图片大,并且在数量多的情况下,手机cpu与内存占用比较高,容易引起OOM。使用方法如下:

1.创建animation-list资源

在drawable资源下创建一个名为“wel.xml”的资源文件,代码如下

<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="true"><item android:duration="30" android:drawable="@drawable/m_00000" /><item android:duration="30" android:drawable="@drawable/m_00001" /><item android:duration="30" android:drawable="@drawable/m_00002" /><item android:duration="30" android:drawable="@drawable/m_00003" /><item android:duration="30" android:drawable="@drawable/m_00004" /><item android:duration="30" android:drawable="@drawable/m_00005" /><item android:duration="30" android:drawable="@drawable/m_00006" /><item android:duration="30" android:drawable="@drawable/m_00007" /><item android:duration="30" android:drawable="@drawable/m_00008" /></animation-list>

其中duration代表帧数 , oneshot代表是否循环播放,true为只播放一次,可以动态设置。

2.java代码中应用

image = findViewById(R.id.image);           // 布局文件需要提前创建一个imageView
image.setImageResource(R.drawable.wel);
anim = (AnimationDrawable) image1.getDrawable();  //创建全局变量  AnimationDrawable anim;
anim.setOneShot(true);     //true-播放一次,结束后停留在最后一帧, false为循环播发anim.start();         //开始播发
anim.stop();          //停止播发
anim.isRunning()      //是否播放
anim.getCurrent()     //当前播放帧数    返回的是一个drawable资源
anim.getNumberOfFrames()    // 序列帧总数
anim.getFrame(anim.getNumberOfFrames() -1)   //得到指定index 的drawable

2.SurfaceView播放序列帧

代码如下,注释的很清楚。注意的地方:设置30ms一张,实际会达不到这个效果,因为图片从png,或者jpeg格式转成png也是需要一部分时间的。
这个方法我借鉴了网上一位大神的写法,他给我提供了一些思路,链接如下:

https://blog.csdn.net/flowerff/article/details/83758695

public class SurfaceViewAnimation extends SurfaceView implements Runnable {private String TAG = "SurfaceViewAnimation";private SurfaceHolder mSurfaceHolder;private boolean mIsRunning = true; // 是否播发private int totalCount;//序列帧总数private Canvas mCanvas;private Bitmap mBitmap;//当前显示的图片private int mCurrentIndext;// 当前动画播放的位置private int mDuration = 30;// 每帧动画持续存在的时间public static boolean mIsDestroy = false;// 是否已经销毁private int[] mResourceIds;              // 图片资源id数组private ArrayList<String> mResourcePaths;// 图片资源path数组private OnAnimationListener mListener;// 动画监听事件private Thread thread;Rect mSrcRect, mDestRect;public SurfaceViewAnimation(Context context) {this(context, null);initView();}public SurfaceViewAnimation(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);initView();}public SurfaceViewAnimation(Context context, AttributeSet attrs) {this(context, attrs, 0);initView();}private void initView() {mSurfaceHolder = this.getHolder();mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_HARDWARE);mResourceIds = new int[1];}/*** 制图方法*/private void drawView() {if (mResourceIds == null && mResourcePaths == null) {Log.e(TAG, "resource is null");mIsRunning = false;return;}Log.d(TAG, "drawView: Thread id = " + Thread.currentThread().getId());SurfaceHolder surfaceHolder = mSurfaceHolder;// 锁定画布synchronized (surfaceHolder) {if (surfaceHolder != null) {mCanvas = surfaceHolder.lockCanvas();Log.d(TAG, "drawView: mCanvas= " + mCanvas);if (mCanvas == null) {return;}}try {if (surfaceHolder != null && mCanvas != null) {synchronized (mResourceIds) {if (mResourceIds != null && mResourceIds.length > 0) {mBitmap = BitmapUtil.decodeSampledBitmapFromResource(getResources(), mResourceIds[mCurrentIndext], getWidth(), getHeight());} else if (mResourcePaths != null && mResourcePaths.size() > 0) {mBitmap = BitmapFactory.decodeFile(mResourcePaths.get(mCurrentIndext));}}mBitmap.setHasAlpha(true);if (mBitmap == null) {return;}Paint paint = new Paint();paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));mCanvas.drawPaint(paint);paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));paint.setAntiAlias(true);paint.setStyle(Paint.Style.STROKE);mSrcRect = new Rect(0, 0, mBitmap.getWidth(), mBitmap.getHeight());mDestRect = new Rect(0, 0, getWidth(), getHeight());mCanvas.drawBitmap(mBitmap, mSrcRect, mDestRect, paint);// 播放到最后一张图片if (mCurrentIndext == totalCount - 1) {mIsRunning = false; //停止播发//设置重复播放//播放到最后一张,当前index置零//mCurrentIndext = 0;}}} catch (Exception e) {Log.d(TAG, "drawView: e =" + e.toString());e.printStackTrace();} finally {mCurrentIndext++;if (mCurrentIndext >= totalCount) {mCurrentIndext = 0;}if (mCanvas != null) {// 将画布解锁并显示在屏幕上if (getHolder() != null) {surfaceHolder.unlockCanvasAndPost(mCanvas);}}if (mBitmap != null) {//recycle通知底层(c++)回收,java有自己的回收机制  GCmBitmap.recycle();}}}}@Overridepublic void run() {if (mListener != null) {mListener.onStart();}while (mIsRunning) {drawView();try {Thread.sleep(mDuration);} catch (Exception e) {e.printStackTrace();}}if (mListener != null) {mListener.onStop();}}/*** 开始播放*/public void start() {if (!mIsDestroy) {mCurrentIndext = 0;mIsRunning = true;thread = new Thread(this);thread.start();} else {// 如果SurfaceHolder已经销毁抛出该异常try {throw new Exception("IllegalArgumentException:Are you sure the SurfaceHolder is not destroyed");} catch (Exception e) {e.printStackTrace();}}}/*** 防止内存泄漏, SurfaceHolder销毁时添加回调*/private void destroy() {mIsRunning = false;try {Thread.sleep(mDuration);} catch (InterruptedException e) {e.printStackTrace();}mIsDestroy = true;thread.interrupt();thread = null;if (mBitmap != null) {mBitmap.recycle();mBitmap = null;}if (mSurfaceHolder != null) {mSurfaceHolder.addCallback(null);}if (mListener != null) {mListener = null;}}/*** 设置动画播放素材的id,图片保存在res资源下** @param ints 图片资源id*/public void setResourceId(int[] ints) {synchronized (mResourceIds) {this.mResourceIds = ints;totalCount = ints.length;}}/*** 设置动画播放素材的路径,图片保存在车机本地** @param resourcePath*/public void setResourcePath(ArrayList resourcePath) {this.mResourcePaths = resourcePath;totalCount = resourcePath.size();}/*** 设置每帧时间*/public void setDuration(int duration) {this.mDuration = duration;}/*** 停止播发*/public void stop() {mIsRunning = false;}/*** 继续动画*/public void reStart() {mIsRunning = false;}/*** 设置动画监听器*/public void setListener(OnAnimationListener listener) {mListener = listener;}/*** 动画监听器** @author qike*/public interface OnAnimationListener {/*** 动画开始*/void onStart();/*** 动画结束*/void onStop();}
}

3.自定义Animation(!!!)

** 通过surfaceView的研究,发现动画其实就是一串连续的图片的切换。我们平时的图片资源是png,或者jpeg格式的,当使用imageView设置resource资源时,原理都是将图片转成bitMap格式,然后进行绘制,当图片量少,分辨率小使用一般的方法即可,但是当图片分辨率大,量大,这个时候播放的时间往往是图片转换的时间 + 我们播放的帧时间 > 理想时间。
**
** 于是我有一个想法,我可以一次性将自己所播放的图片资源转成bitMap资源,然后开启一个线程,指定时间,将imageView替换一次资源。可这样子会发现当图片过多的时候,内存占用过大,这对于手机而言,是不由好的,于是我想到分布加载,播放结束后,释放内存的方法,代码如下 **

/**
*图片加载类
*/
public class AnimationsContainer {private static final String TAG = "AnimationsContainer";private static AnimationsContainer mInstance;public static List<Bitmap> bitMapList = new ArrayList<>(); //我图片较多,所以我200为一轮加载,内存中永远占用0-199张资源,时刻准备加载private OnAnimationStoppedListener listener;private AnimationsContainer() {if (bitMapList .size() == 0) {getBit();}}/*** Get instance of AnimationsContainer.* @return AnimationsContainer.*/public static AnimationsContainer getInstance() {if (mInstance == null) {mInstance = new AnimationsContainer();}return mInstance;}private void getBit() {//第一轮加载,0-199,这部分不会释放,一直保存new Thread(() -> {try {Log.d(TAG, "Read data for the first time ");initList(Constant.WEL_L, bitMapList , 1);listener.readSuccess();} catch (Exception ex) {ex.printStackTrace();}}).start();}private void initList(String name, List<Bitmap> list, int index) {loop(name, list, index);}/**加载bitMap的接口,name代表path,index代表第几轮加载*/public void getList(String name, List<Bitmap> list, int index) {Log.d(TAG, "getList: Load data " + index);new Thread(() -> loop(name, list, index)).start();}private void loop(String name, List<Bitmap> list, int index) {num = (index - 1) * 200;  //我设置的是200为一轮加载sum = 200 * index;for (int i = num; i < sum; i++) {String str;if (i < Constant.TEN) {str = "0000" + i;} else if (i < Constant.ONE_HUNDRED) {str = "000" + i;} else {str = "00" + i;}//Constant.PATH  我图片资源保存在手机的路径     Constant.BMP  图片的后缀 .png或者其他格式String path = Constant.PATH + name + str + Constant.BMP;FileInputStream fs = null;try {fs = new FileInputStream(path);} catch (FileNotFoundException ex) {ex.printStackTrace();Log.i(TAG, "file:" + path + "  is error");}Bitmap bitmap = BitmapFactory.decodeStream(fs);try {fs.close();} catch (IOException ex) {ex.printStackTrace();}list.add(bitmap);}}/*** Set OnAnimStopListener** @param listener set listener.*/public void setOnAnimStopListener(OnAnimationStoppedListener listener) {this.listener = listener;}
}
/*
*播放动画的类
*/
public class FramesSequenceAnimation {private static final String TAG = "FramesSequenceAnimation";private int mIndex = 0; // 当前帧private boolean mShouldRun; // 开始/停止播放用private boolean mIsRunning; // 动画是否正在播放,防止重复播放private SoftReference<ImageView> mSoftReferenceImageView; // 软引用ImageView,以便及时释放掉private final Handler mHandler;private static final int M_DELAY_MILLIS = 30;List<Bitmap> imageList = new ArrayList<>();List<Bitmap> imageList2 = new ArrayList<>();List<Bitmap> imageList3 = new ArrayList<>();private OnAnimationStoppedListener listener;private int mLever = 0;private AnimationsContainer animationsContainer;private String name = "";   //资源路径/**** @param handler.* @param imageView.*/public FramesSequenceAnimation(Handler handler, ImageView imageView) {mHandler = handler;mIndex = -1;mSoftReferenceImageView = new SoftReference<>(imageView);mShouldRun = false;mIsRunning = false;animationsContainer = AnimationsContainer.getInstance();}//循环读取下一帧private int getNext() {mIndex++;//设置只播放一次动画int size = 0;if (mIndex > 199) {mLever ++;Log.i(TAG, "mLever = " + mLever);mIndex = 0;if (mLever == 1) {Log.i(TAG, "mLever = 1");//开始播放第二轮,加载第三轮animationsContainer.getList(name, imageList3, 3);}if (mLever == Constant.THREE) {Log.i(TAG, "mLever = 3");stop();}}return mIndex;}/*** 播发.*/public synchronized void start(String name) {mShouldRun = true;this.name = name;Log.d(TAG, "video start: name :" + name);//开始播放第一轮,加载第二轮animationsContainer.getList(name, imageList2, 2);if (mIsRunning) {return;}Runnable runnable = new Runnable() {@Overridepublic void run() {ImageView imageView = mSoftReferenceImageView.get();if (!mShouldRun || imageView == null) {mIsRunning = false;if (listener != null) {listener.stop();}return;}mIsRunning = true;mHandler.postDelayed(this, M_DELAY_MILLIS);if (imageView.isShown()) {int imageRes = getNext();switch (mLever) {case 0 :if (imageRes >= imageList.size()) {Log.i(TAG, "imageList have not prepared");return;}imageView.setImageBitmap(imageList.get(imageRes));break;case 1 :if (imageRes >= imageList2.size()) {Log.i(TAG, "imageList2 have not prepared");return;}imageView.setImageBitmap(imageList2.get(imageRes));break;case Constant.TWO :if (imageRes >= imageList3.size()) {Log.i(TAG, "imageList3 have not prepared");return;}imageView.setImageBitmap(imageList3.get(imageRes));break;default:stop();Log.d(TAG, "mLever is highest");}}}};mHandler.post(runnable);}/*** 结束.*/public synchronized void stop() {Log.d(TAG, "video stop");mLever = 0;mShouldRun = false;destroy(imageList2);destroy(imageList3);imageList2.clear();imageList3.clear();Log.d(TAG, "stop: clear success");}public void setImageList(List<Bitmap> imageList) {this.imageList = imageList;}public void setListener(OnAnimationStoppedListener listener) {this.listener = listener;}/** 播放结束时的回调*/private void destroy(List<Bitmap> list) {Log.d(TAG, "destroy: Recycle data");Bitmap bitmap = null;for (int i = 0; i < list.size(); i++) {bitmap = list.get(i);if (bitmap != null && !bitmap.isRecycled()) {bitmap.recycle();  //方法二中有介绍bitmap = null;}}System.gc();}
}

Android 序列帧动画相关推荐

  1. android序列帧动画纯代码,H5序列帧动画实现过程(附源码)

    H需朋者说上事是础一发一开程和开数的目前间5序列帧动画实现过程(新直能分支调二浏页器朋代说,事刚附源码) 序列帧动画 序列帧.轻厅设近幸松.备近幸松.备近幸松.备近动画,又称为逐帧动画,是使用多张连续 ...

  2. Shader学习17——序列帧动画

    序列帧动画实际就是动态地去改UV点就行,实现的时候注意一下怎么取行列就可以 Mar-05-2021 15-16-55.gif c#代码里控制_Process的赋值,从1-行*列总数循环即可.测试的时候 ...

  3. android 自定义loading,Android自定义动画-StarLoadingView

    今天来分享第二个自定义loading的动画,起了个名字叫 蹦跶的星星 ,还是老规矩先介绍,后上图. 实现效果在最后,GIF有点大,手机流量慎重. 介绍 首先声明做这个动画的初衷是为了学习和分享,所以从 ...

  4. android 三维动画效果,9款令人惊叹的HTML5 3D动画应用

    原标题:9款令人惊叹的HTML5 3D动画应用 之前我们已经向大家分享了很多HTML5动画应用了,大部分都非常炫酷,也有一小部分是很实用的.今天我们要向各位HTML5动画爱好者介绍更多的HTML5 3 ...

  5. 【学习笔记】Android视图动画学习

    2019独角兽企业重金招聘Python工程师标准>>> 1.Android View动画框架 Animation框架定义了透明度.旋转.缩放和位移几种常见的动画. 实现原理:每次绘制 ...

  6. android矢量动画 充电,android矢量动画

    android矢量动画! 直接来个例子就明白了!(这里我把与动画无关的属性都用-表示) 首先你要有个矢量图 比如这个矢量图xml文件叫"vector1",文件在res\drawab ...

  7. Android视图动画集合AndoridViewAnimations

    Android视图动画集合AndoridViewAnimations Android视图动画是针对视图对象的动画效果,包括对象平移.旋转.缩放和渐变四种类型.通过组合这四种类型,可以创建出无数种动画效 ...

  8. 使用android frame动画定义自己的ProgressBar

    使用android  frame动画定义自己的ProgressBar 在 res  /layout/ainm 目录下面建一个frame_ainm.xml文件 <?xml version=&quo ...

  9. android动画封装,Android属性动画封装,快速构建动画

    Android实现动画效果的方式主要有帧动画.补间动画.属性动画.关于安桌动画的基础知识可以查看这篇文章Android属性动画完全解析 这里我要讲的是如何快速构建出一个动画效果,如下图: 如果我们用属 ...

  10. Android m 自定义下拉菜单,Android实现动画效果的自定义下拉菜单功能

    我们在购物APP里面设置收货地址时,都会有让我们选择省份及城市的下拉菜单项.今天我将使用Android原生的 Spinner 控件来实现一个自定义的下拉菜单功能,并配上一个透明渐变动画效果. 要实现的 ...

最新文章

  1. MVC,三层架构,工厂模型,七层
  2. bugly怎么读_高级功能
  3. 利用gcc的__attribute__编译属性section子项构建初始化函数表【转】
  4. MySQL · 引擎特性 · InnoDB 事务子系统介绍
  5. amp 显示成转义字符 in html,如何在HTML标签中转换转义字符?(How to convert escape characters in HTML tags?)...
  6. 如何开发一个异常检测系统:如何评价一个异常检测算法
  7. c语言100位整数变量声明_C ++程序动态声明一个整数变量并打印其内存地址
  8. 排查 CI Unable to load the requested file
  9. 2011最新XP系统盘下载大全 都是2011最新的系统
  10. java 线性的排序算法_数据结构之排序算法Java实现(9)—— 线性排序之 基数排序算法...
  11. dnf全部使用_DNF:1.13拍卖最后1天物价,花瓣礼箱破千万,果然人人都是黑商
  12. PowerPoint2003制作抛物线动画的方法
  13. linux上tftp上传文件失败,Linux下tftp上传文件失败的几条原因
  14. 吉林银行2021年上半年经营成果丰硕 支持实体经济能力显著增强
  15. 字符串转json对象
  16. 追踪高频交易——华尔街猎狼者(上)
  17. 如何将枯燥的大数据变得生动有趣!
  18. linux下和嵌入式linux下通过udp接收来自vlc播放器的视频并转发播放
  19. DHU数据结构-顺序表- ADT应用-找出两个等长升序序列的中位数
  20. 《流浪地球》的职场隐喻

热门文章

  1. python财务预算分析_从审计转到财务分析是怎样一种体验?
  2. 元数据管理在数据仓库的实践应用
  3. 智慧公厕管理系统不断提升公厕管理水平
  4. 一个程序员眼中的项目经理
  5. vs2010解决方案源文件夹和头文件夹消失
  6. 热门论坛排行top100--2010年07月11日[转]
  7. Linux 系统设置静态ip地址
  8. Python 爬虫之代理服务器
  9. 高性能MySQL读书笔记——开天辟地
  10. I. 知识图谱 应用案例 --- 百度