自定义时间选择器

自定义时间选择器




第一章 效果演示

第01节 效果图




第02节 主要文件

1、来自于 Java 的类文件
	A. 核心文件
		src/java/view.wheel.WheelPicker.java
	B. 实体类和接口
		src/java/view.wheel.WheelBean.java
		src/java/view.wheel.IWheelPicker.java
	C. 原子时间类
		src/java/view.wheel.atom.time.DateTimePickerView.java
		src/java/view.wheel.atom.time.PickerDay.java
		src/java/view.wheel.atom.time.PickerHour.java
		src/java/view.wheel.atom.time.PickerMinute.java
		src/java/view.wheel.atom.time.PickerMonth.java
		src/java/view.wheel.atom.time.PickerSecond.java
		src/java/view.wheel.atom.time.PickerYear.java
	D. 测试Activity
		src/java/DemoActivity.java
		
2、来自于资源文件的类
	A. 布局文件
		res/layout/view_picker_time_date.xml
		res/layout/activity_demo.xml
	B. 属性文件
		res/values/attrs.xml




第二章 案例代码

第01节 核心文件 WheelPicker

位置

src/java/view.wheel.WheelPicker.java

代码

package view.wheel;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Camera;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.graphics.Region.Op;
import android.graphics.Typeface;
import android.os.Build.VERSION;
import android.os.Handler;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.Scroller;

import androidx.annotation.NonNull;

import java.util.ArrayList;
import java.util.List;

public class WheelPicker extends View implements IWheelPicker, Runnable {
    private final Handler mHandler;
    private Paint mPaint;
    private Scroller mScroller;
    private VelocityTracker mTracker;
    private WheelPicker.OnItemSelectedListener mOnItemSelectedListener;
    private WheelPicker.OnWheelChangeListener mOnWheelChangeListener;
    private Rect mRectDrawn;
    private Rect mRectIndicatorHead;
    private Rect mRectIndicatorFoot;
    private Rect mRectCurrentItem;
    private Camera mCamera;
    private Matrix mMatrixRotate;
    private Matrix mMatrixDepth;
    private List<WheelBean> mData;
    private String mMaxWidthText;
    protected String label;
    private int mVisibleItemCount;
    private int mDrawnItemCount;
    private int mHalfDrawnItemCount;
    private int mTextMaxWidth;
    private int mTextMaxHeight;
    private int mItemTextColor;
    private int mSelectedItemTextColor;
    private int mItemTextSize;
    private int mIndicatorSize;
    private int mIndicatorColor;
    private int mCurtainColor;
    private int mItemSpace;
    private int mItemAlign;
    private int mItemHeight;
    private int mHalfItemHeight;
    private int mHalfWheelHeight;
    private int mSelectedItemPosition;
    private int mCurrentItemPosition;
    private int mMinFlingY;
    private int mMaxFlingY;
    private int mMinimumVelocity;
    private int mMaximumVelocity;
    private int mWheelCenterX;
    private int mWheelCenterY;
    private int mDrawnCenterX;
    private int mDrawnCenterY;
    private int mScrollOffsetY;
    private int mTextMaxWidthPosition;
    private int mLastPointY;
    private int mDownPointY;
    private int mTouchSlop;
    private boolean hasSameWidth;
    private boolean hasIndicator;
    private boolean hasCurtain;
    private boolean hasAtmospheric;
    private boolean isCyclic;
    private boolean isCurved;
    private boolean isClick;
    private boolean isForceFinishScroll;
    private int centerRectColor;    // 中间矩形的颜色
    private int centerLineColor;    // 中间线的颜色
    private int centerLineWidth;    // 中间线的粗细
    private boolean centerLineIsShow;  // 中间线是否展示
    private boolean centerRectIsShow;  // 中间线是否展示

    public WheelPicker(Context context) {
        this(context, null);
    }

    public WheelPicker(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.mHandler = new Handler();
        this.mMinimumVelocity = 50;
        this.mMaximumVelocity = 8000;
        this.mTouchSlop = 8;
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.WheelPicker);
        this.mData = new ArrayList<>();
        this.mItemTextSize = typedArray.getDimensionPixelSize(R.styleable.WheelPicker_wheel_item_text_size, dp2px(context, 18));
        this.mVisibleItemCount = typedArray.getInt(R.styleable.WheelPicker_wheel_visible_item_count, 7);
        this.mSelectedItemPosition = typedArray.getInt(R.styleable.WheelPicker_wheel_selected_item_position, 0);
        this.hasSameWidth = typedArray.getBoolean(R.styleable.WheelPicker_wheel_same_width, false);
        this.mTextMaxWidthPosition = typedArray.getInt(R.styleable.WheelPicker_wheel_maximum_width_text_position, -1);
        this.mMaxWidthText = typedArray.getString(R.styleable.WheelPicker_wheel_maximum_width_text);
        this.label = typedArray.getString(R.styleable.WheelPicker_wheel_label);
        this.mSelectedItemTextColor = typedArray.getColor(R.styleable.WheelPicker_wheel_selected_item_text_color, -1);
        this.mItemTextColor = typedArray.getColor(R.styleable.WheelPicker_wheel_item_text_color, -7829368);
        this.mItemSpace = typedArray.getDimensionPixelSize(R.styleable.WheelPicker_wheel_item_space, dp2px(context, 12));
        this.isCyclic = typedArray.getBoolean(R.styleable.WheelPicker_wheel_cyclic, false);
        this.hasIndicator = typedArray.getBoolean(R.styleable.WheelPicker_wheel_indicator, false);
        this.mIndicatorColor = typedArray.getColor(R.styleable.WheelPicker_wheel_indicator_color, -1166541);
        this.mIndicatorSize = typedArray.getDimensionPixelSize(R.styleable.WheelPicker_wheel_indicator_size, dp2px(context, 2));
        this.hasCurtain = typedArray.getBoolean(R.styleable.WheelPicker_wheel_curtain, false);
        this.mCurtainColor = typedArray.getColor(R.styleable.WheelPicker_wheel_curtain_color, -1996488705);
        this.hasAtmospheric = typedArray.getBoolean(R.styleable.WheelPicker_wheel_atmospheric, false);
        this.isCurved = typedArray.getBoolean(R.styleable.WheelPicker_wheel_curved, false);
        this.mItemAlign = typedArray.getInt(R.styleable.WheelPicker_wheel_item_align, 0);
        typedArray.recycle();
        this.updateVisibleItemCount();
        this.mPaint = new Paint(69);
        this.mPaint.setTextSize((float) this.mItemTextSize);
        this.updateItemTextAlign();
        this.computeTextSize();
        this.mScroller = new Scroller(this.getContext());
        if (VERSION.SDK_INT >= 4) {
            ViewConfiguration conf = ViewConfiguration.get(this.getContext());
            this.mMinimumVelocity = conf.getScaledMinimumFlingVelocity();
            this.mMaximumVelocity = conf.getScaledMaximumFlingVelocity();
            this.mTouchSlop = conf.getScaledTouchSlop();
        }
        if (TextUtils.isEmpty(label)) {
            label = "";
        }

        this.mRectDrawn = new Rect();
        this.mRectIndicatorHead = new Rect();
        this.mRectIndicatorFoot = new Rect();
        this.mRectCurrentItem = new Rect();
        this.mCamera = new Camera();
        this.mMatrixRotate = new Matrix();
        this.mMatrixDepth = new Matrix();
    }

    private void updateVisibleItemCount() {
        if (this.mVisibleItemCount < 2) {
            throw new ArithmeticException("Wheel's visible item count can not be less than 2!");
        } else {
            if (this.mVisibleItemCount % 2 == 0) {
                this.mVisibleItemCount++;
            }
            this.mDrawnItemCount = this.mVisibleItemCount + 2;
            this.mHalfDrawnItemCount = this.mDrawnItemCount / 2;
        }
    }

    private void computeTextSize() {
        this.mTextMaxWidth = this.mTextMaxHeight = 0;
        if (this.hasSameWidth) {
            this.mTextMaxWidth = (int) this.mPaint.measureText(String.valueOf(this.mData.get(0)));
        } else if (this.isPosInRang(this.mTextMaxWidthPosition)) {
            this.mTextMaxWidth = (int) this.mPaint.measureText(String.valueOf(this.mData.get(this.mTextMaxWidthPosition)));
        } else {
            if (!TextUtils.isEmpty(this.mMaxWidthText)) {
                this.mTextMaxWidth = (int) this.mPaint.measureText(this.mMaxWidthText);
            } else {
                for (WheelBean bean : this.mData) {
                    String text = String.valueOf(bean);
                    this.mTextMaxWidth = (int) Math.max(mTextMaxWidth, mPaint.measureText(text));
                }
            }
        }

        Paint.FontMetrics metrics = this.mPaint.getFontMetrics();
        this.mTextMaxHeight = (int) (metrics.bottom - metrics.top);
    }

    private void updateItemTextAlign() {
        switch (this.mItemAlign) {
            case 1:
                this.mPaint.setTextAlign(Align.LEFT);
                break;
            case 2:
                this.mPaint.setTextAlign(Align.RIGHT);
                break;
            default:
                this.mPaint.setTextAlign(Align.CENTER);
        }
    }

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
        int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
        int resultWidth = this.mTextMaxWidth;
        int resultHeight = this.mTextMaxHeight * this.mVisibleItemCount + this.mItemSpace * (this.mVisibleItemCount - 1);
        if (this.isCurved) {
            resultHeight = (int) ((double) (2 * resultHeight) / Math.PI);
        }

        resultWidth += this.getPaddingLeft() + this.getPaddingRight();
        resultHeight += this.getPaddingTop() + this.getPaddingBottom();

        resultWidth = this.measureSize(modeWidth, sizeWidth, resultWidth);
        resultHeight = this.measureSize(modeHeight, sizeHeight, resultHeight);
        this.setMeasuredDimension(resultWidth, resultHeight);
    }

    private int measureSize(int mode, int sizeExpect, int sizeActual) {
        int realSize;
        if (mode == 1073741824) {
            realSize = sizeExpect;
        } else {
            realSize = sizeActual;
            if (mode == Integer.MIN_VALUE) {
                realSize = Math.min(sizeActual, sizeExpect);
            }
        }

        return realSize;
    }

    protected void onSizeChanged(int w, int h, int oldW, int oldH) {
        this.mRectDrawn.set(this.getPaddingLeft(), this.getPaddingTop(), this.getWidth() - this.getPaddingRight(), this.getHeight() - this.getPaddingBottom());


        this.mWheelCenterX = this.mRectDrawn.centerX();
        this.mWheelCenterY = this.mRectDrawn.centerY();
        this.computeDrawnCenter();
        this.mHalfWheelHeight = this.mRectDrawn.height() / 2;
        this.mItemHeight = this.mRectDrawn.height() / this.mVisibleItemCount;
        this.mHalfItemHeight = this.mItemHeight / 2;
        this.computeFlingLimitY();
        this.computeIndicatorRect();
        this.computeCurrentItemRect();
    }

    private void computeDrawnCenter() {
        switch (this.mItemAlign) {
            case 1:
                this.mDrawnCenterX = this.mRectDrawn.left;
                break;
            case 2:
                this.mDrawnCenterX = this.mRectDrawn.right;
                break;
            default:
                this.mDrawnCenterX = this.mWheelCenterX;
        }

        this.mDrawnCenterY = (int) ((float) this.mWheelCenterY - (this.mPaint.ascent() + this.mPaint.descent()) / 2.0F);
    }

    private void computeFlingLimitY() {
        int currentItemOffset = this.mSelectedItemPosition * this.mItemHeight;
        this.mMinFlingY = this.isCyclic ? Integer.MIN_VALUE : -this.mItemHeight * (this.mData.size() - 1) + currentItemOffset;
        this.mMaxFlingY = this.isCyclic ? Integer.MAX_VALUE : currentItemOffset;
    }

    private void computeIndicatorRect() {
        if (this.hasIndicator) {
            int halfIndicatorSize = this.mIndicatorSize / 2;
            int indicatorHeadCenterY = this.mWheelCenterY + this.mHalfItemHeight;
            int indicatorFootCenterY = this.mWheelCenterY - this.mHalfItemHeight;
            this.mRectIndicatorHead.set(this.mRectDrawn.left, indicatorHeadCenterY - halfIndicatorSize, this.mRectDrawn.right, indicatorHeadCenterY + halfIndicatorSize);
            this.mRectIndicatorFoot.set(this.mRectDrawn.left, indicatorFootCenterY - halfIndicatorSize, this.mRectDrawn.right, indicatorFootCenterY + halfIndicatorSize);
        }
    }

    private void computeCurrentItemRect() {
        if (this.hasCurtain || this.mSelectedItemTextColor != -1) {
            this.mRectCurrentItem.set(this.mRectDrawn.left, this.mWheelCenterY - this.mHalfItemHeight, this.mRectDrawn.right, this.mWheelCenterY + this.mHalfItemHeight);
        }
    }

    protected void onDraw(@NonNull Canvas canvas) {
        if (null != this.mOnWheelChangeListener) {
            this.mOnWheelChangeListener.onWheelScrolled(this.mScrollOffsetY);
        }

        int drawnDataStartPos = -this.mScrollOffsetY / this.mItemHeight - this.mHalfDrawnItemCount;
        int drawnDataPos = drawnDataStartPos + this.mSelectedItemPosition;

        for (int drawnOffsetPos = -this.mHalfDrawnItemCount; drawnDataPos < drawnDataStartPos + this.mSelectedItemPosition + this.mDrawnItemCount; ++drawnOffsetPos) {

            // 绘制中间的矩形
            drawCenterRect(canvas, drawnOffsetPos);

            String data = "";
            int mDrawnItemCenterY;
            if (this.isCyclic) {
                mDrawnItemCenterY = drawnDataPos % this.mData.size();
                mDrawnItemCenterY = mDrawnItemCenterY < 0 ? mDrawnItemCenterY + this.mData.size() : mDrawnItemCenterY;
                data = String.valueOf(this.mData.get(mDrawnItemCenterY));
            } else if (this.isPosInRang(drawnDataPos)) {
                data = String.valueOf(this.mData.get(drawnDataPos));
            }

            this.mPaint.setColor(this.mItemTextColor);
            this.mPaint.setStyle(Style.FILL);
            mDrawnItemCenterY = this.mDrawnCenterY + drawnOffsetPos * this.mItemHeight + this.mScrollOffsetY % this.mItemHeight;
            int distanceToCenter = 0;
            int lineCenterY;
            if (this.isCurved) {
                float ratio = (float) (this.mDrawnCenterY - Math.abs(this.mDrawnCenterY - mDrawnItemCenterY) - this.mRectDrawn.top) * 1.0F / (float) (this.mDrawnCenterY - this.mRectDrawn.top);
                lineCenterY = 0;
                if (mDrawnItemCenterY > this.mDrawnCenterY) {
                    lineCenterY = 1;
                } else if (mDrawnItemCenterY < this.mDrawnCenterY) {
                    lineCenterY = -1;
                }

                float degree = -(1.0F - ratio) * 90.0F * (float) lineCenterY;
                if (degree < -90.0F) {
                    degree = -90.0F;
                }

                if (degree > 90.0F) {
                    degree = 90.0F;
                }

                distanceToCenter = this.computeSpace((int) degree);
                int transX = this.mWheelCenterX;
                switch (this.mItemAlign) {
                    case 1:
                        transX = this.mRectDrawn.left;
                        break;
                    case 2:
                        transX = this.mRectDrawn.right;
                }

                int transY = this.mWheelCenterY - distanceToCenter;
                this.mCamera.save();
                this.mCamera.rotateX(degree);
                this.mCamera.getMatrix(this.mMatrixRotate);
                this.mCamera.restore();
                this.mMatrixRotate.preTranslate((float) (-transX), (float) (-transY));
                this.mMatrixRotate.postTranslate((float) transX, (float) transY);
                this.mCamera.save();
                this.mCamera.translate(0.0F, 0.0F, (float) this.computeDepth((int) degree));
                this.mCamera.getMatrix(this.mMatrixDepth);
                this.mCamera.restore();
                this.mMatrixDepth.preTranslate((float) (-transX), (float) (-transY));
                this.mMatrixDepth.postTranslate((float) transX, (float) transY);
                this.mMatrixRotate.postConcat(this.mMatrixDepth);
            }

            int drawnCenterY;
            if (this.hasAtmospheric) {
                drawnCenterY = (int) ((float) (this.mDrawnCenterY - Math.abs(this.mDrawnCenterY - mDrawnItemCenterY)) * 1.0F / (float) this.mDrawnCenterY * 255.0F);
                drawnCenterY = Math.max(drawnCenterY, 0);
                this.mPaint.setAlpha(drawnCenterY);
            }

            drawnCenterY = this.isCurved ? this.mDrawnCenterY - distanceToCenter : mDrawnItemCenterY;
            if (this.mSelectedItemTextColor != -1) {
                canvas.save();
                if (this.isCurved) {
                    canvas.concat(this.mMatrixRotate);
                }

                canvas.clipRect(this.mRectCurrentItem, Op.DIFFERENCE);
                canvas.drawText(data, (float) this.mDrawnCenterX, (float) drawnCenterY, this.mPaint);
                canvas.restore();
                this.mPaint.setColor(this.mSelectedItemTextColor);
                canvas.save();
                if (this.isCurved) {
                    canvas.concat(this.mMatrixRotate);
                }

                canvas.clipRect(this.mRectCurrentItem);
                canvas.drawText(data, (float) this.mDrawnCenterX, (float) drawnCenterY, this.mPaint);
                canvas.restore();
            } else {
                canvas.save();
                canvas.clipRect(this.mRectDrawn);
                if (this.isCurved) {
                    canvas.concat(this.mMatrixRotate);
                }

                canvas.drawText(data, (float) this.mDrawnCenterX, (float) drawnCenterY, this.mPaint);
                canvas.restore();
            }
            // 绘制中间线
            drawCenterLine(canvas, drawnOffsetPos);
            drawnDataPos++;
        }

        if (this.hasCurtain) {
            this.mPaint.setColor(this.mCurtainColor);
            this.mPaint.setStyle(Style.FILL);
            canvas.drawRect(this.mRectCurrentItem, this.mPaint);
        }

        if (this.hasIndicator) {
            this.mPaint.setColor(this.mIndicatorColor);
            this.mPaint.setStyle(Style.FILL);
            canvas.drawRect(this.mRectIndicatorHead, this.mPaint);
            canvas.drawRect(this.mRectIndicatorFoot, this.mPaint);
        }
    }

    // 绘制中间选择线
    private void drawCenterRect(Canvas canvas, int drawnOffsetPos) {
        if (!centerRectIsShow) {
            return;
        }
        if (drawnOffsetPos == 0) {
            canvas.save();
            canvas.clipRect(this.mRectDrawn);
            this.mPaint.setColor(centerRectColor);
            this.mPaint.setStyle(Style.FILL);

            this.mPaint.setStrokeWidth(centerLineWidth);
            float startY = mWheelCenterY - this.mHalfItemHeight;
            float endY = mWheelCenterY - this.mHalfItemHeight + this.mItemHeight;
            canvas.drawRect((float) this.mRectDrawn.left, startY, (float) this.mRectDrawn.right, endY, this.mPaint);
            canvas.restore();
        }
    }


    // 绘制中间选择线
    private void drawCenterLine(Canvas canvas, int drawnOffsetPos) {
        if (!centerLineIsShow) {
            return;
        }
        if (drawnOffsetPos == 0) {
            canvas.save();
            canvas.clipRect(this.mRectDrawn);
            this.mPaint.setColor(centerLineColor);
            this.mPaint.setStyle(Style.STROKE);
            this.mPaint.setStrokeWidth(centerLineWidth);
            float startY = mWheelCenterY - this.mHalfItemHeight;
            float endY = mWheelCenterY - this.mHalfItemHeight + this.mItemHeight;
            canvas.drawLine((float) this.mRectDrawn.left, startY, (float) this.mRectDrawn.right, startY, this.mPaint);
            canvas.drawLine((float) this.mRectDrawn.left, endY, (float) this.mRectDrawn.right, endY, this.mPaint);
            canvas.restore();
        }
    }

    private boolean isPosInRang(int position) {
        return position >= 0 && position < this.mData.size();
    }

    private int computeSpace(int degree) {
        return (int) (Math.sin(Math.toRadians(degree)) * (double) this.mHalfWheelHeight);
    }

    private int computeDepth(int degree) {
        return (int) ((double) this.mHalfWheelHeight - Math.cos(Math.toRadians((double) degree)) * (double) this.mHalfWheelHeight);
    }

    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (null != this.getParent()) {
                    this.getParent().requestDisallowInterceptTouchEvent(true);
                }

                if (null == this.mTracker) {
                    this.mTracker = VelocityTracker.obtain();
                } else {
                    this.mTracker.clear();
                }

                this.mTracker.addMovement(event);
                if (!this.mScroller.isFinished()) {
                    this.mScroller.abortAnimation();
                    this.isForceFinishScroll = true;
                }

                this.mDownPointY = this.mLastPointY = (int) event.getY();
                break;
            case MotionEvent.ACTION_UP:
                if (null != this.getParent()) {
                    this.getParent().requestDisallowInterceptTouchEvent(false);
                }

                if (!this.isClick) {
                    this.mTracker.addMovement(event);
                    if (VERSION.SDK_INT >= 4) {
                        this.mTracker.computeCurrentVelocity(1000, (float) this.mMaximumVelocity);
                    } else {
                        this.mTracker.computeCurrentVelocity(1000);
                    }

                    this.isForceFinishScroll = false;
                    int velocity = (int) this.mTracker.getYVelocity();
                    if (Math.abs(velocity) > this.mMinimumVelocity) {
                        this.mScroller.fling(0, this.mScrollOffsetY, 0, velocity, 0, 0, this.mMinFlingY, this.mMaxFlingY);
                        this.mScroller.setFinalY(this.mScroller.getFinalY() + this.computeDistanceToEndPoint(this.mScroller.getFinalY() % this.mItemHeight));
                    } else {
                        this.mScroller.startScroll(0, this.mScrollOffsetY, 0, this.computeDistanceToEndPoint(this.mScrollOffsetY % this.mItemHeight));
                    }

                    if (!this.isCyclic) {
                        if (this.mScroller.getFinalY() > this.mMaxFlingY) {
                            this.mScroller.setFinalY(this.mMaxFlingY);
                        } else if (this.mScroller.getFinalY() < this.mMinFlingY) {
                            this.mScroller.setFinalY(this.mMinFlingY);
                        }
                    }

                    this.mHandler.post(this);
                    if (null != this.mTracker) {
                        this.mTracker.recycle();
                        this.mTracker = null;
                    }
                }
                // 这里是否需要移动一段距离, 距离修正
                float moveFixUp = event.getY() - (float) this.mLastPointY;
                if (!(Math.abs(moveFixUp) < 1.0F)) {
                    this.mScrollOffsetY = (int) ((float) this.mScrollOffsetY + moveFixUp);
                    this.mLastPointY = (int) event.getY();
                    this.invalidate();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (Math.abs((float) this.mDownPointY - event.getY()) < (float) this.mTouchSlop) {
                    this.isClick = true;
                } else {
                    this.isClick = false;
                    this.mTracker.addMovement(event);
                    if (null != this.mOnWheelChangeListener) {
                        this.mOnWheelChangeListener.onWheelScrollStateChanged(1);
                    }

                    // 这里是否需要移动一段距离, 距离修正
                    float moveFixMove = event.getY() - (float) this.mLastPointY;
                    if (!(Math.abs(moveFixMove) < 1.0F)) {
                        this.mScrollOffsetY = (int) ((float) this.mScrollOffsetY + moveFixMove);
                        this.mLastPointY = (int) event.getY();
                        this.invalidate();
                    }
                }
                break;
            case MotionEvent.ACTION_CANCEL:
                if (null != this.getParent()) {
                    this.getParent().requestDisallowInterceptTouchEvent(false);
                }

                if (null != this.mTracker) {
                    this.mTracker.recycle();
                    this.mTracker = null;
                }
        }

        return true;
    }

    public void setCenterLineColor(int centerLineColor) {
        this.centerLineColor = centerLineColor;
    }

    public void setCenterRectColor(int centerRectColor) {
        this.centerRectColor = centerRectColor;
    }

    public void setCenterLineWidth(int centerLineWidth) {
        this.centerLineWidth = centerLineWidth;
    }

    public void setCenterLineIsShow(boolean centerLineIsShow) {
        this.centerLineIsShow = centerLineIsShow;
    }

    public void setCenterRectIsShow(boolean centerRectIsShow) {
        this.centerRectIsShow = centerRectIsShow;
    }

    private int computeDistanceToEndPoint(int remainder) {
        if (Math.abs(remainder) > this.mHalfItemHeight) {
            return this.mScrollOffsetY < 0 ? -this.mItemHeight - remainder : this.mItemHeight - remainder;
        } else {
            return -remainder;
        }
    }

    public void run() {
        if (null != this.mData && this.mData.size() != 0) {
            if (this.mScroller.isFinished() && !this.isForceFinishScroll) {
                if (this.mItemHeight == 0) {
                    return;
                }

                int position = (-this.mScrollOffsetY / this.mItemHeight + this.mSelectedItemPosition) % this.mData.size();
                position = position < 0 ? position + this.mData.size() : position;


                this.mCurrentItemPosition = position;
                if (null != this.mOnItemSelectedListener) {
                    this.mOnItemSelectedListener.onItemSelected(this, this.mData.get(position), position);
                }

                if (null != this.mOnWheelChangeListener) {
                    this.mOnWheelChangeListener.onWheelSelected(position);
                    this.mOnWheelChangeListener.onWheelScrollStateChanged(0);
                }
            }

            if (this.mScroller.computeScrollOffset()) {
                if (null != this.mOnWheelChangeListener) {
                    this.mOnWheelChangeListener.onWheelScrollStateChanged(2);
                }

                this.mScrollOffsetY = this.mScroller.getCurrY();
                this.postInvalidate();
                this.mHandler.postDelayed(this, 16L);
            }

        }
    }

    // dp转px
    private int dp2px(Context context, float value) {
        float scale = context.getResources().getDisplayMetrics().density;
        return (int) (value * scale + 0.5f);
    }

    public int getVisibleItemCount() {
        return this.mVisibleItemCount;
    }

    public void setVisibleItemCount(int count) {
        this.mVisibleItemCount = count;
        this.updateVisibleItemCount();
        this.requestLayout();
    }

    public boolean isCyclic() {
        return this.isCyclic;
    }

    public void setCyclic(boolean isCyclic) {
        this.isCyclic = isCyclic;
        this.computeFlingLimitY();
        this.invalidate();
    }

    public void setLabel(String label) {
        this.label = label;
    }

    public void setOnItemSelectedListener(WheelPicker.OnItemSelectedListener listener) {
        this.mOnItemSelectedListener = listener;
    }

    public int getSelectedItemPosition() {
        return this.mSelectedItemPosition;
    }

    public void setSelectedItemPosition(int position) {
        position = Math.min(position, this.mData.size() - 1);
        position = Math.max(position, 0);
        this.mSelectedItemPosition = position;
        this.mCurrentItemPosition = position;
        this.mScrollOffsetY = 0;
        this.computeFlingLimitY();
        this.requestLayout();
        this.invalidate();
    }

    public int getCurrentItemPosition() {
        return this.mCurrentItemPosition;
    }

    public List<WheelBean> getData() {
        return this.mData;
    }

    public void setData(List<WheelBean> data) {
        if (null == data) {
            throw new NullPointerException("WheelPicker's data can not be null!");
        } else {
            this.mData = data;
            if (this.mSelectedItemPosition <= data.size() - 1 && this.mCurrentItemPosition <= data.size() - 1) {
                this.mSelectedItemPosition = this.mCurrentItemPosition;
            } else {
                this.mSelectedItemPosition = this.mCurrentItemPosition = data.size() - 1;
            }

            this.mScrollOffsetY = 0;
            this.computeTextSize();
            this.computeFlingLimitY();
            this.requestLayout();
            this.invalidate();
        }
    }

    public void setSameWidth(boolean hasSameWidth) {
        this.hasSameWidth = hasSameWidth;
        this.computeTextSize();
        this.requestLayout();
        this.invalidate();
    }

    public boolean hasSameWidth() {
        return this.hasSameWidth;
    }

    public void setOnWheelChangeListener(WheelPicker.OnWheelChangeListener listener) {
        this.mOnWheelChangeListener = listener;
    }

    public String getMaximumWidthText() {
        return this.mMaxWidthText;
    }

    public void setMaximumWidthText(String text) {
        if (null == text) {
            throw new NullPointerException("Maximum width text can not be null!");
        } else {
            this.mMaxWidthText = text;
            this.computeTextSize();
            this.requestLayout();
            this.invalidate();
        }
    }

    public int getMaximumWidthTextPosition() {
        return this.mTextMaxWidthPosition;
    }

    public void setMaximumWidthTextPosition(int position) {
        if (!this.isPosInRang(position)) {
            throw new ArrayIndexOutOfBoundsException("Maximum width text Position must in [0, " + this.mData.size() + "), but current is " + position);
        } else {
            this.mTextMaxWidthPosition = position;
            this.computeTextSize();
            this.requestLayout();
            this.invalidate();
        }
    }

    public int getSelectedItemTextColor() {
        return this.mSelectedItemTextColor;
    }

    public void setSelectedItemTextColor(int color) {
        this.mSelectedItemTextColor = color;
        this.computeCurrentItemRect();
        this.invalidate();
    }

    public int getItemTextColor() {
        return this.mItemTextColor;
    }

    public void setItemTextColor(int color) {
        this.mItemTextColor = color;
        this.invalidate();
    }

    public int getItemTextSize() {
        return this.mItemTextSize;
    }

    public void setItemTextSize(int size) {
        this.mItemTextSize = size;
        this.mPaint.setTextSize((float) this.mItemTextSize);
        this.computeTextSize();
        this.requestLayout();
        this.invalidate();
    }

    public int getItemSpace() {
        return this.mItemSpace;
    }

    public void setItemSpace(int space) {
        this.mItemSpace = space;
        this.requestLayout();
        this.invalidate();
    }

    public void setIndicator(boolean hasIndicator) {
        this.hasIndicator = hasIndicator;
        this.computeIndicatorRect();
        this.invalidate();
    }

    public boolean hasIndicator() {
        return this.hasIndicator;
    }

    public int getIndicatorSize() {
        return this.mIndicatorSize;
    }

    public void setIndicatorSize(int size) {
        this.mIndicatorSize = size;
        this.computeIndicatorRect();
        this.invalidate();
    }

    public int getIndicatorColor() {
        return this.mIndicatorColor;
    }

    public void setIndicatorColor(int color) {
        this.mIndicatorColor = color;
        this.invalidate();
    }

    public void setCurtain(boolean hasCurtain) {
        this.hasCurtain = hasCurtain;
        this.computeCurrentItemRect();
        this.invalidate();
    }

    public boolean hasCurtain() {
        return this.hasCurtain;
    }

    public int getCurtainColor() {
        return this.mCurtainColor;
    }

    public void setCurtainColor(int color) {
        this.mCurtainColor = color;
        this.invalidate();
    }

    public void setAtmospheric(boolean hasAtmospheric) {
        this.hasAtmospheric = hasAtmospheric;
        this.invalidate();
    }

    public boolean hasAtmospheric() {
        return this.hasAtmospheric;
    }

    public boolean isCurved() {
        return this.isCurved;
    }

    public void setCurved(boolean isCurved) {
        this.isCurved = isCurved;
        this.requestLayout();
        this.invalidate();
    }

    public int getItemAlign() {
        return this.mItemAlign;
    }

    public void setItemAlign(int align) {
        this.mItemAlign = align;
        this.updateItemTextAlign();
        this.computeDrawnCenter();
        this.invalidate();
    }

    public Typeface getTypeface() {
        return null != this.mPaint ? this.mPaint.getTypeface() : null;
    }

    public void setTypeface(Typeface tf) {
        if (null != this.mPaint) {
            this.mPaint.setTypeface(tf);
        }

        this.computeTextSize();
        this.requestLayout();
        this.invalidate();
    }

    public interface OnWheelChangeListener {
        void onWheelScrolled(int var1);

        void onWheelSelected(int var1);

        void onWheelScrollStateChanged(int var1);
    }

    public interface OnItemSelectedListener {
        void onItemSelected(WheelPicker wheelPicker, WheelBean wheelBean, int position);
    }
}








第02节 实体类 WheelBean

位置

src/java/view.wheel.WheelBean.java

代码

package view.wheel;

import androidx.annotation.NonNull;

public class WheelBean {

    private int itemNumber;
    private String itemLabel;

    public WheelBean() {
    }

    public WheelBean(int itemNumber, String itemLabel) {
        this.itemNumber = itemNumber;
        this.itemLabel = itemLabel;
    }

    public int getItemNumber() {
        return itemNumber;
    }

    public void setItemNumber(int itemNumber) {
        this.itemNumber = itemNumber;
    }

    public String getItemLabel() {
        return itemLabel;
    }

    public void setItemLabel(String itemLabel) {
        this.itemLabel = itemLabel;
    }

    @NonNull
    @Override
    public String toString() {
        return itemNumber + itemLabel;
    }
}








第03节 接口类 IWheelPicker

位置

src/java/view.wheel.IWheelPicker.java

代码

package view.wheel;

import android.graphics.Typeface;

import java.util.List;

public interface IWheelPicker {
    int getVisibleItemCount();

    void setVisibleItemCount(int var1);

    boolean isCyclic();

    void setCyclic(boolean var1);

    void setOnItemSelectedListener(WheelPicker.OnItemSelectedListener var1);

    void setSelectedItemPosition(int var1);

    int getCurrentItemPosition();

    List<WheelBean> getData();

    void setData(List<WheelBean> var1);

    void setMaximumWidthText(String var1);

    int getSelectedItemTextColor();

    void setSelectedItemTextColor(int var1);

    int getItemTextColor();

    void setItemTextColor(int var1);

    int getItemTextSize();

    void setItemTextSize(int var1);

    int getItemSpace();

    void setItemSpace(int var1);

    void setIndicator(boolean var1);

    boolean hasIndicator();

    int getIndicatorSize();

    void setIndicatorSize(int var1);

    void setIndicatorColor(int var1);

    void setCurtain(boolean var1);

    boolean hasCurtain();

    int getCurtainColor();

    void setCurtainColor(int var1);

    void setAtmospheric(boolean var1);

    boolean hasAtmospheric();

    boolean isCurved();

    void setCurved(boolean var1);

    int getItemAlign();

    void setItemAlign(int var1);

    Typeface getTypeface();

    void setTypeface(Typeface var1);
}








第04节 原子时间类 DateTimePickerView

位置

src/java/view.wheel.atom.time.DateTimePickerView.java

代码

package view.wheel.atom.time;

import android.content.Context;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;

import view.wheel.WheelBean;
import view.wheel.WheelPicker;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;

public class DateTimePickerView extends LinearLayout implements WheelPicker.OnItemSelectedListener {
    private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss", Locale.getDefault());
    private PickerYear mPickerYear;
    private PickerMonth mPickerMonth;
    private PickerDay mPickerDay;
    private PickerHour mPickerHour;
    private PickerMinute mPickerMinute;
    private PickerSecond mPickerSecond;


    private DateTimePickerView.OnDateSelectedListener onDateSelectedListener;

    private int mYear;
    private int mMonth;
    private int mDay;
    private int mHour;
    private int mMinute;
    private int mSecond;

    public void setOnDateSelectedListener(OnDateSelectedListener onDateSelectedListener) {
        this.onDateSelectedListener = onDateSelectedListener;
    }

    public DateTimePickerView(Context context) {
        this(context, null);
    }

    public DateTimePickerView(Context context, AttributeSet attrs) {
        super(context, attrs);
        LayoutInflater.from(context).inflate(R.layout.view_picker_time_date, this);
        this.mPickerYear = findViewById(R.id.picker_year);
        this.mPickerMonth = findViewById(R.id.picker_month);
        this.mPickerDay = findViewById(R.id.picker_day);
        this.mPickerHour = findViewById(R.id.picker_hour);
        this.mPickerMinute = findViewById(R.id.picker_minute);
        this.mPickerSecond = findViewById(R.id.picker_second);


        this.setMaximumWidthTextYear();
        this.mPickerMonth.setMaximumWidthText("00");
        this.mPickerDay.setMaximumWidthText("00");
        this.mPickerHour.setMaximumWidthText("00");
        this.mPickerMinute.setMaximumWidthText("00");
        this.mPickerSecond.setMaximumWidthText("00");


        this.mYear = this.mPickerYear.getCurrentYear();
        this.mMonth = this.mPickerMonth.getCurrentMonth();
        this.mDay = this.mPickerDay.getCurrentDay();
        this.mHour = this.mPickerHour.getCurrentHour();
        this.mMinute = this.mPickerMinute.getCurrentMinute();
        this.mSecond = this.mPickerSecond.getCurrentSecond();

        this.mPickerYear.setOnItemSelectedListener(this);
        this.mPickerMonth.setOnItemSelectedListener(this);
        this.mPickerDay.setOnItemSelectedListener(this);
        this.mPickerHour.setOnItemSelectedListener(this);
        this.mPickerMinute.setOnItemSelectedListener(this);
        this.mPickerSecond.setOnItemSelectedListener(this);
    }

    private void setMaximumWidthTextYear() {
        List<WheelBean> years = this.mPickerYear.getData();
        String lastYear = String.valueOf(years.get(years.size() - 1));
        StringBuilder sb = new StringBuilder();

        for (int i = 0; i < lastYear.length(); ++i) {
            sb.append("0");
        }

        this.mPickerYear.setMaximumWidthText(sb.toString());
    }

    @Override
    public void onItemSelected(WheelPicker picker, WheelBean wheelBean, int position) {
        if (picker.getId() == R.id.picker_year) {
            this.mYear = this.mPickerYear.getCurrentYear();
            this.mPickerDay.setYear(this.mYear);
        } else if (picker.getId() == R.id.picker_month) {
            this.mMonth = this.mPickerMonth.getCurrentMonth();
            this.mPickerDay.setMonth(this.mMonth);
        } else if (picker.getId() == R.id.picker_day) {
            this.mDay = this.mPickerDay.getCurrentDay();
        } else if (picker.getId() == R.id.picker_hour) {
            this.mHour = this.mPickerHour.getCurrentHour();
        } else if (picker.getId() == R.id.picker_minute) {
            this.mMinute = this.mPickerMinute.getCurrentMinute();
        } else if (picker.getId() == R.id.picker_second) {
            this.mSecond = this.mPickerSecond.getCurrentSecond();
        }

        String dateTime = this.mYear + "年" + this.mMonth + "月" + this.mDay + "日 " + this.mHour + ":" + this.mMinute + ":" + this.mSecond;

        if (onDateSelectedListener != null) {
            try {
                onDateSelectedListener.onDateSelected(this, SDF.parse(dateTime), dateTime);
            } catch (ParseException parseException) {
                parseException.printStackTrace();
            }
        }
    }

    // 设置显示的类型
    public void setShowType(boolean[] array) {
        if (array == null || array.length != 6) {
            throw new IllegalArgumentException("数组的长度必须为6");
        }
        this.mPickerYear.setVisibility(array[0] ? View.VISIBLE : View.GONE);
        this.mPickerMonth.setVisibility(array[1] ? View.VISIBLE : View.GONE);
        this.mPickerDay.setVisibility(array[2] ? View.VISIBLE : View.GONE);
        this.mPickerHour.setVisibility(array[3] ? View.VISIBLE : View.GONE);
        this.mPickerMinute.setVisibility(array[4] ? View.VISIBLE : View.GONE);
        this.mPickerSecond.setVisibility(array[5] ? View.VISIBLE : View.GONE);

    }

    public void setVisibleItemCount(int count) {
        this.mPickerYear.setVisibleItemCount(count);
        this.mPickerMonth.setVisibleItemCount(count);
        this.mPickerDay.setVisibleItemCount(count);
        this.mPickerHour.setVisibleItemCount(count);
        this.mPickerMinute.setVisibleItemCount(count);
        this.mPickerSecond.setVisibleItemCount(count);
    }

    public boolean isCyclic() {
        boolean cyclicYear = this.mPickerYear.isCyclic();
        boolean cyclicMonth = this.mPickerMonth.isCyclic();
        boolean cyclicDay = this.mPickerDay.isCyclic();
        boolean cyclicHour = this.mPickerHour.isCyclic();
        boolean cyclicMinute = this.mPickerMinute.isCyclic();
        boolean cyclicSecond = this.mPickerSecond.isCyclic();
        boolean result = cyclicYear && cyclicMonth && cyclicDay && cyclicHour && cyclicMinute && cyclicSecond;
        return result;
    }

    public void setCenterLineColor(int centerLineColor) {
        this.mPickerYear.setCenterLineColor(centerLineColor);
        this.mPickerMonth.setCenterLineColor(centerLineColor);
        this.mPickerDay.setCenterLineColor(centerLineColor);
        this.mPickerHour.setCenterLineColor(centerLineColor);
        this.mPickerMinute.setCenterLineColor(centerLineColor);
        this.mPickerSecond.setCenterLineColor(centerLineColor);
    }

    // 这里需要设置透明度, 才能有效果。不能用全色, 否则UI效果异常
    public void setCenterRectColor(int centerRectColor) {
        this.mPickerYear.setCenterRectColor(centerRectColor);
        this.mPickerMonth.setCenterRectColor(centerRectColor);
        this.mPickerDay.setCenterRectColor(centerRectColor);
        this.mPickerHour.setCenterRectColor(centerRectColor);
        this.mPickerMinute.setCenterRectColor(centerRectColor);
        this.mPickerSecond.setCenterRectColor(centerRectColor);
    }

    public void setCenterRectIsShow(boolean centerRectIsShow) {
        this.mPickerYear.setCenterRectIsShow(centerRectIsShow);
        this.mPickerMonth.setCenterRectIsShow(centerRectIsShow);
        this.mPickerDay.setCenterRectIsShow(centerRectIsShow);
        this.mPickerHour.setCenterRectIsShow(centerRectIsShow);
        this.mPickerMinute.setCenterRectIsShow(centerRectIsShow);
        this.mPickerSecond.setCenterRectIsShow(centerRectIsShow);
    }


    public void setCenterLineWidth(int centerLineWidth) {
        this.mPickerYear.setCenterLineWidth(centerLineWidth);
        this.mPickerMonth.setCenterLineWidth(centerLineWidth);
        this.mPickerDay.setCenterLineWidth(centerLineWidth);
        this.mPickerHour.setCenterLineWidth(centerLineWidth);
        this.mPickerMinute.setCenterLineWidth(centerLineWidth);
        this.mPickerSecond.setCenterLineWidth(centerLineWidth);
    }

    public void setCenterLineIsShow(boolean centerLineIsShow) {
        this.mPickerYear.setCenterLineIsShow(centerLineIsShow);
        this.mPickerMonth.setCenterLineIsShow(centerLineIsShow);
        this.mPickerDay.setCenterLineIsShow(centerLineIsShow);
        this.mPickerHour.setCenterLineIsShow(centerLineIsShow);
        this.mPickerMinute.setCenterLineIsShow(centerLineIsShow);
        this.mPickerSecond.setCenterLineIsShow(centerLineIsShow);
    }

    public void setCyclic(boolean isCyclic) {
        this.mPickerYear.setCyclic(isCyclic);
        this.mPickerMonth.setCyclic(isCyclic);
        this.mPickerDay.setCyclic(isCyclic);
        this.mPickerHour.setCyclic(isCyclic);
        this.mPickerMinute.setCyclic(isCyclic);
        this.mPickerSecond.setCyclic(isCyclic);
    }

    public void setItemTextColorSelected(int color) {
        this.mPickerYear.setSelectedItemTextColor(color);
        this.mPickerMonth.setSelectedItemTextColor(color);
        this.mPickerDay.setSelectedItemTextColor(color);
        this.mPickerHour.setSelectedItemTextColor(color);
        this.mPickerMinute.setSelectedItemTextColor(color);
        this.mPickerSecond.setSelectedItemTextColor(color);
    }


    public void setItemTextColorUnSelect(int color) {
        this.mPickerYear.setItemTextColor(color);
        this.mPickerMonth.setItemTextColor(color);
        this.mPickerDay.setItemTextColor(color);
        this.mPickerHour.setItemTextColor(color);
        this.mPickerMinute.setItemTextColor(color);
        this.mPickerSecond.setItemTextColor(color);
    }

    public void setItemTextSize(int size) {
        this.mPickerYear.setItemTextSize(size);
        this.mPickerMonth.setItemTextSize(size);
        this.mPickerDay.setItemTextSize(size);
        this.mPickerHour.setItemTextSize(size);
        this.mPickerMinute.setItemTextSize(size);
        this.mPickerSecond.setItemTextSize(size);
    }

    public void setItemSpace(int space) {
        this.mPickerYear.setItemSpace(space);
        this.mPickerMonth.setItemSpace(space);
        this.mPickerDay.setItemSpace(space);
        this.mPickerHour.setItemSpace(space);
        this.mPickerMinute.setItemSpace(space);
        this.mPickerSecond.setItemSpace(space);
    }

    public void setIndicator(boolean hasIndicator) {
        this.mPickerYear.setIndicator(hasIndicator);
        this.mPickerMonth.setIndicator(hasIndicator);
        this.mPickerDay.setIndicator(hasIndicator);
        this.mPickerHour.setIndicator(hasIndicator);
        this.mPickerMinute.setIndicator(hasIndicator);
        this.mPickerSecond.setIndicator(hasIndicator);
    }

    public boolean hasIndicator() {
        boolean indicatorYear = this.mPickerYear.hasIndicator();
        boolean indicatorMonth = this.mPickerMonth.hasIndicator();
        boolean indicatorDay = this.mPickerDay.hasIndicator();
        boolean indicatorHour = this.mPickerHour.hasIndicator();
        boolean indicatorMinute = this.mPickerMinute.hasIndicator();
        boolean indicatorSecond = this.mPickerSecond.hasIndicator();
        boolean result = indicatorYear && indicatorMonth && indicatorDay && indicatorHour && indicatorMinute && indicatorSecond;
        return result;
    }

    public void setIndicatorSize(int size) {
        this.mPickerYear.setIndicatorSize(size);
        this.mPickerMonth.setIndicatorSize(size);
        this.mPickerDay.setIndicatorSize(size);
        this.mPickerHour.setIndicatorSize(size);
        this.mPickerMinute.setIndicatorSize(size);
        this.mPickerSecond.setIndicatorSize(size);
    }

    public void setIndicatorColor(int color) {
        this.mPickerYear.setIndicatorColor(color);
        this.mPickerMonth.setIndicatorColor(color);
        this.mPickerDay.setIndicatorColor(color);
        this.mPickerHour.setIndicatorColor(color);
        this.mPickerMinute.setIndicatorColor(color);
        this.mPickerSecond.setIndicatorColor(color);
    }

    public void setCurtain(boolean hasCurtain) {
        this.mPickerYear.setCurtain(hasCurtain);
        this.mPickerMonth.setCurtain(hasCurtain);
        this.mPickerDay.setCurtain(hasCurtain);
        this.mPickerHour.setCurtain(hasCurtain);
        this.mPickerMinute.setCurtain(hasCurtain);
        this.mPickerSecond.setCurtain(hasCurtain);
    }

    public boolean isCurtain() {
        boolean curtainYear = this.mPickerYear.hasCurtain();
        boolean curtainMonth = this.mPickerMonth.hasCurtain();
        boolean curtainDay = this.mPickerDay.hasCurtain();
        boolean curtainHour = this.mPickerHour.hasCurtain();
        boolean curtainMinute = this.mPickerMinute.hasCurtain();
        boolean curtainSecond = this.mPickerSecond.hasCurtain();
        boolean result = curtainYear && curtainMonth && curtainDay && curtainHour && curtainMinute && curtainSecond;
        return result;
    }

    public void setCurtainColor(int color) {
        this.mPickerYear.setCurtainColor(color);
        this.mPickerMonth.setCurtainColor(color);
        this.mPickerDay.setCurtainColor(color);
        this.mPickerHour.setCurtainColor(color);
        this.mPickerMinute.setCurtainColor(color);
        this.mPickerSecond.setCurtainColor(color);
    }

    public void setAtmospheric(boolean hasAtmospheric) {
        this.mPickerYear.setAtmospheric(hasAtmospheric);
        this.mPickerMonth.setAtmospheric(hasAtmospheric);
        this.mPickerDay.setAtmospheric(hasAtmospheric);
        this.mPickerHour.setAtmospheric(hasAtmospheric);
        this.mPickerMinute.setAtmospheric(hasAtmospheric);
        this.mPickerSecond.setAtmospheric(hasAtmospheric);
    }

    public boolean hasAtmospheric() {
        boolean hasAtmosphericYear = this.mPickerYear.hasAtmospheric();
        boolean hasAtmosphericMonth = this.mPickerMonth.hasAtmospheric();
        boolean hasAtmosphericDay = this.mPickerDay.hasAtmospheric();
        boolean hasAtmosphericHour = this.mPickerHour.hasAtmospheric();
        boolean hasAtmosphericMinute = this.mPickerMinute.hasAtmospheric();
        boolean hasAtmosphericSecond = this.mPickerSecond.hasAtmospheric();
        boolean result = hasAtmosphericYear && hasAtmosphericMonth && hasAtmosphericDay && hasAtmosphericHour && hasAtmosphericMinute && hasAtmosphericSecond;
        return result;
    }

    // 判断是否存在弧度
    public boolean isCurved() {
        boolean curvedYear = this.mPickerYear.isCurved();
        boolean curvedMonth = this.mPickerMonth.isCurved();
        boolean curvedDay = this.mPickerDay.isCurved();
        boolean curvedHour = this.mPickerHour.isCurved();
        boolean curvedMinute = this.mPickerMinute.isCurved();
        boolean curvedSecond = this.mPickerSecond.isCurved();
        boolean result = curvedYear && curvedMonth && curvedDay && curvedHour && curvedMinute && curvedSecond;
        return result;
    }

    // 设置是否存在弧度
    public void setCurved(boolean isCurved) {
        this.mPickerYear.setCurved(isCurved);
        this.mPickerMonth.setCurved(isCurved);
        this.mPickerDay.setCurved(isCurved);
        this.mPickerHour.setCurved(isCurved);
        this.mPickerMinute.setCurved(isCurved);
        this.mPickerSecond.setCurved(isCurved);
    }

    // 设置字体样式
    public void setTypeface(Typeface typeface) {
        this.mPickerYear.setTypeface(typeface);
        this.mPickerMonth.setTypeface(typeface);
        this.mPickerDay.setTypeface(typeface);
        this.mPickerHour.setTypeface(typeface);
        this.mPickerMinute.setTypeface(typeface);
        this.mPickerSecond.setTypeface(typeface);
    }

    public interface OnDateSelectedListener {
        void onDateSelected(DateTimePickerView view, Date date, String format);
    }
}








第05节 原子时间类 PickerYear

位置

src/java/view.wheel.atom.time.PickerYear.java

代码

package view.wheel.atom.time;

import android.content.Context;
import android.util.AttributeSet;

import view.wheel.WheelBean;
import view.wheel.WheelPicker;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

// 选择器(年份)
public class PickerYear extends WheelPicker {
    private int mYearStart;
    private int mYearEnd;
    private int mSelectedYear;

    public PickerYear(Context context) {
        this(context, null);
    }

    public PickerYear(Context context, AttributeSet attrs) {
        super(context, attrs);
        mYearStart = 1000;
        mYearEnd = 3000;
        updateYears();
        mSelectedYear = Calendar.getInstance().get(Calendar.YEAR);
        setSelectedItemPosition(mSelectedYear - this.mYearStart);
    }

    private void updateYears() {
        List<WheelBean> data = new ArrayList<>();
        for (int i = this.mYearStart; i <= this.mYearEnd; ++i) {
            WheelBean item = new WheelBean(i, label);
            data.add(item);
        }
        super.setData(data);
    }

    public int getYearStart() {
        return this.mYearStart;
    }

    public void setYearStart(int start) {
        this.mYearStart = start;
        this.mSelectedYear = this.getCurrentYear();
        this.updateYears();
        this.setSelectedItemPosition(this.mSelectedYear - this.mYearStart);
    }

    public int getCurrentYear() {
        return this.getData().get(this.getCurrentItemPosition()).getItemNumber();
    }
}








第06节 原子时间类 PickerMonth

位置

src/java/view.wheel.atom.time.PickerMonth.java

代码

package view.wheel.atom.time;

import android.content.Context;
import android.util.AttributeSet;

import view.wheel.WheelBean;
import view.wheel.WheelPicker;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

// 选择器(月份)
public class PickerMonth extends WheelPicker {

    private int mSelectedMonth;

    public PickerMonth(Context context) {
        this(context, null);
    }

    public PickerMonth(Context context, AttributeSet attrs) {
        super(context, attrs);
        List<WheelBean> data = new ArrayList<>();

        for (int i = 1; i <= 12; i++) {
            WheelBean item = new WheelBean(i, label);
            data.add(item);
        }
        super.setData(data);
        int mSelectedMonth = Calendar.getInstance().get(Calendar.MONTH) + 1;
        setSelectedItemPosition(mSelectedMonth - 1);
    }

    public void setSelectedMonth(int month) {
        this.mSelectedMonth = month;
        this.setSelectedItemPosition(this.mSelectedMonth - 1);
    }

    public int getCurrentMonth() {
        return this.getData().get(this.getCurrentItemPosition()).getItemNumber();
    }
}








第07节 原子时间类 PickerDay

位置

src/java/view.wheel.atom.time.PickerDay.java

代码

package view.wheel.atom.time;

import android.content.Context;
import android.util.AttributeSet;

import view.wheel.WheelBean;
import view.wheel.WheelPicker;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

// 选择器(日期)
public class PickerDay extends WheelPicker {
    private static final Map<Integer, List<WheelBean>> mapData = new HashMap<>();
    private Calendar mCalendar;
    private int mYear;
    private int mMonth;
    private int mSelectedDay;

    public PickerDay(Context context) {
        this(context, null);
    }

    public PickerDay(Context context, AttributeSet attrs) {
        super(context, attrs);
        mCalendar = Calendar.getInstance();
        mYear = mCalendar.get(Calendar.YEAR);
        mMonth = mCalendar.get(Calendar.MONTH);
        updateMapData();
        mSelectedDay = this.mCalendar.get(Calendar.DAY_OF_MONTH);
        updateSelectedDay();
    }

    private void updateMapData() {
        this.mCalendar.set(Calendar.YEAR, this.mYear);
        this.mCalendar.set(Calendar.MONTH, this.mMonth);
        int mDate = this.mCalendar.getActualMaximum(Calendar.DAY_OF_MONTH);
        List<WheelBean> dateList = mapData.get(mDate);
        if (null == dateList) {
            dateList = new ArrayList<>();
            for (int i = 1; i <= mDate; i++) {
                WheelBean item = new WheelBean(i, label);
                dateList.add(item);
            }
            mapData.put(mDate, dateList);
        }
        super.setData(dateList);
    }

    private void updateSelectedDay() {
        this.setSelectedItemPosition(this.mSelectedDay - 1);
    }

    public int getCurrentDay() {
        return this.getData().get(this.getCurrentItemPosition()).getItemNumber();
    }

    private void updateDays() {
        this.mCalendar.set(Calendar.YEAR, this.mYear);
        this.mCalendar.set(Calendar.MONTH, this.mMonth);
        int days = this.mCalendar.getActualMaximum(5);
        List<WheelBean> data = mapData.get(days);
        if (null == data) {
            data = new ArrayList<>();
            for (int i = 1; i <= days; ++i) {
                WheelBean item = new WheelBean(i, label);
                data.add(item);
            }
            mapData.put(days, data);
        }
        super.setData(data);
    }

    public void setYear(int year) {
        this.mYear = year;
        this.updateDays();
    }

    public void setMonth(int month) {
        this.mMonth = month - 1;
        this.updateDays();
    }
}








第08节 原子时间类 PickerHour

位置

src/java/view.wheel.atom.time.PickerHour.java

代码

package view.wheel.atom.time;

import android.content.Context;
import android.util.AttributeSet;

import view.wheel.WheelBean;
import view.wheel.WheelPicker;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

// 选择器(小时)
public class PickerHour extends WheelPicker {
    private int mHourStart;
    private int mHourEnd;
    private int mSelectedHour;

    public PickerHour(Context context) {
        this(context, null);
    }

    public PickerHour(Context context, AttributeSet attrs) {
        super(context, attrs);
        mHourStart = 0;
        mHourEnd = 23;
        updateHour();
        mSelectedHour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
        setSelectedItemPosition(mSelectedHour - this.mHourStart);
    }

    private void updateHour() {
        List<WheelBean> data = new ArrayList<>();
        for (int i = this.mHourStart; i <= this.mHourEnd; ++i) {
            WheelBean item = new WheelBean(i, label);
            data.add(item);
        }
        super.setData(data);
    }

    public int getHourStart() {
        return this.mHourStart;
    }

    public void setHourStart(int start) {
        this.mHourStart = start;
        this.mSelectedHour = this.getCurrentHour();
        this.updateHour();
        this.setSelectedItemPosition(this.mSelectedHour - this.mHourStart);
    }

    public int getCurrentHour() {
        return this.getData().get(this.getCurrentItemPosition()).getItemNumber();
    }
}








第09节 原子时间类 PickerMinute

位置

src/java/view.wheel.atom.time.PickerMinute.java

代码

package view.wheel.atom.time;

import android.content.Context;
import android.util.AttributeSet;

import view.wheel.WheelBean;
import view.wheel.WheelPicker;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

// 选择器(分钟)
public class PickerMinute extends WheelPicker {
    private int mMinuteStart;
    private int mMinuteEnd;
    private int mSelectedMinute;

    public PickerMinute(Context context) {
        this(context, null);
    }

    public PickerMinute(Context context, AttributeSet attrs) {
        super(context, attrs);
        mMinuteStart = 0;
        mMinuteEnd = 59;
        updateMinute();
        mSelectedMinute = Calendar.getInstance().get(Calendar.MINUTE);
        setSelectedItemPosition(mSelectedMinute - this.mMinuteStart);
    }

    private void updateMinute() {
        List<WheelBean> data = new ArrayList<>();
        for (int i = this.mMinuteStart; i <= this.mMinuteEnd; ++i) {
            WheelBean item = new WheelBean(i, label);
            data.add(item);
        }
        super.setData(data);
    }

    public int getMinuteStart() {
        return this.mMinuteStart;
    }

    public void setMinuteStart(int start) {
        this.mMinuteStart = start;
        this.mSelectedMinute = this.getCurrentMinute();
        this.updateMinute();
        this.setSelectedItemPosition(this.mSelectedMinute - this.mMinuteStart);
    }

    public int getCurrentMinute() {
        return this.getData().get(this.getCurrentItemPosition()).getItemNumber();
    }
}








第10节 原子时间类 PickerSecond

位置

src/java/view.wheel.atom.time.PickerSecond.java

代码

package view.wheel.atom.time;

import android.content.Context;
import android.util.AttributeSet;

import view.wheel.WheelBean;
import view.wheel.WheelPicker;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

// 选择器(秒钟)
public class PickerSecond extends WheelPicker {
    private int mSecondStart;
    private int mSecondEnd;
    private int mSelectedSecond;

    public PickerSecond(Context context) {
        this(context, null);
    }

    public PickerSecond(Context context, AttributeSet attrs) {
        super(context, attrs);
        mSecondStart = 0;
        mSecondEnd = 59;
        updateSecond();
        mSelectedSecond = Calendar.getInstance().get(Calendar.SECOND);
        setSelectedItemPosition(mSelectedSecond - this.mSecondStart);
    }


    private void updateSecond() {
        List<WheelBean> data = new ArrayList<>();
        for (int i = this.mSecondStart; i <= this.mSecondEnd; i++) {
            WheelBean item = new WheelBean(i, label);
            data.add(item);
        }
        super.setData(data);
    }

    public int getSecondStart() {
        return this.mSecondStart;
    }

    public void setSecondStart(int start) {
        this.mSecondStart = start;
        this.mSelectedSecond = this.getCurrentSecond();
        this.updateSecond();
        this.setSelectedItemPosition(this.mSelectedSecond - this.mSecondStart);
    }

    public int getCurrentSecond() {
        return this.getData().get(this.getCurrentItemPosition()).getItemNumber();
    }
}








第11节 测试Activity

位置

src/java/DemoActivity.java

代码

public class DemoActivity extends AppCompatActivity {

    private DateTimePickerView mDateTimePickerView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_demo);

        this.mDateTimePickerView = findViewById(R.id.date_time_picker_view);

        // 定义相关的属性状态
        mDateTimePickerView.setAtmospheric(true);    // 条目颜色是否执行衔接处理, 效果更好
        mDateTimePickerView.setCurved(false);        // 是否显示弧度
        mDateTimePickerView.setItemTextColorUnSelect(Color.BLUE);   // 未选中的条目颜色
        mDateTimePickerView.setItemTextColorSelected(Color.RED);   // 选中条目的颜色
        mDateTimePickerView.setCyclic(true);            // 设置是否循环

        mDateTimePickerView.setCenterLineColor(Color.BLUE);     // 设置中间线的颜色
        mDateTimePickerView.setCenterLineWidth(2);      // 设置中间线的宽度
        mDateTimePickerView.setCenterLineIsShow(false);  // 设置中间线是否显示

        mDateTimePickerView.setCenterRectColor(Color.parseColor("#66666666"));     // 设置中间矩形的颜色
        mDateTimePickerView.setCenterRectIsShow(true);  // 设置中间矩形是否显示


        // 设置显示的类型(数组的长度必须是6,分别表示 "年", "月", "日", "时", "分", "秒")
        boolean[] array = new boolean[]{
                true, true, true, true, true, false
        };
        mDateTimePickerView.setShowType(array);
    }
}








第12节 布局文件

位置

res/layout/view_picker_time_date.xml

代码

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:gravity="center"
    android:orientation="horizontal">

    <view.wheel.atom.time.PickerYear
        android:id="@+id/picker_year"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        app:wheel_label="" />

    <view.wheel.atom.time.PickerMonth
        android:id="@+id/picker_month"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        app:wheel_label="" />

    <view.wheel.atom.time.PickerDay
        android:id="@+id/picker_day"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        app:wheel_label="" />

    <view.wheel.atom.time.PickerHour
        android:id="@+id/picker_hour"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        app:wheel_label="" />

    <view.wheel.atom.time.PickerMinute
        android:id="@+id/picker_minute"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        app:wheel_label="" />

    <view.wheel.atom.time.PickerSecond
        android:id="@+id/picker_second"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        app:wheel_label="" />

</LinearLayout>




位置

res/layout/activity_demo.xml

代码

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <view.wheel.atom.time.DateTimePickerView
        android:id="@+id/date_time_picker_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</FrameLayout>








第13节 属性文件

位置

res/values/attrs.xml

代码

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

    <declare-styleable name="WheelPicker">
        <attr name="wheel_data" format="reference" />
        <attr name="wheel_selected_item_position" format="integer" />
        <attr name="wheel_item_text_size" format="dimension" />
        <attr name="wheel_item_text_color" format="color" />
        <attr name="wheel_selected_item_text_color" format="color" />
        <attr name="wheel_same_width" format="boolean" />
        <attr name="wheel_maximum_width_text" format="string" />
        <attr name="wheel_label" format="string" />
        <attr name="wheel_maximum_width_text_position" format="integer" />
        <attr name="wheel_visible_item_count" format="integer" />
        <attr name="wheel_item_space" format="dimension" />
        <attr name="wheel_cyclic" format="boolean" />
        <attr name="wheel_indicator" format="boolean" />
        <attr name="wheel_indicator_color" format="color" />
        <attr name="wheel_indicator_size" format="dimension" />
        <attr name="wheel_curtain" format="boolean" />
        <attr name="wheel_curtain_color" format="color" />
        <attr name="wheel_atmospheric" format="boolean" />
        <attr name="wheel_curved" format="boolean" />
        <attr name="wheel_item_align" format="enum">
            <enum name="center" value="0" />
            <enum name="left" value="1" />
            <enum name="right" value="2" />
        </attr>
    </declare-styleable>

</resources>








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值