Android滚动选择器——水平滚动
16lz
2021-01-24
前言
前面我们讲解了滚动选择器的实现原理,并实现了字符串滚动选择器和图片选择器。没看过的同学建议先去了解一下:
《Android自定义view——滚动选择器》
《android图片滚动选择器的实现》
这些滚动选择器的滚动方向都是垂直的。今天我们在此基础上增加可以水平滚动的选择器。效果如下:
实现
对父类ScrollPickerView进行改进,参照垂直滚动,添加跟水平滑动相关的变量。
private boolean mIsHorizontal = false; // 是否水平滚动 private int mItemHeight = 0; // 每个条目的高度,当垂直滚动时,高度=mMeasureHeight/mVisibleItemCount private int mItemWidth = 0; // 每个条目的宽度,当水平滚动时,宽度=mMeasureWidth/mVisibleItemCount private int mItemSize; // 当垂直滚动时,mItemSize = mItemHeight;水平滚动时,mItemSize = mItemWidth private int mCenterY; // 中间item的起始坐标y(不考虑偏移),当垂直滚动时,y= mCenterPosition*mItemHeight private int mCenterX; // 中间item的起始坐标x(不考虑偏移),当垂直滚动时,x = mCenterPosition*mItemWidth private int mCenterPoint; // 当垂直滚动时,mCenterPoint = mCenterY;水平滚动时,mCenterPoint = mCenterX private float mLastMoveY; // 触摸的坐标y private float mLastMoveX; // 触摸的坐标X private int mLastScrollY = 0; // Scroller的坐标y private int mLastScrollX = 0; // Scroller的坐标x
增加水平滚动后,在不同模式下,drawItem参数的意义相应改变。
/** * 绘制item * * @param canvas * @param data 数据集 * @param position 在data数据集中的位置 * @param relative 相对中间item的位置,relative==0表示中间item,relative<0表示上(左)边的item,relative>0表示下(右)边的item * @param moveLength 中间item滚动的距离,moveLength<0则表示向上(右)滚动的距离,moveLength>0则表示向下(左)滚动的距离 * @param top 当前绘制item的坐标,当垂直滚动时为顶部y的坐标;当水平滚动时为item最左边x的坐标 */ public abstract void drawItem(Canvas canvas, List data, int position, int relative, float moveLength, float top);
接着在子类中绘制item时,根据不同的模式(垂直/水平)进行绘制。
StringScrollPicker:
@Override public void drawItem(Canvas canvas, List data, int position, int relative, float moveLength, float top) { String text = data.get(position); int itemSize = getItemSize(); // 设置文字大小 ... float x = 0; float y = 0; if (isHorizontal()) { // 水平滚动 Paint.FontMetricsInt fmi = mPaint.getFontMetricsInt(); x = top + (itemSize - mPaint.measureText(text)) / 2; y = mMeasureHeight / 2 - fmi.descent + (fmi.bottom - fmi.top) / 2; } else { // 垂直滚动 x = (mMeasureWidth - mPaint.measureText(text)) / 2; Paint.FontMetricsInt fmi = mPaint.getFontMetricsInt(); // 绘制文字时,文字的baseline是对齐y坐标的,下面换算使其垂直居中。fmi.top值是相对baseline的,为负值 y = top + itemSize / 2 - fmi.descent + (fmi.bottom - fmi.top) / 2; } // 计算渐变颜色 computeColor(relative, itemSize, moveLength); canvas.drawText(text, x, y, mPaint); }
BitmapScrollPicker
@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mMeasureWidth = getMeasuredWidth(); mMeasureHeight = getMeasuredHeight(); // 当view的的大小确定后,选择器中item的某些位置也可确定。当水平滚动时,item的顶部和底部的坐标y可确定;当垂直滚动时,item的左边和右边的坐标x可确定 if (mDrawMode == DRAW_MODE_FULL) { // 填充 if (isHorizontal()) { mRect2.top = 0; mRect2.bottom = mMeasureHeight; } else { mRect2.left = 0; mRect2.right = mMeasureWidth; } } else if (mDrawMode == DRAW_MODE_SPECIFIED_SIZE) { // 指定大小 if (mSpecifiedSizeWidth == -1) { mSpecifiedSizeWidth = mMeasureWidth; mSpecifiedSizeHeight = mMeasureHeight; } setDrawModeSpecifiedSize(mSpecifiedSizeWidth, mSpecifiedSizeHeight); } else { // 居中 int size; if (isHorizontal()) { size = Math.min(mMeasureHeight, getItemWidth()); } else { size = Math.min(mMeasureWidth, getItemHeight()); } if (isHorizontal()) { mRect2.top = mMeasureHeight / 2 - size / 2; mRect2.bottom = mMeasureHeight / 2 + size / 2; } else { mRect2.left = mMeasureWidth / 2 - size / 2; mRect2.right = mMeasureWidth / 2 + size / 2; } } } @Override public void drawItem(Canvas canvas, List data, int position, int relative, float moveLength, float top) { int itemSize = getItemSize(); Bitmap bitmap = data.get(position); mRect1.right = bitmap.getWidth(); mRect1.bottom = bitmap.getHeight(); int span = 0; // 根据不同的绘制模式,计算出item内容的最终绘制位置和大小 // 当水平滚动时,计算item的左边和右边的坐标x;当垂直滚动时,item的顶部和底部的坐标y if (mDrawMode == DRAW_MODE_FULL) { // 填充 span = 0; if (isHorizontal()) { mRect2.left = (int) top + span; mRect2.right = (int) (top + itemSize - span); } else { mRect2.top = (int) top + span; mRect2.bottom = (int) (top + itemSize - span); } mRectTemp.set(mRect2); scale(mRectTemp, relative, itemSize, moveLength); canvas.drawBitmap(bitmap, mRect1, mRectTemp, null); } else if (mDrawMode == DRAW_MODE_SPECIFIED_SIZE) { // 指定大小 if (isHorizontal()) { span = (itemSize - mSpecifiedSizeWidth) / 2; mSpecifiedSizeRect.left = (int) top + span; mSpecifiedSizeRect.right = (int) top + span + mSpecifiedSizeWidth; } else { span = (itemSize - mSpecifiedSizeHeight) / 2; mSpecifiedSizeRect.top = (int) top + span; mSpecifiedSizeRect.bottom = (int) top + span + mSpecifiedSizeHeight; } mRectTemp.set(mSpecifiedSizeRect); scale(mRectTemp, relative, itemSize, moveLength); canvas.drawBitmap(bitmap, mRect1, mRectTemp, null); } else { // 居中 if (isHorizontal()) { float scale = mRect2.height() * 1f / bitmap.getHeight(); span = (int) ((itemSize - bitmap.getWidth() * scale) / 2); } else { float scale = mRect2.width() * 1f / bitmap.getWidth(); span = (int) ((itemSize - bitmap.getHeight() * scale) / 2); } if (isHorizontal()) { mRect2.left = (int) (top + span); mRect2.right = (int) (top + itemSize - span); } else { mRect2.top = (int) (top + span); mRect2.bottom = (int) (top + itemSize - span); } mRectTemp.set(mRect2); scale(mRectTemp, relative, itemSize, moveLength); canvas.drawBitmap(bitmap, mRect1, mRectTemp, null); } }
虽然图片滚动选择器BitmapScrollPicker看上去复杂多,但代码逻辑并不复杂,主要是根据不同的绘制模式以及滚动方向计算出最终item内容(即图片)的绘制位置。
另外,相对于之前文章里的图片滚动选择器,这里还增加了图片大小渐变的效果,设置方法为:
... app:spv_max_scale="1.3" app:spv_min_scale="0.8" ... />
或调用BitmapScrollPicker中的方法:
/** * item内容缩放倍数 * * @param minScale 沒有被选中时的最小倍数 * @param maxScale 被选中时的最大倍数 */public void setItemScale(float minScale, float maxScale)
大小渐变的实现原理主要是对计算出的item图片绘制矩阵进行缩放:
private void scale(Rect rect, int relative, int itemSize, float moveLength) { float spanWidth, spanHeight; if (relative == -1 || relative == 1) { // 上一个或下一个 // 处理上一个item且向上滑动 或者 处理下一个item且向下滑动, if ((relative == -1 && moveLength < 0) || (relative == 1 && moveLength > 0)) { spanWidth = (rect.width() - mMinScale * rect.width()) / 2; spanHeight = (rect.height() - mMinScale * rect.height()) / 2; } else { // 计算渐变 float rate = Math.abs(moveLength) / itemSize; spanWidth = (rect.width() - (mMinScale + (mMaxScale - mMinScale) * rate) * rect.width()) / 2; spanHeight = (rect.height() - (mMinScale + (mMaxScale - mMinScale) * rate) * rect.height()) / 2; } } else if (relative == 0) { // 中间item float rate = (itemSize - Math.abs(moveLength)) / itemSize; spanWidth = (rect.width() - (mMinScale + (mMaxScale - mMinScale) * rate) * rect.width()) / 2; spanHeight = (rect.height() - (mMinScale + (mMaxScale - mMinScale) * rate) * rect.height()) / 2; } else { spanWidth = (rect.width() - mMinScale * rect.width()) / 2; spanHeight = (rect.height() - mMinScale * rect.height()) / 2; } rect.left += spanWidth; rect.right -= spanWidth; rect.top += spanHeight; rect.bottom -= spanHeight; }
完整的代码放在了github上:https://github.com/1993hzw/Androids,谢谢大家的支持!
更多相关文章
- Android中设置半个屏幕大小且居中的按钮布局 (layout_weight属性
- 【android】scaleType属性与ImagView中图片的显示的关系
- Android中怎么让你的layout适应屏幕的大小
- osg for android 学习之十:注意事项
- 十一、Android坐标系
- Android中常用的Drawable
- Android: 自定义窗口大小
- android-menu菜单的应用
- Android使用NinePatch图片实现大小可变的Button