Android滚动选择器——水平滚动

前言

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