前面我们讲解了滚动选择器的实现原理,并实现了字符串滚动选择器和图片选择器。没看过的同学建议先去了解一下:
《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,谢谢大家的支持!