Android动画篇——Interpolator展示控件实现

上几篇我们讲解了Interpolator的作用和自定义Interpolator的实现方式,这一篇我们通过自定义一个Interpolator曲线展示控件来更加深入的了解Interpolator的原理。

在这里插入图片描述

原理

之前我们已经讲过Interpolator的作用原理,我们再来回忆一下:动画在执行过程中会不断地调用Interpolator的getInterpolation方法并传入一个当前动画执行时间进度的值input(0——1之间),Interpolator会根据input值计算出一个动画在input时间进度情况下的动画执行进度的值fraction(一般来说fraction取值为0时,意味着当然动画属性值取值为startValue,取值为1时属性值取值为endValue),当然还需要拿fraction去TypeEvaluator中去获取实际的动画属性值。可以发现Interpolator就是一个将动画执行时间和取值相关联起来的一个对象。

实现

Interpolator的作用原理解释完了,我们来看控件的具体实现过程。

public class InterpolatorView extends View {
    private static final String TAG = InterpolatorView.class.getSimpleName();

    private Interpolator interpolator;
    //Interpolation路径
    private Path path;
    //路径画笔
    private Paint pathPaint;
    //边框画笔
    private Paint paint;
    //表示时间和Interpolator取值的小球画笔
    private Paint timeGlobulePaint;
    //模拟动画的小球画笔
    private Paint animationGlobulePaint;
    //文字尺寸
    private float textSize = 25f;
    //动画时间
    private int duration = 3000;

    //表示时间的小球的位置
    private float timeGlobuleCurrentX = 0;
    private float timeGlobuleCurrentY = 0;
    //表示时间进度的小球的半径
    private static final int TIME_GLOBULE_RADIUS = 10;

    //模拟动画的小球的位置
    private float animationGlobuleCurrentX = 0;
    //模拟动画的小球的半径
    private static final int ANIMATION_GLOBULE_RADIUS = 20;

    public InterpolatorView(Context context) {
        this(context, null, 0);
    }

    public InterpolatorView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public InterpolatorView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        initPaint();
    }

    /**
     * 设置Interpolator
     * @param interpolator Interpolator
     */
    public void setInterpolator(Interpolator interpolator){
        this.interpolator = interpolator;

        if(interpolator == null){
            return;
        }

        resetGlobuleLocation();
        initPath();
    }

    /**
     * 重置小球位置
     */
    private void resetGlobuleLocation(){
        timeGlobuleCurrentX = 0;
        timeGlobuleCurrentY = 0;
        animationGlobuleCurrentX = 0;
    }

    /**
     * 开始播放模拟动画效果
     */
    public void startAnimation(){
        if(interpolator == null){
            return;
        }

        //动画小球的移动动画
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, getWidth());
        valueAnimator.setDuration(duration);
        valueAnimator.setInterpolator(interpolator);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int height = getHeight();
                int width = getWidth();

                //X轴表示时间流逝的比值(当前时间 / 总时间 * 绘制宽度)
                timeGlobuleCurrentX = (float)width * animation.getCurrentPlayTime() / duration;
                //Y轴Interpolator返回值(当前进度下的Interpolator返回值 * 绘制高度)
                timeGlobuleCurrentY = height - (interpolator.getInterpolation(animation.getCurrentPlayTime() / (float)duration) * height / 2f + height / 8f);
                Log.d(TAG,"绘制小球:x = " + timeGlobuleCurrentX + "---- y = " + timeGlobuleCurrentY);

                //动画模拟的是TranslateX的动画效果,getAnimatedValue()获取当前X坐标值
                animationGlobuleCurrentX = (float) animation.getAnimatedValue();
                Log.d(TAG,"模拟动画小球:x = " + animationGlobuleCurrentX);

                invalidate();
            }
        });

        valueAnimator.start();
    }

    /**
     * 开始播放模拟动画效果
     */
    public void startAnimation(int duration){
        this.duration = duration;

        startAnimation();
    }

    /**
     * 设置字体尺寸
     * @param textSize 字体尺寸
     */
    public void setTextSize(float textSize){
        this.textSize = textSize;

        invalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if(path == null){
            initPath();
        }
        int height = getHeight();
        int width = getWidth();

        //边框
        canvas.drawLine(0f,0f,width, 0, paint);
        canvas.drawLine(0f,height,width, height, paint);
        canvas.drawLine(0f,0f,0, height, paint);
        canvas.drawLine(width,0f,width, height, paint);

        //横线
        canvas.drawLine(0f,height / 4f,width, height / 4f, paint);
        canvas.drawLine(0f,height * 3 / 8f,width, height * 3 / 8f, paint);
        canvas.drawLine(0f,height - height / 8f,width, height - height / 8f, paint);

        //文字
        paint.setTextSize(textSize);
        canvas.drawText(" 0",0f, height - height / 8f + 30f, paint);
        canvas.drawText(" 1",0f, height * 3 / 8f - 4, paint);
        canvas.drawText("t",width - 15f, height - height / 8f + 30f, paint);

        //绘制Interpolator路径
        canvas.drawPath(path, pathPaint);

        //绘制表示时间进度和动画进度关系的小球
        if(timeGlobuleCurrentX == 0 && timeGlobuleCurrentY == 0){
            timeGlobuleCurrentY = height - height / 8f;
        }
        canvas.drawOval(timeGlobuleCurrentX - TIME_GLOBULE_RADIUS, timeGlobuleCurrentY - TIME_GLOBULE_RADIUS,
                timeGlobuleCurrentX + TIME_GLOBULE_RADIUS, timeGlobuleCurrentY + TIME_GLOBULE_RADIUS, timeGlobulePaint);


        //绘制模拟动画的小球
        float animationGlobuleCurrentY = height / 8f;
        canvas.drawOval(animationGlobuleCurrentX - ANIMATION_GLOBULE_RADIUS, animationGlobuleCurrentY - ANIMATION_GLOBULE_RADIUS,
                animationGlobuleCurrentX + ANIMATION_GLOBULE_RADIUS, animationGlobuleCurrentY + ANIMATION_GLOBULE_RADIUS, animationGlobulePaint);
    }

    /**
     * 初始化画笔
     */
    private void initPaint(){
        //Interpolator路径画笔
        pathPaint = new Paint();
        pathPaint.setColor(Color.RED);
        pathPaint.setStyle(Paint.Style.STROKE);
        pathPaint.setStrokeWidth(4);
        pathPaint.setAntiAlias(true);

        //边框画笔
        paint = new Paint();
        paint.setColor(Color.GRAY);
        paint.setStyle(Paint.Style.FILL);
        paint.setStrokeWidth(2);
        paint.setAntiAlias(true);
        paint.setTextSize(30);

        //时间与进度小球画笔
        timeGlobulePaint = new Paint();
        timeGlobulePaint.setColor(Color.BLUE);
        timeGlobulePaint.setStyle(Paint.Style.FILL);
        timeGlobulePaint.setStrokeWidth(0);
        timeGlobulePaint.setAntiAlias(true);

        //动画小球画笔
        animationGlobulePaint = new Paint();
        animationGlobulePaint.setColor(Color.GREEN);
        animationGlobulePaint.setStyle(Paint.Style.FILL);
        animationGlobulePaint.setStrokeWidth(0);
        animationGlobulePaint.setAntiAlias(true);
    }

    /**
     * 初始化Interpolation路径
     */
    private void initPath(){
        path = new Path();
        int width = getWidth();
        int height = getHeight();
        if(height != 0 && width != 0 && interpolator != null){
            path.reset();
            //以控件Y轴的3/8到7/8处为坐标系绘制曲线
            path.moveTo(0,height - (interpolator.getInterpolation(0) * height / 2f + height / 8f));
            //遍历x坐标,模拟时间进度,从interpolator中取值
            for(int i = 0; i < width; i++){
                path.lineTo(i,height - (interpolator.getInterpolation(i / (float)width) * height / 2f + height / 8f));
            }

            invalidate();
        }
    }
}

代码注释的很详细了,应该能够看得懂。大体说一下思路,分为三个步骤:

  • 1、坐标系的绘制
  • 2、Interpolator变化曲线的绘制
  • 3、动画小球的绘制

1、除了控件边框的绘制,我们将控件的Y轴分为两部分,上部1/4处为动画小球模拟动画区,下部分为曲线绘制区,曲线绘制区又在上下各预留了1/8的空间,用来绘制当Interpolator取值小于0和大于1的部分,Interpolator取值变化较大时可能显示不完整。
2、通过遍历X轴坐标,来模拟动画时间进度的变化,从而去Interpolator中获取当前进度值。
3、定义了一个ValueAnimator来模拟动画自动执行过程,这部分应该是最简单的就不解释了,需要注意的是在ValueAnimator中会根据当前动画进度来获取在Interpolator曲线上移动的小球的位置。

至此我们定义了一个相对完整的展示Interpolator变化曲线的控件,当然还有几处瑕疵,比如:当Interpolator取值变化太大时,控件显示不完整等问题,后面有时间再考虑完善的问题。

参考博客:
http://inloop.github.io/interpolator/
http://nightfarmer.github.io/2016/09/12/InterpolatorPreview/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值