1. 程式人生 > >Android 屬性動畫:實現小球墜落

Android 屬性動畫:實現小球墜落

一、要做什麼

專案需要實現的效果:小球墜落


 1. 首先繪製小球--自定義View 繪製圓;
 2. 模擬小球墜落--屬性動畫,重繪小球軌跡;
 3. 修改小球顏色--實現自定義TypeEvaluator;

實現的簡單效果如下:

這裡寫圖片描述

二、思考怎麼做

實現步驟如下:

1、自定義 AnimPointView:

/**
 * Created by Troy on 2017/3/20.
 *
 * 通過對物件進行值操作來實現動畫效果的功能,這就是ValueAnimator的高階用法
 */
public class AnimPointView extends View {

    public
static final float sRADIUS = 20F; private Point mCurrentPoint; private Paint mPaint; private Paint mTextPaint; //動畫持續時間 預設5S private int mAnimDuration; private int mDefaultAnimDuration = 5; //小球序號 private String mBallText; private String mDefaultBallText = "1"; //初始顏色
private String mBallStartColor; private String mDefaultBallStartColor = "#0000FF"; //結束顏色 private String mBallEndColor; private String mDefaultBallEndColor = "#FF0000"; public AnimPointView(Context context) { super(context); init(); } public AnimPointView(Context context, AttributeSet attrs) { super
(context, attrs); //自定義屬性 TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.Ball); mAnimDuration = typedArray.getInt(R.styleable.Ball_anim_duration, mDefaultAnimDuration); mBallText = typedArray.getString(R.styleable.Ball_ball_text); mBallStartColor = typedArray.getString(R.styleable.Ball_start_color); mBallEndColor = typedArray.getString(R.styleable.Ball_end_color); if(TextUtils.isEmpty(mBallText)){ mBallText = mDefaultBallText; } if(TextUtils.isEmpty(mBallStartColor)){ mBallStartColor = mDefaultBallStartColor; } if(TextUtils.isEmpty(mBallEndColor)){ mBallEndColor = mDefaultBallEndColor; } //回收typedArray typedArray.recycle(); init(); } public AnimPointView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init(){ //畫圓的畫筆 mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setColor(Color.RED); //畫文字的畫筆 mTextPaint = new Paint(); mTextPaint.setAntiAlias(true); mTextPaint.setColor(Color.WHITE); mTextPaint.setTextSize(sRADIUS); mTextPaint.setTextAlign(Paint.Align.CENTER); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if(mCurrentPoint == null){ mCurrentPoint = new Point(sRADIUS, sRADIUS); drawCircle(canvas); startAnimation(); }else { drawCircle(canvas); } } //繪製圓球 private void drawCircle(Canvas canvas){ float x = mCurrentPoint.getX(); float y = mCurrentPoint.getY(); canvas.drawCircle(x, y, sRADIUS, mPaint); canvas.drawText(mBallText, x, y + 5, mTextPaint); } // 呼叫了invalidate()方法,這樣的話 onDraw()方法就會重新呼叫,並且由於currentPoint 物件的座標已經改變了, // 那麼繪製的位置也會改變,於是一個平移的動畫效果也就實現了; private void startAnimation(){ //改變小球的位置 ValueAnimator Point startPoint = new Point(getWidth() / 2, sRADIUS); Point endPoint = new Point(getWidth() / 2, getHeight() - sRADIUS); Log.i("TEST", "startPoint:" + startPoint.getX() + "-" + startPoint.getY()); Log.i("TEST", "endPoint:" + endPoint.getX() + "-" + endPoint.getY()); ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint); //動畫監聽事件,不斷重繪view anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mCurrentPoint = (Point) animation.getAnimatedValue(); //invalidate() 與 requestLayout()的區別,這個地方也可以用requestLayout(); invalidate(); } }); //設定動畫的彈跳差值器 anim.setInterpolator(new BounceInterpolator()); //改變小球的顏色 ObjectAnimator ObjectAnimator anim2 = ObjectAnimator.ofObject(this, "color", new ColorEvaluator(), mBallStartColor, mBallEndColor); //組合動畫 AnimatorSet animSet = new AnimatorSet(); animSet.play(anim).with(anim2); animSet.setDuration(mAnimDuration*1000); animSet.start(); } private String color; public String getColor() { return color; } public void setColor(String color) { this.color = color; mPaint.setColor(Color.parseColor(color)); invalidate(); } }

2、自定義屬性及佈局使用

在attrs.xml 檔案中定義屬性:

<declare-styleable name="Ball">
        <attr name="ball_text" format="string"/>
        <attr name="start_color" format="string"/>
        <attr name="end_color" format="string"/>
        <attr name="anim_duration" format="integer"/>
    </declare-styleable>

在activity 佈局中使用:

<com.troy.bargraph.view.AnimPointView
        android:id="@+id/anim_point_view1"
        android:layout_width="0dp"
        android:layout_weight="1.2"
        android:layout_height="match_parent"
        app:ball_text="1"        //小球序號
        app:end_color="#66CDAA"  //結束顏色
        app:anim_duration="6"/>  //開始顏色

3、小球位置估值器

public class PointEvaluator implements TypeEvaluator {
    @Override
    public Object evaluate(float fraction, Object startValue, Object endValue) {
    //fraction 與時間有關的係數,該值由差值器計算得出,由ValueAnimator呼叫 animateValue 
        Point startPoint = (Point)startValue;
        Point endPoint = (Point)endValue;
        float x = startPoint.getX() + fraction*(endPoint.getX() - startPoint.getX());
        float y = startPoint.getY() + fraction*(endPoint.getY() - startPoint.getY());
        return new Point(x, y);
    }
}

4、關於 evaluate 方法中fraction 因子的值來源

首先應該明白差值器的概念和基本使用,我們一般在程式碼裡給動畫設定一個差值器:

anim.setInterpolator(new BounceInterpolator());

如果沒有設定差值器,系統預設使用加速減速差值器:

// The time interpolator to be used if none is set on the animation
    private static final TimeInterpolator sDefaultInterpolator =
            new AccelerateDecelerateInterpolator();

如果設定 null ,系統預設使用線性差值器:

/**
     * 1、interpolator 的作用:The time interpolator used in calculating the elapsed fraction of this animation. The
     * 2、差值器的賦值:interpolator determines whether the animation runs with linear or non-linear motion,
     * such as acceleration and deceleration. The default value is
     * {@link android.view.animation.AccelerateDecelerateInterpolator}
     *
     * @param value the interpolator to be used by this animation. A value of <code>null</code>
     * will result in linear interpolation.
     */
    @Override
    public void setInterpolator(TimeInterpolator value) {
        if (value != null) {
            mInterpolator = value;
        } else {
            // 當設定 null 時,使用線性差值器
            mInterpolator = new LinearInterpolator();
        }
    }

看ValueAnimator 的原始碼可知 fraction 是由差值器計算出來的:

float fraction = mInterpolator.getInterpolation(fraction);
//getInterpolation 是父介面的方法,具體實現在子類中;

Interpolator 的直接子類如下:

這裡寫圖片描述

我們看最簡單的線性差值器的實現:

/**
 * An interpolator where the rate of change is constant
 */
@HasNativeInterpolator
public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {

    public LinearInterpolator() {
    }

    public LinearInterpolator(Context context, AttributeSet attrs) {
    }

    public float getInterpolation(float input) {
        return input; //輸入什麼返回什麼;
    }

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

4、顏色改變估值器

public class ColorEvaluator implements TypeEvaluator {

    //將十六進位制的顏色表示切割成三段,分別為紅色段、綠色段、藍色段,分別計算其隨時間改變而對應的值;
    private int mCurrentRed = -1;

    private int mCurrentGreen = -1;

    private int mCurrentBlue = -1;

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

        String startColor = (String) startValue;
        String endColor = (String) endValue;

        // Integer.parseInt(String s ,int radix)方法: 輸出一個十進位制數; radix 表示原來的進位制;
        int startRed = Integer.parseInt(startColor.substring(1, 3), 16);
        int startGreen = Integer.parseInt(startColor.substring(3, 5), 16);
        int startBlue = Integer.parseInt(startColor.substring(5, 7), 16);
        int endRed = Integer.parseInt(endColor.substring(1, 3), 16);
        int endGreen = Integer.parseInt(endColor.substring(3, 5), 16);
        int endBlue = Integer.parseInt(endColor.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;
    }

}

基本就是上面一些內容。

三、總結:

做完這個DEMO,應該掌握的知識點如下:

  1. View的知識點;重繪View 有 invalidate() 與 requestLayout();二者的區別。
  2. 常見的幾種估值器 TypeEvaluator ,及如果根據需求自定義 TypeEvaluator ;
  3. 常見的差值器 Interpolator;
  4. fraction 因子值的計算規則;