Android滚动显示数字动画
2020-02-17 04:30
435 查看
项目中需要做一个数字滚动的特效,完成后特意记录下来,方便他人。。。
先上效果图:
以下为主要实行代码:
ScrollPickerView.class
/** * 滚动选择器,带惯性滑动 */ public abstract class ScrollPickerView<T> extends View { private int visibleItemCount = 3; // 可见的item数 4000 量 private boolean isInertiaScroll = true; // 快速滑动时是否惯性滚动一段距离,默认开启 private boolean isCirculation = true; // 是否循环滚动,默认开启 // 不允许父组件拦截触摸事件,设置为true为不允许拦截,此时该设置才生效 // 当嵌入到ScrollView等滚动组件中,为了使该自定义滚动选择器可以正常工作,请设置为true private boolean disallowInterceptTouch = false; private int selected; // 当前选中的item下标 private List<T> 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<T> 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); } 24000 } } 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<T> getData() { return this.data; } public void setData(List<T> data) { if (data == null) { this.data = new ArrayList<T>(); } 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<Bitmap> { // 图片绘制模式:填充 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<Bitmap> 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<Integer, BitmapScrollPicker> 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<Bitmap> bitmaps = new CopyOnWriteArrayList<Bitmap>(); 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<Integer, BitmapScrollPicker>(); 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<Integer, BitmapScrollPicker> 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<Integer, BitmapScrollPicker> 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<Integer, BitmapScrollPicker> 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"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/white" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerInParent="true" android:gravity="center" android:orientation="vertical"> <com.example.demo.control.NumberPickerView android:id="@+id/number_picker_view" android:layout_width="wrap_content" android:layout_height="wrap_content" app:itemHeight="161dp" app:itemWidth="119dp"/> <Button android:id="@+id/button" android:layout_width="150dp" android:layout_height="50dp" android:layout_marginTop="50dp" android:background="@drawable/button" android:stateListAnimator="@null" android:text="开启滚动" android:textColor="#FFFFFFFF" android:textSize="20dp"/> </LinearLayout> </RelativeLayout>
button.xml
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_enabled="true" android:state_pressed="true"> <shape android:shape="rectangle"> <solid android:color="#FFB9860C"/> <gradient android:angle="135" android:centerColor="#FFD5A01A" android:centerX="0.5" android:centerY="0.5" android:endColor="#FFE7BD2F" android:startColor="#FFB9860C"/> <corners android:radius="100dp"/> </shape> </item> <item android:state_enabled="false"> <shape android:shape="rectangle"> <solid android:color="#FFCCCCCC"/> <corners android:radius="100dp"/> </shape> </item> <item> <shape android:shape="rectangle"> <solid android:color="#FFBC8B15"/> <gradient android:angle="135" android:centerColor="#FFD7A62A" android:centerX="0.5" android:centerY="0.5" android:endColor="#FFF3D46E" android:startColor="#FFBC8B15"/> <corners android:radius="100dp"/> </shape> </item> </selector>
- 点赞
- 收藏
- 分享
- 文章举报
相关文章推荐
- 支付密码弹出框 Android
- Android 沉浸式状态栏(透明)适配
- Installing Android Studio
- Android Permission
- Android尺寸,DP,PX
- 记录编译Android7.1源码jack ERROR
- [rk3288][android-5.1]通过prop属性设定camera orientation
- [rk3288][android-5.1]打印机支持 (dev/usb/lp*)
- android 读取excel POI JXL
- Android百度地图开发(三)范围搜索
- Android百度地图开发(三)范围搜索
- android开发--推箱子小游戏(一)
- android开发--推箱子小游戏(二)
- Android删除Alarm的方法
- Android多线程编程 之 Handler机制的简单理解
- Android调用JNI本地方法跟踪目标代码
- Android gdb so
- Android添加系统级顶层窗口 和 WindowManager添加view的动画问题
- AndroidX86模拟器Genymotion的一些使用和另一款Andy模拟器
- android socket file server