Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android動畫解析(一)—— Frame Animation(幀動畫)

Android動畫解析(一)—— Frame Animation(幀動畫)

編輯:關於Android編程

動畫在我們實際開發中占有很重要的地位,一個優秀的動畫能為我們的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項<喎?/kf/ware/vc/" target="_blank" class="keylink">vc3Ryb25nPjxiciAvPg0KPGltZyBhbHQ9"f2" src="/uploadfile/Collfiles/20160310/2016031009175939.png" title="\" />

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標簽中指定圖片的順序進行播放即可。

代碼實現

<code class=" hljs xml"><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">
    </item></item></item></item></item></item></item></item></item></item></animation-list></code>

在animation-list中,item的先後順序就是圖片在動畫中播放的順序。順序設置好了以後,我們就將該anim綁定到我們的ImageView上,然後進行播放。

    

最後就是我們的代碼,獲取到該drawable,然後進行播放。

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

看下動畫效果:
run

是不是很簡單,簡單的幾行代碼就可以做出一個gif的動畫效果,不過現在貌似有開源控件可以加載gif圖片,回頭研究下看看二者的效率如何。

2、裸奔的機器人
  通過前面的一個案例,我們已經基本熟悉了Frame Animation的使用,下面我們在做一個例子,來鞏固下知識點。
這裡寫圖片描述

代碼實現

<code class=" hljs xml"><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">
    </item></item></item></item></item></item></item></item></animation-list></code>

後面的代碼同上面,我們只需要看看我們的實現效果即可。

這裡寫圖片描述

三、幀動畫原理分析
  在上面的開發中,我們在將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
     * 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()
                        + ":  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()
                            + ":  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)進行設置。

基本的流程就是這個樣子,主要理解使用即可。後面會接著分析幾篇關於動畫的文章。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved