Android自定义view --之--仿“滴滴打车”等车倒计时

昨天在网上看了一个 html5 仿滴滴出行的等车倒计时动画,感觉不错,今天就试试写了一下。先看一下效果图:

Android自定义view --之--仿“滴滴打车”等车倒计时_第1张图片

看到这个效果看着很简单,但是就是不知道从何处下手,先分析一下需求大致分为三个模块:

1.一个不变的细圆环,

2.从顶部开始变化的粗圆环,注意是从顶部开始的。

3.就是一个从顶部开始的圆球,和里面的倒计时时间绘制。

大致需求就是这样,你们感觉应该怎么实现的?我先讲一下我看到图的几个实现方案:

1.第一个实现方案:也是我第一反应的解决方案,

我想的是让圆球的 canvas 画布 绕圆心旋转,而且还要保证粗圆环不旋转只跟着圆球增加。这两个一起感觉不好控制。但是细想了一下感觉好麻烦。果断放弃了这种思路

2.第二个方案:在仔细想了一下,圆球的移动不就是在底部圆环上边吗?还有这个粗的圆环不就是可以看成是细圆环的一部分的截取。我只要能动态获取到圆球的圆心不就能绘制圆球和粗圆环了吗?看到这里大家,想到了我们之前的文章中讲过一个 Path的高级用法实现 动态搜索按钮的(Path高级用法链接)。没看过的可以先去看看这篇文章,里面对Path的高级用法从基础到高级,讲的很细致。

好了我们最终的解决方案就是利用 Path,动态获取 圆环的的点坐标,然后绘制圆球和粗圆环。其实这里主要用到的就是 Path中的一个类:PathMeasure为了方便大家学习我还是把我之前的文章的 对 PathMeasure 的讲解复制过来了。如果你已经对这个类非常清楚了,那就直接看例子吧。

类--------PathMeasure字面意思很容易理解--翻译成 路径测量对这就是我们用来测量路径的类。既然是一个类,我们就要看他集体有哪些方法和属性

1.构造方法:

方法名 释义
PathMeasure() 创建一个空的PathMeasure
PathMeasure(Path path, boolean forceClosed) 创建 PathMeasure 并关联一个指定的Path(Path需要已经创建完成)。
一个是有参数,一个无参数。

无参数就很容易理解就是直接创建对象,那有参数呢?

PathMeasure(Path path, boolean forceClosed) ;第一个参数 Path  咿 !这不就是我们的Path 了吗。对既然是测量类,那就是要确定需要测量的那个路径,那第二个参数呢?

第二个参数是用来确保 Path 闭合,如果设置为 true, 则不论之前Path是否闭合,都会自动闭合该 Path(如果Path可以闭合的话)。

2.方法:

返回值 方法名 释义
void setPath(Path path, boolean forceClosed) 关联一个Path
boolean isClosed() 是否闭合
float getLength() 获取Path的长度
boolean nextContour() 跳转到下一个轮廓
boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo) 截取片段
boolean getPosTan(float distance, float[] pos, float[] tan) 获取指定长度的位置坐标及该点切线值
boolean getMatrix(float distance, Matrix matrix, int flags) 获取指定长度的位置坐标及该点Matrix
这方法还挺多,都是干啥的呢?就找几个重要的,常用的讲讲:

第一个那就是 setPath(Path path, boolean forceClosed)

这方法一看就知道设置path 路径的,就是如果我们创建 PathMeasure,时用的是无参的构造方法时 ,这时候就要用此方法,告诉PathMeasure 当前需要测量的是哪一个path路径。(个人喜欢用无参,比较灵活)。

第二个那就是getLength(),很容易理解就是 获得 path 的总长度。

第三个:getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo),这个方法非常重要,截取片段,截取的结果就是,我们的path路径。参数也很好理解。第一个是开始的距离,第二个是结束的距离(相对于起点==既然是起点,我们怎么找他们的起点呢,其实就是我们绘制图画笔开始的哪一点)。第三个参数就是返回的路径。第四个参数起始点是否使用 moveTo 用于保证截取的 Path 第一个点位置不变。

第四个:getPosTan(float distance, float[] pos, float[] tan),这个也是非常重要的,为什么呢?听我解释一下他的参数就知道了。第一个参数是距离(相对于绘制起点的路径距离),第二个参数是距离起点的坐标点,第三个参数返回的是正玄函数tan@角度。


public class DiDiView extends View {

    //时间
    long time =0;
    //倒计时的文本
    String outText = "";
    //外圈的圆环 路径
    Path pathCicle;
    //测量的类
    PathMeasure measure;
    //路径的总长度
    float pathLength = 0;
    //圆的 x y坐标---也就是球心的坐标
    float xy [] ;
    //做过的圆环路径
    Path workePath;
    //画笔
    Paint paint;
    //屏幕宽高
    int w;
    int h;
    public DiDiView(Context context) {
        this(context,null);
    }

    public DiDiView(Context context, @Nullable AttributeSet attrs) {
        this(context,attrs,0);
    }

    public DiDiView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        w = wm.getDefaultDisplay().getWidth();
        h = wm.getDefaultDisplay().getHeight();


        paint = new Paint();
        paint.setDither(true);
        paint.setAntiAlias(true);
        //初始化path
        initPath();
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //移动画布圆心
        canvas.translate(w/2,h/2);
        //固定圆环
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(3);
        paint.setColor(Color.parseColor("#f5dcc0"));
        canvas.drawCircle(0,0,300,paint);

        //绘制走过的路径
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(9);
        paint.setColor(Color.parseColor("#f4bf69"));
        canvas.drawPath(workePath,paint);
        //绘制移动的圆
        paint.setColor(Color.parseColor("#f19734"));
        paint.setStyle(Paint.Style.FILL_AND_STROKE);
        canvas.drawCircle(xy[0],xy[1],50,paint);
        //绘制移动圆的时间
        paint.setColor(Color.WHITE);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(2);
        paint.setTextSize(35);
        canvas.drawText(outText,xy[0]-39,xy[1]+10,paint);
    }

    /**
     * 初始化路径
     */
    public void initPath(){
        //细圆环
        pathCicle = new Path();
        RectF rectF = new RectF(-300,-300,300,300);
        pathCicle.addArc(rectF,270,359.9f);
        measure = new PathMeasure();
        measure.setPath(pathCicle,false);
        //出圆环的path
        workePath = new Path();
        //总路径长
        pathLength = measure.getLength();
        xy = new float[2];
        initAnimation();
    }


    /**
     * 动画
     */
    public void initAnimation(){

        ValueAnimator animator = ValueAnimator.ofFloat(0,pathLength);

        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {

                float currentlenght  = (float) animation.getAnimatedValue();
                //获取当前路径长度的终点
                measure.getPosTan(currentlenght,xy,null);
                //截取路径
                measure.getSegment(0,currentlenght,workePath,true);
                //获取动画时长
                time = (animation.getDuration() - animation.getCurrentPlayTime());
                if (time>0){
                    outText = "00:0"+(time/1000+1);
                }else {
                    outText = "00:00";
                }

                postInvalidate();
            }
        });
        animator.setInterpolator(new LinearInterpolator());
        animator.setDuration(8000);
        animator.start();
    }
}

我把代码都增加了注释,如果你对动画还有自定义view的基础不是很了解,请看之前的文章。






你可能感兴趣的:(Android,自定义,View)