自定義View使用與動畫總結《Android開發藝術》動畫要點精煉


要點提煉|開發藝術之Animation
Android 屬性動畫:這是一篇全面 & 詳細的 屬性動畫 總結&攻略

自定義View

1.空氣質量

在這裏插入圖片描述
可以看出該自定義View包括:(1)頂層文字Text(2)中間的文字Text(3)中間的數字Text(動畫形式)(4)兩個有角度的不同顏色的圓,第一個圓默認畫至一定角度,第二個圓根據空氣質量數值轉化成角度。
我們設定空氣質量爲500時是滿格,空氣質量的數字是Text是動畫效果增長至最終值,而頂層圓也是動畫效果到達對應的角度。提供一個方法當獲取到天氣信息後調用,在updateIndex(int value,String middleText)進行動畫效果,

主要遇到的問題是drawText中的baseline選取。

layout中使用:

<com.example.aaa.coolweather.View.CircleIndexView
       android:id="@+id/circleindexview"
       android:layout_width="170dp"
       android:layout_height="170dp"
       app:topText="污染指數"
       />

2.日出日落

在這裏插入圖片描述
主要思路:繪製一個封閉的半圓,在半圓的左下角與右下角繪製日出時間與日落時間。根據日出日落時間可以計算出總時間。用當前時間-日出時間,可以計算出現在太陽應走過的時間。用應走過的時間/總時間就可以得到當前時間對應的角度。

計算出當前時間對應的最終角度的代碼如下,之後會通過動畫的形式來將太陽圖片移動到對應角度。

mStartTime = startTime;
        mEndTime = endTime;
        mCurrentTime = currentTime;
        mTotalMinute = calculateTime(mStartTime, mEndTime);//計算總時間,單位:分鐘
        mNeedMinute = calculateTime(mStartTime, mCurrentTime);//計算當前所給的時間 單位:分鐘
        mPercentage = formatTime(mTotalMinute, mNeedMinute);//當前時間的總分鐘數佔日出日落總分鐘數的百分比
        mCurrentAngle =  (180 * mPercentage);
        setAnimation(0, mCurrentAngle, 5000);

太陽對應的x座標位置與y座標位置如下,這裏的mCurrentAngle是動畫中當前幀的角度,而不是最終角度。可以通過sin函數和cos函數計算出對應的xy座標值,因爲該自定義View具有屬性radius來代表圓的半徑。

注意:(1)要記得減去這個圖標自己的寬度/2,否則圖片就不在線的正中間了。
(2)同時要注意Math.cos和sin傳入的都是弧度制。
(3)marginTop是用於將圓弧距離頂部一定位置,防止圖片到上面就顯示不全了。

//當太陽在最高點時不會有部分消失
private int marginTop = 30;//離頂部的高度
private int mRadius;  //圓的半徑
...
positionX=mWidth/2-(float)(mRadius*Math.cos((mCurrentAngle)*Math.PI/180))-mSunIcon.getWidth()/2;
positionY=marginTop+mRadius-(float)(mRadius*Math.sin((mCurrentAngle)*Math.PI/180))-mSunIcon.getHeight()/2;

在layout中的具體使用,除了寬高以外,還定義了字體大小,字體顏色,圓的顏色以及圓的半徑(最重要)

<com.example.aaa.coolweather.View.SunView
    android:id="@+id/sunview"
    android:layout_width="match_parent"
    android:layout_height="150dp"
    app:sun_circle_color="#ffff"
    app:sun_circle_radius="120"
    app:sun_font_size="10px"
    app:sun_font_color="#ffff"
    />

自定義View+動畫的整體流程總述

自定義View過程

(1-6是自定義View,7-9是屬性動畫的使用)

  1. 寫一個MyView類,繼承自View。
  2. 在values文件夾下創建attrs.xml文件,在 <declare-styleable name="XXXView">標籤下聲明自定義的屬性(會生存R.styleable.name的數組,幷包含R.styleable.name_attrname的索引)。
  3. 在構造函數中使用TypedArray獲取在佈局中定義的屬性,並使用
  4. 重寫onMeasure以支持wrap_content屬性。定義wrap_content下的寬高值,並當SpecMode(不論寬高)==MeasureSpec.AT_Most,就調用setDimension(定義的寬與高);
  5. 重寫onDraw來繪製我們想要繪製的東西
  6. 在layout佈局中使用自定義屬性,注意開頭要使用app,而非android。

動畫過程

  1. 創建ValueAnimator對象或ObjectAnimator對象,設置動畫持續時間與開始停止位置
  2. 設置動畫從開始到停止的執行邏輯設置插值器與估值器,比如線性與非線性的動畫效果等等,線性不需要設置
    注:(1)(2)過程都是在ValueAnimator.ofObject/ofInt/ofFloat中執行的
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);
  1. 設置AnimatorUpdateListener監聽器,當動畫每播放一幀時會調用其中的onAnimationUpdate方法,如果是ValueAnimator就需要在其中手動設置該對象的屬性,否則則不需要。最後調用invalidate方法讓View重繪,調用onDraw方法。
  2. (如果需要的話)使用AnimatorSet讓兩個動畫同時進行,使用playTogether(Animator A,Animator B)方法。
  3. 如果使用AnimatorSet就要調用其start方法,如果沒有使用,就要調用ValueAnimator對象的start方法。
  4. 之後動畫會按照要求執行,直到結束。

自定義View過程

  1. 寫一個MyView類,繼承自View。
  2. 在values文件夾下創建attrs.xml文件,聲明自定義的屬性。
  3. 在構造函數中使用TypedArray獲取在佈局中定義的屬性,並使用。
  4. 重寫onMeasure以支持wrap_content屬性
  5. 重寫onDraw來繪製我們想要繪製的東西。
  6. 在layout佈局中使用自定義屬性,注意開頭要使用app,而非android。

1.構造函數

只帶有Context的構造函數是在代碼中使用的,而帶有context和AttributeSet(屬性集)的是layout文件中使用的。在最後的構造函數中,我們要初始化參數(將layout中的參數加載進來),以及初始化畫筆(我們要通過畫筆繪製Text與圓)。

public CircleIndexView(Context context)
    {
        this(context, null);
    }

    public CircleIndexView(Context context, AttributeSet attrs)
    {
        this(context, attrs, -1);
    }

    public CircleIndexView(Context context, AttributeSet attrs, int defStyleAttr)
    {
        super(context, attrs, defStyleAttr);
        initParams(context, attrs);
        initPaint();
    }

2.創建attrs.xml文件,並聲明自定義的屬性

需要在values文件夾下創建attrs.xml文件聲明自定義的屬性。在resource標籤下,寫一個declare-styleable的標籤,name一定要寫自定義View的類名。具體屬性使用<attr name="middleText" format="string"/>,name表示名稱,format代表類型。類型包括:string、dimension(尺寸)、color、integer等等。

declare-styleable 不是必要的,但是styleable可以幫我們節省開發工作量,幫我們生成數組的索引常量。每個declare-styleable產生一個R.styleable.name數組,外加每個屬性的R.styleable.name_attrname 對應屬性的下標,可以通過這個下標取出對應屬性存儲的值。

<resources>
    <declare-styleable name="CircleIndexView">
        <attr name="middleText" format="string"/>
        <attr name="middleTextSize" format="dimension"/>
        <attr name="middleTextColor" format="color"/>
        <attr name="topText" format="string"/>
        <attr name="topTextSize" format="dimension"/>
        <attr name="topTextColor" format="color"/>
        <attr name="numberTextSize" format="dimension"/>
        <attr name="numberColor" format="color"/>
        <attr name="outCircleColor" format="color"/>
        <attr name="inCircleColor" format="color"/>
        <attr name="duration" format="integer"/>
    </declare-styleable>
</resources>

3.構造函數中使用TypedArray獲取在佈局中定義的屬性,並使用。

TypedArray幫我們實現了獲取attrs中的id與解析id的功能,因此我們可以根據這個id在TypedArray中獲取想要的屬性值。

(1)取出數組名爲R.styleable.CircleIndexView的TypeArray,之後讀取該TypedArray中的屬性。
(2)最後,recycle()方法的作用就是關閉了這個TypedArray。

可以看到下標爲R.styleable.CircleIndexView_middleText,因此可知declare-styleable爲我們創建了數組,同時可以讓我們通過TypedArray對應下標去查詢屬性。

另外注意一點TypedValue.applyDimension()可以將非標準尺寸(dp,sp等等)轉成Android中的標準尺寸(px像素)。

 /**
     * 初始化View參數
     * 獲取佈局(xml)中的屬性以及使用佈局(xml)中的屬性,
     * 
     * 
     * @param context
     * @param attrs
     */
    private void initParams(Context context, AttributeSet attrs)
    {
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CircleIndexView);
        middleText = ta.getString(R.styleable.CircleIndexView_middleText);
       setMiddleText(middleText);
     	...
     	//這裏其實是一般在layot裏沒有去寫這個屬性,因此這裏直接將默認值轉爲px以便使用
 topTextSize = ta.getDimension(R.styleable.CircleIndexView_topTextSize,
                TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 15, context.getResources().getDisplayMetrics())); 
        ta.recycle();
    }

初始化畫筆就比較簡單了,初始化畫筆的顏色,粗細等等

4.重寫onMeasure以支持wrap_content屬性

常規操作:
1. 首先通過當前的MeasureSpec來獲取寬高測量模式和大小
2. 設定WrapContent模式下的寬高
3. 當測量模式爲AT_MOST且佈局參數爲wrap_content時,直接調用setMeasuredDimension(mWidth, mHeight);,去設置寬高。

完整代碼:

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        // 獲取寬-測量規則的模式和大小
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);

        // 獲取高-測量規則的模式和大小
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        // 設置wrap_content的默認寬 / 高值
        // 默認寬/高的設定並無固定依據,根據需要靈活設置
        // 類似TextView,ImageView等針對wrap_content均在onMeasure()對設置默認寬 / 高值有特殊處理,具體讀者可以自行查看
        int mWidth = 400;
        int mHeight = 400;

        // 當佈局參數設置爲wrap_content時,設置默認值
        if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT && getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
            setMeasuredDimension(mWidth, mHeight);
            // 寬 / 高任意一個佈局參數爲= wrap_content時,都設置默認值
        } else if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT) {
            setMeasuredDimension(mWidth, heightSize);
        } else if (getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
            setMeasuredDimension(widthSize, mHeight);
        }
    }

而本自定義View中爲了好看,默認wrap_content和match_parent都爲寬高200dp,除非爲固定大小,我們才更改size爲固定的大小,注意這裏不管是200dp還是固定大小,都要將dp轉成px。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        setMeasuredDimension(measureWidth(widthMeasureSpec),
                measureHeight(heightMeasureSpec));
    }

5.重寫onDraw來繪製我們想繪製的東西

(1)繪製圓(canvas.drawArc)
RectF的四個參數分別是左上右下,可以通過創建RectF來讓canvas通過canvas.drawArc(mRectF,startAngle,sweepAngle,false,outPaint);來畫圓(其中傳入參數是圓的左上右下四個點的位置,開始的角度,停止的角度),稍微注意一下內圓圈的角度是會變的,所以這裏使用getInSweepAngle()方法來獲取。

(2)繪製文字(canvas.drawText)
canvas.drawText(text, x, y, paint)方法,其中x是這個字符串的左邊在屏幕的位置(也就是最左邊那條豎線的座標),如果設置了paint.setTextAlign(Paint.Align.CENTER);那就是字符串的中心;y是指定這個字符串baseline在屏幕上的位置,而不是字符居中的位置

baseline問題
在這裏插入圖片描述
FontMetrics可以理解爲字體度量,可以獲取到top,ascent,descent與bottom距離base的距離。而值向下增加,值向上減少,也就是說top、ascent的值都是負的

fontMetrics=middleTextPaint.getFontMetrics();

在設計baseline時,我們想讓Text內容居中在某個位置(比如說整個View的正中心)。那麼我們就要讓baseline的位置爲正中心高度middle+(base距離正中心高度的距離)。

我們可以推出這個距離設base距離正中心高度爲x,因爲是正中心(top距離middle和bottom距離middle相等),因此top距離base的距離-x = bottom距離base的距離+x,可以求得x=(dtop-dbottom)/2,而已知top的值是負的,因此x就等於(-top-bottom)/2,將該值加上正中心高度middle即可。

baseline=(getCircleHeight()-fontMetrics.bottom-fontMetrics.top)/2;
 protected void onDraw(Canvas canvas){
        //200dp:middleTextSize:60,middleNumberSize:100,bottomTextSize:45
        super.onDraw(canvas);
        mCenter = getCircleWidth()/2;
        //mRadius = getCircleWidth() / 2 - 50;
        mRadius = getCircleWidth()/2-50;
        //左,上,右,下
        mRectF =new RectF(mCenter-mRadius,mCenter-mRadius+getTopTextSize(),
                mCenter+mRadius,mCenter+mRadius+getTopTextSize());
        //外圓圈
        canvas.drawArc(mRectF,startAngle,sweepAngle,false,outPaint);
        //內圓圈
        canvas.drawArc(mRectF,startAngle,getInSweepAngle(),false,inPaint);

        //中心文字(etc. 良)
        middleTextPaint.setColor(getMiddleTextColor());
        middleTextPaint.setTextSize(getMiddleTextSize());
        Paint.FontMetrics fontMetrics=middleTextPaint.getFontMetrics();
        float baseline=(getCircleHeight()-getNumberTextSize()-getMiddleTextSize()-20-fontMetrics.bottom-fontMetrics.top)/2+getTopTextSize();
       //想讓他在居中的位置(getCircleHeight()/2-getNumberTextSize()/2-getMiddleTextSize()/2-20)
        canvas.drawText(getMiddleText(), getCircleWidth() / 2, baseline , middleTextPaint);

        //中心數字(etc)
        middleTextPaint.setColor(getNumberColor());
        middleTextPaint.setTextSize(getNumberTextSize());
        fontMetrics=middleTextPaint.getFontMetrics();
        baseline=(getCircleHeight()-fontMetrics.bottom-fontMetrics.top)/2+getTopTextSize();
        canvas.drawText(getIndexValue()+"",getCircleWidth()/2,
                baseline,middleTextPaint);


        //頂部文字(etc. 空氣污染指數)
        middleTextPaint.setColor(getTopTextColor());
        middleTextPaint.setTextSize(getTopTextSize());
        fontMetrics=middleTextPaint.getFontMetrics();
        baseline=(getCircleHeight()-2*mRadius-fontMetrics.bottom-fontMetrics.top)/2;
        //canvas.drawText(getBottomText(), getCircleWidth() / 2, getCircleHeight() - 50, middleTextPaint);
        //讓這個與圓的最頂端間隔20px
        canvas.drawText(getTopText(), getCircleWidth() / 2,baseline ,middleTextPaint);
}

6.layout文件中使用

注意我們自己定義的屬性,要使用app:attrname。同時要在前面定義 :xmlns:app=“http://schemas.android.com/apk/res-auto”。

動畫

空氣質量的使用

這是空氣質量動畫暴露給外界使用的函數,當獲取到天氣信息後,會通過updateIndex去更新當前空氣質量的數值與空氣質量的文字,其中使用了動畫。
注意調用invalidate會導致View重繪,最終會調用onDraw。因此在invalidate之前要通過set方法設置當前的角度與當前的空氣質量值。

/**
     *
     * @param value 空氣質量數值
     * @param middleText 空氣質量(良)
     */
    public void updateIndex(int value,String middleText){
        setMiddleText(middleText);
        invalidate();
        //當前角度
        float inSweepAngle = sweepAngle * value / 500;
        //角度由0f到當前角度變化
        ValueAnimator angleAnim = ValueAnimator.ofFloat(0f, inSweepAngle);
        //動畫持續時間
        angleAnim.setDuration(getDuration());
        //數值由0到value變化
        ValueAnimator valueAnim = ValueAnimator.ofInt(0,value);
        valueAnim.setDuration(getDuration());
        //註冊監聽器
        angleAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
        {
            @Override
            //當angleAnim變化回調/
            public void onAnimationUpdate(ValueAnimator valueAnimator)
            {
                float currentValue = (float) valueAnimator.getAnimatedValue();
                //將當前的角度值賦給inSweepAngle
                setInSweepAngle(currentValue);
                //通知view改變,調用這個函數後,會返回到onDraw();
                invalidate();
            }
        });
        valueAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
        {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator)
            {
                int currentValue = (int) valueAnimator.getAnimatedValue();
                setIndexValue(currentValue);
                invalidate();
            }
        });
        //讓兩個動畫同時進行。
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.setInterpolator(new DecelerateInterpolator());
        animatorSet.setStartDelay(150);
        animatorSet.playTogether(angleAnim, valueAnim);
        animatorSet.start();

    }

日出日落動畫的使用

setTimes是暴露給外部使用的方法,用於更新當前的時間。

public void setTimes(String startTime, String endTime, String currentTime)
    {
        mStartTime = startTime;
        mEndTime = endTime;
        mCurrentTime = currentTime;

        mTotalMinute = calculateTime(mStartTime, mEndTime);//計算總時間,單位:分鐘
        mNeedMinute = calculateTime(mStartTime, mCurrentTime);//計算當前所給的時間 單位:分鐘
        mPercentage = formatTime(mTotalMinute, mNeedMinute);//當前時間的總分鐘數佔日出日落總分鐘數的百分比

        mCurrentAngle =  (180 * mPercentage);

        setAnimation(0, mCurrentAngle, 5000);

    }
 private void setAnimation(float startAngle,float currentAngle,int duration){
        ValueAnimator sunAnimator= ValueAnimator.ofFloat(startAngle,currentAngle);
        sunAnimator.setDuration(duration);
        sunAnimator.setTarget(currentAngle);
        sunAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mCurrentAngle=(float)animation.getAnimatedValue();
                invalidateView();
            }
        });
        //千萬別忘了這個
        sunAnimator.start();
    }
 private void invalidateView(){
        //繪製太陽的x座標與y座標
        //記得要減去太陽圖標的大小。(讓太陽始終在半圓上)
        positionX=mWidth/2-(float)(mRadius*Math.cos((mCurrentAngle)*Math.PI/180))-mSunIcon.getWidth()/2;
        positionY=marginTop+mRadius-(float)(mRadius*Math.sin((mCurrentAngle)*Math.PI/180))-mSunIcon.getHeight()/2;
        invalidate();
    }

動畫總結

View動畫

主要包括translate平移動畫、scale縮放動畫、rotate旋轉動畫、alpha透明度動畫等四種。

View動畫的View移動只是視覺效果,並不能真正的改變view的位置。
在這裏插入圖片描述
xml的使用:

首先創建xml文件,地址爲res/anim/xxx.xml,可以看到動畫可以僅是一個動畫,也可以是一堆動畫的組合。其中<set>標籤代表AnimatorSet,<translate>代表平移動畫,<scale>代表縮放動畫,<rotate>代表旋轉動畫,<alpha>代表透明動畫(改變View的透明度)

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:shareInterpolator="true"
    android:fillAfter="true">
    
    <translate
        android:fromXDelta="float"
        android:toXDelta="float"
        android:fromYDelta="float"
        android:toYDelta="float"/>
     <scale
        android:fromXScale="float"
        android:toXScale="float"
        android:fromYScale="float"
        android:toYScale="float"
        android:pivotX="float"
        android:pivotY="float"/>    
     <rotate
        android:fromDegrees="float"
        android:toDegrees="float"
        android:pivotY="float"
        android:pivotX="float"/>
    <alpha 
        android:fromAlpha="float"
        android:toAlpha="float"/>

</set>

幀動畫

幀動畫也是View動畫的一種,它會按照順序播放一組預先定義好的圖片。對應類AnimationDrawable。

通過xml定義:
該xml文件創建在res/drawable/ 下,其中根節點<animation-list>,屬性android:oneshot表示是否執行一次;子節點<item> 下可設置輪播的圖片資源id和持續時間。例如:

<?xml version="1.0" encoding="utf-8"?>
<animation-list  xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item android:drawable="@drawable/xxx1" android:duration="500"/>
    <item android:drawable="@drawable/xxx2" android:duration="500"/>
    <item android:drawable="@drawable/xxx3" android:duration="500"/>
    <item android:drawable="@drawable/xxx4" android:duration="500"/>
</animation-list>

在xml聲明好之後,將它作爲View的背景並通過AnimationDrawable來播放。

mView.setBackgroundResource(R.drawable.XXX);
AnimationDrawable animationDrawable = (AnimationDrawable)mView.getBackground();
animationDrawable.start();

屬性動畫

與View動畫不同之處主要在於:(1)View動畫通過不斷圖形變換,而屬性動畫通過動態改變對象屬性;(2)作用對象分別是View和任何對象;(3)如果是通過xml使用的話,存放位置不同:res/anim和res/animator(4)View動畫沒有真正改變View的位置,而屬性動畫真正改變了View的位置。

爲第四點作補充:
補間動畫只是改變了View的視覺效果,而不會真正去改變View的屬性
如,將屏幕左上角的按鈕 通過補間動畫 移動到屏幕的右下角
點擊當前按鈕位置(屏幕右下角)是沒有效果的,因爲實際上按鈕還是停留在屏幕左上角,補間動畫只是將這個按鈕繪製到屏幕右下角,改變了視覺效果而已。

在這裏插入圖片描述

ValueAnimator與ObjectAnimator

ValueAnimator

ValueAnimator是ObjectAnimator的父類,它繼承自Animator。ValueAnimaotor同樣提供了ofInt、ofFloat、ofObject等靜態方法,傳入的參數是動畫過程的開始值、(中間值可以沒有)、結束值來構造動畫對象。

可以將ValueAnimator看成一個值變化器,通過不斷控制值的變化,再不斷手動賦給對象的屬性,從而實現動畫效果。

在這裏插入圖片描述

ObjectAnimator

直接對對象(自動)的屬性值進行改變操作,從而實現動畫效果,和ValueAnimator相比是直接更改屬性的值。使用ObjectAnimator必須要實現set方法

自動賦值的思路如下:(1)如果沒有傳遞初值會通過get值來獲取;(2)在動畫執行的過程中通過set更改屬性的值
在這裏插入圖片描述

插值器與估值器

時間插值器:TimeInterpolator。**根據時間流逝的百分比來計算當前的屬性值改變的百分比。**包括:線性插值器(LinearInterpolator 勻速動畫)、加速減速插值器(AccelerateDecelerateInterpolator 動畫兩頭慢中間快)、減速插值器(DecelerateInterpolator 動畫持續減速);

類型估值器(TypeEvaluator):根據當前屬性改變的百分比來計算改變後的屬性值。包括針對整形、浮點型、顏色的估值器。

以下圖爲例,View的x屬性需要在40ms內從0到40,結合時間插值器和類型估值器來完成。
在這裏插入圖片描述
以線性插值器爲例,由於動畫默認是10ms一幀,那麼當運行到第二幀是t=20ms,因此時間流逝了0.5,因此屬性值也應該改變0.5。時間插值器計算出0.5並返回,具體屬性改變多少由類型估值器計算。估值器通過估值小數*(屬性終點-屬性起點)來計算出改變後的屬性值。

當然插值器與估值器都可以自定義,尤其是在當前類型的估值器沒有的情況下就必須自定義類型估值器。

監聽器

屬性動畫提供了AnimatorLinstenr用於在動畫開始結束取消重複播放時進行回調,

public static interface AnimatorListener {
    void onAnimationStart(Animator animation); //動畫開始
    void onAnimationEnd(Animator animation); //動畫結束
    void onAnimationCancel(Animator animation); //動畫取消
    void onAnimationRepeat(Animator animation); //動畫重複播放
}

AnimatorUpdateListener :監聽整個動畫過程。每播放一幀onAnimationUpdate()就會被調用一次,如下:

public interface AnimatorUpdateListener {
  void onAnimationUpdate(ValueAnimator var1);//在屬性動畫的屬性值變化是回調。
}

工作流程

(1)創建ValueAnimator或ObjectAnimator,並設置動畫持續時間與開始停止位置
(2)設置動畫從開始到停止的執行邏輯設置插值器與估值器,比如線性與非線性的動畫效果等等,線性不需要設置)
注:(1)(2)過程都是在ValueAnimator.ofObject/ofInt/ofFloat中執行的

ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);

(3)設置AnimatorUpdateListener監聽器,當動畫每播放一幀時會調用其中的onAnimationUpdate方法,如果是ValueAnimator就需要在其中手動設置該對象的屬性,否則則不需要。最後調用invalidate方法讓View重繪,調用onDraw方法。

在這裏插入代碼片

(4)(如果需要的話)使用AnimatorSet讓兩個動畫同時進行,使用playTogether(Animator A,Animator B)方法。
(5)如果使用AnimatorSet就要調用其start方法,如果沒有使用,就要調用ValueAnimator對象的start方法。
(6)之後動畫會按照要求執行,直到結束。
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章