天天看點

《Android動畫高手成長記》跳跳球效果

在介紹本文動畫效果實作之前,先來介紹屬性動畫相關的幾個知識點。

  1. ValueAnimator與ObjectAnimator。
  2. Interpolator插值器與TypeEvaluator估值器。

在Android3.0之前,系統提供了兩種動畫效果實作方式,幀動畫frame-by-frame animation和補間動畫tweened animation。幀動畫就是類似電影播放一樣,将整部影片拆分成一片片的然後連貫起來播放。補間動畫則可以實作對view的縮放、平移、旋轉等操作。在3.0之後,出現了一種新的動畫模式稱為屬性動畫property animation。

屬性動畫産生的原因

屬性動畫的出現不是用來替代補間動畫的,而是用來解決補間動畫所不能夠完成的事件的。如果我們的需求超出了操控view的旋轉、縮放、平移等操作範圍,那麼我們就需選擇屬性動畫去實作了。那麼屬性動畫可以做到哪些補間動畫做不到的事情呢?下面列舉幾點,當然屬性動畫的功能很強大,不僅限于我列舉的幾點。

  1. 屬性動畫可以做到對一個非view類進行動畫操作。
  2. 屬性動畫可以做到真正的改變view對象的屬性值。而補間動畫隻是改變了view的動畫效果。

ValueAnimator與ObjectAnimator

ObjectAnimator是屬性動畫中最總要的執行類,ObjectAnimator可以操作某個對象的屬性值,但是這個屬性值必須要有get和set方法,同時也可以設定view的執行線路,通過插值器來控制。

舉個簡單的例子:

ObjectAnimator
.ofFloat(view, "rotationY", 0.0F, 360.0F)
.setDuration(500)
.start();
           

其可操作的屬性有x/y;scaleX/scaleY;rotationX/ rotationY;transitionX/ transitionY等。

上面的例子是修改view的單個屬性值,同樣也可以同時修改多個屬性,下面介紹兩種

  1. PropertyValuesHolder屬性值持有者
    PropertyValuesHolder pvhLeft = PropertyValuesHolder.ofInt("left", 0, 1);
    
    PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("top", 0, 1);
    
    ObjectAnimator.ofPropertyValuesHolder( this, pvhLeft, pvhTop).
               
  2. 提供一個不存在的屬性值
    ObjectAnimator anim = ObjectAnimator.ofFloat(view, "long", 1.0F, 0.0F).setDuration(500);
    anim.start();
    anim.UpdateListener(new AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            float cVal = (Float) animation.getAnimatedValue();
            view.setAlpha(cVal);
            view.setScaleX(cVal);
            view.setScaleY(cVal);
        }
    });
               

上面提到的都是修改對象已有的setter和getter方法的屬性值。那麼如果對象沒有為某個屬性提供提供setter和getter方法呢,我們也可以做到修改這些屬性值,下面提供兩種實作方式:

  1. 通過valueAnimation來實作,這個下面會講解。
  2. 通過自己寫一個包裝類來實作。然後為該屬性提供setter和getter方法。
    class ViewWrapper{
        private View mView;
        public int getWidth(){
            return mView.getLayoutParams().width;
        }
    }
               

ValueAnimator包含Property Animation動畫的所有核心功能,如動畫時間,開始、結束屬性值,相應時間屬性值計算方法等。

屬性動畫的應用有兩個步驟:

1、計算屬性的值。2、根據屬性值執行相應的動作。

ValueAnimator隻是完成了第一步驟,而要完成第二步還要借助于ValueAnimator.onUpdateListener接口,在此方法中可以通過ValueAnimator對象的getAnimatedValue()函數可以得到目前的屬性值。

ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f);
animation.setDuration(1000);
animation.addUpdateListener(new AnimatorUpdateListener  () {
    @Override
     public void onAnimationUpdate(ValueAnimator animation) {
         Log.i("update", ((Float)animation.getAnimatedValue()).toString());
         // 此處可以根據getAnimatedValue擷取到的屬性值改變view相關的屬性進而執行某些動作
     }
});
animation.setInterpolator(new CycleInterpolator(3));
animation.start();
           

Time Interpolator插值器與TypeEvaluator估值器

Time interplator:定義了屬性值變化的方式,如線性均勻改變,開始慢然後逐漸快等。在Property Animation中是TimeInterplator,在View Animation中是Interplator,這兩個是一樣的,在3.0之前隻有Interplator,3.0之後實作代碼轉移至了TimeInterplator。Interplator繼承自TimeInterplator,内部沒有任何其他代碼。

  1. AccelerateInterpolator 加速,開始時慢中間加速
  2. DecelerateInterpolator 減速,開始時快然後減速
  3. AccelerateDecelerateInterolator 先加速後減速,開始結束時慢,中間加速
  4. AnticipateInterpolator 反向 ,先向相反方向改變一段再加速播放
  5. AnticipateOvershootInterpolator 反向加回彈,先向相反方向改變,再加速播放,會超出目的值然後緩慢移動至目的值
  6. BounceInterpolator 跳躍,快到目的值時值會跳躍,如目的值100,後面的值可能依次為85,77,70,80,90,100
  7. CycleIinterpolator 循環,動畫循環一定次數,值的改變為一正弦函數:Math.sin(2 * mCycles * Math.PI * input)
  8. LinearInterpolator 線性,線性均勻改變
  9. OvershottInterpolator 回彈,最後超出目的值然後緩慢改變到目的值
  10. TimeInterpolator 一個接口,允許你自定義interpolator,以上幾個都是實作了這個接口

TypeEvaluator:根據屬性的開始、結束值與TimeInterpolation計算出的因子計算出目前時間的屬性值,android提供了以下幾個evalutor:

  1. IntEvaluator:屬性的值類型為int。
  2. FloatEvaluator:屬性的值類型為float。
  3. ArgbEvaluator:屬性的值類型為十六進制顔色值。
  4. TypeEvaluator:一個接口,可以通過實作該接口自定義Evaluator。

當然我們也可以自己定義估值器,如下:

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

執行個體

接下來我們要實作的就是如下的 效果圖:

《Android動畫高手成長記》跳跳球效果

在本例的實作中我們的重點也是在ValueAnimator和ObjectAnimator中讀者可以結合上述知識内容來消化本例的動畫技術。附上代碼:

package com.example.custom.animation;

import java.util.ArrayList;

import com.example.custom.R;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RadialGradient;
import android.graphics.Shader;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.LinearLayout;

public class BouncingBalls extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.bouncing_balls);
        LinearLayout container = (LinearLayout) findViewById(R.id.container);
        container.addView(new MyAnimationView(this));
    }

    public class MyAnimationView extends View {

        private static final int RED = ;
        private static final int BLUE = ;

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


        public MyAnimationView(Context context) {
            super(context);
            ValueAnimator colorAnim = ObjectAnimator.ofInt(this, "backgroundColor", RED, BLUE);
            colorAnim.setDuration();
            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 = initBouncingBall(event.getX(), event.getY());

            float startY = newBall.getY();
            float endY = getHeight() - ;
            float h = (float)getHeight();
            float eventY = event.getY();
            int duration = (int)( * ((h - eventY)/h));

            // 操作newBall的Y屬性值
            ValueAnimator bounceAnim = ObjectAnimator.ofFloat(newBall, "y", startY, endY);
            bounceAnim.setDuration(duration);
            bounceAnim.setInterpolator(new AccelerateInterpolator());

            ValueAnimator squashAnim1 = ObjectAnimator.ofFloat(newBall, "x", newBall.getX(),newBall.getX() - f);
            squashAnim1.setDuration(duration/);
            squashAnim1.setRepeatCount();
            squashAnim1.setRepeatMode(ValueAnimator.REVERSE);
            squashAnim1.setInterpolator(new DecelerateInterpolator());

            ValueAnimator squashAnim2 = ObjectAnimator.ofFloat(newBall, "width", newBall.getWidth(),newBall.getWidth() + );
            squashAnim2.setDuration(duration/);
            squashAnim2.setRepeatCount();
            squashAnim2.setRepeatMode(ValueAnimator.REVERSE);
            squashAnim2.setInterpolator(new DecelerateInterpolator());

            ValueAnimator stretchAnim1 = ObjectAnimator.ofFloat(newBall, "y", endY, endY + f);
            stretchAnim1.setDuration(duration/);
            stretchAnim1.setRepeatCount();
            stretchAnim1.setInterpolator(new DecelerateInterpolator());
            stretchAnim1.setRepeatMode(ValueAnimator.REVERSE);

            ValueAnimator stretchAnim2 = ObjectAnimator.ofFloat(newBall, "height",newBall.getHeight(), newBall.getHeight() - );
            stretchAnim2.setDuration(duration/);
            stretchAnim2.setRepeatCount();
            stretchAnim2.setInterpolator(new DecelerateInterpolator());
            stretchAnim2.setRepeatMode(ValueAnimator.REVERSE);

            ValueAnimator bounceBackAnim = ObjectAnimator.ofFloat(newBall, "y", endY, startY);
            bounceBackAnim.setDuration(duration);
            bounceBackAnim.setInterpolator(new DecelerateInterpolator());

            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);

            ValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", f, f);
            fadeAnim.setDuration();
            fadeAnim.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    balls.remove(((ObjectAnimator)animation).getTarget());
                }
            });

            AnimatorSet animatorSet = new AnimatorSet();
            animatorSet.play(bouncer).before(fadeAnim);

            animatorSet.start();

            return true;
        }

        private ShapeHolder initBouncingBall(float x, float y) {

            int red = (int)(Math.random() * );
            int green = (int)(Math.random() * );
            int blue = (int)(Math.random() * );
            int color =  | red <<  | green <<  | blue;
            int darkColor =  | red/ <<  | green/ <<  | blue/;

            // 執行個體化一個圓形
            OvalShape circle = new OvalShape();
            circle.resize(f, f);

            // 設定畫筆的形狀
            ShapeDrawable drawable = new ShapeDrawable(circle);
            Paint paint = drawable.getPaint(); 

            // 第一個,第二個參數表示漸變圓中心坐标,半徑,圓心顔色,圓邊緣顔色,渲染器平鋪模式
            RadialGradient gradient = new RadialGradient(f, f, f, color, darkColor, Shader.TileMode.CLAMP);
            // 給畫筆設定著色器
            paint.setShader(gradient);

            ShapeHolder shapeHolder = new ShapeHolder(drawable);
            shapeHolder.setX(x - f);
            shapeHolder.setY(y - f);
            shapeHolder.setPaint(paint);
            balls.add(shapeHolder);

            return shapeHolder;
        }

        @Override
        protected void onDraw(Canvas canvas) {
            for (int i = ; i < balls.size(); ++i) {
                ShapeHolder shapeHolder = balls.get(i);
                canvas.save();
                canvas.translate(shapeHolder.getX(), shapeHolder.getY());
                shapeHolder.getShape().draw(canvas);
                canvas.restore();
            }
        }
    }
}
           
package com.example.custom.animation;

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


public class ShapeHolder {
    private float x = , y = ;
    private int color;
    private float alpha = f;
    private Paint paint;
    private ShapeDrawable shape;
    private RadialGradient gradient;
    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 * f) + f));
    }

    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;
    }
}
           

繼續閱讀