Android開發總結之動畫(幀動畫+補間動畫)

一、概述

  1. 動畫的概念
    ??動畫的概念不同于一般意義上的動畫片,動畫是一種綜合藝術,它是集合了繪畫、漫畫、電影、數字媒體、攝影、音樂、文學等眾多藝術門類于一身的藝術表現形式。
    ??動畫的英文有很多表述,如animation、cartoon、animated cartoon、cameracature。其中較正式的 "Animation" 一詞源自于拉丁文字根anima,意思為“靈魂”,動詞animate是“賦予生命”的意思,引申為使某物活起來的意思。所以動畫可以定義為使用繪畫的手法,創造生命運動的藝術。
    ??動畫技術較規范的定義是采用逐幀拍攝對象并連續播放而形成運動的影像技術。不論拍攝對象是什么,只要它的拍攝方式是采用的逐格方式,觀看時連續播放形成了活動影像,它就是動畫。

  2. Android系統中的動畫
    ??在Android系統中,動畫可分為三類,分別為幀動畫(Frame Animation),補間動畫(Tweened Animation),屬性動畫。本章主要講述幀動畫和補間動畫。

二、動畫的實現

  1. 幀動畫
    ??幀動畫是一種常見的動畫形式(Frame By Frame),其原理是在“連續的關鍵幀”中分解動畫動作,也就是在時間軸的每幀上逐幀繪制不同的內容,使其連續播放而成動畫。其表現出來的樣式類似于我們常見到的GIF圖。而幀動畫所呈現的結果也依賴于每一“幀”,大家先看效果圖:
    幀動畫效果

    ??這個奔跑的京東小人以及跳躍的魚,其實是一張張不同的圖片很快的切換而形成的動畫效果。看我的資源文件:
    資源文件

    ??這么多圖片資源的切換,內存開銷~~~
    1.1 我們來看看幀動畫的實現,首先上代碼:
<?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/a_0"
        android:duration="100" />
    <item
        android:drawable="@mipmap/a_1"
        android:duration="100" />
    <item
        android:drawable="@mipmap/a_2"
        android:duration="100" />
</animation-list>

??這是在res/drawable目錄下建立一個的animation-list標簽的動畫文件,每個item里面有一張圖,多張圖組合起來就是我們的動畫了。而android:duration則是表示每張圖呈現的時間長短。還有一個android:oneshot屬性,代表是否只展示一遍,true的話該動畫只會執行一遍,false則會循環播放動畫。
1.2 Activity里面的代碼:

public class FrameActivity extends AppCompatActivity {
    private SimpleDraweeView mImgvOne;
    private ImageView mImgvTwo;
    private AnimationDrawable animationDrawableOne;
    private AnimationDrawable animationDrawableTwo;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_frame);
        mImgvOne = (SimpleDraweeView) findViewById(R.id.imgv_one);
        mImgvTwo = (ImageView) findViewById(R.id.imgv_two);
        setAnimation();

    }

    private void setAnimation() {
        //Fresco的動畫 和ImageView用法一樣
        mImgvOne.setImageResource(R.drawable.fram_one);
        animationDrawableOne = (AnimationDrawable) mImgvOne.getDrawable();
        animationDrawableOne.start();
        //ImageView的動畫
        mImgvTwo.setImageResource(R.drawable.fram_two);
        animationDrawableTwo = (AnimationDrawable) mImgvTwo.getDrawable();
        animationDrawableTwo.start();
    }
}

??java代碼很簡單,導入動畫開始動畫,三行代碼就可。大家可以看到我的mImgvOne是一個SimpleDraweeView,因為現在很多人使用Fresco,我只是想證明在使用動畫時SimpleDraweeView的用法和ImageView一樣,大家不必擔心用了Fresco就沒法玩動畫了。而且基于Fresco的強大基因,想要實現幀動畫?人家Fresco是支持gif圖的,直接往里面放一個gif圖,比我們設置幀動畫簡單多了。
??下面我們再看看AnimationDrawable這個類,我們在執行幀動畫時要依靠AnimationDrawablestart方法,先上源碼分析一下:

public class AnimationDrawable extends DrawableContainer implements Runnable, Animatable {
    public AnimationDrawable() {
        throw new RuntimeException("Stub!");
    }

    public boolean setVisible(boolean visible, boolean restart) {
        throw new RuntimeException("Stub!");
    }

    public void start() {
        throw new RuntimeException("Stub!");
    }

    public void stop() {
        throw new RuntimeException("Stub!");
    }

    public boolean isRunning() {
        throw new RuntimeException("Stub!");
    }

    public void run() {
        throw new RuntimeException("Stub!");
    }

    public void unscheduleSelf(Runnable what) {
        throw new RuntimeException("Stub!");
    }

    public int getNumberOfFrames() {
        throw new RuntimeException("Stub!");
    }

    public Drawable getFrame(int index) {
        throw new RuntimeException("Stub!");
    }

    public int getDuration(int i) {
        throw new RuntimeException("Stub!");
    }

    public boolean isOneShot() {
        throw new RuntimeException("Stub!");
    }

    public void setOneShot(boolean oneShot) {
        throw new RuntimeException("Stub!");
    }

    public void addFrame(Drawable frame, int duration) {
        throw new RuntimeException("Stub!");
    }

    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException {
        throw new RuntimeException("Stub!");
    }

    public Drawable mutate() {
        throw new RuntimeException("Stub!");
    }

    protected void setConstantState(DrawableContainerState state) {
        throw new RuntimeException("Stub!");
    }
}
方法 作用
boolean setVisible(boolean visible, boolean restart) visible表示AnimationDrawable是否可見,false則暫停當前動畫運行。restart的值true表示當AnimationDrawable設為Visible時,從第一幀開始播放動畫,false表示當AnimationDrawable設為Visible時,從最近的幀開始執行動畫。
void start() 顧名思義,開始動畫執行。
void stop() 與上面方法相反,停止動畫執行。
boolean isRunning() 當前動畫是否在執行。
void unscheduleSelf(Runnable what) 取消當前動畫上計劃執行的一個Runnable,一般這個Runnable都是用于繪制下一幀的。
int getNumberOfFrames() 獲取幀數(圖片數)
Drawable getFrame(int index) 獲取指定幀
int getDuration(int i) 獲取指定幀的展示時長
boolean isOneShot() 是否循環,true則不循環,false則循環
void setOneShot(boolean oneShot) 設置是否循環,true or false
void addFrame(Drawable frame, int duration) 增加一幀圖片

??可以看到AnimationDrawable繼承了DrawableContainer,而DrawableContainer其實就是Drawable的一個子類,這也是我們animationDrawableTwo = (AnimationDrawable) mImgvTwo.getDrawable();Drawable強轉成AnimationDrawable的原因了。所以AnimationDrawable也是個Drawable罷了。再看它所實現的接口Runnable, Animatable,表明它是一個可執行命令,并且自己支持動畫。源碼不難,下面簡略介紹一下它的相關方法:

方法 作用
boolean setVisible(boolean visible, boolean restart) visible表示AnimationDrawable是否可見,false則暫停當前動畫運行。restart的值true表示當AnimationDrawable設為Visible時,從第一幀開始播放動畫,false表示當AnimationDrawable設為Visible時,從最近的幀開始執行動畫。
void start() 顧名思義,開始動畫執行。
void stop() 與上面方法相反,停止動畫執行。
boolean isRunning() 當前動畫是否在執行。
void unscheduleSelf(Runnable what) 取消當前動畫上計劃執行的一個Runnable,一般這個Runnable都是用于繪制下一幀的。
int getNumberOfFrames() 獲取幀數(圖片數)
Drawable getFrame(int index) 獲取指定幀
int getDuration(int i) 獲取指定幀的展示時長
boolean isOneShot() 是否循環,true則不循環,false則循環
void setOneShot(boolean oneShot) 設置是否循環,true or false
void addFrame(Drawable frame, int duration) 增加一幀圖片

??源碼解析完了,我們也看到了AnimationDrawable的一些方法。下面我們再看看不通過xml文件,直接在代碼中執行幀動畫的方法。先上代碼:

        animationDrawableOne = new AnimationDrawable();
        animationDrawableOne.addFrame(getResources().getDrawable(R.mipmap.a_0),100);
        animationDrawableOne.addFrame(getResources().getDrawable(R.mipmap.a_1),100);
        animationDrawableOne.addFrame(getResources().getDrawable(R.mipmap.a_2),100);
        animationDrawableOne.setOneShot(false);
        mImgvOne.setBackground(animationDrawableOne);
        animationDrawableOne.start();

??同樣,使用代碼依舊可以做出幀動畫的效果,所以大家是使用xml還是代碼創建動畫,看大家喜好咯。

  1. 補間動畫(Tween)
    ??補間動畫指的是做flash動畫時,在兩個關鍵幀中間需要做“補間動畫”,才能實現圖畫的運動;插入補間動畫后兩個關鍵幀之間的插補幀是由計算機自動運算而得到的。也就是說在使用補間動畫時,我們開發者指定了動畫開始、結束的關鍵幀,中間的變化是計算機自動幫助我們補齊的。
    ??補間動畫有四種基本形式,分別是Alpha(透明度),Translate(位移),Scale(縮放),Rotate(旋轉)。當然還可以是多種動畫效果的組合,例如alpha+translatealpha+rotate,甚至四種形式組合在一起的動畫。同樣,和幀動畫一樣,補間動畫的可以以xml的方式實現,也可以以代碼的方式實現。
    2.1 補間動畫的使用
    ?? 屬性解釋:
    | JAVA方法 | xml屬性 | 解釋 |
    |:--------|:------|:----|
    |setDetachWallpaper(boolean)| android:detachWallpaper |是否在壁紙上運行|
    |setDuration(long) |android:duration |設置動畫持續時間,單位為毫秒|
    |setFillAfter(boolean) |android:fillAfter |控件動畫結束時控件是否保持動畫最后狀態|
    |setFillBefore(boolean) |android:fillBefore |控件動畫結束時控件是否還原到開始動畫前的狀態|
    |setFillEnable(boolean)| android:fillEnable(boolean)| 與android:fillBefore效果相同|
    |setInterpolator(boolean) |android:interpolator |設置插值器,有一些動畫效果,如從快到慢,從慢到快,也可以自定義)|
    |setRepeatCount(int) |android:repeatCount |重復次數|
    |setRepeatMode(int)| android:repeatMode |重復類型:reverse倒序回放、restart從頭播放|
    |setStartOffset(long) |android:startOffset |調用start函數后等待開行運行的時間,單位為毫秒|
    |setZadjustment(int) |android:zAdjustment |表示被設置動畫的內容運行時在Z軸的位置(top/bottom/normal),默認為normal|
    |Alpha|
    |AlphaAnimation(float fromAlpha, float toAlpha)|android:fromAlpha|起始透明度|
    |AlphaAnimation(
    float fromAlpha, float toAlpha)| android:toAlpha|結束透明度|
    |Rotate|||
    |RotateAnimation(float fromDegrees, *float toDegrees, *float pivotX, float pivotY) |android:fromDegress |旋轉開始角度,正代表順時針度數,負代表逆時針度數|
    |RotateAnimation(
    float fromDegrees, float toDegrees, *float pivotX, float pivotY)| android:toDegress| 旋轉結束角度(同上)|
    |RotateAnimation(
    float fromDegrees, *float toDegrees, float pivotX, float pivotY)|android:pivotX |縮放起點X坐標(數值、百分數、百分數p,譬如50表示以當前View左上角坐標加50px為初始點、50%表示以當前View的左上角加上當前View寬高的50%做為初始點、50%p表示以當前View的左上角加上父控件寬高的50%做為初始點)|
    |RotateAnimation(
    float fromDegrees, *float toDegrees, float pivotX, float pivotY)|android:pivotY|縮放起點Y坐標(同上)|
    |Scale|||
    |ScaleAnimation(float fromX,
    float toX, *float fromY, *float toY, *float pivotX, float pivotY) |android:fromXScale |初始X軸縮放比例,1.0表示無變化|
    |ScaleAnimation(
    float fromX, float toX, *float fromY, *float toY, *float pivotX, float pivotY)|android:toXScale |結束X軸縮放比例|
    |ScaleAnimation(
    float fromX, *float toX, float fromY, *float toY, *float pivotX, float pivotY)| androd:fromYScale |初始Y軸縮放比例|
    |ScaleAnimation(f
    loat fromX, *float toX, *float fromY, float toY, *float pivotX, float pivotY)| android:toYScale| 結束Y軸縮放比例|
    |ScaleAnimation(
    float fromX, float toX, float fromY, float toY, float pivotX, float pivotY)| android:pivotX |縮放起點X軸坐標(同上)|
    |ScaleAnimation(
    float fromX, float toX, float fromY, float toY, float pivotX, float pivotY)|android:pivotY |縮放起點Y軸坐標(同上)|
    |Translate|||
    |TranslateAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta)| android:fromXDelta |平移起始點X軸坐標|
    |TranslateAnimation(
    float fromXDelta, float toXDelta, float fromYDelta, float toYDelta)|android:toXDelta |平移結束點X軸坐標|
    |TranslateAnimation(
    float fromXDelta, float toXDelta, float fromYDelta, float toYDelta) |android:fromYDelta| 平移起始點Y軸坐標|
    |TranslateAnimation(
    float fromXDelta, float toXDelta, float fromYDelta, float toYDelta)| android:toYDelta |平移結束點Y軸坐標|
    注:
    為忽略該屬性,例如
    |JAVA方法|xml屬性|解釋|
    |---|---|---|
    |AlphaAnimation(float fromAlpha, float toAlpha)|android:fromAlpha|起始透明度|
    |AlphaAnimation(
    float fromAlpha, float toAlpha)| android:toAlpha|結束透明度|
    ??第一行意為只解釋float fromAlpha屬性,第二行意為只解釋float toAlpha屬性。
    ??實現Alpha效果,首先,在
    res
    目錄下新建一個
    anim
    文件夾用來存儲動畫文件,然后新建一個
    Animation resource file
    xml
    文件,我的代碼里命名其為
    alpha_one
    ,項目地址在文末,大家可以下載參考一下。上面已經總結了各種屬性,所以我們直接看代碼。先是
    xml
    文件:
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:fromAlpha="1.0"
    android:toAlpha="0.0"
    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
    />

??然后Activity中代碼:

 animation = AnimationUtils.loadAnimation(this, R.anim.alpha_one);
 mImgvTween.startAnimation(animation);

??使用AnimationUtils裝載動畫配置文件。兩行代碼,即可實現如下效果:

Alpha效果

??不清楚anim文件夾建在哪里的同學請看我的目錄結構:
目錄結果

??我們再看看不通過xml文件,純代碼實現補間動畫的方法。

  AlphaAnimation anim = new AlphaAnimation(0.0f, 1.0f);
  anim.setDuration(3000);
  mImgvTween.startAnimation(anim);

??如果要實現其它旋轉、縮放、位移等效果,使用不同的Animation例如 RotateAnimation、 ScaleAnimation、TranslateAnimation,根據傳入不同的參數實現我們想要的效果。當然也可以使用xml文件的方式,代碼很簡單,大家可以看我傳到github上的代碼。
??動畫組合:
??我們可以使用動畫組合把多個動畫效果結合到一起展示。如果我們要實現一個縮放和透明度同時變化的效果,像這樣:

動畫組合

??和其它效果一樣,我們仍然有xml文件和純java代碼實現的兩種實現方法。如果使用xml方式的話,按照之前的步驟,在Animation resource file創建個節點為setxml文件即可。先上xml實現的代碼:

<set xmlns:android="http://schemas.android.com/apk/res/android">
    <alpha
        android:duration="2000"
        android:fromAlpha="0.0"
               android:interpolator="@android:anim/accelerate_decelerate_interpolator"
        android:toAlpha="1.0" />
    <scale
        android:duration="2000"
        android:fromXScale="0.0"
        android:fromYScale="0.0"
        android:pivotX="50%p"
        android:pivotY="50%p"
        android:toXScale="1.0"
        android:toYScale="1.0" />
</set>

??Activity中的代碼:

 animation = AnimationUtils.loadAnimation(this, R.anim.set_one);
 mImgvTween.startAnimation(animation);

??從這里可以看出,我們若使用xml方式實現補間動畫,無論要實現哪一種動畫效果或是組合效果,其java代碼是相同的,只是xml文件中的節點不同而已。
??我們再看看純代碼的動畫組合效果實現,上代碼:

                AnimationSet animationSet = new AnimationSet(this,null);
                AlphaAnimation alphaAnimation = new AlphaAnimation(0.0f,1.0f);
                alphaAnimation.setDuration(2000);
                animationSet.addAnimation(alphaAnimation);
                ScaleAnimation scaleAnimation = new ScaleAnimation(0.0f,1.0f,0.0f,1.0f,Animation.RELATIVE_TO_PARENT,0.5f,Animation.RELATIVE_TO_PARENT,0.5f);
                scaleAnimation.setDuration(2000);
                animationSet.addAnimation(scaleAnimation);
                mImgvTween.startAnimation(animationSet);

??這種方式最終效果和xml方式完全一樣。所以到底用哪種方法,也是個人喜好咯。

三、總結

??動畫的可玩性還是很高的,我們可以用幀動畫實現下拉刷新的刷新頭動圖效果(如京東,美團),也可以用補間動畫實現Dialog進出場動畫效果。通過不同的動畫組合,我們可以大大提升我們app的美觀程度。
github項目地址:https://github.com/tangxuesong6/animation

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容