自定义一个可滑动时间刻度尺

2,565 阅读4分钟

首先来张截图:

154006_4EMW_2000932.png

控件的外观可能不是很美观,不过功能基本都有了,可以自己设置选中的时间片段,暂时没有支持自定义样式。。。

接下来介绍如何实现这个控件。

首先要先知道android' view的绘制过程,首先的view先计算Measure,然后在进行Layout,最后Draw。那view的坐标系为左上角(0,0),

图片网络的:

200211_bYoI_2000932.jpg

那么在view上面画一些简单的图形的时候,也是根据这个坐标来绘制,如果你的画的图形超过了屏幕,就需要的移动view才能看见。因为view的本身是没有边界的,Canvas对象本身也是没有边界的。view的本身有两个函数scrollTo和scrollBy函数,可以用来移动view到自定义的位置。自己新建一个按钮,点击事件调用这两个函数的时候,可以看到按钮上的字移动,因为函数本身是view的移动,对于view的内容来说就是,坐标改变了。那么也就是说要实现时间刻度的效果,就是在屏幕上绘制一条很长的时间刻度,然后根据手指的移动,调用scrollTo或者scrollBy来移动view,对于里面的内容就是看起来移动了。

对于直接调用scrollTo或者scrollBy来移动view显得有点生硬,没有平滑的效果,所以要引入Scroller类来辅助处理滑动,不过还有一个加速度的类可以实现移动带有弹性的效果,不过现在暂时没有用到。对于Scroller类可以上网查查资料,是一个辅助类,可以计算出view移动的距离,并且计算出移动距离的平滑的点。使用方法比较简单。

scroller = new Scroller(context);

介绍一下computeScroll,computeScroll()是View类的一个空函数,在view需要重新绘制的时候,会调用computeScroll函数,那么在computeScroll可以调用scrollTo或者scrollBy传入Scroller的坐标,再重新绘制view就可以实现view的滚动。

 @Override
    public void computeScroll() {
        super.computeScroll();
        if (scroller.computeScrollOffset()) {
            scrollTo(scroller.getCurrX(), scroller.getCurrY());
            invalidate();
        }
    }

view的重新绘制可以调用invalidate和postInvalidate 函数来通知系统重新绘制该view,前者要在ui线程里调用,后者可以在非ui线程里面调用。 接下来要重新写触摸事件:

@Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (scroller != null && !scroller.isFinished()) {
                    scroller.abortAnimation();
                }
                lastX = x;
                return true;
            case MotionEvent.ACTION_MOVE:
                float dataX = lastX - x;
                int finalx = scroller.getFinalX();
                //右边
                if (dataX < 0) {
                    if (finalx < -viewWidth / 2) {
                        return super.onTouchEvent(event);
                    }
                }
                if (dataX > 0) {
                    if (finalx > timeScale * 21) {
                        return super.onTouchEvent(event);
                    }
                }
               /**
                  对于这里调用startScroll函数,就是从某个点移动一段距离,这里传入scroller.getFinalX(), scroller.getFinalY()的原因是,获取的点都是手指移动的点
               */
                scroller.startScroll(scroller.getFinalX(), scroller.getFinalY(), (int) dataX, 0);
                lastX = x;
                postInvalidate();
                return true;
            case MotionEvent.ACTION_UP:
                int finalx1 = scroller.getFinalX();
                if (finalx1 < -viewWidth / 2) {
                    scroller.setFinalX(-viewWidth / 2);
                }
                if (finalx1 > timeScale * 21) {
                    scroller.setFinalX(timeScale * 21);
                }
                if (scrollListener != null) {
                    int finalX = scroller.getFinalX();
                    //表示每一个屏幕刻度的一半的总秒数,每一个屏幕有6格
                    int sec = 3 * 3600;
                    //滚动的秒数
                    int temsec = (int) Math.rint((double) finalX / (double) timeScale * 3600);
                    sec += temsec;
                    //获取的时分秒
                    int thour = sec / 3600;
                    int tmin = (sec - thour * 3600) / 60;
                    int tsec = sec - thour * 3600 - tmin * 60;
                    scrollListener.onScrollFinish(thour, tmin, tsec);
                }
                postInvalidate();
                break;
        }
        return super.onTouchEvent(event);
    }

也就是在移动的时候的调用 scroller.startScroll(scroller.getFinalX(), scroller.getFinalY(), (int) dataX, 0); 不停的生成新的点然后不停的重新画.

边界的处理,对于边界的处理,我的处理是判断坐标。

int finalx1 = scroller.getFinalX();
 if (finalx1 < -viewWidth / 2) {
   scroller.setFinalX(-viewWidth / 2);
  }
 if (finalx1 > timeScale * 21) {
   scroller.setFinalX(timeScale * 21);
  }

控制坐标的左右最大值。这个可以自己定。

接下就是画图形,绘画就是在onDraw里面用Canvas来绘画。

画刻度:

public void drawLines(Canvas canvas) {
        //底部的线
        canvas.drawLine(0, (float) (viewHeight * 0.9), totalTime,
                (float) (viewHeight * 0.9), linePaint);
        for (int i = 0; i <= totalTime; i++) {
            if (i % timeScale == 0) {
                canvas.drawLine(i, (float) (viewHeight * 0.7), i,
                        (float) (viewHeight * 0.9), linePaint);
                //画刻度值
                canvas.drawText(
                        formatString(i / timeScale, 0, 0), i, (float) (viewHeight * 0.6), linePaint);
            }
        }
    }

画指针

public void drawMidLine(Canvas canvas) {
        //移动的距离整个view内容移动的距离
        int finalX = scroller.getFinalX();
        //表示每一个屏幕刻度的一半的总秒数,每一个屏幕有6格
        int sec = 3 * 3600;
        //滚动的秒数
        int temsec = (int) Math.rint((double) finalX / (double) timeScale * 3600);
        sec += temsec;
        //获取的时分秒
        int thour = sec / 3600;
        int tmin = (sec - thour * 3600) / 60;
        int tsec = sec - thour * 3600 - tmin * 60;
        //滚动时的监听
        if (scrollListener != null) {
            scrollListener.onScroll(thour, tmin, tsec);
        }
        //画指针
        canvas.drawLine(timeScale * 3 + finalX, 0,
                timeScale * 3 + finalX, viewHeight, midPaint);
        //画数字
        canvas.drawText(formatString(thour, tmin, tsec), timeScale * 3 + finalX,
                (float) (viewHeight * 0.3), textPaint);
    }

画背景

public void drawBg(Canvas canvas) {
        rect.set(-1, 0, timeScale * 24 + 1, viewHeight);
        canvas.drawRect(rect, bgPaint);
    }

画时间片段

public void drawTimeRect(Canvas canvas) {
        for (TimePart temp : data) {
            int seconds1 = temp.sHour * 3600 + temp.sMinute * 60 + temp.sSeconds;
            int seconds2 = temp.eHour * 3600 + temp.eMinute * 60 + temp.eSeconds;
            //如果是先除以3600小数点的数据会被舍去 位置就不准确了
            int x1 = seconds1 * timeScale / 3600;
            int x2 = seconds2 * timeScale / 3600;
            rect.set(x1, 0, x2, (int) (viewHeight * 0.9));
            canvas.drawRect(rect, timePaint);
        }
    }

定义一个时间片段的类:

//时间片段 用于标记选中的时间
    public static class TimePart {
        //开始的时间
        public int sHour, sMinute, sSeconds;
        //结束的时间
        public int eHour, eMinute, eSeconds;

        public TimePart(int sHour, int sMinute, int sSeconds, int eHour, int eMinute, int eSeconds) {
            this.sHour = sHour;
            this.sMinute = sMinute;
            this.sSeconds = sSeconds;
            this.eHour = eHour;
            this.eMinute = eMinute;
            this.eSeconds = eSeconds;
        }
    }

这个项目是我之前发布的,现在新版本as可能无法打开,直接查看里面的view类就可以了。

项目地址:github.com/absolve/Tim…