Android 自定义数字选择器的实现(自定义View的思想)

近日在工作中,有需要到一个滚动的数值选择器,主要是滑动的时候的文字动画过渡效果计算。起初预想使用 ListView 实现,后发现需要 Adapter 和 ListView 的共同协助,高耦合,不易使用。而后转为全部自定义,自己处理触摸事件等。效果如下:


具体实现过程如下:

1. 先在构造函数中获得自定义属性

TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.NumPickView);
        for (int i = 0; i < ta.getIndexCount(); i++) {
            int index = ta.getIndex(i);
            switch (index) {
                case R.styleable.NumPickView_totalNum:
                    int total = ta.getInteger(index, 24);
                    for (int j = 0; j < total; j++) {
                        if (j < 10) {
                            mData.add("0" + String.valueOf(j));
                        } else {
                            mData.add(String.valueOf(j));
                        }
                    }
                    break;
                case R.styleable.NumPickView_showNum:
                    mShowNum = ta.getInteger(index, 6);
                    break;
                case R.styleable.NumPickView_textColor:
                    mTextColor = ta.getColor(index, mTextColor);
            }
        }
        ta.recycle();
        init();
2. 初始化各个使用工具,这里的工具重要的是 ValueAnimator 计算过渡时的改变值,ArgbEvaluator 计算不同位置文字的颜色值。

 mPaint = new Paint();
            mPaint.setAntiAlias(true);
            mPaint.setTextAlign(Paint.Align.CENTER);
            mRect = new Rect();
            mArgvEvlauator = new ArgbEvaluator();
            mValueAnimator = new ValueAnimator();
            mValueAnimator.setDuration(300);
            mValueAnimator.setInterpolator(new LinearInterpolator());
            mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    float value = (float) animation.getAnimatedValue();
                    if (abs(pivot) > mUnitHeight) {
                        return;
                    }
                    pivot = value;
                    mScale = min(1, abs(pivot / mUnitHeight));
                    invalidate();
                }
            });
            mValueAnimator.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) {}

                @Override
                public void onAnimationEnd(Animator animation) {
                    if (mStatus == Status.UP && pivot != 0) {
                        mCurrentPostion = clamp(mCurrentPostion + 1);
                    } else if (mStatus == Status.DOWN && pivot != 0) {
                        mCurrentPostion = clamp(mCurrentPostion - 1);
                    }
                    invalidate();
                    pivot = 0;
                    mStatus = Status.IDEL;
                    mScale = 0;
                    if (mListener != null) {
                        mListener.onSelected(mCurrentPostion);
                    }
                }

                @Override
                public void onAnimationCancel(Animator animation) {

                }

                @Override
                public void onAnimationRepeat(Animator animation) {

                }
            });
3. 在 onMeasure 函数里面取得需要的参数,特别的是由高度计算出单位高度 mUnitHeight,使得之后的计算依靠这个为参照。

 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mHeight = getMeasuredHeight();
        mWidth = getMeasuredWidth();
        middleHeight = mHeight / 2;
        middleWidht = mWidth / 2;
        mUnitHeight = (mHeight - getPaddingTop() + getPaddingBottom()) / mShowNum;
        textSize = mUnitHeight / 2;
        textStep = mUnitHeight / 9;
4. 核心代码一:在 onDraw 里面画出文字

//画选中字体
        drawText(canvas, mData.get(mCurrentPostion), 0, 1);
        //画除中间外上下字体
        int num = mShowNum / 2;
        for (int i = 1; i <= num; i++) {
            drawText(canvas, mData.get(clamp(mCurrentPostion + i)), i, 1);
            drawText(canvas, mData.get(clamp(mCurrentPostion - i)), i, -1);
        }
5. 核心代码二: 在 onTouchEvent 里面处理触摸事件,这里的关键是 pivot 的取值:滑动就取滑动距离,也就是字体下一次画的时候需要偏移量,但是偏移到下个 Item 的时候就要归零即:在新的 Item,旧偏移量对于新 Item 来说是零的。

 switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downY = event.getY();
                mValueAnimator.cancel();
                break;
            case MotionEvent.ACTION_MOVE:
                pivot = event.getY() - downY;
                if (pivot > 0) {
                    //向下
                    mStatus = Status.DOWN;
                    if (abs(pivot) > mUnitHeight) {
                        mCurrentPostion = clamp(mCurrentPostion - 1);
                        downY = event.getY();
                        pivot = 0;
                    } else {
                        invalidate();
                    }
                } else {
                    //向上
                    mStatus = Status.UP;
                    if (abs(pivot) > mUnitHeight) {
                        mCurrentPostion = clamp(mCurrentPostion + 1);
                        downY = event.getY();
                        pivot = 0;
                    } else {
                        invalidate();
                    }
                }
                mScale = min(1, abs(pivot / mUnitHeight));
                break;

            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:

                if (pivot == 0) {
                    //把点击事件统一为滑动事件处理,简化流程。
                    pivot = 0.00001f;
                }
                if (abs(pivot) > mUnitHeight / 2) {
                    //需要过渡
                    int rest = (int) abs(mUnitHeight / 2 - pivot);
                    if (mStatus == Status.UP) {
                        mValueAnimator.setFloatValues(pivot, -rest);
                    } else if (mStatus == Status.DOWN) {
                        //这里需要注意
                        mValueAnimator.setFloatValues(pivot, (int) pivot + rest + mUnitHeight / 2);
                    }
                } else {
                    //过渡失败,返回原数值,所以终点都是 0
                    if (mStatus == Status.UP) {
                        mValueAnimator.setFloatValues(pivot, 0);
                    } else if (mStatus == Status.DOWN) {
                        mValueAnimator.setFloatValues(pivot, 0);
                    }
                }
                if (mValueAnimator.getValues() == null || mValueAnimator.getValues().length == 0) {
                    return false;
                }
                mValueAnimator.start();
                break;
        }
        return true;
总结:总体来说这个实现是不难的,主要就是观察分析滑动的时候的上下字体变化规律,然后自己以此设定不同的滑动规则来应对不同的变化效果。主体代码和设计思想已经出来,读者如有自己个性化需要也可以在这基础上修改便可。 源码移步GitHub。自学小记,无伤大雅。


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值