滚动选择器主要用于在数据列表中快速选择其中一条数据,比如日期选择.首先给大伙看看最终的实现效果:
实现原理
代码中定义了几个重要的变量:
private int mVisibleItemCount = 3; // 可见的item数量 private int mSelected; // 当前选中的item下标 private int mItemHeight = 0; // 每个条目的高度 = mMeasureHeight/mVisibleItemCount private int mCenterY; // 中间item的起始坐标y = mCenterPosition*mItemHeight private float mMoveLength = 0; // item移动长度,负数表示向上移动,正数表示向下移动
通过上图可发现,这几个变量决定了滚动选择器中每个条目的绘制位置
protected void onDraw(Canvas canvas) { mPaint.setColor(mCenterItemBackground); canvas.drawRect(0, y, getWidth(), y + mItemHeight, mPaint); drawItem(canvas, mData, mSelected, 0, mMoveLength, mCenterY + mMoveLength); int length = Math.max(mCenterPosition, mVisibleItemCount - mCenterPosition); for (int i = 1; i <= length && i <= mData.size(); i++) { if (i <= mCenterPosition + 1) { // 上面的items,相对位置为 -i positon = mSelected - i < 0 ? mData.size() + mSelected - i drawItem(canvas, mData, positon, -i, mMoveLength, mCenterY + mMoveLength - i * mItemHeight); } else if (mSelected - i >= 0) { // 非循环滚动 drawItem(canvas, mData, positon, -i, mMoveLength, mCenterY + mMoveLength - i * mItemHeight); if (i <= mVisibleItemCount - mCenterPosition) { // 下面的items,相对位置为 i positon = mSelected + i >= mData.size() ? mSelected + i - mData.size() : mSelected + i; drawItem(canvas, mData, positon, i, mMoveLength, mCenterY + mMoveLength + i * mItemHeight); } else if (mSelected + i < mData.size()) { // 非循环滚动 drawItem(canvas, mData, positon, i, mMoveLength, mCenterY + mMoveLength + i * mItemHeight);
为了便以拓展,drawItem()负责每个item的绘制.接下来只要改变上面几个重要变量的值就可以实现滚动的效果.
系统自带的android.widget.Scroller 类封装了滚动相关的操作。可以使用Scroller获取可以产生滚动效果的数据;例如,在响应一个滑动手势时,Scroller会帮你计算
滚动偏移量,你可以根据获取的偏移量来设置你的view的位置,从而实现滚动效果。 主要方法有:
startScroll(int startX, int startY, int dx, int dy) //开始计算平滑滚动,dx、dy为滚动的距离,dx = finalX - startX; fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY) //根据滑动的速度,开始计算惯性滚动 computeScrollOffset()//计算当前时间滚动的偏移量,如果返回true,则滚动还未结束 getCurrY() //获取当前Y轴坐标的偏移量 getCurrX() //获取当前X轴坐标的偏移量 mScroller.abortAnimation() //停止滚动
在自定义View中常用的方法为:mScroller.startScroll(startX, startY, dx, dy); // 调用invalidate()方法刷新view时,在绘制(onDraw)之前会调用view.computeScroll()方法, public void computeScroll() { if (mScroller.computeScrollOffset()) { // 正在滚动 int curY = mScroller.getCurrY(); int curX = mScroller.getCurrX();
在滚动选择器中,当手指滑动时,把滑动的距离赋值给mMoveLength,当手指抬起时,则调用Scroller.startScroll()方法,将当前item移动到中间位置;如果是快速滑动后抬起手指,则调用Scroller.fling()方法惯性滑动一段距离,惯性结束后再调用Scroller.startScroll()方法,将当前item移动到中间位置.
public void computeScroll() { if (mScroller.computeScrollOffset()) { // 正在滚动 // 可以把scroller看做模拟的触屏滑动操作,mLastScrollY为上次滑动的坐标 mMoveLength = mMoveLength + mScroller.getCurrY() - mLastScrollY; mLastScrollY = mScroller.getCurrY(); checkCirculation(); // 检测当前选中的item moveToCenter(); // 滚动到中间位置 } else if (mIsMovingCenter) { // 选择完成,回调给监听器
private void checkCirculation() { if (mMoveLength >= mItemHeight) { // 向下滑动 int span = (int) (mMoveLength / mItemHeight); if (mSelected < 0) { // 滚动顶部,判断是否循环滚动 mSelected = mData.size() + mSelected; } while (mSelected < 0); // 当越过的item数量超过一圈时 mMoveLength = (mMoveLength - mItemHeight) % mItemHeight; mMoveLength = mItemHeight; if (mIsFling) { // 停止惯性滑动,根据computeScroll()中的逻辑,下一步将调用moveToCenter() mScroller.forceFinished(true); if (mIsMovingCenter) { // 移回中间位置 mMoveLength = (mMoveLength - mItemHeight) % mItemHeight; } else if (mMoveLength <= -mItemHeight) { // 向上滑动 int span = (int) (-mMoveLength / mItemHeight); if (mSelected >= mData.size()) { // 滚动末尾,判断是否循环滚动 mSelected = mSelected - mData.size(); } while (mSelected >= mData.size()); // 当越过的item数量超过一圈时 mMoveLength = (mMoveLength + mItemHeight) % mItemHeight; mSelected = mData.size() - 1; mMoveLength = -mItemHeight; if (mIsFling) { // 停止惯性滑动,根据computeScroll()中的逻辑,下一步将调用moveToCenter() mScroller.forceFinished(true); if (mIsMovingCenter) { // 移回中间位置 mMoveLength = (mMoveLength + mItemHeight) % mItemHeight;
类的定义
为了拓展方便,这里把滚动选择器定义为抽象类,文章最开始的效果图是用继承自ScrollPickerView的字符串滚动选择器实现.
public abstract class ScrollPickerView<T> extends View{ * @param position 在data数据集中的位置 * @param relative 相对中间item的位置,relative=position-getSelected() * @param moveLength 中间item滚动的距离,moveLength<0则表示向上滚动的距离,moveLength>0则表示向上滚动 * @param top 当前绘制item的顶部坐标 public abstract void drawItem(Canvas canvas, List<T> data, int position, int relative, float moveLength, float top);
文章开头的演示图中的控件为字符串选择器,主要继承了ScrollPickerView,并在drawItem方法中绘制字符串。
public class StringScrollPicker extends ScrollPickerView<String>;
如果要实现图片滚动选择器,只有继承ScrollPickerView,实现drawItem()方法即可.
完整的代码放在了github上:https://github.com/1993hzw/Androids
|