项目中需要做一个数字滚动的特效,完成后特意记录下来,方便他人。。。
先上效果图:

以下为主要实行代码:
ScrollPickerView.class

/** * 滚动选择器,带惯性滑动 */public abstract class ScrollPickerView extends View {    private int visibleItemCount = 3; // 可见的item数量    private boolean isInertiaScroll = true; // 快速滑动时是否惯性滚动一段距离,默认开启    private boolean isCirculation = true; // 是否循环滚动,默认开启    // 不允许父组件拦截触摸事件,设置为true为不允许拦截,此时该设置才生效    // 当嵌入到ScrollView等滚动组件中,为了使该自定义滚动选择器可以正常工作,请设置为true    private boolean disallowInterceptTouch = false;    private int selected; // 当前选中的item下标    private List data;    private int itemHeight = 0; // 每个条目的高度,当垂直滚动时,高度=mMeasureHeight/visibleItemCount    private int itemWidth = 0; // 每个条目的宽度,当水平滚动时,宽度=mMeasureWidth/visibleItemCount    private int itemSize; // 当垂直滚动时,itemSize = itemHeight;水平滚动时,itemSize = itemWidth    private int centerPosition = -1; // 中间item的位置,0<=centerPosition<visibleItemCount,默认为 visibleItemCount / 2    private int centerY; // 中间item的起始坐标y(不考虑偏移),当垂直滚动时,y= centerPosition*itemHeight    private int centerX; // 中间item的起始坐标x(不考虑偏移),当垂直滚动时,x = centerPosition*itemWidth    private int centerPoint; // 当垂直滚动时,centerPoint = centerY;水平滚动时,centerPoint = centerX    private float lastMoveY; // 触摸的坐标y    private float lastMoveX; // 触摸的坐标X    private float moveLength = 0; // item移动长度,负数表示向上移动,正数表示向下移动    private GestureDetector gestureDetector;    private OnSelectedListener listener;    private Scroller scroller;    private boolean isFling; // 是否正在惯性滑动    private boolean isMovingCenter; // 是否正在滑向中间    // 可以把scroller看做模拟的触屏滑动操作,mLastScrollY为上次触屏滑动的坐标    private int lastScrollY = 0; // Scroller的坐标y    private int lastScrollX = 0; // Scroller的坐标x    private boolean disallowTouch = false; // 不允许触摸    private Paint paint;    private Drawable centerItemBackground = null; // 中间选中item的背景色    private boolean canTap = true; // 单击切换选项或触发点击监听器    private boolean isHorizontal = false; // 是否水平滚动    private boolean drawAllItem = false; // 是否绘制每个item(包括在边界外的item)    private boolean isAutoScrolling = false;    private ValueAnimator autoScrollAnimator;    private final static SlotInterpolator autoScrollInterpolator = new SlotInterpolator();    private int defaultValue = -1;    public ScrollPickerView(Context context) {        super(context);        this.initView(context);        return;    }    public ScrollPickerView(Context context, AttributeSet attrs) {        super(context, attrs);        this.initView(context);        return;    }    public ScrollPickerView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        this.initView(context);        return;    }    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)    public ScrollPickerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {        super(context, attrs, defStyleAttr, defStyleRes);        this.initView(context);        return;    }    /**     * 初始化视图     *     * @param context     */    private void initView(Context context) {        this.gestureDetector = new GestureDetector(getContext(), new FlingOnGestureListener());        this.scroller = new Scroller(getContext());        this.autoScrollAnimator = ValueAnimator.ofInt(0, 0);        this.paint = new Paint(Paint.ANTI_ALIAS_FLAG);        this.paint.setStyle(Paint.Style.FILL);        return;    }    @Override    protected void onDraw(Canvas canvas) {        if (this.data == null || this.data.size() <= 0) {            return;        }        // 选中item的背景色        if (this.centerItemBackground != null) {            this.centerItemBackground.draw(canvas);        }        // 只绘制可见的item        int length = Math.max(this.centerPosition + 1, this.visibleItemCount - this.centerPosition);        int position;        int start = Math.min(length, this.data.size());        if (this.drawAllItem) {            start = this.data.size();        }        // 上下两边        for (int i = start; i >= 1; i--) { // 先从远离中间位置的item绘制,当item内容偏大时,较近的item覆盖在较远的上面            if (this.drawAllItem || i <= this.centerPosition + 1) {  // 上面的items,相对位置为 -i                position = this.selected - i < 0 ? this.data.size() + this.selected - i : this.selected - i;                // 传入位置信息,绘制item                if (this.isCirculation) {                    this.drawItem(canvas, this.data, position, -i, this.moveLength, this.centerPoint + this.moveLength - i * this.itemSize);                } else if (selected - i >= 0) { // 非循环滚动                    this.drawItem(canvas, this.data, position, -i, this.moveLength, this.centerPoint + this.moveLength - i * this.itemSize);                }            }            if (this.drawAllItem || i <= this.visibleItemCount - this.centerPosition) {  // 下面的items,相对位置为 i                position = this.selected + i >= this.data.size() ? this.selected + i - this.data.size() : this.selected + i;                // 传入位置信息,绘制item                if (this.isCirculation) {                    this.drawItem(canvas, this.data, position, i, this.moveLength, this.centerPoint + this.moveLength + i * this.itemSize);                } else if (selected + i < data.size()) { // 非循环滚动                    this.drawItem(canvas, this.data, position, i, this.moveLength, this.centerPoint + this.moveLength + i * this.itemSize);                }            }        }        // 选中的item        this.drawItem(canvas, this.data, this.selected, 0, this.moveLength, this.centerPoint + this.moveLength);        return;    }    /**     * 绘制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);    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        return;    }    @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);        this.reset();        return;    }    private void reset() {        if (this.centerPosition < 0) {            this.centerPosition = this.visibleItemCount / 2;        }        if (this.isHorizontal) {            this.itemHeight = this.getMeasuredHeight();            this.itemWidth = this.getMeasuredWidth() / this.visibleItemCount;            this.centerY = 0;            this.centerX = this.centerPosition * this.itemWidth;            this.itemSize = this.itemWidth;            this.centerPoint = this.centerX;        } else {            this.itemHeight = this.getMeasuredHeight() / this.visibleItemCount;            this.itemWidth = this.getMeasuredWidth();            this.centerY = this.centerPosition * this.itemHeight;            this.centerX = 0;            this.itemSize = this.itemHeight;            this.centerPoint = this.centerY;        }        if (this.centerItemBackground != null) {            this.centerItemBackground.setBounds(this.centerX, this.centerY, this.centerX + this.itemWidth, this.centerY + this.itemHeight);        }        return;    }    @Override    public boolean onTouchEvent(MotionEvent event) {        if (this.disallowTouch) { // 不允许触摸            return true;        }        if (this.gestureDetector.onTouchEvent(event)) {            return true;        }        switch (event.getActionMasked()) {            case MotionEvent.ACTION_MOVE:                if (this.isHorizontal) {                    if (Math.abs(event.getX() - this.lastMoveX) < 0.1f) {                        return true;                    }                    this.moveLength += event.getX() - this.lastMoveX;                } else {                    if (Math.abs(event.getY() - this.lastMoveY) < 0.1f) {                        return true;                    }                    this.moveLength += event.getY() - this.lastMoveY;                }                this.lastMoveY = event.getY();                this.lastMoveX = event.getX();                this.checkCirculation();                this.invalidate();                break;            case MotionEvent.ACTION_UP:                this.lastMoveY = event.getY();                this.lastMoveX = event.getX();                this.moveToCenter();                break;        }        return true;    }    /**     * @param curr     * @param end     */    private void computeScroll(int curr, int end, float rate) {        if (rate < 1) { // 正在滚动            if (this.isHorizontal) {                // 可以把scroller看做模拟的触屏滑动操作,mLastScrollX为上次滑动的坐标                this.moveLength = this.moveLength + curr - this.lastScrollX;                this.lastScrollX = curr;            } else {                // 可以把scroller看做模拟的触屏滑动操作,mLastScrollY为上次滑动的坐标                this.moveLength = this.moveLength + curr - this.lastScrollY;                this.lastScrollY = curr;            }            this.checkCirculation();            this.invalidate();        } else { // 滚动完毕            this.isMovingCenter = false;            this.lastScrollY = 0;            this.lastScrollX = 0;            // 直接居中,不通过动画            if (this.moveLength > 0) { //// 向下滑动                if (this.moveLength < this.itemSize / 2) {                    this.moveLength = 0;                } else {                    this.moveLength = this.itemSize;                }            } else {                if (-this.moveLength < this.itemSize / 2) {                    this.moveLength = 0;                } else {                    this.moveLength = -this.itemSize;                }            }            this.checkCirculation();            this.moveLength = 0;            this.lastScrollY = 0;            this.lastScrollX = 0;            this.notifySelected();            this.invalidate();        }        return;    }    @Override    public void computeScroll() {        if (this.scroller.computeScrollOffset()) { // 正在滚动            if (this.isHorizontal) {                // 可以把scroller看做模拟的触屏滑动操作,mLastScrollX为上次滑动的坐标                this.moveLength = this.moveLength + this.scroller.getCurrX() - this.lastScrollX;            } else {                // 可以把scroller看做模拟的触屏滑动操作,mLastScrollY为上次滑动的坐标                this.moveLength = this.moveLength + this.scroller.getCurrY() - this.lastScrollY;            }            this.lastScrollY = this.scroller.getCurrY();            this.lastScrollX = this.scroller.getCurrX();            this.checkCirculation(); // 检测当前选中的item            this.invalidate();        } else { // 滚动完毕            if (this.isFling) {                this.isFling = false;                this.moveToCenter(); // 滚动到中间位置            } else if (this.isMovingCenter) { // 选择完成,回调给监听器                this.moveLength = 0;                this.isMovingCenter = false;                this.lastScrollY = 0;                this.lastScrollX = 0;                this.notifySelected();            }        }        return;    }    public void cancelScroll() {        this.lastScrollY = 0;        this.lastScrollX = 0;        this.isFling = false;        this.isMovingCenter = false;        this.scroller.abortAnimation();        this.stopAutoScroll();        return;    }    // 检测当前选择的item位置    private void checkCirculation() {        if (this.moveLength >= this.itemSize) { // 向下滑动            // 该次滚动距离中越过的item数量            int span = (int) (this.moveLength / this.itemSize);            this.selected -= span;            if (this.selected < 0) {  // 滚动顶部,判断是否循环滚动                if (this.isCirculation) {                    do {                        this.selected = this.data.size() + this.selected;                    } while (this.selected < 0); // 当越过的item数量超过一圈时                    this.moveLength = (this.moveLength - this.itemSize) % this.itemSize;                } else { // 非循环滚动                    this.selected = 0;                    this.moveLength = this.itemSize;                    if (this.isFling) { // 停止惯性滑动,根据computeScroll()中的逻辑,下一步将调用moveToCenter()                        this.scroller.forceFinished(true);                    }                    if (this.isMovingCenter) { //  移回中间位置                        this.scroll(this.moveLength, 0);                    }                }            } else {                this.moveLength = (this.moveLength - this.itemSize) % this.itemSize;            }        } else if (this.moveLength <= -this.itemSize) { // 向上滑动            // 该次滚动距离中越过的item数量            int span = (int) (-this.moveLength / this.itemSize);            this.selected += span;            if (this.selected >= this.data.size()) { // 滚动末尾,判断是否循环滚动                if (this.isCirculation) {                    do {                        this.selected = this.selected - this.data.size();                    } while (this.selected >= this.data.size()); // 当越过的item数量超过一圈时                    this.moveLength = (this.moveLength + this.itemSize) % this.itemSize;                } else { // 非循环滚动                    this.selected = this.data.size() - 1;                    this.moveLength = -this.itemSize;                    if (this.isFling) { // 停止惯性滑动,根据computeScroll()中的逻辑,下一步将调用moveToCenter()                        this.scroller.forceFinished(true);                    }                    if (this.isMovingCenter) { //  移回中间位置                        this.scroll(this.moveLength, 0);                    }                }            } else {                this.moveLength = (this.moveLength + this.itemSize) % this.itemSize;            }        }        return;    }    // 移动到中间位置    private void moveToCenter() {        if (!this.scroller.isFinished() || this.isFling || this.moveLength == 0) {            return;        }        this.cancelScroll();        // 向下滑动        if (this.moveLength > 0) {            if (this.isHorizontal) {                if (this.moveLength < this.itemWidth / 2) {                    this.scroll(this.moveLength, 0);                } else {                    this.scroll(this.moveLength, this.itemWidth);                }            } else {                if (this.moveLength < this.itemHeight / 2) {                    this.scroll(this.moveLength, 0);                } else {                    this.scroll(this.moveLength, this.itemHeight);                }            }        } else {            if (this.isHorizontal) {                if (-this.moveLength < this.itemWidth / 2) {                    this.scroll(this.moveLength, 0);                } else {                    this.scroll(this.moveLength, -this.itemWidth);                }            } else {                if (-this.moveLength < this.itemHeight / 2) {                    this.scroll(this.moveLength, 0);                } else {                    this.scroll(this.moveLength, -this.itemHeight);                }            }        }        return;    }    // 平滑滚动    private void scroll(float from, int to) {        if (this.isHorizontal) {            this.lastScrollX = (int) from;            this.isMovingCenter = true;            this.scroller.startScroll((int) from, 0, 0, 0);            this.scroller.setFinalX(to);        } else {            this.lastScrollY = (int) from;            this.isMovingCenter = true;            this.scroller.startScroll(0, (int) from, 0, 0);            this.scroller.setFinalY(to);        }        this.invalidate();        return;    }    // 惯性滑动,    private void fling(float from, float vel) {        if (this.isHorizontal) {            this.lastScrollX = (int) from;            this.isFling = true;            // 最多可以惯性滑动10个item            this.scroller.fling((int) from, 0, (int) vel, 0, -10 * this.itemWidth, 10 * this.itemWidth, 0, 0);        } else {            this.lastScrollY = (int) from;            this.isFling = true;            // 最多可以惯性滑动10个item            this.scroller.fling(0, (int) from, 0, (int) vel, 0, 0, -10 * this.itemHeight, 10 * this.itemHeight);        }        this.invalidate();        return;    }    private void notifySelected() {        if (this.listener != null) {            // 告诉监听器选择完毕            this.postDelayed(new Runnable() {                @Override                public void run() {                    ScrollPickerView.this.listener.onSelected(ScrollPickerView.this, ScrollPickerView.this.selected);                    return;                }            }, 10);        }        return;    }    /**     * 自动滚动(必须设置为可循环滚动)     *     * @param position     * @param duration     * @param speed    每毫秒移动的像素点     */    public void autoScrollFast(final int position, long duration, float speed, final Interpolator interpolator) {        if (this.isAutoScrolling || !this.isCirculation) {            return;        }        this.cancelScroll();        this.isAutoScrolling = true;        int length = (int) (speed * duration);        int circle = (int) (length * 1f / (this.data.size() * this.itemSize) + 0.5f); // 圈数        circle = circle <= 0 ? 1 : circle;        int aPlan = circle * (this.data.size()) * this.itemSize + (this.selected - position) * this.itemSize;        int bPlan = aPlan + (this.data.size()) * this.itemSize; // 多一圈        // 让其尽量接近length        final int end = Math.abs(length - aPlan) < Math.abs(length - bPlan) ? aPlan : bPlan;        this.autoScrollAnimator.cancel();        this.autoScrollAnimator.setIntValues(0, end);        this.autoScrollAnimator.setInterpolator(interpolator);        this.autoScrollAnimator.setDuration(duration);        this.autoScrollAnimator.removeAllUpdateListeners();        if (end != 0) {            // itemHeight为0 导致endy=0            this.autoScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {                @Override                public void onAnimationUpdate(ValueAnimator animation) {                    float rate = 0;                    rate = animation.getCurrentPlayTime() * 1f / animation.getDuration();                    ScrollPickerView.this.computeScroll((int) animation.getAnimatedValue(), end, rate);                    return;                }            });            this.autoScrollAnimator.removeAllListeners();            this.autoScrollAnimator.addListener(new AnimatorListenerAdapter() {                @Override                public void onAnimationEnd(Animator animation) {                    super.onAnimationEnd(animation);                    ScrollPickerView.this.autoScrollStop();                    return;                }            });            this.autoScrollAnimator.start();        } else {            this.computeScroll(end, end, 1);            this.autoScrollStop();        }        return;    }    /**     * 设置停止标记     */    private void autoScrollStop() {        this.selected = this.defaultValue;        this.isAutoScrolling = false;        return;    }    /**     * 自动滚动,默认速度为 0.6dp/ms     *     * @see ScrollPickerView#autoScrollFast(int, long, float, Interpolator)     */    public void autoScrollFast(final int position, long duration) {        float speed = dip2px(0.6f);        this.autoScrollFast(position, duration, speed, this.autoScrollInterpolator);        return;    }    /**     * 自动滚动     *     * @see ScrollPickerView#autoScrollFast(int, long, float, Interpolator)     */    public void autoScrollFast(final int position, long duration, float speed) {        this.autoScrollFast(position, duration, speed, this.autoScrollInterpolator);        return;    }    /**     * 滚动到指定位置     *     * @param toPosition    需要滚动到的位置     * @param duration      滚动时间     * @param interpolator     */    public void autoScrollToPosition(int toPosition, long duration, final Interpolator interpolator) {        toPosition = toPosition % this.data.size();        final int endY = (this.selected - toPosition) * this.itemHeight;        this.autoScrollTo(endY, duration, interpolator, false);        return;    }    /**     * @param endY          需要滚动到的位置     * @param duration      滚动时间     * @param interpolator     * @param canIntercept 能否终止滚动,比如触摸屏幕终止滚动     */    public void autoScrollTo(final int endY, long duration, final Interpolator interpolator, boolean canIntercept) {        if (this.isAutoScrolling) {            return;        }        final boolean temp = this.disallowTouch;        this.disallowTouch = !canIntercept;        this.isAutoScrolling = true;        this.autoScrollAnimator.cancel();        this.autoScrollAnimator.setIntValues(0, endY);        this.autoScrollAnimator.setInterpolator(interpolator);        this.autoScrollAnimator.setDuration(duration);        this.autoScrollAnimator.removeAllUpdateListeners();        this.autoScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                float rate = 0;                rate = animation.getCurrentPlayTime() * 1f / animation.getDuration();                ScrollPickerView.this.computeScroll((int) animation.getAnimatedValue(), endY, rate);                return;            }        });        this.autoScrollAnimator.removeAllListeners();        this.autoScrollAnimator.addListener(new AnimatorListenerAdapter() {            @Override            public void onAnimationEnd(Animator animation) {                super.onAnimationEnd(animation);                ScrollPickerView.this.autoScrollStop();                ScrollPickerView.this.disallowTouch = temp;                return;            }        });        this.autoScrollAnimator.start();        return;    }    /**     * 停止自动滚动     */    public void stopAutoScroll() {        this.autoScrollStop();        this.autoScrollAnimator.cancel();        return;    }    private static class SlotInterpolator implements Interpolator {        @Override        public float getInterpolation(float input) {            return (float) (Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;        }    }    /**     * 快速滑动时,惯性滑动一段距离     *     * @author huangziwei     */    private class FlingOnGestureListener extends SimpleOnGestureListener {        private boolean mIsScrollingLastTime = false;        public boolean onDown(MotionEvent e) {            if (ScrollPickerView.this.disallowInterceptTouch) {  // 不允许父组件拦截事件                ViewParent parent = getParent();                if (parent != null) {                    parent.requestDisallowInterceptTouchEvent(true);                }            }            this.mIsScrollingLastTime = isScrolling(); // 记录是否从滚动状态终止            // 点击时取消所有滚动效果            ScrollPickerView.this.cancelScroll();            ScrollPickerView.this.lastMoveY = e.getY();            ScrollPickerView.this.lastMoveX = e.getX();            return true;        }        @Override        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, final float velocityY) {            // 惯性滑动            if (ScrollPickerView.this.isInertiaScroll) {                cancelScroll();                if (ScrollPickerView.this.isHorizontal) {                    ScrollPickerView.this.fling(ScrollPickerView.this.moveLength, velocityX);                } else {                    ScrollPickerView.this.fling(ScrollPickerView.this.moveLength, velocityY);                }            }            return true;        }        @Override        public boolean onSingleTapUp(MotionEvent e) {            ScrollPickerView.this.lastMoveY = e.getY();            ScrollPickerView.this.lastMoveX = e.getX();            float lastMove = 0;            if (isHorizontal()) {                ScrollPickerView.this.centerPoint = ScrollPickerView.this.centerX;                lastMove = ScrollPickerView.this.lastMoveX;            } else {                ScrollPickerView.this.centerPoint = ScrollPickerView.this.centerY;                lastMove = ScrollPickerView.this.lastMoveY;            }            if (ScrollPickerView.this.canTap && !ScrollPickerView.this.isScrolling() && !this.mIsScrollingLastTime) {                if (lastMove >= ScrollPickerView.this.centerPoint && lastMove <= ScrollPickerView.this.centerPoint + ScrollPickerView.this.itemSize) {                    ScrollPickerView.this.performClick();                } else if (lastMove < ScrollPickerView.this.centerPoint) {                    int move = itemSize;                    ScrollPickerView.this.autoScrollTo(move, 150, ScrollPickerView.this.autoScrollInterpolator, false);                } else if (lastMove > ScrollPickerView.this.centerPoint + ScrollPickerView.this.itemSize) {                    int move = -ScrollPickerView.this.itemSize;                    ScrollPickerView.this.autoScrollTo(move, 150, ScrollPickerView.this.autoScrollInterpolator, false);                } else {                    ScrollPickerView.this.moveToCenter();                }            } else {                ScrollPickerView.this.moveToCenter();            }            return true;        }    }    public List getData() {        return this.data;    }    public void setData(List data) {        if (data == null) {            this.data = new ArrayList();        } else {            this.data = data;        }        this.selected = this.data.size() / 2;        this.invalidate();        return;    }    public T getSelectedItem() {        return this.data.get(this.selected);    }    public int getSelectedPosition() {        return this.selected;    }    public void setSelectedPosition(int position) {        if (position < 0 || position > this.data.size() - 1 || position == this.selected) {            return;        }        this.selected = position;        this.invalidate();        if (this.listener != null) {            this.notifySelected();        }        return;    }    public void setOnSelectedListener(OnSelectedListener listener) {        this.listener = listener;        return;    }    public OnSelectedListener getListener() {        return this.listener;    }    public boolean isInertiaScroll() {        return this.isInertiaScroll;    }    public void setInertiaScroll(boolean inertiaScroll) {        this.isInertiaScroll = inertiaScroll;        return;    }    public boolean isIsCirculation() {        return this.isCirculation;    }    public void setIsCirculation(boolean isCirculation) {        this.isCirculation = isCirculation;        return;    }    public boolean isDisallowInterceptTouch() {        return this.disallowInterceptTouch;    }    public int getVisibleItemCount() {        return this.visibleItemCount;    }    public void setVisibleItemCount(int visibleItemCount) {        this.visibleItemCount = visibleItemCount;        this.reset();        this.invalidate();        return;    }    /**     * 是否允许父元素拦截事件,设置true后可以保证在ScrollView下正常滚动     */    public void setDisallowInterceptTouch(boolean disallowInterceptTouch) {        this.disallowInterceptTouch = disallowInterceptTouch;        return;    }    public int getItemHeight() {        return this.itemHeight;    }    public int getItemWidth() {        return this.itemWidth;    }    /**     * @return 当垂直滚动时,itemSize = itemHeight;水平滚动时,itemSize = itemWidth     */    public int getItemSize() {        return this.itemSize;    }    /**     * @return 中间item的起始坐标x(不考虑偏移), 当垂直滚动时,x = centerPosition*itemWidth     */    public int getCenterX() {        return this.centerX;    }    /**     * @return 中间item的起始坐标y(不考虑偏移), 当垂直滚动时,y= centerPosition*itemHeight     */    public int getCenterY() {        return this.centerY;    }    /**     * @return 当垂直滚动时,centerPoint = centerY;水平滚动时,centerPoint = centerX     */    public int getCenterPoint() {        return this.centerPoint;    }    public boolean isDisallowTouch() {        return this.disallowTouch;    }    /**     * 设置是否允许手动触摸滚动     *     * @param disallowTouch     */    public void setDisallowTouch(boolean disallowTouch) {        this.disallowTouch = disallowTouch;        return;    }    /**     * 中间item的位置,0 <= centerPosition <= visibleItemCount     *     * @param centerPosition     */    public void setCenterPosition(int centerPosition) {        if (centerPosition < 0) {            this.centerPosition = 0;        } else if (centerPosition >= this.visibleItemCount) {            this.centerPosition = this.visibleItemCount - 1;        } else {            this.centerPosition = centerPosition;        }        this.centerY = this.centerPosition * this.itemHeight;        this.invalidate();        return;    }    /**     * 中间item的位置,默认为 visibleItemCount / 2     *     * @return     */    public int getCenterPosition() {        return this.centerPosition;    }    public void setCenterItemBackground(Drawable centerItemBackground) {        this.centerItemBackground = centerItemBackground;        this.centerItemBackground.setBounds(this.centerX, this.centerY, this.centerX + this.itemWidth, this.centerY + this.itemHeight);        this.invalidate();        return;    }    public void setCenterItemBackground(int centerItemBackgroundColor) {        this.centerItemBackground = new ColorDrawable(centerItemBackgroundColor);        this.centerItemBackground.setBounds(this.centerX, this.centerY, this.centerX + this.itemWidth, this.centerY + this.itemHeight);        this.invalidate();        return;    }    public Drawable getCenterItemBackground() {        return this.centerItemBackground;    }    public boolean isScrolling() {        return this.isFling || this.isMovingCenter || this.isAutoScrolling;    }    public boolean isFling() {        return this.isFling;    }    public boolean isMovingCenter() {        return this.isMovingCenter;    }    public boolean isAutoScrolling() {        return this.isAutoScrolling;    }    public boolean isCanTap() {        return this.canTap;    }    /**     * 设置 单击切换选项或触发点击监听器     *     * @param canTap     */    public void setCanTap(boolean canTap) {        this.canTap = canTap;        return;    }    public boolean isHorizontal() {        return this.isHorizontal;    }    public boolean isVertical() {        return !this.isHorizontal;    }    public void setHorizontal(boolean horizontal) {        if (this.isHorizontal == horizontal) {            return;        }        this.isHorizontal = horizontal;        this.reset();        if (this.isHorizontal) {            this.itemSize = this.itemWidth;        } else {            this.itemSize = this.itemHeight;        }        this.invalidate();        return;    }    public void setVertical(boolean vertical) {        if (this.isHorizontal == !vertical) {            return;        }        this.isHorizontal = !vertical;        this.reset();        if (this.isHorizontal) {            this.itemSize = this.itemWidth;        } else {            this.itemSize = this.itemHeight;        }        this.invalidate();        return;    }    public boolean isDrawAllItem() {        return this.drawAllItem;    }    public void setDrawAllItem(boolean drawAllItem) {        this.drawAllItem = drawAllItem;        return;    }    /**     * @author huangziwei     */    public interface OnSelectedListener {        void onSelected(ScrollPickerView scrollPickerView, int position);    }    public int dip2px(float dipVlue) {        DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();        float sDensity = metrics.density;        return (int) (dipVlue * sDensity + 0.5F);    }    @Override    public void setVisibility(int visibility) {        super.setVisibility(visibility);        if (visibility == VISIBLE) {            this.moveToCenter();        }        return;    }    public void setDefaultValue(int defaultValue) {        this.defaultValue = defaultValue;        return;    }}

BitmapScrollPicker.class

/** * 图片滚动选择器 */public class BitmapScrollPicker extends ScrollPickerView {    // 图片绘制模式:填充    public final static int DRAW_MODE_FULL = 1;    // 图片绘制模式:居中    public final static int DRAW_MODE_CENTER = 2;    // 图片绘制模式:指定大小    public final static int DRAW_MODE_SPECIFIED_SIZE = 3;    private int measureWidth;    private int measureHeight;    private Rect rect1;    private Rect rect2;    private Rect specifiedSizeRect;    private Rect rectTemp;    private int drawMode = BitmapScrollPicker.DRAW_MODE_CENTER;    // item内容缩放倍数    private float minScale = 1;    private float maxScale = 1;    private int specifiedSizeWidth = -1;    private int specifiedSizeHeight = -1;    public BitmapScrollPicker(Context context) {        super(context);        this.initView(context);        return;    }    public BitmapScrollPicker(Context context, AttributeSet attrs) {        super(context, attrs);        this.initView(context);        return;    }    public BitmapScrollPicker(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        this.initView(context);        return;    }    public BitmapScrollPicker(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {        super(context, attrs, defStyleAttr, defStyleRes);        this.initView(context);        return;    }    /**     * 初始化视图     *     * @param context     */    private void initView(Context context) {        this.rect1 = new Rect();        this.rect2 = new Rect();        this.specifiedSizeRect = new Rect();        this.rectTemp = new Rect();        return;    }    @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);        this.measureWidth = this.getMeasuredWidth();        this.measureHeight = this.getMeasuredHeight();        // 当view的的大小确定后,选择器中item的某些位置也可确定。当水平滚动时,item的顶部和底部的坐标y可确定;当垂直滚动时,item的左边和右边的坐标x可确定        if (this.drawMode == BitmapScrollPicker.DRAW_MODE_FULL) { // 填充            if (isHorizontal()) {                this.rect2.top = 0;                this.rect2.bottom = this.measureHeight;            } else {                this.rect2.left = 0;                this.rect2.right = this.measureWidth;            }        } else if (this.drawMode == BitmapScrollPicker.DRAW_MODE_SPECIFIED_SIZE) { // 指定大小            if (this.specifiedSizeWidth == -1) {                this.specifiedSizeWidth = this.measureWidth;                this.specifiedSizeHeight = this.measureHeight;            }            this.setDrawModeSpecifiedSize(this.specifiedSizeWidth, this.specifiedSizeHeight);        } else { // 居中            int size;            if (this.isHorizontal()) {                size = Math.min(this.measureHeight, this.getItemWidth());            } else {                size = Math.min(this.measureWidth, this.getItemHeight());            }            if (this.isHorizontal()) {                this.rect2.top = this.measureHeight / 2 - size / 2;                this.rect2.bottom = this.measureHeight / 2 + size / 2;            } else {                this.rect2.left = this.measureWidth / 2 - size / 2;                this.rect2.right = this.measureWidth / 2 + size / 2;            }        }        return;    }    @Override    public void drawItem(Canvas canvas, List data, int position, int relative, float moveLength, float top) {        int itemSize = this.getItemSize();        Bitmap bitmap = data.get(position);        this.rect1.right = bitmap.getWidth();        this.rect1.bottom = bitmap.getHeight();        int span = 0;        // 根据不同的绘制模式,计算出item内容的最终绘制位置和大小        // 当水平滚动时,计算item的左边和右边的坐标x;当垂直滚动时,item的顶部和底部的坐标y        if (this.drawMode == BitmapScrollPicker.DRAW_MODE_FULL) { // 填充            span = 0;            if (this.isHorizontal()) {                this.rect2.left = (int) top + span;                this.rect2.right = (int) (top + itemSize - span);            } else {                this.rect2.top = (int) top + span;                this.rect2.bottom = (int) (top + itemSize - span);            }            this.rectTemp.set(this.rect2);            this.scale(this.rectTemp, relative, itemSize, moveLength);            canvas.drawBitmap(bitmap, this.rect1, this.rectTemp, null);        } else if (this.drawMode == BitmapScrollPicker.DRAW_MODE_SPECIFIED_SIZE) { // 指定大小            if (this.isHorizontal()) {                span = (itemSize - this.specifiedSizeWidth) / 2;                this.specifiedSizeRect.left = (int) top + span;                this.specifiedSizeRect.right = (int) top + span + this.specifiedSizeWidth;            } else {                span = (itemSize - this.specifiedSizeHeight) / 2;                this.specifiedSizeRect.top = (int) top + span;                this.specifiedSizeRect.bottom = (int) top + span + this.specifiedSizeHeight;            }            this.rectTemp.set(this.specifiedSizeRect);            this.scale(this.rectTemp, relative, itemSize, moveLength);            canvas.drawBitmap(bitmap, this.rect1, this.rectTemp, null);        } else { // 居中            if (this.isHorizontal()) {                float scale = this.rect2.height() * 1f / bitmap.getHeight();                span = (int) ((itemSize - bitmap.getWidth() * scale) / 2);            } else {                float scale = this.rect2.width() * 1f / bitmap.getWidth();                span = (int) ((itemSize - bitmap.getHeight() * scale) / 2);            }            if (this.isHorizontal()) {                this.rect2.left = (int) (top + span);                this.rect2.right = (int) (top + itemSize - span);            } else {                this.rect2.top = (int) (top + span);                this.rect2.bottom = (int) (top + itemSize - span);            }            this.rectTemp.set(this.rect2);            this.scale(this.rectTemp, relative, itemSize, moveLength);            canvas.drawBitmap(bitmap, this.rect1, this.rectTemp, null);        }        return;    }    // 缩放item内容    private void scale(Rect rect, int relative, int itemSize, float moveLength) {        if (this.minScale == 1 && this.maxScale == 1) {            return;        }        float spanWidth;        float spanHeight;        if (this.minScale == this.maxScale) {            spanWidth = (rect.width() - this.minScale * rect.width()) / 2;            spanHeight = (rect.height() - this.minScale * rect.height()) / 2;            rect.left += spanWidth;            rect.right -= spanWidth;            rect.top += spanHeight;            rect.bottom -= spanHeight;            return;        }        if (relative == -1 || relative == 1) { // 上一个或下一个            // 处理上一个item且向上滑动 或者 处理下一个item且向下滑动,            if ((relative == -1 && moveLength < 0) || (relative == 1 && moveLength > 0)) {                spanWidth = (rect.width() - this.minScale * rect.width()) / 2;                spanHeight = (rect.height() - this.minScale * rect.height()) / 2;            } else { // 计算渐变                float rate = Math.abs(moveLength) / itemSize;                spanWidth = (rect.width() - (this.minScale + (this.maxScale - this.minScale) * rate) * rect.width()) / 2;                spanHeight = (rect.height() - (this.minScale + (this.maxScale - this.minScale) * rate) * rect.height()) / 2;            }        } else if (relative == 0) { // 中间item            float rate = (itemSize - Math.abs(moveLength)) / itemSize;            spanWidth = (rect.width() - (this.minScale + (this.maxScale - this.minScale) * rate) * rect.width()) / 2;            spanHeight = (rect.height() - (this.minScale + (this.maxScale - this.minScale) * rate) * rect.height()) / 2;        } else {            spanWidth = (rect.width() - this.minScale * rect.width()) / 2;            spanHeight = (rect.height() - this.minScale * rect.height()) / 2;        }        rect.left += spanWidth;        rect.right -= spanWidth;        rect.top += spanHeight;        rect.bottom -= spanHeight;        return;    }    /**     * 图片绘制模式 ,默认为居中     *     * @param mode     */    public void setDrawMode(int mode) {        int size = 0;        if (this.isHorizontal()) {            size = Math.min(this.measureHeight, this.getItemWidth());        } else {            size = Math.min(this.measureWidth, this.getItemHeight());        }        this.drawMode = mode;        if (this.drawMode == BitmapScrollPicker.DRAW_MODE_FULL) {            if (this.isHorizontal()) {                this.rect2.top = 0;                this.rect2.bottom = this.measureHeight;            } else {                this.rect2.left = 0;                this.rect2.right = this.measureWidth;            }        } else if (this.drawMode == BitmapScrollPicker.DRAW_MODE_SPECIFIED_SIZE) {        } else {            if (this.isHorizontal()) {                this.rect2.top = this.measureHeight / 2 - size / 2;                this.rect2.bottom = this.measureHeight / 2 + size / 2;            } else {                this.rect2.left = this.measureWidth / 2 - size / 2;                this.rect2.right = this.measureWidth / 2 + size / 2;            }        }        this.invalidate();        return;    }    public void setDrawModeSpecifiedSize(int width, int height) {        if (this.isHorizontal()) {            this.specifiedSizeRect.top = (this.measureHeight - height) / 2;            this.specifiedSizeRect.bottom = (this.measureHeight - height) / 2 + height;        } else {            this.specifiedSizeRect.left = (this.measureWidth - width) / 2;            this.specifiedSizeRect.right = (this.measureWidth - width) / 2 + width;        }        this.specifiedSizeWidth = width;        this.specifiedSizeHeight = height;        this.invalidate();        return;    }}

NumberPickerView.class

/** * 数字滚动控件 */public class NumberPickerView extends LinearLayout implements ScrollPickerView.OnSelectedListener {    private Map bitmapScrollPickerMap;    private MediaPlayer dingMedia;    private boolean playing;    private OnScrollListener onScrollListener;    private int numberLenght;    private int scrollIndex;    private int itemWidth;    private int itemHeight;    private int number;    public interface OnScrollListener {        void onScrolled();    }    public NumberPickerView(Context context) {        super(context);        this.initView(context, null);        return;    }    public NumberPickerView(Context context, AttributeSet attrs) {        super(context, attrs);        this.initView(context, attrs);        return;    }    public NumberPickerView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        this.initView(context, attrs);        return;    }    public NumberPickerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {        super(context, attrs, defStyleAttr, defStyleRes);        this.initView(context, attrs);        return;    }    /**     * 初始化视图     *     * @param context     * @param attrs     */    private void initView(Context context, AttributeSet attrs) {        this.itemWidth = 70;        this.itemHeight = 95;        if (attrs != null) {            TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.number_scroll_view);            this.itemWidth = array.getDimensionPixelSize(R.styleable.number_scroll_view_itemWidth, itemWidth);            this.itemHeight = array.getDimensionPixelSize(R.styleable.number_scroll_view_itemHeight, itemHeight);            array.recycle();        }        this.setClickable(false);        this.setOrientation(LinearLayout.HORIZONTAL);        this.setBackgroundColor(Color.TRANSPARENT);        this.playing = false;        CopyOnWriteArrayList bitmaps = new CopyOnWriteArrayList();        bitmaps.add(BitmapFactory.decodeResource(this.getContext().getResources(), R.mipmap.num_0));        bitmaps.add(BitmapFactory.decodeResource(this.getContext().getResources(), R.mipmap.num_1));        bitmaps.add(BitmapFactory.decodeResource(this.getContext().getResources(), R.mipmap.num_2));        bitmaps.add(BitmapFactory.decodeResource(this.getContext().getResources(), R.mipmap.num_3));        bitmaps.add(BitmapFactory.decodeResource(this.getContext().getResources(), R.mipmap.num_4));        bitmaps.add(BitmapFactory.decodeResource(this.getContext().getResources(), R.mipmap.num_5));        bitmaps.add(BitmapFactory.decodeResource(this.getContext().getResources(), R.mipmap.num_6));        bitmaps.add(BitmapFactory.decodeResource(this.getContext().getResources(), R.mipmap.num_7));        bitmaps.add(BitmapFactory.decodeResource(this.getContext().getResources(), R.mipmap.num_8));        bitmaps.add(BitmapFactory.decodeResource(this.getContext().getResources(), R.mipmap.num_9));        if (this.bitmapScrollPickerMap != null) {            this.bitmapScrollPickerMap.clear();        }        this.bitmapScrollPickerMap = new LinkedHashMap();        int margin = 2;        int total = 5;  // 最大数值99999        LayoutParams params = new LayoutParams(this.itemWidth, this.itemHeight);        params.rightMargin = margin;        params.leftMargin = margin;        for (int i = 0; i < total; i++) {            BitmapScrollPicker bitmapScrollPicker = new BitmapScrollPicker(this.getContext(), null);            bitmapScrollPicker.setVisibleItemCount(1);            bitmapScrollPicker.setDrawMode(BitmapScrollPicker.DRAW_MODE_SPECIFIED_SIZE);            bitmapScrollPicker.setData(bitmaps);            bitmapScrollPicker.setSelectedPosition(0);            bitmapScrollPicker.setDisallowTouch(true);            bitmapScrollPicker.setDrawModeSpecifiedSize(params.width, params.height);            this.bitmapScrollPickerMap.put(i, bitmapScrollPicker);            this.addView(bitmapScrollPicker, params);        }        return;    }    @Override    public void onSelected(ScrollPickerView scrollPickerView, int position) {        if (scrollPickerView != null && this.playing) {            this.scrollIndex++;            if (this.dingMedia != null) {                // this.dingMedia.start();            }            if (this.scrollIndex == this.numberLenght) {                if (this.onScrollListener != null) {                    this.onScrollListener.onScrolled();                }                this.playing = false;            }        }        return;    }    /**     * 设置数字     *     * @param number     */    public void setNumber(int number) {        this.number = number;        int length = String.valueOf(number).length();        if (length > 4) {            double f = 1;//            if (length == 6) {//                f = 0.75;//            } else if (length == 7) {//                f = 0.65;//            }            for (Map.Entry entry : this.bitmapScrollPickerMap.entrySet()) {                BitmapScrollPicker bitmapScrollPicker = entry.getValue();                LayoutParams params = (LayoutParams) bitmapScrollPicker.getLayoutParams();                params.width = (int) (this.itemWidth * f);                params.height = (int) (this.itemHeight * f);                bitmapScrollPicker.setLayoutParams(params);                bitmapScrollPicker.setDrawModeSpecifiedSize(params.width, params.height);                bitmapScrollPicker.setVisibility(GONE);            }        }        int total = this.bitmapScrollPickerMap.size();        int start = total - length;        for (Map.Entry entry : this.bitmapScrollPickerMap.entrySet()) {            BitmapScrollPicker bitmapScrollPicker = entry.getValue();            int key = entry.getKey();            if (key >= start) {                bitmapScrollPicker.setOnSelectedListener(this);                bitmapScrollPicker.setVisibility(View.VISIBLE);            } else {                bitmapScrollPicker.setOnSelectedListener(null);                bitmapScrollPicker.setVisibility(GONE);            }        }        return;    }    /**     * 开始滚动数字     *     * @param onScrollListener     */    public void startNumber(OnScrollListener onScrollListener) {        if (this.number < 0 || this.number > 9999999) {            if (this.onScrollListener != null) {                this.onScrollListener.onScrolled();            }            return;        }        if (this.playing) {            return;        }        this.playing = true;        char[] numbers = String.valueOf(number).toCharArray();        this.numberLenght = numbers.length;        this.onScrollListener = onScrollListener;        this.scrollIndex = 0;        int duration = 2000;        int add = 300;        int total = this.bitmapScrollPickerMap.size();        int start = total - numbers.length;        for (Map.Entry entry : this.bitmapScrollPickerMap.entrySet()) {            BitmapScrollPicker bitmapScrollPicker = entry.getValue();            int key = entry.getKey();            if (key >= start) {                int num = Integer.parseInt(String.valueOf(numbers[key - start]));                bitmapScrollPicker.setDefaultValue(num);                bitmapScrollPicker.autoScrollFast(num, duration + (total - key - start + 1) * add);            }        }        return;    }    public void stop() {        if (this.dingMedia != null) {            this.dingMedia.stop();        }        return;    }

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>                    

button.xml

<?xml version="1.0" encoding="utf-8"?>                                                                                                                                                                        

Demo下载地址

更多相关文章

  1. 【代码】android通过criteria选择合适的地理位置服务
  2. Android(安卓)自定义控件 GuideView 引导界面
  3. android MediaPlayer 简易播放器的实现 及类似斗地主音频池实现
  4. Android(安卓)多点触摸
  5. android 自定义View设置自定义监听 框架(监听自定义字符)
  6. android 记录和恢复ListView滚动的位置 四种方法
  7. Android界面的架构图
  8. android之数组排序
  9. android 广告栏 viewpager

随机推荐

  1. Android(安卓)Webservice 开发总结
  2. PHP学习之初:基本语法
  3. Android(安卓)画顶部带锯齿的长方形
  4. Android线程安全
  5. Android内核源码Abi目录学习笔记
  6. Android(安卓)Mms专题——接收信息流程
  7. Android(安卓)Wi-Fi Wi-Fi Protected Set
  8. Android:dagger2让你爱不释手-终结篇
  9. Android录屏单帧获取方案
  10. 让你自己编写的Android的Launcher成为系