首先来张截图:
控件的外观可能不是很美观,不过功能基本都有了,可以自己设置选中的时间片段,暂时没有支持自定义样式。。。
接下来介绍如何实现这个控件。
首先要先知道android’ view的绘制过程,首先的view先计算Measure,然后在进行Layout,最后Draw。那view的坐标系为左上角(0,0),
图片网络的:
那么在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类就可以了。