屬性動畫的基本使用

Android中動畫大概可以分爲補間動畫,幀動畫,屬性動畫三種
(一)補間動畫Animation分以下4種:
1.TranslateAnimation 平移動畫 x,y方向上的平移 對應xml標籤
2.ScaleAnimation 縮放動畫 x,y方向上的縮放 對應xml標籤
3.RotateAnimation 旋轉動畫 對應xml標籤
4.AlphaAnimation 透明度動畫 對應xml標籤
創建的xml動畫文件放在res/anim文件夾下
(二)幀動畫是通過順序播放一組圖片實現的
系統使用AnimationDrawable類來定義幀動畫也可以通過xml來實現,對應使用標籤中包含標籤來實現
(三)屬性動畫是api11新加入的,api11以下的需要使用NineOldAndroid.jar來做兼容,屬性動畫的實現原理是通過修改對象的屬性值來實現的,可作用於任何對象。補間動畫中的四種動畫效果,也可以通過屬性動畫來實現。
下面主要介紹一下屬性動畫的使用,屬性動畫中主要用到以下幾個類:
1)ObjectAnimator 該類繼承於ValueAnimator 對應xml標籤
2)ValueAnimator 值動畫 對應xml標籤
3)AnimatorSet 屬性動畫集合 對應xml標籤
4)PropertyValueHolder
5)TypeEvaluator 估值器 控制屬性值的變化
6)Interpolator 插值器 控制屬性值變化的速度,加速減速勻速等變化
當然屬性動畫也可以通過xml來定義,xml文件要放在res/animator的目錄下,相關定義標籤。通常情況下,我們都是通過代碼方式來實現屬性動畫的。

一.ValueAnimator

通過以下方法創建
ValueAnimator.ofFloat(“對象屬性”,“屬性值1…屬性值n”);
ValueAnimator.ofArgb(“對象屬性”,“屬性值1…屬性值n”);
ValueAnimator.ofInt(“對象屬性”,“屬性值1…屬性值n”);

常用方法有以下幾種
setDuration(500);//設置動畫時長 時間單位爲ms
setRepeatCount(ValueAnimator.INFINITE);//設置循環次數 INFINITE表示循環執行動畫
setRepeatMode(ValueAnimator.RESTART);//設置重複模式 RESTART重新開始/REVERSE反轉
setStartDelay(200);//延遲執行動畫 時間單位爲ms
setEvaluator(new FloatEvaluator());//設置動畫的估值器
setInterpolator(new LinearInterpolator());//設置動畫的插值器
addUpdateListener(AnimatorUpdateListener listener);//添加監聽值改變的監聽器
addListener(AnimatorListener listener)//添加動畫監聽 當前動畫執行狀態 onAnimationStart / onAnimationEnd / onAnimationCancel
getAnimatedFraction()//在監聽中可以通過該方法來獲取當前動畫執行進度
getAnimatedValue()//在監聽中可以通過該方法來獲取當前動畫執行的值
cancel()//取消當前動畫
isRunning()
isStarted()
start()

二.ObjectAnimator

ObjectAnimator繼承自ValueAnimator
ObjectAnimator對象可以使用以下方法來創建
ObjectAnimator.ofFloat(“作用對象類”,“對象屬性”,“屬性值1…屬性值n”);
ObjectAnimator.ofArgb(“作用對象類”,“對象屬性”,“屬性值1…屬性值n”);
ObjectAnimator.ofInt(“作用對象類”,“對象屬性”,“屬性值1…屬性值n”);

先看一下View中自帶的實現屬性動畫的方法

		//將ImageView水平向左平移500
        iv.setTranslationX(500);
        //將ImageView垂直向下平移500
        iv.setTranslationY(500);
        //將ImageView水平方向上縮放爲原來的1.5倍
        iv.setScaleX(1.5f);
        //將ImageView垂直方向上縮放爲原來的1.5倍
        iv.setScaleY(1.5f);
        //將ImageView水平方向上旋轉30度
        iv.setRotationX(30);
        //將ImageView垂直方向上旋轉30度
        iv.setRotationY(30);
        //將ImageView的透明度設置爲100
        iv.setAlpha(100);
        //改變ImageView的背景色
        iv.setBackgroundColor(Color.RED);

以上代碼都是View的一些屬性方法
這裏我們也可以通過ObjectAnimator來對ImageView的屬性進行改變,可實現相關動畫效果

//實現水平方向上平移 向左500 設置一個值的時候,默認初始值爲0
        ObjectAnimator translateXAnim=ObjectAnimator.ofFloat(iv,//作用的對象
                "translationX", //改變對象相關的屬性
                500f);//這裏是一個泛型數組,可以有多個值 表示改變對象屬性的值
        translateXAnim.setDuration(500);//設置動畫時長
        translateXAnim.start();

        //實現垂直方向上的平移 向下500
        ObjectAnimator translateYAnim=ObjectAnimator.ofFloat(iv,//作用的對象
                "translationY", //改變對象相關的屬性
                500f);//這裏是一個泛型數組,可以有多個值 表示改變對象屬性的值
        translateYAnim.setDuration(500);
        translateYAnim.start();

        //實現水平方向上的縮放 爲原來的1.5倍
        ObjectAnimator scaleXAnim=ObjectAnimator.ofFloat(iv,//作用的對象
                "scaleX", //改變對象相關的屬性
                1.5f);//這裏是一個泛型數組,可以有多個值 表示改變對象屬性的值
        scaleXAnim.setDuration(500);
        scaleXAnim.start();

        //實現垂直方向上的縮放 爲原來的1.5倍
        ObjectAnimator scaleYAnim=ObjectAnimator.ofFloat(iv,//作用的對象
                "scaleY", //改變對象相關的屬性
                1.5f);//這裏是一個泛型數組,可以有多個值 表示改變對象屬性的值
        scaleYAnim.setDuration(500);
        scaleYAnim.start();

        //實現水平方向上的旋轉
        ObjectAnimator rotateXAnim=ObjectAnimator.ofFloat(iv,//作用的對象
                "rotationX", //改變對象相關的屬性
                30f);//這裏是一個泛型數組,可以有多個值 表示改變對象屬性的值
        rotateXAnim.setDuration(500);
        rotateXAnim.start();

        //實現垂直方向上的旋轉
        ObjectAnimator rotateYAnim=ObjectAnimator.ofFloat(iv,//作用的對象
                "rotationY", //改變對象相關的屬性
                30f);//這裏是一個泛型數組,可以有多個值 表示改變對象屬性的值
        rotateYAnim.setDuration(500);
        rotateYAnim.start();

爲什麼通過以上方法就可以實現對ImageView的動畫效果呢,主要是通過改變View的屬性值來實現,這個就涉及到屬性動畫的實現原理了,後面我們會通過對屬性動畫的源碼分析來它的具體實現

多個動畫同時執行可通過PropertyValuesHolder來實現,也可以通過下面的AnimatorSet來實現

PropertyValuesHolder holder1 = PropertyValuesHolder.ofFloat("alpha", 1f,0.5f);
PropertyValuesHolder holder2 = PropertyValuesHolder.ofFloat("scaleX", 1f,0.5f);
PropertyValuesHolder holder3 = PropertyValuesHolder.ofFloat("scaleY", 1f,0.5f);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(iv, holder1,holder2,holder3);
animator.setDuration(200);
animator.start();

三.AnimatorSet

AnimatorSet表示一個動畫集合控制多個動畫一起播放,主要通過以下方法來實現:
play(Animator anim)
playSequentially(Animator… items) //表示按照順序播放動畫
playSequentially(List items)
playTogether(Animator… items) //一起播放所有動畫
playTogether(Collection items)
play()方法可以和AnimatorSet.Builder中的方法一起使用來控制動畫播放順序
with(Animator anim)
before(Animator anim)
after(Animator anim)
after(long delay)

下面我們來實現多個動畫同時執行

ObjectAnimator animator1 = ObjectAnimator.ofFloat(iv, "translationX", 0f,100f);
animator1.setRepeatCount(3);
ObjectAnimator animator2 = ObjectAnimator.ofFloat(iv, "alpha", 0f,1f);
animator2.setStartDelay(startDelay)//設置延遲執行
ObjectAnimator animator3 = ObjectAnimator.ofFloat(iv, "scaleX", 0f,2f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(500);
animatorSet.play(animator3).with(animator2).after(animator1);//animator1在前面
animatorSet.play(animator3).with(animator2).before(animator1);//animator1在後面
animatorSet.playTogether(animator1,animator2,animator3);
animatorSet.playSequentially(animator1,animator2,animator3);
animatorSet.start();

四.TypeEvaluator

Android系統給我們提供了一些常用的估值器,TypeEvaluator有以下子類可供使用
IntEvaluator
FloatEvaluator
ArgbEvaluator
IntArrayEvaluator
FloatArrayEvaluator
RectEvaluator
PointFEvaluator
我們也可以根據自己的需求來自定義估值器,TypeEvaluator的源碼如下,TypeEvaluator只是一個接口,定義了一個計算值的方法evaluate

ublic interface TypeEvaluator<T> {

    /**
     * This function returns the result of linearly interpolating the start and end values, with
     * <code>fraction</code> representing the proportion between the start and end values. The
     * calculation is a simple parametric calculation: <code>result = x0 + t * (x1 - x0)</code>,
     * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
     * and <code>t</code> is <code>fraction</code>.
     *
     * @param fraction   The fraction from the starting to the ending values 這個是一個進度值百分比
     * @param startValue The start value.開始值
     * @param endValue   The end value.結束值
     * @return A linear interpolation between the start and end values, given the
     *         <code>fraction</code> parameter.
     */
    public T evaluate(float fraction, T startValue, T endValue);

}

自定義估值器需要實現TypeEvaluator接口,並實現evaluate方法,主要代碼實現邏輯是在evaluate方法中根據當前進度值對要返回的值進行動態改變,例如系統提供的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);
    }
}

五.Interpolator

Android系統給我們提供了一些常用的插值器,供我們日常使用
Interpolator繼承自TimeInterpolator,BaseInterpolator和LookupTableInterpolator都繼承自Interpolator
BaseInterpolator主要有以下子類
AccelerateDecelerateInterpolator
AnticipateInterpolator
PathInterpolator
BounceInterpolator
OvershootInterpolator
AnticipateOvershootInterpolator
LinearInterpolator
AccelerateInterpolator
DecelerateInterpolator
CycleInterpolator
LookupTableInterpolator主要有以下子類
FastOutSlowInInterpolator
FastOutLinearInInterpolator
LinearOutSlowInInterpolator
在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述
我們也可以自定義插值器對當前動畫的進行速度進行動態改變,這裏需要實現TimeInterpolator 接口來實現自定義類

TimeInterpolator的源碼如下

public interface TimeInterpolator {

    /**
     * Maps a value representing the elapsed fraction of an animation to a value that represents
     * the interpolated fraction. This interpolated value is then multiplied by the change in
     * value of an animation to derive the animated value at the current elapsed animation time.
     *
     * @param input A value between 0 and 1.0 indicating our current point
     *        in the animation where 0 represents the start and 1.0 represents
     *        the end
     * @return The interpolation value. This value can be more than 1.0 for
     *         interpolators which overshoot their targets, or less than 0 for
     *         interpolators that undershoot their targets.
     */
    float getInterpolation(float input);
}

AccelerateInterpolator的源碼實現,代碼的主要實現邏輯在getInterpolation方法中
BaseInterpolator extends Interpolator
Interpolator extends TimeInterpolator

public class AccelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
    private final float mFactor;
    private final double mDoubleFactor;

    public AccelerateInterpolator() {
        mFactor = 1.0f;
        mDoubleFactor = 2.0;
    }

    /**
     * Constructor
     *
     * @param factor Degree to which the animation should be eased. Seting
     *        factor to 1.0f produces a y=x^2 parabola. Increasing factor above
     *        1.0f  exaggerates the ease-in effect (i.e., it starts even
     *        slower and ends evens faster)
     */
    public AccelerateInterpolator(float factor) {
        mFactor = factor;
        mDoubleFactor = 2 * mFactor;
    }

    public AccelerateInterpolator(Context context, AttributeSet attrs) {
        this(context.getResources(), context.getTheme(), attrs);
    }

    /** @hide */
    public AccelerateInterpolator(Resources res, Theme theme, AttributeSet attrs) {
        TypedArray a;
        if (theme != null) {
            a = theme.obtainStyledAttributes(attrs, R.styleable.AccelerateInterpolator, 0, 0);
        } else {
            a = res.obtainAttributes(attrs, R.styleable.AccelerateInterpolator);
        }

        mFactor = a.getFloat(R.styleable.AccelerateInterpolator_factor, 1.0f);
        mDoubleFactor = 2 * mFactor;
        setChangingConfiguration(a.getChangingConfigurations());
        a.recycle();
    }
	//主要實現方法
    public float getInterpolation(float input) {
        if (mFactor == 1.0f) {
            return input * input;
        } else {
            return (float)Math.pow(input, mDoubleFactor);
        }
    }

    /** @hide */
    @Override
    public long createNativeInterpolator() {
        return NativeInterpolatorFactoryHelper.createAccelerateInterpolator(mFactor);
    }
}

六.實現今日頭條點贊效果

在這裏插入圖片描述
實現原理
1.在點擊按鈕的時候,不斷的向佈局中添加不同的ImageView
2.自定義TypeEvaluator重寫evaluate方法,對座標值進行二階貝塞爾曲線計算,並返回計算後的座標值
3.然後使用ValueAnimator添加AnimatorUpdateListener監聽來獲取當前點的座標值
4.在動畫改變的過程中對ImageView的x,y值進行處理,同時改變它的透明度和大小縮放

實現代碼如下:

public class AnimatorActivity extends AppCompatActivity implements CompoundButton.OnCheckedChangeListener {
    private static final String TAG = "AnimatorActivity";
    //表情資源
    private static final int[] faces = {
            R.mipmap.face1, R.mipmap.face2, R.mipmap.face3, R.mipmap.face4,
            R.mipmap.face5, R.mipmap.face6, R.mipmap.face7, R.mipmap.face8,
            R.mipmap.face9, R.mipmap.face10, R.mipmap.face11, R.mipmap.face12,
            R.mipmap.face13, R.mipmap.face14, R.mipmap.face15, R.mipmap.face16,
            R.mipmap.face17, R.mipmap.face18, R.mipmap.face19, R.mipmap.face20,
    };

    private Stack<ImageView> cacheViews;//用於緩存ImageView 重複使用
    private Random random;
    private int screenWidth;
    private int screenHeight;
    private List<PointF> points;
    private PointF pointF;
    private PointF leftTopP;
    private PointF midTopP;
    private PointF rightTopP;
    private PointF leftBottomP;
    private PointF midBottomP;
    private PointF rightBottomP;

    private RelativeLayout content;
    private CheckBox checkbox;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_animator);
        checkbox = findViewById(R.id.checkbox);
        checkbox.setOnCheckedChangeListener(this);
        content = findViewById(R.id.content);
        cacheViews = new Stack<>();
        screenWidth = getScreenSize()[0];
        screenHeight = getScreenSize()[1];
        points = new ArrayList<>();
        random = new Random();

        //初始化點贊起始點得座標值 爲手機屏幕中央
        pointF = new PointF(screenWidth / 2, screenHeight / 2);
        //初始化點贊表情移動軌跡結束點6個 分別爲左上,中上,右上,左底,中底,右底
        leftTopP = new PointF(0, 0);
        midTopP = new PointF(screenWidth / 2, 0);
        rightTopP = new PointF(screenWidth, 0);
        leftBottomP = new PointF(0, screenHeight / 2);
        midBottomP = new PointF(screenWidth / 2, screenHeight);
        rightBottomP = new PointF(screenWidth, screenHeight);

        points.add(leftTopP);
        points.add(midTopP);
        points.add(rightTopP);
        points.add(leftBottomP);
        points.add(midBottomP);
        points.add(rightBottomP);
    }

    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        ImageView imageView = addFaceView();
        startAnim(imageView);
    }

    /**
     * 每點擊一次生成一個View 執行平移和縮放和淡出動畫
     */
    private ImageView addFaceView() {
        //生成一個隨機數來獲取圖片資源
        int index = random.nextInt(faces.length - 1);
        Log.e(TAG, "addFaceView: index=" + index);
        ImageView imageView;
        //先從緩存中取控件
        if (!cacheViews.empty()) {
            imageView = cacheViews.pop();
        } else {
            imageView = new ImageView(this);
            RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
                    RelativeLayout.LayoutParams.WRAP_CONTENT,
                    RelativeLayout.LayoutParams.WRAP_CONTENT);
            params.addRule(RelativeLayout.CENTER_IN_PARENT);
            imageView.setLayoutParams(params);
        }
        imageView.setImageResource(faces[index]);
        //當前結束點只有6 個,而圖片有20個 這裏需要轉換一下index避免數組越界
        imageView.setTag(index >= points.size() ? index / (faces.length / points.size() + 1) : index);
        content.addView(imageView);

        return imageView;
    }

    private void startAnim(final ImageView view) {
        int index = (int) view.getTag();
        //這裏動態生成二階貝塞爾曲線路徑控制點的座標
        PointF controlP = new PointF();
        if (index < index / 2) {
            //控制點x座標不變 y座標屏幕上半部分使用結束點y座標加上屏幕四分之一
            controlP.x = points.get(index).x;
            controlP.y = points.get(index).y + screenHeight / 4;
        } else {
            //控制點x座標不變 y座標屏幕上半部分使用結束點y座標減去屏幕四分之一
            controlP.x = points.get(index).x;
            controlP.y = points.get(index).y - screenHeight / 4;
        }
        //這裏將控制點座標傳入估值器
        FaceEvaluator faceEvaluator = new FaceEvaluator(controlP);
        //在值動畫中傳入 動畫執行的開始點座標和結束點座標 開始點座標不變都是屏幕中心 結束點則隨機生成
        ValueAnimator valueAnimator = ValueAnimator.ofObject(faceEvaluator, pointF, points.get(index));
        valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
        valueAnimator.setDuration(2000);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                PointF pointF = (PointF) animation.getAnimatedValue();
                //移動路徑動畫
                view.setX(pointF.x);
                view.setY(pointF.y);
                //透明度動畫
                view.setAlpha(1-animation.getAnimatedFraction()/2);
                //縮放動畫
                view.setScaleX(0.9f+(animation.getAnimatedFraction()/2));
                view.setScaleY(0.9f+(animation.getAnimatedFraction()/2));
            }
        });
        valueAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                //動畫執行完成後將view緩存起來,方便下次使用
                content.removeView(view);
                cacheViews.push(view);
            }
        });
        valueAnimator.start();
    }

    private static class FaceEvaluator implements TypeEvaluator<PointF> {

        //控制點座標
        private PointF controlPoint;

        public FaceEvaluator(PointF controlPoint) {
            this.controlPoint = controlPoint;
        }

        @Override
        public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
            //(1 - t)^2 P0 + 2 t (1 - t) P1 + t^2 P2
            //使用二階貝塞爾曲線來改變值得走勢
            PointF point = new PointF();
            point.x = (float) (Math.sqrt(1 - fraction) * startValue.x + 2 * fraction * (1 - fraction) * controlPoint.x + Math.sqrt(fraction) * endValue.x);
            point.y = (float) (Math.sqrt(1 - fraction) * startValue.y + 2 * fraction * (1 - fraction) * controlPoint.y + Math.sqrt(fraction) * endValue.y);
//            point.x=startValue.x+(endValue.x-startValue.x)*fraction;
//            point.y=startValue.y+(endValue.y-startValue.y)*fraction;
            return point;
        }
    }

    /**
     * 獲取屏幕得寬高
     *
     * @return 寬高的數組
     */
    private int[] getScreenSize() {
        int[] screenSize = new int[2];
        DisplayMetrics metrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(metrics);
        screenSize[0] = metrics.widthPixels;
        screenSize[1] = metrics.heightPixels;
        return screenSize;
    }
}

XML佈局文件如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/content"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".AnimatorActivity">

    <CheckBox
        android:id="@+id/checkbox"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:button="@drawable/agree_selector"/>
</RelativeLayout>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章