Android动画解析(一)—— Frame Animation(帧动画)

动画在我们实际开发中占有很重要的地位,一个优秀的动画能为我们的app应用增色很多,同时一个优秀的动画衔接能够增加我们app的逻辑展示。在Android系统中,系统给我们提供了几种动画的支持,分别是Frame Animation(帧动画)、Tween Animation(补间动画)以及3.0系统以后增加的Property Animator(属性动画)。这些动画的熟练使用可以帮助我们设计出perfect效果的动画,下面就开始我们的学习吧!

一、概述
  帧动画,顾名思义就是这个动画的效果是由一帧帧的图片组合出来的。通过制定图片展示的顺序,达到动画的展示效果。

  在Android开发中,系统给我们提供了”animation-list” 节点用于我们配置帧动画。

实现步骤

1、在res目录下创建用于存储xml动画文件的anim文件夹,res/anim,也可以放在drawable目录下

这里写图片描述

2、动画配置,在animation-list节点中配置item项
f2

2、将文件设置到ImageView控件的背景上,然后获取背景转换为AnimationDrawable对象进行播放动画

    iv_imageView.setBackgroundResource(R.drawable.frame_animation);
    AnimationDrawable animation = (AnimationDrawable)iv_imageView.getBackground();
    animation.start();

二、实例讲解

1、奔跑的飞马
  我们先来看下效果图,第一个实例,飞奔的飞马。
                   waking

  对于这种动画效果使用Frame Animation动画即可完成,我们只需要将gif动画进行分帧切割成图片,然后我们在animation-list标签中指定图片的顺序进行播放即可。

代码实现

<animation-list xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:drawable="@mipmap/Horse_start" android:duration="200"/>
        <item android:drawable="@mipmap/Horse1" android:duration="200"/>
        <item android:drawable="@mipmap/Horse2" android:duration="200"/>
        <item android:drawable="@mipmap/Horse3" android:duration="200"/>
        <item android:drawable="@mipmap/Horse4" android:duration="200"/>
        <item android:drawable="@mipmap/Horse5" android:duration="200"/>
        <item android:drawable="@mipmap/Horse6" android:duration="200"/>
        <item android:drawable="@mipmap/Horse7" android:duration="200"/>
        <item android:drawable="@mipmap/Horse8" android:duration="200"/>
        <item android:drawable="@mipmap/Horse_start" android:duration="200"/>
    </animation-list>

在animation-list中,item的先后顺序就是图片在动画中播放的顺序。顺序设置好了以后,我们就将该anim绑定到我们的ImageView上,然后进行播放。

    <ImageView
        android:id="@+id/iv_frame"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@anim/run_horse"
        android:layout_centerHorizontal="true"/>

最后就是我们的代码,获取到该drawable,然后进行播放。

    ImageView iv_animaView = (ImageView) findViewById(R.id.iv_frame);
    AnimationDrawable animationDrawable = (AnimationDrawable) iv_animaView.getBackground();
    animationDrawable.start();

看下动画效果:
               run

是不是很简单,简单的几行代码就可以做出一个gif的动画效果,不过现在貌似有开源控件可以加载gif图片,回头研究下看看二者的效率如何。

2、裸奔的机器人
  通过前面的一个案例,我们已经基本熟悉了Frame Animation的使用,下面我们在做一个例子,来巩固下知识点。
              这里写图片描述

代码实现

<animation-list xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:drawable="@mipmap/zzlx1" android:duration="200" />
        <item android:drawable="@mipmap/zzlx2" android:duration="200" />
        <item android:drawable="@mipmap/zzlx3" android:duration="200" />
        <item android:drawable="@mipmap/zzlx4" android:duration="200" />
        <item android:drawable="@mipmap/zzlx5" android:duration="200" />
        <item android:drawable="@mipmap/zzlx6" android:duration="200" />
        <item android:drawable="@mipmap/zzlx7" android:duration="200" />
        <item android:drawable="@mipmap/zzlx8" android:duration="200" />
    </animation-list>

后面的代码同上面,我们只需要看看我们的实现效果即可。

              这里写图片描述

三、帧动画原理分析
  在上面的开发中,我们在将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方法。
mthod

  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
     * <a href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.
     */
    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)进行设置。

在使用帧动画的时候,我们需要注意,我们不能在Activity的onCreate()中使用AnimationDrawable的start方法,这是因为AnimationDrawable还没有完全绑定到Activity所依附的Window上。如果我们想立即使用,就需要在onWindowFocusChanged()方法中进行开启动画,调用start()方法。

基本的流程就是这个样子,主要理解使用即可。后面会接着分析几篇关于动画的文章。

源码地址:https://github.com/dengshiwei/StudyTest

作者:mr_dsw 欢迎转载,与人分享是进步的源泉!

转载请保留地址:http://blog.csdn.net/mr_dsw

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值