前言

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

更多相关文章

  1. Android中设置半个屏幕大小且居中的按钮布局 (layout_weight属性
  2. 【android】scaleType属性与ImagView中图片的显示的关系
  3. Android中怎么让你的layout适应屏幕的大小
  4. osg for android 学习之十:注意事项
  5. 十一、Android坐标系
  6. Android中常用的Drawable
  7. Android: 自定义窗口大小
  8. android-menu菜单的应用
  9. Android使用NinePatch图片实现大小可变的Button

随机推荐

  1. Android相对布局属性
  2. Android(安卓)模拟器 无法上网问题
  3. Android(安卓)存储选项之 SQLiteDatabase
  4. 横竖屏切换 android:screenOrientation属
  5. flutter 与 android 混合开发 以及 Andro
  6. 如何使android应用程序使用当前的主题样
  7. Android开发随想:iPhone和Android之间的对
  8. android开发每日汇总【2011-11-26】
  9. Android中IPC框架的理解
  10. android:screenOrientation属性(转)