PickerView滚轮选择
来源:互联网 发布:天行green软件下载 编辑:程序博客网 时间:2024/05/16 00:50
PickerView是一个滚轮选择器。已经有很多的帖子论述。
Android自定义控件实战——滚动选择器PickerView
http://blog.csdn.net/zhongkejingwang/article/details/38513301
要求:项目中需要采用滚轮选择器选择一个列表项。要求滚轮数字不进行循环播放,如果滚动到结尾,不再往下滚动。需要对原项目进行改造。
实现方式:
1、PickerView数据模型PickerObject。
滚动模式提供了两种模式:
A 循环滚动。滚动到结尾仍然可以继续滚动
B边界模式。滚动到边界不再滚动。
public class PickerObject implements Serializable { private static final long serialVersionUID = 1L; /** 滚动模式 */ private int mode; /** 标题 */ private String title; /** 子项目数据 */ private List<PickerItem> list; public int getMode() { return mode; } public void setMode(int mode) { this.mode = mode; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public List<PickerItem> getList() { return list; } public void setList(List<PickerItem> list) { this.list = list; } public static class PickerItem implements Serializable { private static final long serialVersionUID = 1L; /** * 唯一编号 */ public String id; /** * 显示的string */ public String text; public Object other; public String info; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getText() { return text; } public void setText(String text) { this.text = text; } public Object getOther() { return other; } public void setOther(Object other) { this.other = other; } public String getInfo() { return info; } public void setInfo(String info) { this.info = info; } }}
2)PickerView组件。这一部分主要进行以下的修改
A)滚动模式分为循环模式(原有的模式就是循环模式)和边界模式。
B)数据控制模式方式修改:数据项内容不进行变化,只是数据读取的方式进行变化。
public class MyPickerView extends View { /** 滚动模式:循环滚动 */ public static final int MODE_LOOP = 0; /** 滚动模式:边界模式(滚动到边界不再进行滚动) */ public static final int MODE_BOUNDARY = 1; public static final String TAG = "PickerView"; /** * text之间间距和minTextSize之比 */ public static final float MARGIN_ALPHA = 2.8f; /** * 自动回滚到中间的速度 */ public static final float SPEED = 2; /** * 数据项列表 */ private List<PickerItem> mDataList; /** * 选中的位置,这个位置是mDataList的中心位置,一直不变 */ private int mCurrentSelected; private Paint mPaint; /** 滚动模式:默认为循环滚动 */ private int pickerMode = MODE_LOOP; private float mMaxTextSize = 20; private float mMinTextSize = 10; private float mMaxTextAlpha = 255; private float mMinTextAlpha = 120; /** 边界移出的百分比 */ private float boundaryScale = 1.3f; private int mColorText = 0x333333; private int mViewHeight; private int mViewWidth; private float mLastDownY; /** * 滑动的距离 */ private float mMoveLen = 0; private boolean isInit = false; private onSelectListener mSelectListener; private Timer timer; private MyTimerTask mTask; Handler updateHandler = new Handler() { @Override public void handleMessage(Message msg) { if (Math.abs(mMoveLen) < SPEED) { mMoveLen = 0; if (mTask != null) { mTask.cancel(); mTask = null; performSelect(); } } else { // 这里mMoveLen / Math.abs(mMoveLen)是为了保有mMoveLen的正负号,以实现上滚或下滚 mMoveLen = mMoveLen - mMoveLen / Math.abs(mMoveLen) * SPEED; } invalidate(); } }; public MyPickerView(Context context) { super(context); init(); } public MyPickerView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public void setOnSelectListener(onSelectListener listener) { mSelectListener = listener; } private void performSelect() { if (mSelectListener != null) mSelectListener.onSelect(mDataList.get(mCurrentSelected)); } public void setData(List<PickerItem> datas) { mDataList = datas; mCurrentSelected = 0; invalidate(); } /** * 设置滚动模式 * * @param pickerMode : 滚动模式 */ public void setPickerMode(int pickerMode) { this.pickerMode = MODE_BOUNDARY == pickerMode ? MODE_BOUNDARY : MODE_LOOP; } public int getPickerMode() { return pickerMode; } /** * 从1开始计数 * @param selected */ public void setSelected(int selected) { mCurrentSelected = selected; } /** 获取当前选中项 */ public int getSelected() { return mCurrentSelected; } private void moveHeadToTail() { PickerItem head = mDataList.get(0); mDataList.remove(0); mDataList.add(head); } private void moveTailToHead() { PickerItem tail = mDataList.get(mDataList.size() - 1); mDataList.remove(mDataList.size() - 1); mDataList.add(0, tail); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mViewHeight = getMeasuredHeight(); mViewWidth = getMeasuredWidth(); // 按照View的高度计算字体大小 mMaxTextSize = mViewHeight / 4.0f; mMinTextSize = mMaxTextSize / 2f; isInit = true; invalidate(); } private void init() { timer = new Timer(); mDataList = new ArrayList<PickerItem>(); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setStyle(Paint.Style.FILL); mPaint.setTextAlign(Paint.Align.CENTER); mPaint.setColor(mColorText); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // 根据index绘制view if (isInit) drawData(canvas); } private void drawData(Canvas canvas) { // 先绘制选中的text再往上往下绘制其余的text float scale = parabola(mViewHeight / 4.0f, mMoveLen)/4; float size = (mMaxTextSize - mMinTextSize) * scale + mMinTextSize; mPaint.setTextSize(size); mPaint.setColor(Color.parseColor("#88000000")); mPaint.setAlpha((int) ((mMaxTextAlpha - mMinTextAlpha) * scale * 4 + mMinTextAlpha)); // text居中绘制,注意baseline的计算才能达到居中,y值是text中心坐标 float x = (float) (mViewWidth / 2.0); float y = (float) (mViewHeight / 2.0 + mMoveLen); Paint.FontMetricsInt fmi = mPaint.getFontMetricsInt(); float baseline = (float) (y - (fmi.bottom / 2.0 + fmi.top / 2.0)); canvas.drawText(mDataList.get(mCurrentSelected).getText(), x, baseline, mPaint); drawLineUp(canvas, 1, -1); drawLineUp(canvas, 2, -1); // 绘制上方data drawUpText(canvas); // 绘制下方data drawDownText(canvas); } /** * 绘制中间选择行上方data */ private void drawDownText(Canvas canvas) { if (MODE_BOUNDARY == pickerMode) { for (int i = 1; (mCurrentSelected + i) < mDataList.size(); i++) { drawOtherText(canvas, i, 1); } } else { for (int i = 1; i <= 3; i++) { drawOtherText(canvas, i, 1); } } } /** * 绘制中间选择行上方data */ private void drawUpText(Canvas canvas) { if (MODE_BOUNDARY == pickerMode) { for (int i = 1; (mCurrentSelected - i) >= 0; i++) { drawOtherText(canvas, i, -1); } } else { for (int i = 1; i <= 3; i++) { drawOtherText(canvas, i, -1); } } } /** * @param canvas * @param position 距离mCurrentSelected的差值 * @param type 1表示向下绘制,-1表示向上绘制 */ private void drawOtherText(Canvas canvas, int position, int type) { float d = (float) (MARGIN_ALPHA * mMinTextSize * position + type * mMoveLen); float scale = parabola(mViewHeight / 4.0f, d)/3; float size = (mMaxTextSize - mMinTextSize) * scale + mMinTextSize; mPaint.setTextSize(size); mPaint.setAlpha((int) ((mMaxTextAlpha - mMinTextAlpha) * scale + mMinTextAlpha)); float y = (float) (mViewHeight / 2.0 + type * d); Paint.FontMetricsInt fmi = mPaint.getFontMetricsInt(); float baseline = (float) (y - (fmi.bottom / 2.0 + fmi.top / 2.0)); int index = (mCurrentSelected + type * position + mDataList.size() ) % mDataList.size(); canvas.drawText(mDataList.get(index).getText(), (float) (mViewWidth / 2.0), baseline, mPaint); } /** * @param canvas * @param position 距离mCurrentSelected的差值 * @param type 1表示向下绘制,-1表示向上绘制 */ private void drawLineUp(Canvas canvas, int position, int type) { float d = (float) (MARGIN_ALPHA * mMinTextSize * position + type); mPaint.setColor(Color.GRAY); canvas.drawLine(0, d, mViewWidth, d + 1, mPaint); } /** * 抛物线 * * @param zero 零点坐标 * @param x 偏移量 * @return scale */ private float parabola(float zero, float x) { float f = (float) (1 - Math.pow(x / zero, 2)); return f < 0 ? 0 : f; } @Override public boolean onTouchEvent(MotionEvent event) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) { switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: doDown(event); break; case MotionEvent.ACTION_MOVE: doMove(event); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: doUp(event); break; } } return true; } private void doDown(MotionEvent event) { if (mTask != null) { mTask.cancel(); mTask = null; } mLastDownY = event.getY(); } private void doMove(MotionEvent event) { if(!canPickMove()) { return; } mMoveLen += (event.getY() - mLastDownY); if (mMoveLen > MARGIN_ALPHA * mMinTextSize / 2) { // 往下滑超过离开距离 decrementSelect(); } else if (mMoveLen < -MARGIN_ALPHA * mMinTextSize / 2) { // 往上滑超过离开距离 incrementSelect(); } mLastDownY = event.getY(); invalidate(); } /** * PickerView是否允许拖拽 */ private boolean canPickMove() { if (MODE_BOUNDARY == pickerMode) { if (mCurrentSelected == 0 && mMoveLen > MARGIN_ALPHA * mMinTextSize * boundaryScale) { //防止第一个数据项向下滑动出屏幕 return false; } else if ((mCurrentSelected == mDataList.size() - 1) && mMoveLen < -MARGIN_ALPHA * mMinTextSize * boundaryScale) { //防止最后一个数据项向上滑动出屏幕 return false; } } return true; } /** * 计算向下滑动的选中项 */ private void decrementSelect() { int lastSelect = mCurrentSelected; if (MODE_BOUNDARY == pickerMode) { //如果是边界滚动不能超过边界 mCurrentSelected = Math.max(0, mCurrentSelected - 1); if (lastSelect != mCurrentSelected) { mMoveLen = mMoveLen - MARGIN_ALPHA * mMinTextSize; } } else { //如果是循环滚动模式 mCurrentSelected = (mCurrentSelected - 1 + mDataList.size()) % mDataList.size(); if (lastSelect != mCurrentSelected) { mMoveLen = mMoveLen - MARGIN_ALPHA * mMinTextSize; } } } /** * 计算向上滑动的选中项 */ private void incrementSelect() { int lastSelect = mCurrentSelected; if (MODE_BOUNDARY == pickerMode) { //如果是边界滚动不能超过边界 mCurrentSelected = Math.min(mCurrentSelected + 1, mDataList.size() - 1); } else { mCurrentSelected = (mCurrentSelected + 1) % mDataList.size(); } if (lastSelect != mCurrentSelected) { mMoveLen = mMoveLen + MARGIN_ALPHA * mMinTextSize; } } private void doUp(MotionEvent event) { // 抬起手后mCurrentSelected的位置由当前位置move到中间选中位置 if (Math.abs(mMoveLen) < 0.0001) { mMoveLen = 0; return; } if (mTask != null) { mTask.cancel(); mTask = null; } mTask = new MyTimerTask(updateHandler); timer.schedule(mTask, 0, 10); } class MyTimerTask extends TimerTask { Handler handler; public MyTimerTask(Handler handler) { this.handler = handler; } @Override public void run() { handler.sendMessage(handler.obtainMessage()); } } public interface onSelectListener { void onSelect(PickerItem text); }}
3、下面分析边界滚动的实现原理
当前控件滚动控制的原理:
核心的控制事件为onTouchEvent。mMoveLen表示每一次滑动的滑动距离(DOWN 0--》MOVE --》UP 0)。
UP时滑动到一个数后,滑动距离mMoveLen恢复为0。
A)第一项的最大下拉距离。
视图默认选中第一项,那么往下进行拉动时,第一项如果完全拉倒控件的下边界时,视图就会显示空白数据,因此,
最佳的是下拉一个行倍距多一些,不到2个行倍距。此处定义的
/** 边界移出的百分比 */
private float boundaryScale = 1.3f;
同样的道理,最后一行上拉也相似的原理。
B)第一项达到最大下拉,滑动距离还未恢复为0时,继续增加持续的下拉操作,这样会导致第一项的滑动距离特别长,复位的时间变得特别长。因此已经下拉达到最大时,不允许进行步下拉滑动
private void doMove(MotionEvent event) { if(!canPickMove()) { return; }
}
/** * PickerView是否允许拖拽 */ private boolean canPickMove() { if (MODE_BOUNDARY == pickerMode) { if (mCurrentSelected == 0 && mMoveLen > MARGIN_ALPHA * mMinTextSize * boundaryScale) { //防止第一个数据项向下滑动出屏幕 return false; } else if ((mCurrentSelected == mDataList.size() - 1) && mMoveLen < -MARGIN_ALPHA * mMinTextSize * boundaryScale) { //防止最后一个数据项向上滑动出屏幕 return false; } } return true; }
达到最大的下拉,就不再处理继续的下拉事件,不再持续下拉。
- PickerView滚轮选择
- 安卓学习笔记---Android-PickerView实现 3D滚轮效果(时间选择器、省市区三级联动,单项选择效果)
- 城市选择pickerView
- ios pickerview选择城市
- 地址选择 PickerView
- PickerView时间,地点选择视图
- PickerView可以滑动选择的pickerView工具类(转载)
- 自封时间选择滚轮
- PickerView
- PickerView
- pickerView
- PickerView
- PickerView
- pickerVIew - 点餐,每列单独选择
- pickerView - 选择国旗,单列同时滚动
- PickerView实现全国地址的选择
- pickerview实现底部弹出选择时间
- 仿iOS滚轮选择控
- 静态代码监测工具
- 横竖屏切换Activity和activity跳转时生命周期的流程
- Spring Security权限管理相关配置加注解
- Python md5加密
- opengl es纹理
- PickerView滚轮选择
- JAVA常量命名规范
- LINQ与SQL的关系
- 机器学习中的维度灾难
- Spring security 相关名词解释
- 解决MAC电脑下魅族手机adb无法连接问题
- 【Android 进阶】Android Home 键监听
- 20. Valid Parentheses
- 自定义toolbar