Android 屬性動畫:一文讓你徹底瞭解和掌握屬性動畫用法

在這裏插入圖片描述

屬性動畫概述

前面給我們分析了Android中的幀動畫補間動畫的特點和用法
Android動畫之補間動畫用法最全詳解
Android 動畫之幀動畫用法詳解

Android官方在Anrdoid 3.0以後又推出了一種新的動畫即屬性動畫,既然前面的幀動畫補間動畫能幫助我們實現大部分的Android動畫效果,那麼官方爲什麼還要推出這種新的屬性動畫呢?

原因1:

補間動畫作用的對象是View,也就是作用的對象是Android中的控件,如ImageViewButtonTextView等,也可以作用在佈局上如LinearLayoutConstraintLayoutRelativeLayout等,但是對於一些不是View的對象,無法對這些對象進行動畫操作。比如我們要對某個控件的某個屬性做進行動畫操作,如其顏色,這個顏色也可以看成一個對象,但其並不是View對象,補間動畫就無法實現,屬性動畫可以對這個顏色值做動畫, 能實現一些更加複雜的動畫效果。

原因2:

補間動畫只是改變了View的視覺效果,而不會真正去改變View的屬性
在這裏插入圖片描述
比如我們對一個圖片進行AlphaAnimation,並在動畫前後打印其值

Log.i("MainActivity","動畫開始前mImageView alpha="+mImageView.getAlpha());
animation = new AlphaAnimation(0, 1);
animation.setDuration(2000);
mImageView.startAnimation(animation);
Log.i("MainActivity","動畫結束後mImageView alpha="+mImageView.getAlpha());

在這裏插入圖片描述
從打印的結果可以看出,補間動畫並沒有改變View的屬性值,而屬性動畫不但會幫助我們實現View動畫的一些視覺效果,而且還能改變View的屬性值。

屬性動畫用法

1、屬性動畫都是通過ValueAnimator 類和ObjectAnimator 類來完成,其中ObjectAnimator類是對對象做動畫,ValueAnimator 類是對值做動畫。
2、PropertyValueHolder類可以同時執行多個動畫,AnimatorSetl類可以將多個動畫按一定的秩序先後執行。
3、TypeEvaluator估值器和Interpolator 差值器

我們瞭解了下面6個類的基本用法,就基本徹底掌握了屬性動畫

  • ObjectAnimator 對象動畫
  • ValueAnimator 值動畫
  • PropertyValueHolder 用於同時執行多個動畫
  • TypeEvaluator 估值器
  • AnimatorSet 動畫集合
  • Interpolator 差值器

對象動畫(ObjectAnimator)

ObjectAnimator類是屬性動畫中非常重要的一個類,可以通過該類對View不僅可以實現一些基本的移、旋轉、縮放和透明度四種基本變換動畫,還能實現一些其他屬性值的變換動畫。
實現方式既可以通過Java代碼,也可以通過XML方式來實現,下面我們來分別介紹下兩種方式基本用法。

方法1:Java代碼實現對象動畫

首先我們先來看一下ObjectAnimator類最基本的方法

 public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
        anim.setFloatValues(values);
        return anim;
    }

方法中第一個參數Object target 的作用對象通常是View,也就是Android中的控件或佈局。
方法中第二個參數String propertyName 通常是需要執行動畫的屬性,具體值如下表所示

屬性 值的用法
rotation 以屏幕方向爲軸的旋轉度數
alpha 透明度
translationX / translationY X/Y方向的位移
scaleX /scaleY X/Y方向的縮放倍數
rotationX / rotationY 以X/Y軸爲軸的旋轉度數

方法中第三個參數float... values 表示屬性的變換範圍,該參數可以傳多個值。

添加一些代碼來看一下效果

ImageView imageView = findViewById(R.id.imageView);
ObjectAnimator animator = ObjectAnimator.ofFloat(imageView, "alpha", 1f, 0f, 1f);
animator.setDuration(5000);
animator.start();

該動畫效果表示控件ImageView的透明度在5s內由1變換到0,再由0變回 1。效果如下:
在這裏插入圖片描述
ObjectAnimator的其他方法使用如下:

 ImageView imageView = findViewById(R.id.imageView);
 ObjectAnimator animator = ObjectAnimator.ofFloat(imageView, "alpha", 1f, 0f, 1f);
 animator.setDuration(2000);

 //動畫延遲500ms執行
 animator.setStartDelay(500);

 //執行重複次數 +1
 animator.setRepeatCount(3);

 // 設置動畫重複播放模式 RESTART -執行完一遍後重新執行
 // REVERSE -執行完一遍後 從末位置往前執行
 animator.setRepeatMode(ValueAnimator.RESTART);

 //監聽值變換
 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
     @Override
     public void onAnimationUpdate(ValueAnimator animation) {
         Log.i("MainActivity","value:" +animation.getAnimatedValue());
     }
 });
 animator.start();

運行效果:
在這裏插入圖片描述

onAnimationUpdate回調方法的部分值打印如下
在這裏插入圖片描述

方法2:XML實現對象動畫

在這裏插入圖片描述

XML實現對象動畫
1、在res目錄下新建animator文件夾
2、animator文件夾下創建動畫XML文件,如animator_alpha.xml
往該xml文件中輸入如下代碼

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:propertyName="alpha"
    android:valueFrom="1"
    android:valueTo="0"
    android:valueType="floatType" />

Java代碼中通過加載該xml啓動動畫

ImageView imageView = findViewById(R.id.imageView);
Animator animator = AnimatorInflater.loadAnimator(Main2Activity.this, R.animator.animator_alpha);
animator.setTarget(imageView);
animator.start();

值動畫(ValueAnimator)

值動畫通過控制值的變化,之後 手動賦值給對象的屬性,從而實現動畫。

ValueAnimator的核心方法如下

ValueAnimator ofFloat(float... values) -- 浮點型數值
ValueAnimator  ofInt(int... values) -- 整型數值
ValueAnimator  ofObject(TypeEvaluator evaluator, Object... values) -- 自定義對象類型

下面我們來添加值動畫,在值動畫的監聽函數裏 來獲取值得變化,根據值的變化對控件設置相應的屬性。這裏的屬性可以是控件的任意屬性

 final ImageView imageView = findViewById(R.id.imageView);
 ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
 anim.setDuration(5000);
 anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
     @Override
     public void onAnimationUpdate(ValueAnimator animation) {
         float currentValue = (float) animation.getAnimatedValue();
         Log.d("MainActivity", "cuurent value is " + currentValue);
         imageView.setAlpha(currentValue);
     }
 });
 anim.start();

效果如下
在這裏插入圖片描述
從下面的打印結果中也可以看出,值動畫返回了一系列值。
在這裏插入圖片描述

PropertyValueHolder

PropertyValueHolder可以讓前面的一些動畫同時執行。

 ImageView imageView = findViewById(R.id.imageView);
 PropertyValuesHolder alphaProper = PropertyValuesHolder.ofFloat("alpha", 0.5f, 1f);
 PropertyValuesHolder scaleXProper = PropertyValuesHolder.ofFloat("scaleX", 0f, 1f);
 PropertyValuesHolder scaleYProper = PropertyValuesHolder.ofFloat("scaleY", 0f, 1f);
 PropertyValuesHolder translationXProper = PropertyValuesHolder.ofFloat("translationX", -100, 100);
 PropertyValuesHolder translationYProper = PropertyValuesHolder.ofFloat("translationY", -100, 100);
 PropertyValuesHolder rotationProper = PropertyValuesHolder.ofFloat("rotation", 0, 360);
 ValueAnimator animator = ObjectAnimator.ofPropertyValuesHolder(imageView, alphaProper,
         scaleXProper, scaleYProper,translationXProper,translationYProper,rotationProper);
 animator.setDuration(5000);
 animator.start();

在這裏插入圖片描述

動畫組合(AnimatorSet)

前面的PropertyValueHolder類能實現將多個動畫同時執行,AnimatorSet類不僅能讓多個動畫同時執行,還能讓多個動畫按一定的順序執行,同時也能穿插多個動畫同時執行。
主要的方法如下:

after(Animator anim)將現有動畫插入到傳入的動畫之後執行
after(long delay) 將現有動畫延遲指定毫秒後執行
before(Animator anim) 將現有動畫插入到傳入的動畫之前執行
with(Animator anim) 將現有動畫和傳入的動畫同時執行

 ImageView imageView = findViewById(R.id.imageView);
 ObjectAnimator rotate = ObjectAnimator.ofFloat(imageView, "rotation", 0f, 360f);
 ObjectAnimator translationX = ObjectAnimator.ofFloat(imageView, "translationX", -100, 100f);
 ObjectAnimator translationY = ObjectAnimator.ofFloat(imageView, "translationY", -100, 100f);
 ObjectAnimator scaleX = ObjectAnimator.ofFloat(imageView, "scaleX", 0, 1f);
 ObjectAnimator scaleY = ObjectAnimator.ofFloat(imageView, "scaleY", 0, 1f);
 ObjectAnimator alpha = ObjectAnimator.ofFloat(imageView, "alpha", 1f, 0f, 1f);
 AnimatorSet animSet = new AnimatorSet();
 animSet.play(rotate)
         .with(alpha)
         .after(scaleX)
         .before(translationX)
         .after(1000)
         .before(translationY)
         .with(scaleY);
 animSet.setDuration(5000);
 animSet.start();

效果如下:
在這裏插入圖片描述

差值器(Interpolator)

前面的動畫屬性的變換都是均勻變換,可以通過差值器(Interpolator)來控制值變化的速率

ImageView imageView = findViewById(R.id.imageView);
ObjectAnimator animator = ObjectAnimator.ofFloat(imageView, "alpha",  0f, 1f);
animator.setDuration(5000);
//加速查值器,參數越大,速度越來越快
animator.setInterpolator(new AccelerateInterpolator(5));
animator.start();

從下面的運行的效果可以看出,用了加速差值器以後,該圖片alpha動畫前期變化很慢,都後面變化越來越快。

在這裏插入圖片描述
系統我們提供了九種默認的差值器分別如下:

動畫名稱 效果
AccelerateInterpolator 加速查值器,參數越大,速度越來越快
DecelerateInterpolator 減速差值起,和加速查值器相反
AccelerateDecelerateInterpolator 先加速後減速
AnticipateInterpolator 先後退在加速前進
AnticipateOvershootInterpolator 以X/Y軸爲軸的旋轉度數
BounceInterpolator 彈球效果插值
CycleInterpolator 週期運動插值
LinearInterpolator 勻速插值
OvershootInterpolator 先快速完成動畫,再回到結束樣式

我們還可以通過自定義類實現Interpolator接口來實現一些自定義的差值器效果。

估值器(TypeEvaluator)

在前面的值動畫(ValueAnimator)中和對象動畫(ObjectAnimator)有一個傳對象的方法:

ValueAnimator  ofObject(TypeEvaluator evaluator, Object... values)
ObjectAnimator ofObject(Object target, String propertyName,
            TypeEvaluator evaluator, Object... values)

這些方法動都需要傳一個TypeEvaluator,我們先來看下這個類的源碼

 
public interface TypeEvaluator<T> {

    public T evaluate(float fraction, T startValue, T endValue);

}

TypeEvaluator估值器的源碼可以看出該類的作用就是告訴動畫,如何從起始值過度到結束值。
Android源碼中有好幾個類實現來該接口,也就是系統提供的一些默認估值器, 我們以FloatEvaluator爲例看下其實現代碼。

public class FloatEvaluator implements TypeEvaluator<Number> {
    public Float evaluate(float fraction, Number startValue, Number endValue) {
        float startFloat = startValue.floatValue();
        return startFloat + fraction * (endValue.floatValue() - startFloat);
    }
}

FloatEvaluator的實現可以看出在evaluate方法中用結束值減去初始值,算出它們之間的差值,然後乘以fraction這個係數,再加上初始值,那麼就得到當前動畫的值了

我們也可以以該方法爲例 實現一個自定義的估值器實現一個背景顏色值的變化

我們先定義一個默認估值器類MyTypeEvaluator,該類自定義了顏色過渡的方式

package com.lucashu.animation;

import android.animation.TypeEvaluator;

public class MyTypeEvaluator implements TypeEvaluator<String> {
    private int mCurrentRed = -1;
    private int mCurrentGreen = -1;
    private int mCurrentBlue = -1;

    @Override
    public String evaluate(float fraction, String startValue, String endValue) {

        
        int startRed = Integer.parseInt(startValue.substring(1, 3), 16);
        int startGreen = Integer.parseInt(startValue.substring(3, 5), 16);
        int startBlue = Integer.parseInt(startValue.substring(5, 7), 16);
        int endRed = Integer.parseInt(endValue.substring(1, 3), 16);
        int endGreen = Integer.parseInt(endValue.substring(3, 5), 16);
        int endBlue = Integer.parseInt(endValue.substring(5, 7), 16);
        // 初始化顏色的值
        if (mCurrentRed == -1) {
            mCurrentRed = startRed;
        }
        if (mCurrentGreen == -1) {
            mCurrentGreen = startGreen;
        }
        if (mCurrentBlue == -1) {
            mCurrentBlue = startBlue;
        }
        // 計算初始顏色和結束顏色之間的差值
        int redDiff = Math.abs(startRed - endRed);
        int greenDiff = Math.abs(startGreen - endGreen);
        int blueDiff = Math.abs(startBlue - endBlue);
        int colorDiff = redDiff + greenDiff + blueDiff;
        if (mCurrentRed != endRed) {
            mCurrentRed = getCurrentColor(startRed, endRed, colorDiff, 0,
                    fraction);
        } else if (mCurrentGreen != endGreen) {
            mCurrentGreen = getCurrentColor(startGreen, endGreen, colorDiff,
                    redDiff, fraction);
        } else if (mCurrentBlue != endBlue) {
            mCurrentBlue = getCurrentColor(startBlue, endBlue, colorDiff,
                    redDiff + greenDiff, fraction);
        }
        // 將計算出的當前顏色的值組裝返回
        String currentColor = "#" + getHexString(mCurrentRed)
                + getHexString(mCurrentGreen) + getHexString(mCurrentBlue);
        return currentColor;
    }


    /**
     * 根據fraction值來計算當前的顏色。
     */
    private int getCurrentColor(int startColor, int endColor, int colorDiff,
                                int offset, float fraction) {
        int currentColor;
        if (startColor > endColor) {
            currentColor = (int) (startColor - (fraction * colorDiff - offset));
            if (currentColor < endColor) {
                currentColor = endColor;
            }
        } else {
            currentColor = (int) (startColor + (fraction * colorDiff - offset));
            if (currentColor > endColor) {
                currentColor = endColor;
            }
        }
        return currentColor;
    }

    /**
     * 將10進制顏色值轉換成16進制。
     */
    private String getHexString(int value) {
        String hexString = Integer.toHexString(value);
        if (hexString.length() == 1) {
            hexString = "0" + hexString;
        }
        return hexString;
    }
}

再自定義一個View,在該類中畫一個矩形框

public class MyView extends View {

    private String color;
    private Paint mPaint;
    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.WHITE);
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
        mPaint.setColor(Color.parseColor(color));
        invalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawRect(0, 0, 500,500, mPaint);
    }
}

自頂一個View在佈局文件中添加如下

    <com.lucashu.animation.MyView
        android:id="@+id/myview"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_marginBottom="100dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

將在估值器加到動畫中,該動畫作用在我們自定義的View上

MyView imageView = findViewById(R.id.myview);
ObjectAnimator anim = ObjectAnimator.ofObject(
        imageView,"color", new MyTypeEvaluator(),
        "#0000FF","#FF0000");
anim.setDuration(5000);
anim.start();

效果如下:
在這裏插入圖片描述

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