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

来源:互联网 发布:公仔用什么软件画 编辑:程序博客网 时间:2024/04/29 03:38

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


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

1.一个不变的细圆环,

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

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

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

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

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

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

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

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

1.构造方法:

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

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

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

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

2.方法:

返回值方法名释义voidsetPath(Path path, boolean forceClosed)关联一个PathbooleanisClosed()是否闭合floatgetLength()获取Path的长度booleannextContour()跳转到下一个轮廓booleangetSegment(float startD, float stopD, Path dst, boolean startWithMoveTo)截取片段booleangetPosTan(float distance, float[] pos, float[] tan)获取指定长度的位置坐标及该点切线值booleangetMatrix(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的基础不是很了解,请看之前的文章。






原创粉丝点击