草图如下:
公司产品的新版本中要实现一个效果,需求如下: - 定义4种状态:未知-休息-上班-下班 - 要锁定‘未知’状态,锁定之后,未知状态不可点击 - 用户可以点击除了锁定状态的之外的任意状态,滑块自动滑动到指定状态并触发回调去请求接口,如果请求失败,则控制滑块再滑动到之前的位置。 - 用户可以滑动滑块到任意位置,如果滑动到锁定状态,要再次滑动重置到之前的状态。如果滑动到其他位置,则触发回调去请求接口,如果请求失败,则控制滑块再滑动到之前的位置。 - 如果滑块滑动到两个状态之间,则松开手之后,滑块要自动滑动到距离最近的状态上,然后触发回调去请求接口,如果请求失败,则控制滑块再滑动到之前的位置。
主要说一下实现过程中一些需要注意的地方
在onMeasure()方法中,主要针对MeasureSpec.AT_MOST这种测量模式做了处理:使用默认的宽高设置,对于其他测量模式,统一使用父view传递过来的尺寸。这里需要注意的是使用默认宽高尺寸时,需要与父view传递过来的尺寸作对比,取最小值作为最终的view宽高大小,不然会造成margin值设置失效的问题。
在绘制时,View与ViewGroup是有区别的:对于View,我们在onDraw(Canvas canvas)中绘制,但对于ViewGroup,我们应该在dispatchDraw(Canvas canvas)方法中进行绘制。因为在ViewGroup中,onDraw(Canvas canvas)方法不一定会执行,只有在为ViewGroup设置了背景等情况时,才会执行onDraw(Canvas canvas)方法。而dispatchDraw(Canvas canvas)方法不管是View还是ViewGroup,都会执行。那么既然dispatchDraw(Canvas canvas)方法必定会执行,为什么在View中不使用dispatchDraw(Canvas canvas)方法而使用onDraw(Canvas canvas)方法方法呢?其实主要是为了节省性能,因为只有符合相关条件才会执行onDraw(Canvas canvas)方法,而什么条件下才会执行绘制这些事情Android已经帮我们做好了。 从View的源码中我们可以看到onDraw(Canvas canvas)和dispatchDraw(Canvas canvas)执行的条件:
public void draw(Canvas canvas) { //...省略部分源码 /* * Draw traversal performs several drawing steps which must be executed * in the appropriate order: * * 1. Draw the background * 2. If necessary, save the canvas' layers to prepare for fading * 3. Draw view's content * 4. Draw children * 5. If necessary, draw the fading edges and restore layers * 6. Draw decorations (scrollbars for instance) */ // Step 1, draw the background, if needed int saveCount; if (!dirtyOpaque) { drawBackground(canvas); } // skip step 2 & 5 if possible (common case) final int viewFlags = mViewFlags; boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0; boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0; if (!verticalEdges && !horizontalEdges) { // Step 3, draw the content if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); // Overlay is part of the content and draws beneath Foreground if (mOverlay != null && !mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(canvas); } // Step 6, draw decorations (foreground, scrollbars) onDrawForeground(canvas); // we're done... return; } //...省略部分源码 // Step 3, draw the content if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); //...省略部分源码 // Step 6, draw decorations (foreground, scrollbars) onDrawForeground(canvas); }dispatchDraw(Canvas canvas)是每个ViewGroup都会重写的方法,用来向子view分发绘制事件,所以我们在继承某个ViewGroup重写dispatchDraw(Canvas canvas)方法时,如果我们先调用super.dispatchDraw(canvas),则会先绘制子view,如果后调用super.dispatchDraw(canvas),则会先绘制我们自己的内容。这里的先后顺序要弄清楚。
这里我使用RectF类存储每个item的边界值,RectF类提供了contains方法用来判断任意点坐标是否在范围内:
private int findPositionByPoint(float x, float y) { for (int i = 0; i < mItemBounds.size(); i++) { if (mItemBounds.get(i).contains(x, y)) { return i; } } //纠偏,防止数组越界 if (x < mItemBounds.get(0).left) { return 0; } //纠偏,防止数组越界 if (x >= mItemBounds.get(mItemBounds.size() - 1).right) { return mItemBounds.size() - 1; } return INVALIDATE_POSITION; }