手记

Android自定义view 模仿win10进度条

流程

  • 1.一个匀速圆周运动的点

  • 2.多个匀速圆周运动的点

  • 3.多个圆周运动的点,速度由快到慢

  • 4.点与点之间的间距线性减少,动画的最后合为一个点

  • 5.为了让动画看起来更加流畅,需要在动画即将结束的时候手动绘制点


核心控件

  • PathMeasure:截取Path中的一部分并显示

  • ValueAnimator:完成动画从初始值平滑的过度到结束值的效果,同时还负责管理动画的播放次数、播放模式、以及对动画设置监听器等


流程一

思路

  1. 先用path画一个圆

  2. ValueAnimator设置为0f-1f的平滑

  3. 用PathMeasure根据ValueAnimator返回的值截取path上的一个点

    private Paint mPaint;    private Path mPath;    private PathMeasure mPathMeasure;    private int mWidth,mHeight;    private ValueAnimator valueAnimator;    //用这个来接受ValueAnimator的返回值,代表整个动画的进度
    private float t;
  • 初始化画笔

    mPaint = new Paint();
    mPaint.setStyle(Paint.Style.STROKE);
    mPaint.setStrokeWidth(15);
    mPaint.setColor(Color.WHITE);
    //设置画笔为园笔
    mPaint.setStrokeCap(Paint.Cap.ROUND);
    //抗锯齿
    mPaint.setAntiAlias(true);
  • 初始化Path和mPathMeasure 
    这里角度不能选360,否则会测量失误,具体原因和android的内部优化有关

    mPath = new Path();
    RectF rect = new RectF(-150,-150,150,150);
    mPath.addArc(rect,-90,359.9f);
    mPathMeasure = new PathMeasure(mPath,false);
  • 初始化ValueAnimator

        valueAnimator = ValueAnimator.ofFloat(0f,1f).setDuration(3000);
        valueAnimator.setRepeatCount(-1);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                t = (float) animation.getAnimatedValue();
                invalidate();
            }
        });

这里的ValueAnimator设置的是一个时长3秒的动画,再这3秒中,ValueAnimator会返回一个由0f-1f平滑的数字 
ValueAnimator.ofFloat(0f,1f).setDuration(3000) 
在这里我们用t来接受返回值,同时刷新视图

t = (float) animation.getAnimatedValue();
invalidate();

这里可以看出t的值,有0到1,这里就可以把t理解为我们这个动画的进度

  • 开始绘制

    @Override
    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);
        canvas.translate(mWidth/2,mHeight/2);
        Path dst = new Path();
        mPathMeasure.getSegment(mPathMeasure.getLength()*t,mPathMeasure.getLength()*t+1,dst,true);
        canvas.drawPath(dst,mPaint);

    }

流程二

思路

  • 我们设置让t每间隔0.05就画一个点,总共画4个点,注意这里getSegment()的最后一个要设置为true来保证画出来的是多个点而不是一条线

    @Override
    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);
        canvas.translate(mWidth/2,mHeight/2);
        Path dst = new Path();        int num = (int) (t/0.05);        float s,y,x;        switch(num){            default:            case 3:
                x = t-0.15f;
                s = mPathMeasure.getLength();
                y = s*x;
                mPathMeasure.getSegment(y,y+1,dst,true);            case 2:
                x = t-0.10f;
                s = mPathMeasure.getLength();
                y = s*x;
                mPathMeasure.getSegment(y,y+1,dst,true);            case 1:
                x = t-0.05f;
                s = mPathMeasure.getLength();
                y = s*x;
                mPathMeasure.getSegment(y,y+1,dst,true);            case 0:
                x = t;
                s = mPathMeasure.getLength();
                y = s*x;
                mPathMeasure.getSegment(y,y+1,dst,true);                break;
        }
        canvas.drawPath(dst,mPaint);
    }

流程三

思路

我们先绘制出路程-时间的函数图像 
函数为y = -x*x + 2*x,当x=1时,y=mPathMeasure.getLength(); 
设s = mPathMeasure.getLength(); 
最终我们套用函数:y = -s*x*x+2*s*x; 
这里的Y轴代表的是path的长度,X轴对应时间 
所以把流程二中的y = s*x改成y = -s*x*x+2*s*x即可

switch(num){
            default:
            case 3:                x = t-0.15f;                s = mPathMeasure.getLength();                y = -s*x*x+2*s*x;
                mPathMeasure.getSegment(y,y+1,dst,true);
            case 2:                x = t-0.10f;                s = mPathMeasure.getLength();                y = -s*x*x+2*s*x;
                mPathMeasure.getSegment(y,y+1,dst,true);
            case 1:                x = t-0.05f;                s = mPathMeasure.getLength();                y = -s*x*x+2*s*x;
                mPathMeasure.getSegment(y,y+1,dst,true);
            case 0:                x = t;                s = mPathMeasure.getLength();                y = -s*x*x+2*s*x;
                mPathMeasure.getSegment(y,y+1,dst,true);                break;
        }
        canvas.drawPath(dst,mPaint);

流程四

思路

虽然流程3中点与点的间距已经开始减少,不过这只是因为速度不同间距才改变的,我们的目的是让这些点到最后合并为1个点,也就是说开始的时候每个点的X间距0.05,结束的时候要让他们的X相同 
目前点之间X的间距函数如下: 
我们最后要让当X=1时,他们的Y值相等,而且他们X的间距由0.05线性平滑到0 
看函数图像已经很清楚了,修改后代码如下:

        switch(num){
            default:
            case 3:                x = t-0.15f*(1-t);                s = mPathMeasure.getLength();                y = -s*x*x+2*s*x;
                mPathMeasure.getSegment(y,y+1,dst,true);
            case 2:                x = t-0.10f*(1-t);                s = mPathMeasure.getLength();                y = -s*x*x+2*s*x;
                mPathMeasure.getSegment(y,y+1,dst,true);
            case 1:                x = t-0.05f*(1-t);                s = mPathMeasure.getLength();                y = -s*x*x+2*s*x;
                mPathMeasure.getSegment(y,y+1,dst,true);
            case 0:                x = t;                s = mPathMeasure.getLength();                y = -s*x*x+2*s*x;
                mPathMeasure.getSegment(y,y+1,dst,true);                break;
        }

流程五

思路

这里已经完成了99.9%了,但细心的同学会发现,进度条每次转动一圈聚成一个点后都会闪一下,这是因为重新开始动画刷新视图的原因,这里的补救方法就是我们在动画快结束的时候手动画一个点

        if(t>=0.95){            canvas.drawPoint(0,-150,mPaint);
        }

这样我们就完成了这个进度条 
最后这里附上源码:

package com.example.zhangml.view;import android.animation.ValueAnimator;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Path;import android.graphics.PathMeasure;import android.graphics.RectF;import android.util.AttributeSet;import android.view.View;/**
 * Created by zhangml on 2016/9/12 0012.
 */public class Win8Search extends View{

    private Paint mPaint;    private Path mPath;    private PathMeasure mPathMeasure;    private int mWidth,mHeight;    private ValueAnimator valueAnimator;    //用这个来接受ValueAnimator的返回值,代表整个动画的进度
    private float t;    public Win8Search(Context context) {        super(context);
    }    public Win8Search(Context context, AttributeSet attrs) {        super(context, attrs);
        init();
        valueAnimator.start();
    }    private void init() {

        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(15);
        mPaint.setColor(Color.WHITE);        //设置画笔为园笔
        mPaint.setStrokeCap(Paint.Cap.ROUND);        //抗锯齿
        mPaint.setAntiAlias(true);

        mPath = new Path();
        RectF rect = new RectF(-150,-150,150,150);
        mPath.addArc(rect,-90,359.9f);

        mPathMeasure = new PathMeasure(mPath,false);

        valueAnimator = ValueAnimator.ofFloat(0f,1f).setDuration(3000);
        valueAnimator.setRepeatCount(-1);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                t = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
    }    @Override
    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);
        canvas.translate(mWidth/2,mHeight/2);
        Path dst = new Path();        if(t>=0.95){
            canvas.drawPoint(0,-150,mPaint);
        }        int num = (int) (t/0.05);        float s,y,x;        switch(num){            default:            case 3:
                x = t-0.15f*(1-t);
                s = mPathMeasure.getLength();
                y = -s*x*x+2*s*x;
                mPathMeasure.getSegment(y,y+1,dst,true);            case 2:
                x = t-0.10f*(1-t);
                s = mPathMeasure.getLength();
                y = -s*x*x+2*s*x;
                mPathMeasure.getSegment(y,y+1,dst,true);            case 1:
                x = t-0.05f*(1-t);
                s = mPathMeasure.getLength();
                y = -s*x*x+2*s*x;
                mPathMeasure.getSegment(y,y+1,dst,true);            case 0:
                x = t;
                s = mPathMeasure.getLength();
                y = -s*x*x+2*s*x;
                mPathMeasure.getSegment(y,y+1,dst,true);                break;
        }
        canvas.drawPath(dst,mPaint);
    }    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
    }

}

原文链接:http://www.apkbus.com/blog-865196-76712.html

0人推荐
随时随地看视频
慕课网APP