Android实现自定义滑动刻度尺方法示例
一 基础:
自定义view实现跟随手指滚动的刻度尺,实现了类似seekbar的滑动选中效果。项目地址,欢迎star!
ui图:
功能:
- 通过设置最小值跟最大值的范围,以及offset值。view将根据这些数据去计算出需要几个小刻度和几个长刻度,和每个长刻度上面显示的数值。
- 指针可以随意的定制。
- 当滑动停止后,刻度尺会根据四舍五入将距离指针最近的长刻度滑动到指针的位置。
- 支持范围越界回弹。
- 支持设置默认值。
二 实现:
先扯一下,再看别人写的控件的时候总有一种一脸懵逼的感觉,好多凌乱的变量和一大堆的计算逻辑都不知道干嘛用的。比如:pulltorefreshlayout。除非自己按着整体的设计流程写一遍,一步步的写,等出了bug你就明白那些操作的价值。结合之前读第三方控件的经验,写这个刻度尺控件的时候就一步步的去完成,从简单的绘制,到点击事件,再到滑动fling,最后滑动结束更正滑动位置。每一步遇到的问题都记录下来,之后再补全解决方法,这就是成长。
1.绘制刻度
这里省略了onmeasure,这里的需求只是计算一下高度就好了。接着看ondraw方法:
private void drawruler(canvas canvas) { mtextindex = 0; for (int index = 0; index <= mrulerhelper.getcounts(); index++) { boolean longline = mrulerhelper.islongline(index); int linecount = mlinewidth * index; mrect.left = index * mlinespace + linecount + mmarginleft; mrect.top = getstarty(longline); mrect.right = mrect.left + mlinewidth; mrect.bottom = getendy(); if (longline) { if (!mrulerhelper.isfull()) { mrulerhelper.addpoint(mrect.left); } string text = mrulerhelper.gettextbyindex(mtextindex); mtextindex++; canvas.drawtext(text, mrect.centerx(), getmeasuredheight() - dpfor14, mtextpaint); } canvas.drawrect(mrect, mlinepaint); mrect.setempty(); } }
这里解释一下为什么刻度采用rect而不是设置line的宽度,其实最简单的就是设置paint的宽度然后canvas.drawline()。刚绘制的时候就是采用的canvas.drawline(),绘制完之后发现每个刻度的宽度都被削减了一半,canvas.drawline()是在设置的(x,y)坐标开始平分line的宽度的(这个你要去体验一下就会明白)。所以给定坐标之后每个刻度看起来就像是被挤了一样,所以才采用rect简单方便一点。进入正题,绘制有几个问题:
- 怎么确定要绘制几个rect?
这个比较灵活,要看具体的需求了。也就是一大格里面包含几个刻度,一般是包含10个刻度,刻度包括长短刻度。然后一大格刻度表示多少数值,也就是offset值是多少。之后刻度的范围也要明确并且能被offset整除,比如范围是(low,height),那么(height-low)/(offset/10)就是你需要绘制多少个刻度。
public void setscope(int start, int count,int offset) { if(offset != 0) { this.offset = offset; } linenumbers = (count - start) / (this.offset / 10); }
- 怎么确定那个是长刻度?
这个问题要确定一大格之间有几个小刻度了,一般为10个的话,那么当前的index/10能整除就是到了该绘制长刻度的时候了,mrulerhelper.getcounts()就是我们计算出的总共有几个刻度。
for (int index = 0; index <= mrulerhelper.getcounts(); index++) { boolean longline = mrulerhelper.islongline(index); ... if (longline) { canvas.drawtext(text, mrect.centerx(), getmeasuredheight() - dpfor14, mtextpaint); } canvas.drawrect(mrect, mlinepaint); }
之后呢就是我们计算rect的左边跟绘制text的坐标了。。。不细讲。。。具体可看这里啊。
有个问题就是你得明白rect的left top right bottom分别表示那个区间:
[图片上传失败...(image-5d1f26-1554206618213)]
2.处理点击事件
目前采取的是点击该view的事件全拦截,感觉也没别的什么需求需要过滤事件了。事件处理起来很简单的就是计算出每次移动的差值就好了:
case motionevent.action_down: mpressup = false; isfling = false; startx = event.getx(); break; case motionevent.action_move: mpressup = false; float distance = event.getx() - startx; if (mpredistance != distance) { doscroll((int) -distance, 0, 0); invalidate(); } startx = event.getx(); break;
问题就是:
- 怎么实现滑动的效果?
刻度尺如果范围很大的话总宽度肯定会超出屏幕的,但是canvas不会绘制屏幕之外的部分,除非等到屏幕之外的部分显示出来。另外让view滑动的方法很多,最初使用的是scrollto方法,该方法滑动的是view的内容,也符合我们要的效果,不过结果查强人意。差值计算之后稍微一滑动,刻度直接没了,成了一片空白,看起来那个变化值也不大,ok!这是一个疑问scrollto+invalidate内容不会显示,直接没了。之后呢换成了scroller,这个玩意不用太多的介绍了,使用之后便达到了我们想要的效果,一样的变化值。
private void doscroll(int dx, int dy, int duration) { mscroller.startscroll(mscroller.getfinalx(), mscroller.getfinaly(), dx, dy, duration); }
是否有疑问?既然屏幕之外的东西canvas不会去绘制,那么滑动的时候肯定是将屏幕之外的部分滑到屏幕中,也就是在滑动的过程中要继续绘制。从上面的绘制代码能看到这个绘制过程中跟滑动并没有任何的联系,只是单纯的for循环绘制而已,为什么呢?第一 我们scrollto移动的是view的内容,一开始view的实际宽度会超过屏幕的宽度,当没有滑动的时候,view只会绘制屏幕中的可见区域,即使for循环依然执行也不会绘制到屏幕外面,然后在滑动的时候会不断的触发invalidate()方法,也就是for循环会被触发,view开始在新出现的未绘制的区域绘制。已经绘制过的区域会被滑出屏幕,这样就会给用户一个平滑的效果。做完以上两步你的刻度尺已经有了滑动的效果了。下面就是解决边界的问题。
3.边界的处理
ui说当超过边界之后松手回弹,这样的交互效果好。这种交互其实最简单了,在手指离开的时候计算当前的x坐标距离中心指针的x坐标的距离,然后让scroller去执行回弹的效果。不过这个操作是整个控件中最为重要的一步,因为当手指抬起的时候,中间指针必须指向一个长刻度,不能停留再短刻度上面,那这个操作就跟边界回弹的操作重合了,边界回弹也是让最小或者最大长刻度滑动到中间指针的位置。所以松手之后的操作就分为三种:
currentx :滑动停止时的x坐标。
point:中间指针位置。
low:刻度尺的最小边界。
height:刻度尺的最大边界。
- 当前的currentx小于中间指针刻度point的x坐标,并且小于刻度的最小值low的x坐标。
-----------------point-currentx--low------height----------
- 当前的currentx小于中间指针刻度point的x坐标,并且大于刻度的最小值low表示的x坐标小于刻度尺的最大刻度height的x坐标。
------low-------currentx--point--------height----------
- 当前的currentx大于中间指针刻度point的x坐标,并且大于刻度的最大值height表示的x坐标。
------low-------height-----currentx-point-------
简单的表示了一下三种位置。
处理就是,先计算出滑动结束之后的当前x坐标跟中间point的x坐标的距离,然后不为0就使用scroller滑动:
//计算距离 public int getscrolldistance(int x) { for (int i = 0; i < mpoints.size(); i++) { int pointx = mpoints.get(i); if (0 == i && x < pointx) { //当前的x比第一个位置的x坐标都小 也就是需要往右移动到第一个长线的位置. setcurrenttext(0); return x - pointx; } else if (i == mpoints.size() - 1 && x > pointx) { //当前的x比最后一个左边的x都大,也就是需要往左移动到最后一个长线位置. setcurrenttext(texts.size() - 1); return x - pointx; } else { if (i + 1 < mpoints.size()) { int nextx = mpoints.get(i + 1); if (x > pointx && x <= nextx) { int distance = (nextx - pointx) / 2; int dis = x - pointx; if (dis > distance) { //说明往下一个移动 setcurrenttext(i + 1); return x - nextx; } else { setcurrenttext(i); //往前一个移动 return x - pointx; } } } } } return 0; }
开始执行滑动:
public void scrollfinish() { int finalx = mscroller.getfinalx(); int centerpointx = mrulerhelper.getcenterpointx(); int currentx = centerpointx + finalx; int scrolldistance = mrulerhelper.getscrolldistance(currentx); if (0 != scrolldistance) { //第一个参数是滚动开始时的x的坐标 //第二个参数是滚动开始时的y的坐标 //第三个参数是在x轴上滚动的距离, 负数向右滚动. //第四个参数是在y轴上滚动的距离,负数向下滚动. mscroller.startscroll(mscroller.getfinalx(), mscroller.getfinaly(), -scrolldistance, 0, 300); invalidate(); if (scrollselected != null) { scrollselected.selected(getcurrenttext()); } } }
这样已经可以使用了,滑动的刻度尺已经完成了。不过交给ui一看,人家说这东西怎么那么难滑动呢,每次怎么只能滑一大格呢,我要那种fling的感觉。确实,因为在motionevent.action_up的时候都会去矫正一下位置,所以给使用者的感觉就是一次只能滑一格,滑动体验很不好,只能去增加fling。。。
4.fling
增加fling多简单啊,scroller不是有这个方法吗mscroller.fling(),使用方法这里不再介绍了。fling增加之后,用户的体验确实好了很多,不过一个新的问题出现了,就是在fling停止之后怎么矫正位置呢?这是个大问题,卡住了好大一会儿,最终找到了解决方法:
@override public void computescroll() { if (mscroller.computescrolloffset()) { //这里是结束之后调用矫正位置的方法。scrollfinish()。 if (mscroller.getcurrx() == mscroller.getfinalx() && mpressup && isfling) { mpressup = false; isfling = false; scrollfinish(); } scrollto(mscroller.getcurrx(), 0); invalidate(); } super.computescroll(); }
三 结束
效果在文章一开始已经展示出来了,指针并没有在该自定义view中绘制,底部的线也是,因为对于指针的需求是多变的,所以用了一个自定义的viewgroup去完成剩余的指针和底部的实线。底部的实线放在group中是因为我们的ui效果,底部的实线上面可以没有刻度,也就是这个底部的线是固定在底部,比我画在刻度下面跟随刻度滑动要简单的多。想到之后的变体,感觉刻度本身的view跟指针分开是比较好扩展的,group只需要给刻度尺控件传入中间指针的(x,y)坐标就好了。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对的支持。
上一篇: Android 横竖屏处理的知识小结
下一篇: redis实战---读书笔记