-
2020-09-24 20:56:43
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); 即可。
更多相关内容 -
帧动画控件,优化帧动画加载的内存占用、解决帧动画销毁内存不释放的问题
2022-05-11 14:33:59优化帧动画加载的内存占用、解决帧动画销毁内存不释放的问题 帧动画的两大弊端 1.启动时加载所有图片,内存暴增 2.帧动画停止后无法回收内存 加载原理 每次只加载接下来要展示的图片 使用重复的内存区块读取图片,... -
android 帧动画 不卡顿加载
2016-10-27 11:33:56当我们需要定义一个帧动画的时候,如果我们的动画比较长,我们不可能把所有的图片都一下子加载在内存中,这样android系统会吃不消的,所以我们可以一帧一帧的加载图片 -
Android动画之逐帧动画(Frame Animation)实例详解
2021-01-05 22:54:41本文实例分析了Android动画之逐帧动画。分享给大家供大家参考,具体如下: 在开始实例讲解之前,先引用官方文档中的一段话: Frame动画是一系列图片按照一定的顺序展示的过程,和放电影的机制很相似,我们称为逐帧... -
Android性能优化 _ 帧动画OOM?优化帧动画之SurfaceView逐帧解析
2022-01-14 13:18:01优化帧动画之 SurfaceView逐帧解析 Android性能优化 | 大图做帧动画卡顿?优化帧动画之 SurfaceView滑动窗口式帧复用 Android性能优化 | 把构建布局用时缩短 20 倍(上) Android性能优化 | 把构建布局用时缩短 20 ...Android 提供了
AnimationDrawable
用于实现帧动画。在动画开始之前,所有帧的图片都被解析并占用内存,一旦动画较复杂帧数较多,在低配置手机上容易发生 OOM。即使不发生 OOM,也会对内存造成不小的压力。下面代码展示了一个帧数为4的帧动画:原生帧动画
AnimationDrawable drawable = new AnimationDrawable();
drawable.addFrame(getDrawable(R.drawable.frame1), frameDuration);
drawable.addFrame(getDrawable(R.drawable.frame2), frameDuration);
drawable.addFrame(getDrawable(R.drawable.frame3), frameDuration);
drawable.addFrame(getDrawable(R.drawable.frame4), frameDuration);
drawable.setOneShot(true);ImageView ivFrameAnim = ((ImageView) findViewById(R.id.frame_anim));
ivFrameAnim.setImageDrawable(drawable);
drawable.start();有没有什么办法让帧动画的数据逐帧加载,而不是一次性全部加载到内存?
SurfaceView
就提供了这种能力。SurfaceView
屏幕的显示机制和帧动画类似,也是一帧一帧的连环画,只不过刷新频率很高,感觉像连续的。为了显示一帧,需要经历计算和渲染两个过程,CPU 先计算出这一帧的图像数据并写入内存,然后调用 OpenGL 命令将内存中数据渲染成图像存放在 GPU Buffer 中,显示设备每隔一定时间从 Buffer 中获取图像并显示。
上述过程中的计算,对于
View
来说,就好比在主线程遍历 View树 以决定视图画多大(measure),画在哪(layout),画些啥(draw),计算结果存放在内存中,SurfaceFlinger 会调用 OpenGL 命令将内存中的数据渲染成图像存放在 GPU Buffer 中。每隔16.6ms,显示器从 Buffer 中取出帧并显示。所以自定义 View 可以通过重载onMeasure()
、onLayout()
、onDraw()
来定义帧内容,但不能定义帧刷新频率。SurfaceView
可以突破这个限制。而且它可以将计算帧数据放到独立的线程中进行。下面是自定义SurfaceView
的模版代码:public abstract class BaseSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
public static final int DEFAULT_FRAME_DURATION_MILLISECOND = 50;
//用于计算帧数据的线程
private HandlerThread handlerThread;
private Handler handler;
//帧刷新频率
private int frameDuration = DEFAULT_FRAME_DURATION_MILLISECOND;
//用于绘制帧的画布
private Canvas canvas;
private boolean isAlive;public BaseSurfaceView(Context context) {
super(context);
init();
}protected void init() {
getHolder().addCallback(this);
//设置透明背景,否则SurfaceView背景是黑的
setBackgroundTransparent();
}private void setBackgroundTransparent() {
getHolder().setFormat(PixelFormat.TRANSLUCENT);
setZOrderOnTop(true);
}@Override
public void surfaceCreated(SurfaceHolder holder) {
isAlive = true;
startDrawThread();
}@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}@Override
public void surfaceDestroyed(SurfaceHolder holder) {
stopDrawThread();
isAlive = false;
}//停止帧绘制线程
private void stopDrawThread() {
handlerThread.quit();
handler = null;
}//启动帧绘制线程
private void startDrawThread() {
handlerThread = new HandlerThread(“SurfaceViewThread”);
handlerThread.start();
handler = new Handler(handlerThread.getLooper());
handler.post(new DrawRunnable());
}private class DrawRunnable implements Runnable {
@Override
public void run() {
if (!isAlive) {
return;
}
try {
//1.获取画布
canvas = getHolder().lockCanvas();
//2.绘制一帧
onFrameDraw(canvas);
} catch (Exception e) {
e.printStackTrace();
} finally {
//3.将帧数据提交
getHolder().unlockCanvasAndPost(canvas);
//4.一帧绘制结束
onFrameDrawFinish();
}
//不停的将自己推送到绘制线程的消息队列以实现帧刷新
handler.postDelayed(this, frameDuration);
}
}protected abstract void onFrameDrawFinish();
protected abstract void onFrameDraw(Canvas canvas);
}- 用
HandlerThread
作为独立帧绘制线程,好处是可以通过与其绑定的Handler
方便地实现“每隔一段时间刷新”,而且在Surface
被销毁的时候可以方便的调用HandlerThread.quit()
来结束线程执行的逻辑。 DrawRunnable.run()
运用模版方法模式定义了绘制算法框架,其中帧绘制逻辑的具体实现被定义成两个抽象方法,推迟到子类中实现,因为绘制的东西是多样的,对于本文来说,绘制的就是一张张图片,所以新建BaseSurfaceView
的子类FrameSurfaceView
:
逐帧解析 & 及时回收
public class FrameSurfaceView extends BaseSurfaceView {
public static final int INVALID_BITMAP_INDEX = Integer.MAX_VALUE;
private List bitmaps = new ArrayList<>();
//帧图片
private Bitmap frameBitmap;
//帧索引
private int bitmapIndex = INVALID_BITMAP_INDEX;
private Paint paint = new Paint();
private BitmapFactory.Options options = new BitmapFactory.Options();
//帧图片原始大小
private Rect srcRect;
//帧图片目标大小
private Rect dstRect = new Rect();
private int defaultWidth;
private int defaultHeight;public void setDuration(int duration) {
int frameDuration = duration / bitmaps.size();
setFrameDuration(frameDuration);
}public void setBitmaps(List bitmaps) {
if (bitmaps == null || bitmaps.size() == 0) {
return;
}
this.bitmaps = bitmaps;
//默认情况下,计算第一帧图片的原始大小
getBitmapDimension(bitmaps.get(0));
}private void getBitmapDimension(Integer integer) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(this.getResources(), integer, options);
defaultWidth = options.outWidth;
defaultHeight = options.outHeight;
srcRect = new Rect(0, 0, defaultWidth, defaultHeight);
requestLayout();
}public FrameSurfaceView(Context context) {
super(context);
}@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
dstRect.set(0, 0, getWidth(), getHeight());
}@Override
protected void onFrameDrawFinish() {
//在一帧绘制完后,直接回收它
recycleOneFrame();
}//回收帧
private void recycleOneFrame() {
if (frameBitmap != null) {
frameBitmap.recycle();
frameBitmap = null;
}
}@Override
protected void onFrameDraw(Canvas canvas) {
//绘制一帧前需要先清画布,否则所有帧都叠在一起同时显示
clearCanvas(canvas);
if (!isStart()) {
return;
}
if (!isFinish()) {
drawOneFrame(canvas);
} else {
onFrameAnimationEnd();
}
}//绘制一帧,是张Bitmap
private void drawOneFrame(Canvas canvas) {
frameBitmap = BitmapUtil.decodeOriginBitmap(getResources(), bitmaps.get(bitmapIndex), options);
canvas.drawBitmap(frameBitmap, srcRect, dstRect, paint);
bitmapIndex++;
}private void onFrameAnimationEnd() {
reset();
}private void reset() {
bitmapIndex = INVALID_BITMAP_INDEX;
}//帧动画是否结束
private boolean isFinish() {
return bitmapInde
x >= bitmaps.size();
}//帧动画是否开始
private boolean isStart() {
return bitmapIndex != INVALID_BITMAP_INDEX;
_BITMAP_INDEX;
}//帧动画是否结束
private boolean isFinish() {
return bitmapInde[外链图片转存中…(img-Eug6Luq9-1642137464297)]
x >= bitmaps.size();
}//帧动画是否开始
private boolean isStart() {
return bitmapIndex != INVALID_BITMAP_INDEX; - 用
-
Android 使用帧动画内存溢出解决方案
2021-01-20 09:49:14Android 使用帧动画内存溢出解决方案 最近在项目遇到的动画效果不好实现,就让UI切成图,采用帧动画实现效果,但是在使用animation-list时,图片也就11张,每张图片大概560k左右,结果内存溢出,崩溃 了,自己用了三... -
Android性能优化 | 帧动画OOM?优化帧动画之SurfaceView逐帧解析
2020-05-15 21:33:46Android 提供了AnimationDrawable用于实现帧动画。在动画开始之前,所有帧的图片都被解析并占用内存,一旦动画较复杂帧数较多,在低配置手机上容易发生 OOM。即使不发生 OOM,也会对内存造成不小的压力。下面代码...Android 提供了
AnimationDrawable
用于实现帧动画。在动画开始之前,所有帧的图片都被解析并占用内存,一旦动画较复杂帧数较多,在低配置手机上容易发生 OOM。即使不发生 OOM,也会对内存造成不小的压力。下面代码展示了一个帧数为4的帧动画:原生帧动画
AnimationDrawable drawable = new AnimationDrawable(); drawable.addFrame(getDrawable(R.drawable.frame1), frameDuration); drawable.addFrame(getDrawable(R.drawable.frame2), frameDuration); drawable.addFrame(getDrawable(R.drawable.frame3), frameDuration); drawable.addFrame(getDrawable(R.drawable.frame4), frameDuration); drawable.setOneShot(true); ImageView ivFrameAnim = ((ImageView) findViewById(R.id.frame_anim)); ivFrameAnim.setImageDrawable(drawable); drawable.start();
有没有什么办法让帧动画的数据逐帧加载,而不是一次性全部加载到内存?
SurfaceView
就提供了这种能力。SurfaceView
屏幕的显示机制和帧动画类似,也是一帧一帧的连环画,只不过刷新频率很高,感觉像连续的。为了显示一帧,需要经历计算和渲染两个过程,CPU 先计算出这一帧的图像数据并写入内存,然后调用 OpenGL 命令将内存中数据渲染成图像存放在 GPU Buffer 中,显示设备每隔一定时间从 Buffer 中获取图像并显示。
上述过程中的计算,对于
View
来说,就好比在主线程遍历 View树 以决定视图画多大(measure),画在哪(layout),画些啥(draw),计算结果存放在内存中,SurfaceFlinger 会调用 OpenGL 命令将内存中的数据渲染成图像存放在 GPU Buffer 中。每隔16.6ms,显示器从 Buffer 中取出帧并显示。所以自定义 View 可以通过重载onMeasure()
、onLayout()
、onDraw()
来定义帧内容,但不能定义帧刷新频率。SurfaceView
可以突破这个限制。而且它可以将计算帧数据放到独立的线程中进行。下面是自定义SurfaceView
的模版代码:public abstract class BaseSurfaceView extends SurfaceView implements SurfaceHolder.Callback { public static final int DEFAULT_FRAME_DURATION_MILLISECOND = 50; //用于计算帧数据的线程 private HandlerThread handlerThread; private Handler handler; //帧刷新频率 private int frameDuration = DEFAULT_FRAME_DURATION_MILLISECOND; //用于绘制帧的画布 private Canvas canvas; private boolean isAlive; public BaseSurfaceView(Context context) { super(context); init(); } protected void init() { getHolder().addCallback(this); //设置透明背景,否则SurfaceView背景是黑的 setBackgroundTransparent(); } private void setBackgroundTransparent() { getHolder().setFormat(PixelFormat.TRANSLUCENT); setZOrderOnTop(true); } @Override public void surfaceCreated(SurfaceHolder holder) { isAlive = true; startDrawThread(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { stopDrawThread(); isAlive = false; } //停止帧绘制线程 private void stopDrawThread() { handlerThread.quit(); handler = null; } //启动帧绘制线程 private void startDrawThread() { handlerThread = new HandlerThread("SurfaceViewThread"); handlerThread.start(); handler = new Handler(handlerThread.getLooper()); handler.post(new DrawRunnable()); } private class DrawRunnable implements Runnable { @Override public void run() { if (!isAlive) { return; } try { //1.获取画布 canvas = getHolder().lockCanvas(); //2.绘制一帧 onFrameDraw(canvas); } catch (Exception e) { e.printStackTrace(); } finally { //3.将帧数据提交 getHolder().unlockCanvasAndPost(canvas); //4.一帧绘制结束 onFrameDrawFinish(); } //不停的将自己推送到绘制线程的消息队列以实现帧刷新 handler.postDelayed(this, frameDuration); } } protected abstract void onFrameDrawFinish(); protected abstract void onFrameDraw(Canvas canvas); }
- 用
HandlerThread
作为独立帧绘制线程,好处是可以通过与其绑定的Handler
方便地实现“每隔一段时间刷新”,而且在Surface
被销毁的时候可以方便的调用HandlerThread.quit()
来结束线程执行的逻辑。 DrawRunnable.run()
运用模版方法模式定义了绘制算法框架,其中帧绘制逻辑的具体实现被定义成两个抽象方法,推迟到子类中实现,因为绘制的东西是多样的,对于本文来说,绘制的就是一张张图片,所以新建BaseSurfaceView
的子类FrameSurfaceView
:
逐帧解析 & 及时回收
public class FrameSurfaceView extends BaseSurfaceView { public static final int INVALID_BITMAP_INDEX = Integer.MAX_VALUE; private List<Integer> bitmaps = new ArrayList<>(); //帧图片 private Bitmap frameBitmap; //帧索引 private int bitmapIndex = INVALID_BITMAP_INDEX; private Paint paint = new Paint(); private BitmapFactory.Options options = new BitmapFactory.Options(); //帧图片原始大小 private Rect srcRect; //帧图片目标大小 private Rect dstRect = new Rect(); private int defaultWidth; private int defaultHeight; public void setDuration(int duration) { int frameDuration = duration / bitmaps.size(); setFrameDuration(frameDuration); } public void setBitmaps(List<Integer> bitmaps) { if (bitmaps == null || bitmaps.size() == 0) { return; } this.bitmaps = bitmaps; //默认情况下,计算第一帧图片的原始大小 getBitmapDimension(bitmaps.get(0)); } private void getBitmapDimension(Integer integer) { final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(this.getResources(), integer, options); defaultWidth = options.outWidth; defaultHeight = options.outHeight; srcRect = new Rect(0, 0, defaultWidth, defaultHeight); requestLayout(); } public FrameSurfaceView(Context context) { super(context); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); dstRect.set(0, 0, getWidth(), getHeight()); } @Override protected void onFrameDrawFinish() { //在一帧绘制完后,直接回收它 recycleOneFrame(); } //回收帧 private void recycleOneFrame() { if (frameBitmap != null) { frameBitmap.recycle(); frameBitmap = null; } } @Override protected void onFrameDraw(Canvas canvas) { //绘制一帧前需要先清画布,否则所有帧都叠在一起同时显示 clearCanvas(canvas); if (!isStart()) { return; } if (!isFinish()) { drawOneFrame(canvas); } else { onFrameAnimationEnd(); } } //绘制一帧,是张Bitmap private void drawOneFrame(Canvas canvas) { frameBitmap = BitmapUtil.decodeOriginBitmap(getResources(), bitmaps.get(bitmapIndex), options); canvas.drawBitmap(frameBitmap, srcRect, dstRect, paint); bitmapIndex++; } private void onFrameAnimationEnd() { reset(); } private void reset() { bitmapIndex = INVALID_BITMAP_INDEX; } //帧动画是否结束 private boolean isFinish() { return bitmapIndex >= bitmaps.size(); } //帧动画是否开始 private boolean isStart() { return bitmapIndex != INVALID_BITMAP_INDEX; } //开始播放帧动画 public void start() { bitmapIndex = 0; } private void clearCanvas(Canvas canvas) { paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); canvas.drawPaint(paint); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); } }
FrameSurfaceView
继承自BaseSurfaceView
,所以它复用了基类的绘制框架算法,并且定了自己每一帧的绘制内容:一张Bitmap
。Bitmap
资源 id 通过setBitmaps()
传递进来, 绘制一帧解析一张 ,在每一帧绘制完毕后,调用Bitmap.recycle()
释放图片 native 内存并去除 java 堆中图片像素数据的引用。这样当 GC 发生时,图片像素数据可以及时被回收。
一切都是这么地能够自圆其说,我迫不及待地运行代码并打开
AndroidStudio
的Profiler
标签页,切换到MEMORY
,想用真实内存数据验证下性能。但残酷的事实狠狠地打了下脸。。。多次播放帧动画后,内存占用居然比原生AnimationDrawable
还大,而且每播放一次,内存中都会多出 N 个Bitmap
对象(N为帧动画总帧数)。唯一令人欣慰的是,手动触发 GC 后帧动画图片能够被回收。(AnimationDrawable
中的图片数据不会被 GC)原因就在于自作聪明地及时回收,每一帧绘制完后帧数据被回收,那下一帧解析
Bitmap
时只能新申请一块内存。帧动画每张图片大小是一致的,是不是能复用上一帧Bitmap
的内存空间?于是乎有了下面这个版本的FrameSurfaceView
:逐帧解析 & 帧复用
public class FrameSurfaceView extends BaseSurfaceView { public static final int INVALID_BITMAP_INDEX = Integer.MAX_VALUE; private List<Integer> bitmaps = new ArrayList<>(); private Bitmap frameBitmap; private int bitmapIndex = INVALID_BITMAP_INDEX; private Paint paint = new Paint(); private BitmapFactory.Options options; private Rect srcRect; private Rect dstRect = new Rect(); public void setDuration(int duration) { int frameDuration = duration / bitmaps.size(); setFrameDuration(frameDuration); } public void setBitmaps(List<Integer> bitmaps) { if (bitmaps == null || bitmaps.size() == 0) { return; } this.bitmaps = bitmaps; getBitmapDimension(bitmaps.get(0)); } private void getBitmapDimension(Integer integer) { final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(this.getResources(), integer, options); defaultWidth = options.outWidth; defaultHeight = options.outHeight; srcRect = new Rect(0, 0, defaultWidth, defaultHeight);; } public FrameSurfaceView(Context context) { super(context); } @Override protected void init() { super.init(); //定义解析Bitmap参数为可变类型,这样才能复用Bitmap options = new BitmapFactory.Options(); options.inMutable = true; } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); dstRect.set(0, 0, getWidth(), getHeight()); } @Override protected int getDefaultWidth() { return defaultWidth; } @Override protected int getDefaultHeight() { return defaultHeight; } @Override protected void onFrameDrawFinish() { //每帧绘制完毕后不再回收 // recycle(); } public void recycle() { if (frameBitmap != null) { frameBitmap.recycle(); frameBitmap = null; } } @Override protected void onFrameDraw(Canvas canvas) { clearCanvas(canvas); if (!isStart()) { return; } if (!isFinish()) { drawOneFrame(canvas); } else { onFrameAnimationEnd(); } } private void drawOneFrame(Canvas canvas) { frameBitmap = BitmapUtil.decodeOriginBitmap(getResources(), bitmaps.get(bitmapIndex), options); //复用上一帧Bitmap的内存 options.inBitmap = frameBitmap; canvas.drawBitmap(frameBitmap, srcRect, dstRect, paint); bitmapIndex++; } private void onFrameAnimationEnd() { reset(); } private void reset() { bitmapIndex = INVALID_BITMAP_INDEX; } private boolean isFinish() { return bitmapIndex >= bitmaps.size(); } private boolean isStart() { return bitmapIndex != INVALID_BITMAP_INDEX; } public void start() { bitmapIndex = 0; } private void clearCanvas(Canvas canvas) { paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); canvas.drawPaint(paint); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); } }
- 将
Bitmap
的解析参数inBitmap
设置为已经成功解析的Bitmap
对象以实现复用。
这一次不管重新播放多少次帧动画,内存中
Bitmap
数量只会增加1,因为只在解析第一张图片是分配了内存。而这块内存可以在FrameSurfaceView
生命周期结束时手动调用recycle()
回收。 - 用
-
spine 加载多个相同动画优化
2020-11-25 17:49:04cocos2dx升级spine3.8。解决多个相同动画同时加载卡顿的问题,自测可用,200个相同动画瞬间加载完成,不卡帧 -
Android 简单又炫酷的帧动画(加载动画)
2018-08-01 11:53:48博主声明: 转载请在开头附加本文链接及作者信息,并... Android 提供了一种简单而好用的帧动画,所谓帧动画,就是一帧一帧的播放,你可以想象一下民国时期那种电影片的效果,它用的就是一帧一帧的播放,说白了就是...博主声明:
转载请在开头附加本文链接及作者信息,并标记为转载。本文由博主 威威喵 原创,请多支持与指教。
Android 提供了一种简单而好用的帧动画,所谓帧动画,就是一帧一帧的播放,你可以想象一下民国时期那种电影片的效果,它用的就是一帧一帧的播放,说白了就是一张一张图片的播放,若两张图片切换的时间间隔非常短的话,我们眼睛看上去就会形成一种连贯性的动画,这就是称为帧动画。
当然了,前面说到的帧动画,其实是播放一张张的图片。所以呢,我们做这个效果之前,肯定需要把一个图片序列材料给准备好。我可以提供一个我自己的制作方式,图片序列咱们不好搞,直接去搜那种动态 gif 的图片,然后我们用 gif 分解器,把这些动画分解成一张张图片即可。
好了,上面介绍了帧动画,还有如何制作素材。下面,我们来看看要实现的效果图吧。
这种帧动画在很多应用中都会有,比如美团的加载动画,京东的加载动画等等,上面的效果也就是我从网上搜罗来的一个 gif 图片,然后分解掉得来的,个人感觉这个有点可爱,傻傻的,哈哈,所以选了这个。
首先,第一步要导入我们的图片,不用多说了。接下来,在 res -> drawable 下面建立的 anim.xml 的资源文件,我们需要在此设置图片的序列及每一帧持续的时间。
<?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false"> // 表示循环播放 <item android:drawable="@drawable/load0" android:duration="50"/> <item android:drawable="@drawable/load1" android:duration="50"/> <item android:drawable="@drawable/load2" android:duration="50" /> <item android:drawable="@drawable/load3" android:duration="50"/> <item android:drawable="@drawable/load4" android:duration="50"/> <item android:drawable="@drawable/load5" android:duration="50"/> <item android:drawable="@drawable/load6" android:duration="50"/> <item android:drawable="@drawable/load7" android:duration="50"/> <item android:drawable="@drawable/load8" android:duration="50"/> <item android:drawable="@drawable/load9" android:duration="50"/> <item android:drawable="@drawable/load10" android:duration="50"/> <item android:drawable="@drawable/load11" android:duration="50"/> <item android:drawable="@drawable/load12" android:duration="50"/> <item android:drawable="@drawable/load13" android:duration="50"/> <item android:drawable="@drawable/load14" android:duration="50"/> <item android:drawable="@drawable/load15" android:duration="50"/> <item android:drawable="@drawable/load16" android:duration="50"/> <item android:drawable="@drawable/load17" android:duration="50"/> <item android:drawable="@drawable/load18" android:duration="50"/> <item android:drawable="@drawable/load19" android:duration="50"/> <item android:drawable="@drawable/load20" android:duration="50"/> <item android:drawable="@drawable/load21" android:duration="50"/> <item android:drawable="@drawable/load22" android:duration="50"/> <item android:drawable="@drawable/load23" android:duration="50"/> <item android:drawable="@drawable/load24" android:duration="50"/> <item android:drawable="@drawable/load25" android:duration="50"/> <item android:drawable="@drawable/load26" android:duration="50"/> <item android:drawable="@drawable/load27" android:duration="50"/> <item android:drawable="@drawable/load28" android:duration="50"/> <item android:drawable="@drawable/load29" android:duration="50"/> <item android:drawable="@drawable/load30" android:duration="50"/> <item android:drawable="@drawable/load31" android:duration="50"/> <item android:drawable="@drawable/load32" android:duration="50"/> <item android:drawable="@drawable/load33" android:duration="50"/> <item android:drawable="@drawable/load34" android:duration="50"/> <item android:drawable="@drawable/load35" android:duration="50"/> <item android:drawable="@drawable/load36" android:duration="50"/> <item android:drawable="@drawable/load37" android:duration="50"/> <item android:drawable="@drawable/load38" android:duration="50"/> <item android:drawable="@drawable/load39" android:duration="50"/> <item android:drawable="@drawable/load40" android:duration="50"/> <item android:drawable="@drawable/load41" android:duration="50"/> <item android:drawable="@drawable/load42" android:duration="50"/> <item android:drawable="@drawable/load43" android:duration="50"/> <item android:drawable="@drawable/load44" android:duration="50"/> <item android:drawable="@drawable/load45" android:duration="50"/> <item android:drawable="@drawable/load46" android:duration="50"/> <item android:drawable="@drawable/load47" android:duration="50"/> <item android:drawable="@drawable/load48" android:duration="50"/> <item android:drawable="@drawable/load49" android:duration="50"/> <item android:drawable="@drawable/load50" android:duration="50"/> <item android:drawable="@drawable/load51" android:duration="50"/> <item android:drawable="@drawable/load52" android:duration="50"/> <item android:drawable="@drawable/load53" android:duration="50"/> <item android:drawable="@drawable/load54" android:duration="50"/> <item android:drawable="@drawable/load55" android:duration="50"/> <item android:drawable="@drawable/load56" android:duration="50"/> <item android:drawable="@drawable/load57" android:duration="50"/> <item android:drawable="@drawable/load58" android:duration="50"/> <item android:drawable="@drawable/load59" android:duration="50"/> <item android:drawable="@drawable/load60" android:duration="50"/> <item android:drawable="@drawable/load61" android:duration="50"/> <item android:drawable="@drawable/load62" android:duration="50"/> <item android:drawable="@drawable/load63" android:duration="50"/> <item android:drawable="@drawable/load64" android:duration="50"/> <item android:drawable="@drawable/load65" android:duration="50"/> <item android:drawable="@drawable/load66" android:duration="50"/> <item android:drawable="@drawable/load67" android:duration="50"/> <item android:drawable="@drawable/load68" android:duration="50"/> <item android:drawable="@drawable/load69" android:duration="50"/> <item android:drawable="@drawable/load70" android:duration="50"/> <item android:drawable="@drawable/load71" android:duration="50"/> </animation-list>
我这里把那个 gif 图片分解了,里面居然是 70 几张图片合成的,算了,既然选择了你,还是得搞一下。不过这里得说明一下,不建议图片太多,10 张以内就好了,而且图片也别太大了,容易导致 oom 的出现,它这个帧动画是把图片一帧一帧的绘制出来的,而我们放入的是 ImageView 里面,没做优化处理的话,不太行。
接下来,在主布局里面放一个 ImageView,背景设置为我们刚刚创建的 anim.xml 文件
<ImageView android:id="@+id/loading" android:layout_width="320dp" android:layout_height="200dp" android:layout_gravity="center" android:background="@drawable/loading_anim" />
好了,到了这一步,它还是不会自动播放的。接下来,我们需要用代码开启动画效果,很简单
ImageView loading = findViewById(R.id.loading); AnimationDrawable animationDrawable = (AnimationDrawable) loading.getBackground(); animationDrawable.start();
我们拿到动画的实例,然后 start() 就好了,现在运行一下就会产生动画效果了。这里要注意一下,图片的序列千万别搞错了,不然动画会变成畸形的,错乱的。
-
FrameAnimation帧动画以及LruCache优化的自定动画
2015-12-14 14:44:59此DEMO主要是针对帧动画,实现方式有三种:1、FrameAnimation+xml;2、代码中加载每一帧;3、自定义动画,采用LruCache对每一帧图片进行内存优化防止图片oom。 -
【实战总结】帧动画调优实践
2021-05-27 07:17:49原标题:【实战总结】帧...最常见的是Android原生的帧动画,位移动画,旋转动画,属性动画等等,具体根据动画效果选择实现方案;针对那些有规律,不太复杂的矢量动画我们往往也采取自定义View来实现,比如各种狂拽... -
帧动画OOM?不存在的!—— 优化帧动画之SurfaceView逐帧解析
2019-05-03 15:14:15—— 优化帧动画之SurfaceView逐帧解析 大图做帧动画卡顿?不存在的!—— 优化帧动画之SurfaceView 滑动窗口式帧复用 Android 提供了AnimationDrawable用于实现帧动画。在动画开始之前,所有帧的图片都被解析并... -
Android开发之逐帧动画优化
2017-04-04 19:46:57Android上如果使用逐帧动画的话,可以很方便地使用AnimationDrawable,无论是先声明xml还是直接代码里设置,都是几分钟的事,但使用AnimationDrawable有一个致命的弱点,那就是需要一次性加载所有图片到内存,万一帧... -
unity序列帧优化
2022-05-11 07:46:54开发过程中,有可能遇到分辨率很大,数量也很多的序列帧动画需求,在甲方要求高清晰度 ,不能压缩的情况下,正常做成动画直接打包,1.5G的序列帧动画,打出来的包在3.5G左右,加载卡顿严重。在这种情况下,建议使用... -
《laya 性能优化》Laya 分帧加载优化
2020-12-08 15:18:05基础逻辑:一帧只处理部分时间,剩余的事件留到下一帧去处理,减少一帧的卡顿 伪代码如下 curIdx: number = 0; st: number = 0; tLimit: number = 1000 / Laya.timer.currFrame * 0.25;// 处理事件不超过这一阵所需要... -
Android帧动画的持续优化
2019-07-19 13:23:41Android 系统提供了两种帧动画实现方式 1.xml 文件定义 animation-list 2.java 文件设置 AnimationDrawable # [缺点] - 系统会把每一帧图片读取到内存中 - 当图片很多且每张都很大的情况下,容易出现卡顿,甚至 ... -
Unity——用代码实现序列帧动画(动态加载)
2019-11-14 11:41:39最近的项目用SpriteRenderer+Animation的方式播放序列帧,我那项目序列帧很多,项目大小差不多40G。。。 unity编辑器里是可以正常播放没问题的,但打包出去后,发现一闪一闪的,什么玩意啊。。。然后搜索了一下文章... -
unity 序列帧动画 UGUI GPU版
2021-11-30 14:02:56之前的项目因序列帧数量量大,图片大,耗费不少的资源,想搞一版GPU版本的序列帧试试水,看看性能,初见成效,但是弊端也蛮明显,就是不能合并drawcall,不过还能凑合着用 流程大概就是把图片资源全部压进GPU,用... -
Android 解决帧动画卡顿问题
2018-11-30 13:02:46Android帧动画一次性加载会造成ui卡顿,所以就有了这份代码。通过handle队列和Bitmap复用,每次加载一张并显示,可以解决帧动画卡顿问题。 使用方法: int[] right_res_id = new int[]{R.drawable.r_00072, R... -
关于Json动画与帧动画的对比
2020-08-13 11:43:17关于Json动画与帧动画的异同点,本文主要从流畅度、CPU、内存和文件大小,这4个方面进行的比较。 1、运行时的状态图: Json动画运行时,CPU和内存动态图如下: 帧动画运行时,CPU和内存动态图如下: 2、对比分析 ... -
微信小程序播放序列帧动画优化处理
2020-09-24 16:10:49如题,最近要实现一个微信小程序播放序列帧动画的功能。 1、用setInterval计时,依次累加序列帧下标。canvas渲染序列帧图片,序列帧图片直接放到云上,发现现在太耗时了,网络不好的情况下,图片没下载好,下标已经... -
iOS - 帧动画优化点
2018-04-03 14:17:00腾讯帧动画优化方案 基础内存优化 1.I/O性能优化 减少I/O次数是性能优化的关键点: 将零碎的内容作为一个整理进行写入 使用合适的I/O操作API 使用合适的线程 使用NSCache做缓存能够减少I/O 1-1.NSCache 自动清理... -
前端逐帧动画性能探究和比较
2020-03-30 18:48:18什么是逐帧动画? 首先看一下维基百科中的定义: 定格动画,又名逐帧动画,是一种动画技术,其原理即将每帧不同的图像连续播放,从而产生动画效果。 简单的来说就是: 以一定的速度切换几张连续图像,让它动起来... -
使用多张图片做帧动画的性能优化
2017-11-08 00:26:15所以需要研究一些优化方案提升加载图片和帧动画的性能。原理分析iOS系统从磁盘加载一张图片,使用UIImageView显示到屏幕上,需要经过以下步骤:从磁盘拷贝图片数据到内核缓冲区。从内核缓冲区复制数据到用户空间。... -
帧动画的多种实现方式与性能对比
2018-07-11 10:42:47前面我分享了《Web动画形式》,各种动画形式都可以制作出一种类型的动画,那就是帧动画,也叫序列帧动画,定格动画,逐帧动画等,这里我们统一用帧动画来表述,接下来我们就来看看帧动画有哪些打开方式吧。... -
iOS性能优化教程之页面加载速率详解
2020-08-27 00:22:43在软件开发领域里经常能听到这样一句话,“过早的优化是万恶之源”,不要过早优化或者过度优化。下面这篇文章主要给大家介绍了关于iOS性能优化教程之页面加载速率的相关资料,需要的朋友可以参考下 -
android 解决帧动画卡顿情况
2018-05-22 22:12:50最近公司有一个项目需求,需要在一个特定的...但是在公司的设备上就非常卡顿,也就是低端机,由于图片过多,效果异常卡顿,所以在这篇文章中就说一下帧动画的优化问题。 首先还是先来看一下android原生的帧动... -
Android性能优化-你的lottie动画今天跳帧了吗?
2020-12-24 13:46:26问题现象底部tab,现在大家都很熟悉了,点击一个tab 就切换一个...然而在实际开发中,我们发现如果这个动画稍微复杂一些,就会出现不易察觉的丢帧现象。往往表现在:第一次点击tab的时候,因为涉及到对应fragmen... -
giffit:Gatsby插件和Transformer可轻松使用优化的动画GIF
2021-05-10 03:35:32对于支持它的浏览器,优化大小和转换为WebP以及将单个提取的Base64编码的GIF动画帧作为加载期间的预览已经(大部分)在工作。前言这是由lerna管理的 ,因此请查看以下内容的各个自述文件:贡献我们非常欢迎每个人都...