今天,简单讲讲android里如何使用帧动画。
其实也很简单,不过之前自己需要写一个帧动画时,忘记了具体怎么写,在网上查找资料后,解决了这个问题。所以这里记录一下。
一、概述
帧动画,顾名思义就是这个动画的效果是由一帧帧的图片组合出来的。通过制定图片展示的顺序,达到动画的展示效果。
在Android开发中,系统给我们提供了”animation-list” 节点用于我们配置帧动画。
一.使用xml实现帧动画
实现步骤
1、在res目录下创建用于存储xml动画文件的anim文件夹,res/anim,也可以放在drawable目录下
具体代码如下:
<?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false"> <item android:drawable="@mipmap/sample_1" android:duration="100" /> <item android:drawable="@mipmap/sample_2" android:duration="100" /> <item android:drawable="@mipmap/sample_3" android:duration="100" /> <item android:drawable="@mipmap/sample_4" android:duration="100" /> <item android:drawable="@mipmap/sample_5" android:duration="100" /> <item android:drawable="@mipmap/sample_6" android:duration="100" /> </animation-list>
简单讲讲,这里提供了6张图片,每张图片显示100毫秒进行切换,其中android:oneshot="false" 是设置是否循环播放,默认的是循环播放,true为只播放一次
2、将文件设置到ImageView控件的背景上,然后获取背景转换为AnimationDrawable对象进行播放动画
iv_imageView.setBackgroundResource(R.drawable.frame_animation); AnimationDrawable animation = (AnimationDrawable)iv_imageView.getBackground(); animation.start();
如果想要停止播放动画可以调用AnimationDrawable的stop方法
iv_imageView.setBackgroundResource(R.drawable.frame_animation); AnimationDrawable animation = (AnimationDrawable)iv_imageView.getBackground(); animation.stop();
二.直接使用java代码创建帧动画
public class MainActivity extends AppCompatActivity { private ImageView iv_ani; private AnimationDrawable mAnimationDrawable; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); iv_ani = (ImageView) findViewById(R.id.iv_ani); initAnimationDrawable(); } private void initAnimationDrawable(){ mAnimationDrawable = new AnimationDrawable(); for (int i = 1; i <= 4; i++) { int id = getResources().getIdentifier("sample_" + i, "mipmap", getPackageName()); Drawable drawable = getResources().getDrawable(id); mAnimationDrawable.addFrame(drawable, 100); } mAnimationDrawable.setOneShot(false); iv_ani.setImageDrawable(mAnimationDrawable); } }
addFrame(Drawable frame, int duration) : 添加一帧,并设置该帧显示的持续时间
接下来简单介绍帧动画的原理,大家有兴趣的可以看看。
三、帧动画原理分析
在上面的开发中,我们在将backgroud对应的Drawable对象转换为一个AnimationDrawable对象,然后由这个对象启动Frame动画,那么这个类究竟是由何方神圣呢?让我们一起look look。
1、AnimationDrawable概述
AnimationDrawable用于创建frame-by-frame(逐帧)动画,它定义了一些列的Drawable对象可用于设置View的backgroud背景属性。frame-by-frame动画最简单的方式是通过XML文件进行创建,然后将xml文件放到res/drawable/folder文件夹下,同时将此drawa对象设置到view的backgroud属性。Xml文件的组成:
animation-list:根节点,包含一系列的item item:每个item对应一个frame(帧)
下面是在代码中创建和使用帧动画:
ImageView img = (ImageView)findViewById(R.id.spinning_wheel_image); img.setBackgroundResource(R.drawable.spin_animation); AnimationDrawable frameAnimation = (AnimationDrawable) img.getBackground(); frameAnimation.start();
我们在来看看AnimationDrawable对象给我们提供的属性。
AnimationDrawable_visible:设置是否可见 AnimationDrawable_variablePadding: AnimationDrawable_oneshot:设置是否只播放一次,true是,false否 AnimationDrawableItem_duration:设置每帧动画之间的时间间隔 AnimationDrawableItem_drawable:设置每帧之间间隔的drawable对象
2、AnimationDrawable源码分析
上面我们已经对AnimationDrawable进行了一个简要的分析,了解了一些它的属性,我们心中获取对帧动画还有一些疑惑,比如一些问题:
AnimationDrawable是如何形成一个个帧画面? Frame Animation是如何实现不断循环播放? 我们能否通过代码控制Frame动画的播放?
下面我们就围绕上面的几个问题对AnimationdDrawable进行分析。查看源码,我们可以看到AnimationDrawable暴露的public方法。
AnimationDrawable根据名称,我们也能推算到这是一个Drawable的子类,我们仔细一想,为什么通过getBackgroud()方法获得的Drawable对象可以转换到AnimationDrawable这个子类呢?这就需要我们去看下源码。在Drawable类中,有一个方法createFromXml()方法:
/** * Create a drawable from an XML document. For more information on how to * create resources in XML, see * Drawable Resources. */ public static Drawable createFromXml(Resources r, XmlPullParser parser) throws XmlPullParserException, IOException { return createFromXml(r, parser, null); }
这个方法就是用于将我的XML文件转换成一个drawable对象,我们接着深入下去,看下createFromXml()这个方法。
public static Drawable createFromXml(Resources r, XmlPullParser parser, Theme theme) throws XmlPullParserException, IOException { AttributeSet attrs = Xml.asAttributeSet(parser); int type; while ((type=parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { // Empty loop } if (type != XmlPullParser.START_TAG) { throw new XmlPullParserException("No start tag found"); } Drawable drawable = createFromXmlInner(r, parser, attrs, theme); if (drawable == null) { throw new RuntimeException("Unknown initial tag: " + parser.getName()); } return drawable; }
在这里我们看到了XML文件转换成Drawable的内部,在Android系统中,同样是通过XmlPullParser进行Xml文件的解析,在上面的方法中,首先进行xml文件的开始标签和结束标签,判断xml文件内部是否为空节点。然后通过:
Drawable drawable = createFromXmlInner(r, parser, attrs, theme);
进行XML文件解析,最后转换成Drawable对象。
public static Drawable createFromXmlInner(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { final Drawable drawable; final String name = parser.getName(); switch (name) { case "selector": drawable = new StateListDrawable(); break; case "animated-selector": drawable = new AnimatedStateListDrawable(); break; case "level-list": drawable = new LevelListDrawable(); break; case "layer-list": drawable = new LayerDrawable(); break; case "transition": drawable = new TransitionDrawable(); break; case "ripple": drawable = new RippleDrawable(); break; case "color": drawable = new ColorDrawable(); break; case "shape": drawable = new GradientDrawable(); break; case "vector": drawable = new VectorDrawable(); break; case "animated-vector": drawable = new AnimatedVectorDrawable(); break; case "scale": drawable = new ScaleDrawable(); break; case "clip": drawable = new ClipDrawable(); break; case "rotate": drawable = new RotateDrawable(); break; case "animated-rotate": drawable = new AnimatedRotateDrawable(); break; case "animation-list": drawable = new AnimationDrawable(); break; case "inset": drawable = new InsetDrawable(); break; case "bitmap": drawable = new BitmapDrawable(); break; case "nine-patch": drawable = new NinePatchDrawable(); break; default: throw new XmlPullParserException(parser.getPositionDescription() + ": invalid drawable tag " + name); } drawable.inflate(r, parser, attrs, theme); return drawable; }
在createFromXmlInner方法中,首先获取我们都xml文件的标签,然后根绝我们对应的标签名称创建对应的drawable对象,比如我们这次创建的AnimationDrawable对象。然后调用inflater()方法,由于AnimationDrawable方法中已经对inflater方法进行了重写,所以此时这个就是:
@Override public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.AnimationDrawable); super.inflateWithAttributes(r, parser, a, R.styleable.AnimationDrawable_visible); updateStateFromTypedArray(a); a.recycle(); inflateChildElements(r, parser, attrs, theme); setFrame(0, true, false); }
至此,我们已经基本理清了从XML文件到Drawable对象的转换流程,现在我们就开始分析animation-list节点下的节点如何形成一个个帧动画效果的。在进行分析之前,我们先了解下AnimationState类。这个类用于存储我们的一系列drawable。通过源码发现:
private final static class AnimationState extends DrawableContainerState
这个类继承DrawableContainerState类,DrawableContainerState中有一个成员变量Drawable[] mDrawables;用于存储我们的drawable信息。明白这一点,我们就可以分析方法inflateChildElements方法。
private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { int type; final int innerDepth = parser.getDepth()+1; int depth; while ((type=parser.next()) != XmlPullParser.END_DOCUMENT && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) { if (type != XmlPullParser.START_TAG) { continue; } if (depth > innerDepth || !parser.getName().equals("item")) { continue; } final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.AnimationDrawableItem); final int duration = a.getInt(R.styleable.AnimationDrawableItem_duration, -1); if (duration < 0) { throw new XmlPullParserException(parser.getPositionDescription() + ": <item> tag requires a 'duration' attribute"); } Drawable dr = a.getDrawable(R.styleable.AnimationDrawableItem_drawable); a.recycle(); if (dr == null) { while ((type=parser.next()) == XmlPullParser.TEXT) { // Empty } if (type != XmlPullParser.START_TAG) { throw new XmlPullParserException(parser.getPositionDescription() + ": <item> tag requires a 'drawable' attribute or child tag" + " defining a drawable"); } dr = Drawable.createFromXmlInner(r, parser, attrs, theme); } mAnimationState.addFrame(dr, duration); if (dr != null) { dr.setCallback(this); } } }
在这方法里面通过TypeArray获取drawable的相关信息,然后调用mAnimationState的addFrame方法,将一系列动画信息就存储在drawable数组中。
通过上面的分析,一系列的动画已经转出并进行了存储,我们接下来的任务就是进行start的分析,分析动画的开启。
public void start() { mAnimating = true; if (!isRunning()) { // Start from 0th frame. setFrame(0, false, mAnimationState.getChildCount() > 1 || !mAnimationState.mOneShot); } }
通过setFrame方法设置我们的drawable,里面有selectDrawable(frame)进行设置
这里简单讲讲,帧动画其实就是解析xml文件获取drawable,然后一次加载到内存,进行显示。所以帧动画的图片不要过度,会导致内存使用过大。
android 帧动画的使用就讲完了。
就这么简单。