Android 属性动画:实现小球坠落

一、要做什么

项目需要实现的效果:小球坠落


 1. 首先绘制小球--自定义View 绘制圆;
 2. 模拟小球坠落--属性动画,重绘小球轨迹;
 3. 修改小球颜色--实现自定义TypeEvaluator;

实现的简单效果如下:

Android 属性动画:实现小球坠落_第1张图片

二、思考怎么做

实现步骤如下:

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 null
     * 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 的直接子类如下:

Android 属性动画:实现小球坠落_第2张图片

我们看最简单的线性差值器的实现:

/**
 * 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 因子值的计算规则;

参考致谢:
(1)郭霖 http://blog.csdn.net/guolin_blog/article/details/44171115

你可能感兴趣的:(Android,进阶)