>

逐帧动画,逐帧动画Drawable

- 编辑:澳门新葡亰平台游戏 -

逐帧动画,逐帧动画Drawable

逐帧动画 (Frame By Frame) 是 Android 系统提供的一种常见的卡通方式,通过广播一组一而再的图纸财富变成动画。当我们想用一组一连的图样播放动画时,首先想到的就是利用系统提供的逐帧动画方式。接下来,我们将轻便表明什么运用逐帧动画,以及解析逐帧动画存在的利弊,最终交给大家的缓和方案。

1. 介绍

Drawable Animation 能够兑现依次加载一组 Drawable 能源, 它和早先时期的影片同样 , 加载一组分裂的图纸,然后像胶卷同样播放就产生了动画.
能够行使 ** AnimationDrawable
**类的相关API 在代码中动态加载动画帧数据. 最轻易完成的方法是利用三个单身的XML来罗列出播放动画必要的Drawable图片财富. 那几个XML文件要寄放在res/drawable路径下.

作者:邹峰立,微博:zrunker,邮箱:zrunker@yahoo.com,微教徒人号:紫风流创作,个人平台:www.ibooker.cc。

正文选自女郎花创作平台第17篇文章。阅读原来的小说 。

  • 率先步,将大家所急需的动画素材财富放置在 res/drawable 目录下,切记不要因为是卡通所以就大错特错的将资料能源放置在 res/anim 目录下。
  • 第二步,在 res/anim 目录下新建 drawable 文件 loading.xml ,如下

    图片 1

2. XML兑现格局.

图片 2

animation-list 为 drawable 文件的根标签,android:oneshot 设置动画是或不是只播放三遍,子标签 item 具体定义每一帧的动画片,android:drawable 定义这一帧动画所使用的能源,android:duration 设置动画的持续时间。

2.1 在 res/drawable/ 路线下开创 wifi_wave.xml 文件.

<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="true">
    <item android:drawable="@drawable/wifi1" android:duration="200" />
    <item android:drawable="@drawable/wifi2" android:duration="200" />
    <item android:drawable="@drawable/wifi3" android:duration="200" />
</animation-list>
  1. 根节点使用 : <animation-list>
    1.1 android:oneshot="true"卡通是还是不是只进行一次.
  2. 每一帧动画使用二个<item> 节点.
    2.1 android : drawable 图片能源引用.
    2.2 android:duration 当前图片能源的播音时间.

紫风流创作

  • 其三步,给想要展现动画的 ImageView 设置财富动画,然后打开动画

    图片 3

2.2 引用

AnimationDrawable wifiAnimation;

public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);

  ImageView wifiImage = (ImageView) findViewById(R.id.wifi_wave);
  wifiImage.setBackgroundResource(R.drawable.wifi_wave);
  wifiAnimation = (AnimationDrawable) rocketImage.getBackground();
}
public void btnClicked(View view){
  // 开始动画.
   wifiAnimation.start();
}

留意尽量不要在 onCreate() 中调用 start() 方法. 因为此时能源文件大概还未曾完全加载成功 . 如若想在一发端就张开动画能够思虑在 onWindowFocusChanged())回调中调用 start() .

Frame Animation(帧动画/逐帧动画/Drawable Animation),以管窥天是指将镜头一帧一帧的张开始播放报的卡通,它有二种达成格局,第一种方法是通过Java代码完毕,这里要求运用到AnimationDrawable。第三种艺术XML举行落实,这里必要用到<animation-list>标签。

咱俩能观察,逐帧动画使用起来是这么的简便方便,所以当大家想要通过一组图片素材来兑现动画的时候首推的就是上述的方案。可是大家却忽略了二个情状,当图片素材比非常多何况每张图纸都相当大的境况下,使用上述的办法手提式有线电话机缘出现OOM 以及卡顿难题,那是帧动画的二个相比较鲜明的毛病。

首先种方法,Java达成:

咱俩领会,在第三步给 ImageView 设置图片能源的时候,因为 loading.xml 文件中定义了一体系的图片素材,系统会依照种种定义的一一把具备的图片都读取到内部存款和储蓄器中,而系统读取图片的章程是 Bitmap 位图形式,所以就产生了 OOM 的发出。

那边本身在res/drawable下放置loading_01~loading_12张png文件,用来显示每帧的效果图。先要从该文件夹下抽出Drawable最后经过anim.addFrame(drawable,100)增添进去就可以,在那之中anim.setOneShot(false);这么些艺术是用来设置该动画作用是还是不是为一遍广播,true的时候表示播放一回就告一段落,false的时候表示能够调换播放。

既然一遍性读取全数的图样财富会促成内部存储器溢出,那么我们能体会精通的消除方法正是安份守己动画的顺序,每一遍只读取一帧动画能源,读取达成再展现出来,借使图片过大,大家还要求对图片张开削减管理。

AnimationDrawable anim =new AnimationDrawable();
for(int i =1; i <=12; i++) {
   int id;
   // 获取Drawable文件夹下的图片文件
   if(i <10)
      id = getResources().getIdentifier("loading_0"+ i,"drawable",getPackageName());
   else
      id = getResources().getIdentifier("loading_"+ i,"drawable",getPackageName());
   Drawable drawable = getResources().getDrawable(id);
   anim.addFrame(drawable,100);
}
anim.setOneShot(false);
imageView.setImageDrawable(anim);

全部思路是那样的,大家在子线程里读取图片能源(满含图形过大,对图纸打开始拍录卖),读取达成后透过主线程的 Handler 将要子线程的数量(首即使 Bitmap)发送到主线程中,然后再把 Bitmp 绘制展现出来,每隔一段时间不断读取,然后逐条展现出来,那样视觉上就有了动画片的作用。完结代码如下

其次种格局:XML举行落到实处。

public class AnimationView extends View implements Handler.Callback { public static final int DEFAULT_ANIM_TIME = 100; public static final int PROCESS_DATA = 1; public static final int PROCESS_ANIM_FINISH = 1 << 1; public static final int PROCESS_DELAY = 1 << 2; public AnimData mCurAnimData; public int mCurAnimPos; public boolean mIsRepeat; public int mAnimTime; private Handler mHandler ; private ProcessAnimThread mProcessThread; private Bitmap mCurShowBmp; private List<AnimData> mAnimDataList = new ArrayList<>(); public AnimationView(Context context) { this(context,null); } public AnimationView(Context context, @Nullable AttributeSet attrs) { this(context, attrs,0); } public AnimationView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init(){ mHandler = new Handler; mProcessThread = new ProcessAnimThread(getContext(),mHandler); mAnimTime = DEFAULT_ANIM_TIME; } public void setIsRepeat(boolean repeat){ mIsRepeat = repeat; } private int mGravity; public void SetGravity(int gravity) { mGravity = gravity; invalidate(); } public void setData(List<AnimData> list){ if (list != null ){ mAnimDataList.addAll; } } private Matrix mTempMatrix = new Matrix(); @Override protected void onDraw(Canvas canvas) { if(mCurShowBmp != null && !mCurShowBmp.isRecycled { int x = 0; int y = 0; float scaleX = 1f; float scaleY = 1f; switch(mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.LEFT: x = 0; break; case Gravity.RIGHT: x = this.getWidth() - mCurShowBmp.getWidth(); break; case Gravity.CENTER_HORIZONTAL: x = (this.getWidth() - mCurShowBmp.getWidth / 2; break; case Gravity.FILL_HORIZONTAL: { int w = mCurShowBmp.getWidth(); if { scaleX = this.getWidth() / w; } break; } default: break; } switch(mGravity & Gravity.VERTICAL_GRAVITY_MASK) { case Gravity.TOP: y = 0; break; case Gravity.BOTTOM: y = this.getHeight() - mCurShowBmp.getHeight(); break; case Gravity.CENTER_VERTICAL: y = (this.getHeight() - mCurShowBmp.getHeight / 2; break; case Gravity.FILL_VERTICAL: { int h = mCurShowBmp.getHeight(); if { scaleY = this.getHeight() / h; } break; } default: break; } if(scaleX == 1 && scaleY != 1) { scaleX = scaleY; switch(mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.RIGHT: x = this.getWidth(mCurShowBmp.getWidth() * scaleX); break; case Gravity.CENTER_HORIZONTAL: x = (this.getWidth(mCurShowBmp.getWidth() * scaleX)) / 2; break; } } else if(scaleX != 1 && scaleY == 1) { scaleY = scaleX; switch(mGravity & Gravity.VERTICAL_GRAVITY_MASK) { case Gravity.BOTTOM: y = this.getHeight(mCurShowBmp.getHeight() * scaleY); break; case Gravity.CENTER_VERTICAL: y = (this.getHeight(mCurShowBmp.getHeight() * scaleY)) / 2; break; } } mTempMatrix.reset(); mTempMatrix.postScale(scaleX, scaleY); mTempMatrix.postTranslate; canvas.drawBitmap(mCurShowBmp, mTempMatrix, null); } } private boolean mHasStarted = false; public void start(){ mHasStarted = true; if (mWidth == 0 || mHeight == 0 ){ return; } startPlay(); } private void startPlay() { if ( mAnimDataList != null && mAnimDataList.size() > 0 ){ mCurAnimPos = 0; AnimData animData = mAnimDataList.get(mCurAnimPos); mCurShowBmp = ImageUtil.getBitmap(getContext(),animData.filePath,mWidth,mHeight); invalidate(); if (mListener != null ){ mListener.onAnimChange(mCurAnimPos,mCurShowBmp); } checkIsPlayNext(); } } private void playNext(final int curAnimPosition ){ Message msg = Message.obtain(); msg.what = PROCESS_DELAY; msg.arg1 = curAnimPosition; mHandler.sendMessageDelayed(msg,mAnimTime); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow; } private void quit(){ mHasStarted = false; if (mProcessThread != null ){ mProcessThread.clearAll(); } } private int mWidth; private int mHeight; @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mWidth = w; mHeight = h; if (mProcessThread != null ){ mProcessThread.setSize; } if (mHasStarted){ startPlay(); } } private boolean mHavePause = false; public void pause(){ mHavePause = true; mHandler.removeMessages(PROCESS_DELAY); } public void resume(){ if (mHavePause && mHasStarted){ checkIsPlayNext(); } } @Override public boolean handleMessage(Message msg) { switch { case PROCESS_ANIM_FINISH:{ Bitmap bitmap =  msg.obj; if (bitmap != null){ if (mCurShowBmp != null ){ mCurShowBmp.recycle(); mCurShowBmp = null; } mCurShowBmp = bitmap; if (mListener != null ){ mListener.onAnimChange(mCurAnimPos,bitmap); } invalidate(); } checkIsPlayNext(); break; } case PROCESS_DELAY:{ int curAnimPosition = msg.arg1; AnimData data = mAnimDataList.get(curAnimPosition); mProcessThread.processData; break; } } return true; } private void checkIsPlayNext() { mCurAnimPos ++; if ( mCurAnimPos >= mAnimDataList.size{ if (mIsRepeat){ mCurAnimPos = 0; playNext(mCurAnimPos); } else { if ( mListener != null ){ mListener.onAnimEnd(); } } } else { playNext(mCurAnimPos); } } private AnimCallBack mListener; public void setAnimCallBack(AnimCallBack callBack){ mListener = callBack; } public interface AnimCallBack{ void onAnimChange(int position, Bitmap bitmap); void onAnimEnd(); } public static class AnimData{ public Object filePath; } public static class ProcessAnimThread{ private HandlerThread mHandlerThread; private Handler mProcessHandler; private Handler mUiHandler; private AnimData mCurAnimData; private int mWidth; private int mHeight; private WeakReference<Context> mContext; public ProcessAnimThread(Context context, Handler handler){ mUiHandler = handler; mContext = new WeakReference<Context>; init(); } public void setSize(int width,int height){ mWidth = width; mHeight = height; } private void init(){ mHandlerThread = new HandlerThread("process_anim_thread"); mHandlerThread.start(); mProcessHandler = new Handler(mHandlerThread.getLooper(), new Handler.Callback() { @Override public boolean handleMessage(Message msg) { // 消息是在子线程 HandlerThread 里面被处理,所以这里的 handleMessage 在 //子线程里被调用 switch { case PROCESS_DATA:{ AnimData animData =  msg.obj; Bitmap bitmap = ImageUtil.getBitmap(mContext.get(),animData.filePath,mWidth,mHeight); if (bitmap != null ){ Message finishMsg = Message.obtain(); finishMsg.what = PROCESS_ANIM_FINISH; finishMsg.obj = bitmap; //消息处理完毕,使用主线程的 Handler 将消息发送到主线程 mUiHandler.sendMessage(finishMsg); } break; } } return true; } }); } public void processData(AnimData animData){ if ( animData != null ){ Message msg = Message.obtain(); msg.what = PROCESS_DATA; msg.obj = animData; mProcessHandler.sendMessage; } } public void clearAll(){ mHandlerThread.quit(); mHandlerThread = null; } }}
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
   android:oneshot="false">
   <!--oneshot:是否只展示一遍-->
   <item android:drawable="@drawable/loading_01" android:duration="100"/>
   ......
   <item android:drawable="@drawable/loading_12" android:duration="100"/>
</animation-list>
  • 率先定义静态的里边类 AnimData,作为大家的卡通片实体类,filePath 为卡通的门路,能够是 res 财富目录下,也能够是 外界存款和储蓄的路子。

    图片 4

  • 接下去定义封装 ProcessAnimThread 类,用以将财富图形读取为 Bitmap,假使图片过大,大家还须要将其缩减管理。ProcessAnimThread 类中,最为根本的是 HandlerThread ,那是自带有 Looper 的 Thread,承接自 Thread。后边大家说过在子线程里读取 Bitmap, HandlerThread 正是咱们地方聊起的子线程,使用办法上,咱们先构造 HandlerThread ,然后调用 start() 方法开启线程,那时候 HandlerThread 里的 Looper 已经起步能够出去音讯了,最终通过这一个 Looper 构造 Handler(例子中为 mProcessHandler 变量)。完毕以上步骤之后,大家因此mProcessHandler 发送的音讯最后会在 子线程里被拍卖,管理实现之后,再讲结果发送到主线程

    图片 5

  • 接下去看主线程收到信息后如哪管理。首先将结果抽出来,然后刷新展现,接着决断队列是还是不是以及管理终结,未竣事则通过发送延迟的新闻持续读取图片。

    图片 6

这么就将该四个帧动画设置完结,然后只需求在XML布局文件中开展调用。

<ImageView
   android:id="@+id/imageView"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:contentDescription="@null"
   android:src="@drawable/loading"/>
  • 结构帧动画数据队列

    图片 7

  • 调用 AnimationView 的 start() 方法开运转画

唯独当大家运维的时候会意识动画未有运营而是停留在率先帧,那是为啥吧?

AnimationView 是建设方案里的三个大致完成,由于文化水平有限,难免有荒唐和疏漏,款待指正。末了,附上项目地址

那是因为AnimationDrawable播放动画是从属在window上边的,而在Activity onCreate方法中调用时Window还未开端化实现,全数才会滞留在率先帧,要想完结播放能够在onWindowFocusChanged或onCreate方法中增加如下代码:

// XML实现逐帧动画
imageView.setImageResource(R.drawable.loading);
AnimationDrawable animationDrawable = (AnimationDrawable)imageView.getDrawable();
animationDrawable.start();

补充:AnimationDrawable 多少个广泛的api。

  1. void start()- 初步播报动画
  2. void stop()- 停播动画
  3. addFrame(Drawable frame, int duration)- 增添一帧,并设置该帧突显的持续时间
  4. void setOneShoe(boolean flag)- false为循环播放,true为仅播放壹回
  5. boolean isRunning()- 是还是不是正在播放

小结:逐帧动画使用起来较为轻巧,不过大量的行使,会占据抢先四分之二的CPU能源,是分界面卡顿,并且假设画面使用图片过大时候还大概会出现OOM难点。

GitHub地址
翻阅最初的作品


图片 8

微教徒人号:辛夷创作

本文由java编程发布,转载请注明来源:逐帧动画,逐帧动画Drawable