源码下载:https://github.com/victorfan336/WheelView 喜欢的话就star下吧。
当初写这个控件基于三个原因:
先上效果图:
自定义流程主要包括:
1)接收xml属性配置;
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.WheelView, defStyleAttr, 0); int count = typedArray.getIndexCount(); for (int i = 0; i < count; i++) { int attr = typedArray.getIndex(i); if (attr == R.styleable.WheelView_textColor) { textColor = typedArray.getColor(attr, 0x000000); } else if (attr == R.styleable.WheelView_textSize) { textSize = typedArray.getDimension(attr, 19f); } else if (attr == R.styleable.WheelView_dragOut) { canDragOutBorder = typedArray.getBoolean(attr, true); } else if (attr == R.styleable.WheelView_itemHeight) { eachItemHeight = (int) typedArray.getDimension(attr, 12); } } typedArray.recycle();
2)实现public voidonMeasure(intwidthMeasureSpec, intheightMeasureSpec);
@Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthMOde = MeasureSpec.getMode((widthMeasureSpec)); int heightM = MeasureSpec.getSize(heightMeasureSpec); int widthM = MeasureSpec.getSize(widthMeasureSpec); if (heightMode == MeasureSpec.EXACTLY && widthMOde == MeasureSpec.EXACTLY) { setMeasuredDimension(widthM, heightM); } else if (heightMode == MeasureSpec.EXACTLY) { setMeasuredDimension(600, heightM); } else if (widthMOde == MeasureSpec.EXACTLY) { setMeasuredDimension(widthM, 600); } else { setMeasuredDimension(400, 600); } }
3)实现public voidonDraw(Canvas canvas);
@Override public void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawColor(0xffffff); clipView(canvas);// 指定绘制区域 drawText(canvas);// 绘制文本内容 drawLine(canvas);// 绘制两根分割线 drawShadows(canvas);// 绘制阴影遮罩 }
关于怎么自定义控件,请参考:http://blog.csdn.net/column/details/androidcustomview.html
自定义onTouch主要实现:
1)边界检查
2)滚动效果处理
3)滚动定位
4)选中位置回调
通过手势实现onFling()方法来处理快速滑动效果,不然快速时界面会出现卡顿。代码中是通过scroller来处理滚动的。
@Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { int dy = (int) (-vTracker.getYVelocity() / 8); // 边距检测 if (getScrollY() + dy <= wheelViewMode.getTopMaxScrollHeight()) { dy = wheelViewMode.getTopMaxScrollHeight() - getScrollY(); } if (getScrollY() + dy >= wheelViewMode.getBottomMaxScrollHeight()) { dy = wheelViewMode.getBottomMaxScrollHeight() - getScrollY(); } // 取整每次onfling的距离 int scrollDy = getScrollY() % eachItemHeight; if (scrollDy + dy % eachItemHeight > eachItemHeight / 2) { dy += eachItemHeight - (scrollDy + dy % eachItemHeight); } else if (scrollDy + dy % eachItemHeight > 0 && scrollDy + dy % eachItemHeight <= eachItemHeight / 2) { dy -= scrollDy + dy % eachItemHeight; } else if (scrollDy + dy % eachItemHeight < 0 && scrollDy + dy % eachItemHeight >= -eachItemHeight / 2) { dy -= scrollDy + dy % eachItemHeight; } else if (scrollDy + dy % eachItemHeight < -eachItemHeight / 2) { dy -= scrollDy + dy % eachItemHeight + eachItemHeight; } Log.e(TAG, "GestureDetector: dy = " + dy + " -- getScrollY() = " + getScrollY()); scroller.startScroll(0, getScrollY(), 0, dy, 400); invalidate(); return true; }
首先,我们拿到vTracker当前y方向上的速度,换算成我们需要滚动的距离,我这里处理比较简单,每次去的距离是-vTracker.getYVelocity() / 8;负号的话只是方向问题;
其次,检测快速滚动dy距离后,是否已经滚出了屏幕;
最后,取整滚动的距离,因为onFling方法是在松手后才会执行的,所以为了确保滚动后能够让文本信息还是居中显示,松后前滚动的距离和快速滑动的距离dy之和必须是eachItemHeight的倍数。
最终再通过scroller滚动界面,来达到快速滚动的效果,scroller可以设置弹性动画,这就是使用scroller很大的一个好处。
显示模式目前处理了三种,当然你也可以还有其他的显示方式。
显示模式主要实现以下接口即可:
public abstract class IWheelViewMode { int eachItemHeight; int childrenSize; public int getEachItemHeight() { return eachItemHeight; } public void setEachItemHeight(int eachItemHeight) { this.eachItemHeight = eachItemHeight; } public int getChildrenSize() { return childrenSize; } public void setChildrenSize(int childrenSize) { this.childrenSize = childrenSize; } public IWheelViewMode(int eachItemHeight, int childrenSize) { this.eachItemHeight = eachItemHeight; this.childrenSize = childrenSize; } public abstract int getSelectedIndex(int baseIndex);// 将滚动的位置换算成当前的选中位置 public abstract int getTopMaxScrollHeight(); // 向上最大滚动距离 public abstract int getBottomMaxScrollHeight(); // 想下最大滚动距离 public abstract float getTextDrawY(int height, int index, Paint paint); // 绘制text的Y方向的位置 public float getCenterY(int height, Paint paint) { return (height - paint.getFontMetrics().bottom - paint.getFontMetrics().top) / 2; } }
getSelectedIndex(int baseIndex):baseIndex是scrollY滚动后的当前位置,
float offset = 0.5f; if (getScrollY() < 0) { offset = -0.5f; } int moveIndex = (int) (getScrollY() * 1f / eachItemHeight + offset); int selected = wheelViewMode.getSelectedIndex(moveIndex);
代码很容易,scrollY / eachItemHeight就是滚动了多少个item,然后四舍五入,因为这是在松手后调用的,后面可能还有onFling方法会执行。
1)WheelViewStartMode:当前默认位置是0,返回的参数和baseIndex参数是一样,也就是说从第一个文本内容开始显示;向下滚动2,则返回2;
public class WheelViewStartMode extends IWheelViewMode { public WheelViewStartMode(int eachItemHeight, int childrenSize) { super(eachItemHeight, childrenSize); } @Override public int getSelectedIndex(int baseIndex) { return baseIndex; } @Override public int getTopMaxScrollHeight() { return 0; } @Override public int getBottomMaxScrollHeight() { return eachItemHeight * (childrenSize - 1); } @Override public float getTextDrawY(int height, int index, Paint paint) { return (getCenterY(height, paint) + index * eachItemHeight); } }
2)WheelViewCenterMode:默认显示位置为(childrenSize - 1) / 2 ;所以滚动后的位置就是baseIndex + (childrenSize- 1) /2就很好理解了。
public class WheelViewCenterMode extends IWheelViewMode { public WheelViewCenterMode(int eachItemHeight, int childrenSize) { super(eachItemHeight, childrenSize); } @Override public int getSelectedIndex(int baseIndex) { return baseIndex + (childrenSize - 1) / 2; } @Override public int getTopMaxScrollHeight() { return (childrenSize - 1) / 2 * (-eachItemHeight); } @Override public int getBottomMaxScrollHeight() { return childrenSize / 2 * eachItemHeight; } @Override public float getTextDrawY(int height, int index, Paint paint) { return (getCenterY(height, paint) + (index - (childrenSize - 1) / 2) * eachItemHeight); } }
3)WheelViewRecycleMode:默认显示位置为0,因为除数不能为0,所以避免出错,多加了个判断。
public class WheelViewRecycleMode extends IWheelViewMode { public WheelViewRecycleMode(int eachItemHeight, int childrenSize) { super(eachItemHeight, childrenSize); } @Override public int getSelectedIndex(int baseIndex) { int index = baseIndex; while (index <= 0) { index += childrenSize; } if (childrenSize == 0) { Log.e("Wheelview", "WheelViewRecycleMode childrenSize == 0"); } return index % (childrenSize == 0?1:childrenSize); } @Override public int getTopMaxScrollHeight() { return Integer.MIN_VALUE; } @Override public int getBottomMaxScrollHeight() { return Integer.MAX_VALUE; } @Override public float getTextDrawY(int height, int index, Paint paint) { return (getCenterY(height, paint) + index * eachItemHeight); } }
2.5 边界检查