安卓動畫系列之五, 屬性動畫PropertyAnimation(下) - 通過官方例子深入瞭解

這裏繼續之前寫的上篇屬性動畫PropertyAnimation(上)之初步印象 來寫下篇,瞭解一下自定義的對象如何調用實現屬性動畫,還有AnimatorSet的一些靈活用法.本來也嘗試像之前那樣寫demo去講,但發現android官方在這方面已經提供了非常好的例子,於是就拿官方的這個小球下落回彈來作爲例子,深入的瞭解屬性動畫的用法吧. 代碼中的註釋我已經非常詳細,所以不再另外寫出來了.過一遍代碼,相信我們自己以後也能靈活運用屬性動畫了.


先上效果圖:



代碼:

ShapeHolder:

package com.test.android.objectanimationmain;

import android.graphics.Paint;
import android.graphics.RadialGradient;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.Shape;

/**
 * ShapeHolder作爲屬性動畫的對象,必須設置setter和getter方法才能使動畫生效.
 * 這個類本質上也是通過ShapeDrawable來創建可視化的實體對象.
 * 裏面都是主要的getter和setter方法
 */
public class ShapeHolder {
    private float x = 0, y = 0;
    private ShapeDrawable shape;
    private int color;
    private RadialGradient gradient;
    private float alpha = 1f;
    private Paint paint;

    public void setPaint(Paint value) {
        paint = value;
    }
    public Paint getPaint() {
        return paint;
    }

    public void setX(float value) {
        x = value;
    }
    public float getX() {
        return x;
    }
    public void setY(float value) {
        y = value;
    }
    public float getY() {
        return y;
    }
    public void setShape(ShapeDrawable value) {
        shape = value;
    }
    public ShapeDrawable getShape() {
        return shape;
    }
    public int getColor() {
        return color;
    }
    public void setColor(int value) {
        shape.getPaint().setColor(value);
        color = value;
    }
    public void setGradient(RadialGradient value) {
        gradient = value;
    }
    public RadialGradient getGradient() {
        return gradient;
    }

    public void setAlpha(float alpha) {
        this.alpha = alpha;
        shape.setAlpha((int)((alpha * 255f) + .5f));
    }

    public float getWidth() {
        return shape.getShape().getWidth();
    }
    public void setWidth(float width) {
        Shape s = shape.getShape();
        s.resize(width, s.getHeight());
    }

    public float getHeight() {
        return shape.getShape().getHeight();
    }
    public void setHeight(float height) {
        Shape s = shape.getShape();
        s.resize(s.getWidth(), height);
    }

    public ShapeHolder(ShapeDrawable s) {
        shape = s;
    }

}


主類方法:

 public class MyAnimationView extends View {

        private static final int RED = 0xffFF8080;
        private static final int BLUE = 0xff8080FF;
        private static final int CYAN = 0xff80ffff;
        private static final int GREEN = 0xff80ff80;

        public final ArrayList<ShapeHolder> balls = new ArrayList<ShapeHolder>();
        AnimatorSet animation = null;

        public MyAnimationView(Context context) {
            super(context);

            // Animate background color
            // Note that setting the background color will automatically invalidate the
            // view, so that the animated color, and the bouncing balls, get redisplayed on
            // every frame of the animation.
            //設置背景顏色的動畫
            ValueAnimator colorAnim = ObjectAnimator.ofInt(this, "backgroundColor", RED, BLUE);
            colorAnim.setDuration(3000);
            //設置屬性值計算器
            colorAnim.setEvaluator(new ArgbEvaluator());
            colorAnim.setRepeatCount(ValueAnimator.INFINITE);
            colorAnim.setRepeatMode(ValueAnimator.REVERSE);
            colorAnim.start();
        }

        @Override
        public boolean onTouchEvent(MotionEvent event) {
            //如果觸摸事件不是按下或者移動事件,則不攔截
            if (event.getAction() != MotionEvent.ACTION_DOWN &&
                    event.getAction() != MotionEvent.ACTION_MOVE) {
                return false;
            }
            ShapeHolder newBall = addBall(event.getX(), event.getY());

            // Bouncing animation with squash and stretch
            //獲取觸摸的事件X,Y座標
            float startY = newBall.getY();
            //小球最後下落點的Y座標,由屏幕高度減去小球高度得到
            float endY = getHeight() - 50f;
            //獲取shapeholder的高度
            float h = (float)getHeight();
            float eventY = event.getY();
            int duration = (int)(500 * ((h - eventY)/h));
            //這裏設置了"y","x"這些屬性,但是系統針對newBall這對象是沒有setX(), setY()的方法支持的.
            //所以我們在newBall所繼承的自定義的ShapeHolder了中,必須自己去實現setX() setY()
            //和getX() , getY()方法,這樣才能是屬性動畫在使用過程中可以根據時間不斷的getter和setter.
            //這裏就是我們所使用自定義的對象去調用屬性動畫的關鍵地方.
            //換言之,如果ShapeHolder中將x,y換成locationX,locationY,只要類中實現類似setLocationY(),getLocationY()方法就行了,
            //下面這句照樣可以換成ValueAnimator bounceAnim = ObjectAnimator.ofFloat(newBall, "locationY", startY, endY);
            ValueAnimator bounceAnim = ObjectAnimator.ofFloat(newBall, "y", startY, endY);
            bounceAnim.setDuration(duration);
            //設置插值器,由慢到快.這樣小球(也就是一個圓)在下落的過程中是從慢到快的,看起來是受了地心引力的影響...更真實一些
            bounceAnim.setInterpolator(new AccelerateInterpolator());
            //這裏創建的squashAnim1屬性動畫,是實現小球掉落在底部的Y座標時的壓扁效果.X座標偏移25f
            ValueAnimator squashAnim1 = ObjectAnimator.ofFloat(newBall, "x", newBall.getX(),
                    newBall.getX() - 25f);
            //這裏設置的動畫時間只有bounceAnim動畫時間的1/4,因爲壓扁的過程是迅速的,所以要時間值小很多才合理
            squashAnim1.setDuration(duration/4);
            //設置重複次數爲1次
            squashAnim1.setRepeatCount(1);
            //設置重複模式是逆向返回起點
            squashAnim1.setRepeatMode(ValueAnimator.REVERSE);
            //設置插值器,一直減速
            squashAnim1.setInterpolator(new DecelerateInterpolator());
            //這裏一連串的squashAnim1,squashAnim2等等都是構成小球壓扁效果的動畫,寬度增加至50
            ValueAnimator squashAnim2 = ObjectAnimator.ofFloat(newBall, "width", newBall.getWidth(),
                    newBall.getWidth() + 50);
            squashAnim2.setDuration(duration/4);
            squashAnim2.setRepeatCount(1);
            //因爲壓扁完還要恢復原狀,這裏必須是逆向返回起始值,使用ValueAnimator.REVERSE
            squashAnim2.setRepeatMode(ValueAnimator.REVERSE);
            squashAnim2.setInterpolator(new DecelerateInterpolator());
            //依舊是構成壓扁效果的動畫,小球壓扁後Y軸上移25f,也就是半個小球的高度,看起來是壓扁到恢復原狀的效果了.
            ValueAnimator stretchAnim1 = ObjectAnimator.ofFloat(newBall, "y", endY,
                    endY + 25f);
            stretchAnim1.setDuration(duration/4);
            stretchAnim1.setRepeatCount(1);
            stretchAnim1.setInterpolator(new DecelerateInterpolator());
            stretchAnim1.setRepeatMode(ValueAnimator.REVERSE);
            //依舊是構成壓扁效果的動畫,小球高度減小25.
            ValueAnimator stretchAnim2 = ObjectAnimator.ofFloat(newBall, "height",
                    newBall.getHeight(), newBall.getHeight() - 25);
            stretchAnim2.setDuration(duration/4);
            stretchAnim2.setRepeatCount(1);
            stretchAnim2.setInterpolator(new DecelerateInterpolator());
            stretchAnim2.setRepeatMode(ValueAnimator.REVERSE);
            //小球回彈動畫,彈回原點.
            ValueAnimator bounceBackAnim = ObjectAnimator.ofFloat(newBall, "y", endY,
                    startY);
            bounceBackAnim.setDuration(duration);
            bounceBackAnim.setInterpolator(new DecelerateInterpolator());
            // Sequence the down/squash&stretch/up animations
            //這裏是AnimatorSet的靈活用法,上篇中只講了AnimatorSet的playSequentially()和playTogether()方法
            //AnimatorSet作爲一個動畫容器,這裏規定了bounceAnim在squashAnim1動畫前面播放,
            //然後squashAnim1播放的同時又播放squashAnim2,stretchAnim1,stretchAnim2這三個動畫
            //最後規定bounceBackAnim這個小球彈起動畫必須放在stretchAnim2這個動畫後面去播放,也就是放最後.
            //這些規則,保證了小球從下落到底部,然後在底部產生壓扁效果,然後恢復原狀,再彈回原點的整個過程.
            AnimatorSet bouncer = new AnimatorSet();
            bouncer.play(bounceAnim).before(squashAnim1);
            bouncer.play(squashAnim1).with(squashAnim2);
            bouncer.play(squashAnim1).with(stretchAnim1);
            bouncer.play(squashAnim1).with(stretchAnim2);
            bouncer.play(bounceBackAnim).after(stretchAnim2);

            // Fading animation - remove the ball when the animation is done
            //這裏動畫定義透明度從可見到完全不可見,就是小球彈回原點後消失了的效果.
            ValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
            //消失的時間很短,所以這個值不要設置過長.
            fadeAnim.setDuration(250);
            //添加監聽接口,當動消失的動畫結束,就將小球(ShapeHolder的子類)從隊列中刪去.
            fadeAnim.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    balls.remove(((ObjectAnimator)animation).getTarget());

                }
            });

            // Sequence the two animations to play one after the other
            AnimatorSet animatorSet = new AnimatorSet();
            //這裏規定消失動畫必須在整個下落回彈結束後纔開始
            animatorSet.play(bouncer).before(fadeAnim);

            // Start the animation
            //啓動動畫
            animatorSet.start();

            //攔截觸摸事件,返回true.
            return true;
        }

        private ShapeHolder addBall(float x, float y) {
            //創建橢圓Shape
            OvalShape circle = new OvalShape();
            //X和Y都是50f,則代表這個橢圓circle是一個半徑爲25f的圓
            circle.resize(50f, 50f);
            //上篇分析過ShapeDrawable的源碼,可知下面是使用OvalShape去創建shapedrawable對象
            ShapeDrawable drawable = new ShapeDrawable(circle);
            ShapeHolder shapeHolder = new ShapeHolder(drawable);
            shapeHolder.setX(x - 25f);
            shapeHolder.setY(y - 25f);
            //下面是通過隨機的方法去生成紅綠藍三個值.,從而組合成ARGB的值,設置爲該圓的顏色
            int red = (int)(Math.random() * 255);
            int green = (int)(Math.random() * 255);
            int blue = (int)(Math.random() * 255);
            int color = 0xff000000 | red << 16 | green << 8 | blue;
            //每個ShapeDrawable對象都有自己的paint,直接getPaint()就能獲取了
            Paint paint = drawable.getPaint(); //new Paint(Paint.ANTI_ALIAS_FLAG);
            int darkColor = 0xff000000 | red/4 << 16 | green/4 << 8 | blue/4;
            //給上面生成的ARGB值,添加圓的中心到邊緣顏色從深到淺的漸變效果
            RadialGradient gradient = new RadialGradient(37.5f, 12.5f,
                    50f, color, darkColor, Shader.TileMode.CLAMP);
            paint.setShader(gradient);
            //到了這一步,圓的顏色效果已經確定了
            shapeHolder.setPaint(paint);
            balls.add(shapeHolder);
            return shapeHolder;
        }

        @Override
        protected void onDraw(Canvas canvas) {
            for (int i = 0; i < balls.size(); ++i) {
                //畫出小球
                ShapeHolder shapeHolder = balls.get(i);
                canvas.save();
                canvas.translate(shapeHolder.getX(), shapeHolder.getY());
                shapeHolder.getShape().draw(canvas);
                canvas.restore();
            }
        }
    }


在自己新建的Activity的view中直接container.addView(new MyAnimationView(getActivity())); 就能看到效果了.


上面官方的例子沒有用到自定義屬性計算器Evaluator,於是爲了和上面的效果對比明顯,就寫了一個奇葩的.

 /**
     * 自定義屬性計算器
     */
    public class MyEvaluator implements TypeEvaluator
    {
        @Override
        public Object evaluate(float fraction, Object startValue, Object endValue) {
            fraction = (Float)startValue - (Float)endValue/10;
            return fraction;
        }
    }

然後在第一個動畫中去設置它:

bounceAnim.setEvaluator(new MyEvaluator());

這樣看看效果,是不是點擊屏幕的時候,小球不再是直接從按下的地方開始下落,而是從按下的Y軸座標更往上一些的地方下落.



值得注意的是,如果要使用自定義的對象去調用屬性動畫,必須在屬性動畫所用到的對象中設置相應屬性的getter和setter方法纔行.不然是沒有效果的.


以上demo所用到的方法已經是比較複雜的屬性動畫的進階用法,相關說明也在註釋寫上,所有複雜的動畫都不是一兩個動畫就一蹴而就的,複雜的東西都由較爲簡單的東西去組合而成.就像代碼中AnimatorSet的動畫集制定了各種播放規則,組合了5,6個動畫才實現一個小球從下落到底部,然後壓扁恢復原狀,再彈起,最後消失,這樣的過程.


最後,希望能給你提供一點點幫助.感謝你閱讀本文.


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