Android Ios-like time wheel control

I used it in the previous project and referred to a demo from others, but the link to the original text cannot be found. . . .

Let me talk about how to use it first:

//parameter 1: context 
//parameter 2: ResultHandler callback if yes is selected 
//parameter 3: earliest time of the selector 
//parameter 4: latest time of the selector (current time used in this article) 
//parameter 5: selector 
val currentCalender = Calendar.getInstance() val 
currentDate = DateUtil.format(currentCalender.getTime(), "yyyy/MM/dd") 
val timeSelector2 = TimeSelector(this,object :TimeSelector.ResultHandler{ 
    override fun handle( time: String?) { 
        birthday_tv.setText(time) 
    } 
},"1971/01/01", currentDate, "2001/01/01") 

//When you need to display optional hours and minutes, add it later The corresponding two parameters. At this time, the hour and minute of the selector will also be automatically displayed 
val timeSelector2 = TimeSelector(this,object :TimeSelector.ResultHandler{ 
    override fun handle(time: String?) { 
        birthday_tv.setText(time) 
    }
},"1971/01/01", currentDate, "2001/01/01","08:30","20:30")
timeSelector.setMode(TimeSelector.MODE.YMD)
timeSelector.show()

The scrolling control is a custom view, PickerView:

R.styleable.PickerView

public class PickerView extends View {
    /**
     * 新增字段 控制是否首尾相接循环显示 默认为循环显示
     */
    private boolean loop = true;


    /**
     * text之间间距和minTextSize之比
     */
    public static final float MARGIN_ALPHA = 2.8f;
    /**
     * 自动回滚到中间的速度
     */
    public static final float SPEED = 10;

    private List<String> mDataList;
    /**
     * 选中的位置,这个位置是mDataList的中心位置,一直不变
     */
    private int mCurrentSelected;
    private Paint mPaint, nPaint;

    private float mMaxTextSize = 80;
    private float mMinTextSize = 40;

    private float mMaxTextAlpha = 255;
    private float mMinTextAlpha = 120;

    private int mColorText = 0x333333;
    private int nColorText = 0x666666;

    private int mViewHeight;
    private int mViewWidth;

    private float mLastDownY;
    /**
     * 滑动的距离
     */
    private float mMoveLen = 0;
    private boolean isInit = false;
    private onSelectListener mSelectListener;
    private Timer timer;
    private MyTimerTask mTask;

    Handler updateHandler = new Handler() {

        @Override
        public void handleMessage(Message msg) {
            if (Math.abs(mMoveLen) < SPEED) {
                mMoveLen = 0;
                if (mTask != null) {
                    mTask.cancel();
                    mTask = null;
                    performSelect();
                }
            } else
                // 这里mMoveLen / Math.abs(mMoveLen)是为了保有mMoveLen的正负号,以实现上滚或下滚
                mMoveLen = mMoveLen - mMoveLen / Math.abs(mMoveLen) * SPEED;
            invalidate();
        }

    };
    private boolean canScroll = true;

    public PickerView(Context context) {
        super(context);
        init();
    }

    public PickerView(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.PickerView);
        loop = typedArray.getBoolean(R.styleable.PickerView_isLoop, loop);
        init();
    }

    public void setOnSelectListener(onSelectListener listener) {
        mSelectListener = listener;
    }

    private void performSelect() {
        if (mSelectListener != null)
            mSelectListener.onSelect(mDataList.get(mCurrentSelected));
    }

    public void setData(List<String> datas) {
        mDataList = datas;
        mCurrentSelected = datas.size() / 4;
        invalidate();
    }

    /**
     * 选择选中的item的index
     *
     * @param selected
     */
    public void setSelected(int selected) {
        mCurrentSelected = selected;
        if (loop) {
            int distance = mDataList.size() / 2 - mCurrentSelected;
            if (distance < 0)
                for (int i = 0; i < -distance; i++) {
                    moveHeadToTail();
                    mCurrentSelected--;
                }
            else if (distance > 0)
                for (int i = 0; i < distance; i++) {
                    moveTailToHead();
                    mCurrentSelected++;
                }
        }
        invalidate();
    }

    /**
     * 选择选中的内容
     *
     * @param mSelectItem
     */
    public void setSelected(String mSelectItem) {
        for (int i = 0; i < mDataList.size(); i++)
            if (mDataList.get(i).equals(mSelectItem)) {
                setSelected(i);
                break;
            }
    }


    private void moveHeadToTail() {
        if (loop) {
            String head = mDataList.get(0);
            mDataList.remove(0);
            mDataList.add(head);
        }
    }

    private void moveTailToHead() {
        if (loop) {
            String tail = mDataList.get(mDataList.size() - 1);
            mDataList.remove(mDataList.size() - 1);
            mDataList.add(0, tail);
        }

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mViewHeight = getMeasuredHeight();
        mViewWidth = getMeasuredWidth();
        // 按照View的高度计算字体大小
        mMaxTextSize = mViewHeight / 7f;
        mMinTextSize = mMaxTextSize / 2.2f;
        isInit = true;
        invalidate();
    }

    private void init() {
        timer = new Timer();
        mDataList = new ArrayList<String>();
        //第一个paint
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setStyle(Style.FILL);
        mPaint.setTextAlign(Align.CENTER);
        mPaint.setColor(getResources().getColor(R.color.main_color));
        //第二个paint
        nPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        nPaint.setStyle(Style.FILL);
        nPaint.setTextAlign(Align.CENTER);
        nPaint.setColor(mColorText);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 根据index绘制view
        if (isInit)
            drawData(canvas);
    }

    private void drawData(Canvas canvas) {
        // 先绘制选中的text再往上往下绘制其余的text
        float scale = parabola(mViewHeight / 4.0f, mMoveLen);
        float size = (mMaxTextSize - mMinTextSize) * scale + mMinTextSize;
        mPaint.setTextSize(size);
        mPaint.setAlpha((int) ((mMaxTextAlpha - mMinTextAlpha) * scale + mMinTextAlpha));
        // text居中绘制,注意baseline的计算才能达到居中,y值是text中心坐标
        float x = (float) (mViewWidth / 2.0);
        float y = (float) (mViewHeight / 2.0 + mMoveLen);
        FontMetricsInt fmi = mPaint.getFontMetricsInt();
        float baseline = (float) (y - (fmi.bottom / 2.0 + fmi.top / 2.0));

        canvas.drawText(mDataList.get(mCurrentSelected), x, baseline, mPaint);
        // 绘制上方data
        for (int i = 1; (mCurrentSelected - i) >= 0; i++) {
            drawOtherText(canvas, i, -1);
        }
        // 绘制下方data
        for (int i = 1; (mCurrentSelected + i) < mDataList.size(); i++) {
            drawOtherText(canvas, i, 1);
        }

    }

    /**
     * @param canvas
     * @param position 距离mCurrentSelected的差值
     * @param type     1表示向下绘制,-1表示向上绘制
     */
    private void drawOtherText(Canvas canvas, int position, int type) {
        float d = (float) (MARGIN_ALPHA * mMinTextSize * position + type
                * mMoveLen);
        float scale = parabola(mViewHeight / 4.0f, d);
        float size = (mMaxTextSize - mMinTextSize) * scale + mMinTextSize;
        nPaint.setTextSize(size);
        nPaint.setAlpha((int) ((mMaxTextAlpha - mMinTextAlpha) * scale + mMinTextAlpha));
        float y = (float) (mViewHeight / 2.0 + type * d);
        FontMetricsInt fmi = nPaint.getFontMetricsInt();
        float baseline = (float) (y - (fmi.bottom / 2.0 + fmi.top / 2.0));
        canvas.drawText(mDataList.get(mCurrentSelected + type * position),
                (float) (mViewWidth / 2.0), baseline, nPaint);
    }

    /**
     * 抛物线
     *
     * @param zero 零点坐标
     * @param x    偏移量
     * @return scale
     */
    private float parabola(float zero, float x) {
        float f = (float) (1 - Math.pow(x / zero, 2));
        return f < 0 ? 0 : f;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                doDown(event);
                break;
            case MotionEvent.ACTION_MOVE:
                mMoveLen += (event.getY() - mLastDownY);

                if (mMoveLen > MARGIN_ALPHA * mMinTextSize / 2) {
                    if (!loop && mCurrentSelected == 0) {
                        mLastDownY = event.getY();
                        invalidate();
                        return true;
                    }
                    if (!loop) mCurrentSelected--;
                    // 往下滑超过离开距离
                    moveTailToHead();
                    mMoveLen = mMoveLen - MARGIN_ALPHA * mMinTextSize;
                } else if (mMoveLen < -MARGIN_ALPHA * mMinTextSize / 2) {
                    if (mCurrentSelected == mDataList.size() - 1) {
                        mLastDownY = event.getY();
                        invalidate();
                        return true;
                    }
                    if (!loop) mCurrentSelected++;
                    // 往上滑超过离开距离
                    moveHeadToTail();
                    mMoveLen = mMoveLen + MARGIN_ALPHA * mMinTextSize;
                }

                mLastDownY = event.getY();
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                doUp(event);
                break;
        }
        return true;
    }

    private void doDown(MotionEvent event) {
        if (mTask != null) {
            mTask.cancel();
            mTask = null;
        }
        mLastDownY = event.getY();
    }

//    private void doMove(MotionEvent event) {
//
//        mMoveLen += (event.getY() - mLastDownY);
//
//        if (mMoveLen > MARGIN_ALPHA * mMinTextSize / 2) {
//            // 往下滑超过离开距离
//            moveTailToHead();
//            mMoveLen = mMoveLen - MARGIN_ALPHA * mMinTextSize;
//        } else if (mMoveLen < -MARGIN_ALPHA * mMinTextSize / 2) {
//            // 往上滑超过离开距离
//            moveHeadToTail();
//            mMoveLen = mMoveLen + MARGIN_ALPHA * mMinTextSize;
//        }
//
//        mLastDownY = event.getY();
//        invalidate();
//    }

    private void doUp(MotionEvent event) {
        // 抬起手后mCurrentSelected的位置由当前位置move到中间选中位置
        if (Math.abs(mMoveLen) < 0.0001) {
            mMoveLen = 0;
            return;
        }
        if (mTask != null) {
            mTask.cancel();
            mTask = null;
        }
        mTask = new MyTimerTask(updateHandler);
        timer.schedule(mTask, 0, 10);
    }

    class MyTimerTask extends TimerTask {
        Handler handler;

        public MyTimerTask(Handler handler) {
            this.handler = handler;
        }

        @Override
        public void run() {
            handler.sendMessage(handler.obtainMessage());
        }

    }

    public interface onSelectListener {
        void onSelect(String text);
    }

    public void setCanScroll(boolean canScroll) {
        this.canScroll = canScroll;
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        if (canScroll)
            return super.dispatchTouchEvent(event);
        else
            return false;
    }

    /**
     * 新增字段 控制内容是否首尾相连
     * by liuli
     *
     * @param isLoop
     */
    public void setIsLoop(boolean isLoop) {
        loop = isLoop;
    }
}

R.styleable.PickerView:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="PickerView">

        <attr name="isLoop" format="boolean" />

    </declare-styleable>
</resources>

 The main operation class is: TimeSelector:
 


public class TimeSelector {

    public interface ResultHandler {
        void handle(String time);
    }

    public enum SCROLLTYPE {

        HOUR(1),
        MINUTE(2);

        private SCROLLTYPE(int value) {
            this.value = value;
        }

        public int value;

    }

    /**
     * 模式,一种只显示年月日,一种显示年月日时分
     */
    public enum MODE {

        YMD(1),
        YMDHM(2);

        private MODE(int value) {
            this.value = value;
        }

        public int value;

    }


    private int scrollUnits = SCROLLTYPE.HOUR.value + SCROLLTYPE.MINUTE.value;//3
    private ResultHandler handler;
    private Context context;
//    private final String FORMAT_STR = "yyyy-MM-dd HH:mm";
    private final String FORMAT_STR = "yyyy/MM/dd";
    private final String RESULT_FORMAT_STR = "dd/MM/yyyy";
    private final String YEAR_FORMAT_STR = "yyyy";
    private final String MONTH_FORMAT_STR = "MM";
    private final String DAY_FORMAT_STR = "DD";

    private Dialog seletorDialog;//选择日期与时间弹框
    private PickerView year_pv;//控件 年
    private PickerView month_pv;//控件 月
    private PickerView day_pv;//控件 天
    private PickerView hour_pv;//控件 时
    private PickerView minute_pv;//控件 分

    private final int MAXMINUTE = 59;//最大分钟数
    private int MAXHOUR = 23;//最大小时数(结束时间小时数)
    private final int MINMINUTE = 0;//最小分钟数
    private int MINHOUR = 0;//最小小时数(开始时间小时数)
    private final int MAXMONTH = 12;//最大月份数

    private ArrayList<String> year, month, day, hour, minute;//根据设定的开始日期时间、结束日期时间 存储年月日时分的ArrayList
    //开始年、月、时、分 结束年、月、时、分
    private int startYear, startMonth, startDay, startHour, startMininute,
            endYear, endMonth, endDay, endHour, endMininute,
            minute_workStart, minute_workEnd, hour_workStart, hour_workEnd;
    private boolean spanYear, spanMon, spanDay, spanHour, spanMin;//年月日时分是否是时间跨度
    private Calendar selectedCalender = Calendar.getInstance();//获取选中的时间日历
    private final long ANIMATORDELAY = 200L;//延时动画
    private final long CHANGEDELAY = 90L;//修改延时
    private String workStart_str;//开始时间
    private String workEnd_str;//结束时间
    private Calendar startCalendar;//开始日期日历
    private Calendar endCalendar;//结束日期日历
    private TextView tv_cancle;//取消按钮
    private TextView tv_select, tv_title;//选择按钮、弹框标题
    private TextView hour_text;//“时” 字样
    private TextView minute_text;//“分” 字样

    private Calendar defaultCalendar;//默认选择的日历

    public TimeSelector(Context context, ResultHandler resultHandler, String startDate, String endDate, String defaultDate) {
        this.context = context;
        this.handler = resultHandler;
        startCalendar = Calendar.getInstance();
        endCalendar = Calendar.getInstance();
        defaultCalendar = Calendar.getInstance();
        startCalendar.setTime(DateUtil.parse(startDate, FORMAT_STR));//开始日期
        endCalendar.setTime(DateUtil.parse(endDate, FORMAT_STR));//结束日期
        defaultCalendar.setTime(DateUtil.parse(defaultDate, FORMAT_STR));//默认选择日期
        initDialog();
        initView();
    }


    public TimeSelector(Context context, ResultHandler resultHandler, String startDate, String endDate, String defaultDate, String workStartTime, String workEndTime) {
        this(context, resultHandler, startDate, endDate,defaultDate);
        this.workStart_str = workStartTime;
        this.workEnd_str = workEndTime;
    }

    /**
     * 外部调用,弹出时间选择dialog
     */
    public void show() {
        if (startCalendar.getTime().getTime() >= endCalendar.getTime().getTime()) {
            Toast.makeText(context, "start>end", Toast.LENGTH_SHORT).show();
            return;
        }
        if (defaultCalendar.getTime().getTime() < startCalendar.getTime().getTime()) {
            Toast.makeText(context, "default<start", Toast.LENGTH_SHORT).show();
            return;
        }
        if (defaultCalendar.getTime().getTime() > endCalendar.getTime().getTime()) {
            Toast.makeText(context, "default>end", Toast.LENGTH_SHORT).show();
            return;
        }

        if (!excuteWorkTime()) return;
        initParameter();
        initTimer();
        addListener();
        seletorDialog.show();


    }

    /**
     * 初始化日期时间选择弹框
     */
    private void initDialog() {
        if (seletorDialog == null) {
            seletorDialog = new Dialog(context, R.style.time_dialog);
            seletorDialog.setCancelable(false);
            seletorDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
            seletorDialog.setContentView(R.layout.dlg_selector);
            Window window = seletorDialog.getWindow();
            window.setGravity(Gravity.BOTTOM);
            WindowManager.LayoutParams lp = window.getAttributes();
            //宽度为屏幕的宽
            int width = ScreenUtil.getInstance(context).getScreenWidth();
            lp.width = width;
            window.setAttributes(lp);
        }
    }

    /**
     * 初始化控件id,设置点击事件
     */
    private void initView() {
        year_pv = (PickerView) seletorDialog.findViewById(R.id.year_pv);
        month_pv = (PickerView) seletorDialog.findViewById(R.id.month_pv);
        day_pv = (PickerView) seletorDialog.findViewById(R.id.day_pv);
        hour_pv = (PickerView) seletorDialog.findViewById(R.id.hour_pv);
        minute_pv = (PickerView) seletorDialog.findViewById(R.id.minute_pv);
        tv_cancle = (TextView) seletorDialog.findViewById(R.id.tv_cancle);
        tv_select = (TextView) seletorDialog.findViewById(R.id.tv_select);
        tv_title = (TextView) seletorDialog.findViewById(R.id.tv_title);
        hour_text = (TextView) seletorDialog.findViewById(R.id.hour_text);
        minute_text = (TextView) seletorDialog.findViewById(R.id.minute_text);

        tv_cancle.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                seletorDialog.dismiss();
            }
        });
        tv_select.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                handler.handle(DateUtil.format(selectedCalender.getTime(), RESULT_FORMAT_STR));
                seletorDialog.dismiss();
            }
        });

    }

    /**
     * 此时的startCalendar、endCalendar都是经过检测是,正确时间,来分别获取年月日时分
     */
    private void initParameter() {
        startYear = startCalendar.get(Calendar.YEAR);
        startMonth = startCalendar.get(Calendar.MONTH) + 1;
        startDay = startCalendar.get(Calendar.DAY_OF_MONTH);
        startHour = startCalendar.get(Calendar.HOUR_OF_DAY);
        startMininute = startCalendar.get(Calendar.MINUTE);
        endYear = endCalendar.get(Calendar.YEAR);
        endMonth = endCalendar.get(Calendar.MONTH) + 1;
        endDay = endCalendar.get(Calendar.DAY_OF_MONTH);
        endHour = endCalendar.get(Calendar.HOUR_OF_DAY);
        endMininute = endCalendar.get(Calendar.MINUTE);
        spanYear = startYear != endYear;//
        spanMon = (!spanYear) && (startMonth != endMonth);//???年等 月不等
        spanDay = (!spanMon) && (startDay != endDay);//???月等 日不等
        spanHour = (!spanDay) && (startHour != endHour);
        spanMin = (!spanHour) && (startMininute != endMininute);
//        selectedCalender.setTime(startCalendar.getTime());
        //--------------------------------------
//        startYear = defaultCalendar.get(Calendar.YEAR);
//        startMonth = defaultCalendar.get(Calendar.MONTH) + 1;
//        startDay = defaultCalendar.get(Calendar.DAY_OF_MONTH);
//        startHour = defaultCalendar.get(Calendar.HOUR_OF_DAY);
//        startMininute = defaultCalendar.get(Calendar.MINUTE);
        //设置初始化时默认选择时间
        selectedCalender.setTime(defaultCalendar.getTime());

    }

    /**
     * ???
     * 根据年月日时分开始结束时间,造数据
     *
     */
    private void initTimer() {
        initArrayList();

        if (spanYear)  {//分别向年月日时分list里添加数据
            for (int i = startYear; i <= endYear; i++) {
                year.add(String.valueOf(i));
            }
            for (int i = startMonth; i <= MAXMONTH; i++) {//<12
                month.add(fomatTimeUnit(i));
            }
            for (int i = startDay; i <= startCalendar.getActualMaximum(Calendar.DAY_OF_MONTH); i++) {//startCalendar.getActualMaximum(Calendar.DAY_OF_MONTH) startCalendar中月份的最大值
                day.add(fomatTimeUnit(i));
            }
            if ((scrollUnits & SCROLLTYPE.HOUR.value) != SCROLLTYPE.HOUR.value) {//(0 & 1) !=1
                hour.add(fomatTimeUnit(startHour));
            } else {
                for (int i = startHour; i <= MAXHOUR; i++) {//MAXHOUR 开始时间的小时数
                    hour.add(fomatTimeUnit(i));
                }
            }

            if ((scrollUnits & SCROLLTYPE.MINUTE.value) != SCROLLTYPE.MINUTE.value) {//(0 & 2) !=1
                minute.add(fomatTimeUnit(startMininute));
            } else {
                for (int i = startMininute; i <= MAXMINUTE; i++) {
                    minute.add(fomatTimeUnit(i));
                }
            }

        } else if (spanMon) {
            year.add(String.valueOf(startYear));
            for (int i = startMonth; i <= endMonth; i++) {
                month.add(fomatTimeUnit(i));
            }
            for (int i = startDay; i <= startCalendar.getActualMaximum(Calendar.DAY_OF_MONTH); i++) {
                day.add(fomatTimeUnit(i));
            }
            if ((scrollUnits & SCROLLTYPE.HOUR.value) != SCROLLTYPE.HOUR.value) {
                hour.add(fomatTimeUnit(startHour));
            } else {
                for (int i = startHour; i <= MAXHOUR; i++) {
                    hour.add(fomatTimeUnit(i));
                }
            }

            if ((scrollUnits & SCROLLTYPE.MINUTE.value) != SCROLLTYPE.MINUTE.value) {
                minute.add(fomatTimeUnit(startMininute));
            } else {
                for (int i = startMininute; i <= MAXMINUTE; i++) {
                    minute.add(fomatTimeUnit(i));
                }
            }
        } else if (spanDay) {
            year.add(String.valueOf(startYear));
            month.add(fomatTimeUnit(startMonth));
            for (int i = startDay; i <= endDay; i++) {
                day.add(fomatTimeUnit(i));
            }
            if ((scrollUnits & SCROLLTYPE.HOUR.value) != SCROLLTYPE.HOUR.value) {
                hour.add(fomatTimeUnit(startHour));
            } else {
                for (int i = startHour; i <= MAXHOUR; i++) {
                    hour.add(fomatTimeUnit(i));
                }
            }

            if ((scrollUnits & SCROLLTYPE.MINUTE.value) != SCROLLTYPE.MINUTE.value) {
                minute.add(fomatTimeUnit(startMininute));
            } else {
                for (int i = startMininute; i <= MAXMINUTE; i++) {
                    minute.add(fomatTimeUnit(i));
                }
            }

        } else if (spanHour) {
            year.add(String.valueOf(startYear));
            month.add(fomatTimeUnit(startMonth));
            day.add(fomatTimeUnit(startDay));

            if ((scrollUnits & SCROLLTYPE.HOUR.value) != SCROLLTYPE.HOUR.value) {
                hour.add(fomatTimeUnit(startHour));
            } else {
                for (int i = startHour; i <= endHour; i++) {
                    hour.add(fomatTimeUnit(i));
                }

            }

            if ((scrollUnits & SCROLLTYPE.MINUTE.value) != SCROLLTYPE.MINUTE.value) {
                minute.add(fomatTimeUnit(startMininute));
            } else {
                for (int i = startMininute; i <= MAXMINUTE; i++) {
                    minute.add(fomatTimeUnit(i));
                }
            }


        } else if (spanMin) {
            year.add(String.valueOf(startYear));
            month.add(fomatTimeUnit(startMonth));
            day.add(fomatTimeUnit(startDay));
            hour.add(fomatTimeUnit(startHour));


            if ((scrollUnits & SCROLLTYPE.MINUTE.value) != SCROLLTYPE.MINUTE.value) {
                minute.add(fomatTimeUnit(startMininute));
            } else {
                for (int i = startMininute; i <= endMininute; i++) {
                    minute.add(fomatTimeUnit(i));
                }
            }
        }


        loadComponent();

    }

    /**
     * 主要为startCalendar、endCalendar设置正确与结束的日期、时间
     * 并根据startCalendar、endCalendar设置MINHOUR与MAXHOUR
     * @return
     */
    private boolean excuteWorkTime() {
        boolean res = true;
        //如果设置了开始时间与结束时间
        if (!TextUtil.isEmpty(workStart_str) && !TextUtil.isEmpty(workEnd_str)) {
            String[] start = workStart_str.split(":");//":"分割时间
            String[] end = workEnd_str.split(":");
            hour_workStart = Integer.parseInt(start[0]);//开始时间的小时
            minute_workStart = Integer.parseInt(start[1]);//开始时间的分钟
            hour_workEnd = Integer.parseInt(end[0]);//结束时间的小时
            minute_workEnd = Integer.parseInt(end[1]);//结束时间的分钟
            //1971-01-01T08:00:00.000+0800 此时的workStartCalendar是有小时和分钟的,但startCalendar不一定有,因为在只设置日期不设置时间时不会有小时和分钟
            Calendar workStartCalendar = Calendar.getInstance();
            workStartCalendar.setTime(startCalendar.getTime());//使用日历,获取初始化时设置开始的日期,再设置给workStartCalendar
            workStartCalendar.set(Calendar.HOUR_OF_DAY, hour_workStart);//根据获取的开始时间的小时数,设置小时
            workStartCalendar.set(Calendar.MINUTE, minute_workStart);//根据获取的开始时间的分钟数,设置分钟
            //2022-03-18T20:00:00.000+0800 此时的workEndCalendar是有小时和分钟的,但endCalendar不一定有,因为在只设置日期不设置时间时不会有小时和分钟
            Calendar workEndCalendar = Calendar.getInstance();
            workEndCalendar.setTime(endCalendar.getTime());//使用日历,获取初始化时设置结束的日期,再设置给workEndCalendar
            workEndCalendar.set(Calendar.HOUR_OF_DAY, hour_workEnd);//根据获取的结束时间的小时数,设置小时
            workEndCalendar.set(Calendar.MINUTE, minute_workEnd);//根据获取的结束时间的分钟数,设置分钟


            Calendar startTime = Calendar.getInstance();
            Calendar endTime = Calendar.getInstance();
            Calendar startWorkTime = Calendar.getInstance();
            Calendar endWorkTime = Calendar.getInstance();
            //这时的startCalendar、endCalendar 如果没有小时分钟的话,startTime、endTime的小时分钟也是0
            startTime.set(Calendar.HOUR_OF_DAY, startCalendar.get(Calendar.HOUR_OF_DAY));
            startTime.set(Calendar.MINUTE, startCalendar.get(Calendar.MINUTE));
            endTime.set(Calendar.HOUR_OF_DAY, endCalendar.get(Calendar.HOUR_OF_DAY));
            endTime.set(Calendar.MINUTE, endCalendar.get(Calendar.MINUTE));
            //走到这时,workStartCalendar、workEndCalendar一般都是有小时分钟的,赋值给startWorkTime
            startWorkTime.set(Calendar.HOUR_OF_DAY, workStartCalendar.get(Calendar.HOUR_OF_DAY));
            startWorkTime.set(Calendar.MINUTE, workStartCalendar.get(Calendar.MINUTE));
            endWorkTime.set(Calendar.HOUR_OF_DAY, workEndCalendar.get(Calendar.HOUR_OF_DAY));
            endWorkTime.set(Calendar.MINUTE, workEndCalendar.get(Calendar.MINUTE));

            //比较毫秒值~~不正常的情况  startTime与endTime是否相同       startWorkTime是否小于startTime        endWorkTime是否小于startTime
            if (startTime.getTime().getTime() == endTime.getTime().getTime() || (startWorkTime.getTime().getTime() < startTime.getTime().getTime() && endWorkTime.getTime().getTime() < startTime.getTime().getTime())) {
                Toast.makeText(context, "Wrong parames!", Toast.LENGTH_LONG).show();
                return false;
            }
            //比较毫秒值~~正常的情况    startCalendar比workStartCalendar 毫秒值小 取大的?? 1971-01-01T00:00:00.000+0800 < 1971-01-01T08:00:00.000+0800
            startCalendar.setTime(startCalendar.getTime().getTime() < workStartCalendar.getTime().getTime() ? workStartCalendar.getTime() : startCalendar.getTime());
            //endCalendar比workEndCalendar 毫秒值小 取小的?? 2022-03-18T00:00:00.000+0800 >2022-03-18T20:00:00.000+0800
            endCalendar.setTime(endCalendar.getTime().getTime() > workEndCalendar.getTime().getTime() ? workEndCalendar.getTime() : endCalendar.getTime());
            MINHOUR = workStartCalendar.get(Calendar.HOUR_OF_DAY);
            MAXHOUR = workEndCalendar.get(Calendar.HOUR_OF_DAY);

        }
        return res;


    }

    /**
     * 格式月日,时分,单位补0
     * @param unit
     * @return
     */
    private String fomatTimeUnit(int unit) {
        return unit < 10 ? "0" + String.valueOf(unit) : String.valueOf(unit);
    }

    /**
     * 初始化年月日时分list
     */
    private void initArrayList() {
        if (year == null) year = new ArrayList<>();
        if (month == null) month = new ArrayList<>();
        if (day == null) day = new ArrayList<>();
        if (hour == null) hour = new ArrayList<>();
        if (minute == null) minute = new ArrayList<>();
        year.clear();
        month.clear();
        day.clear();
        hour.clear();
        minute.clear();
    }


    /**
     * 初始化各个PickerView监听
     */
    private void addListener() {
        year_pv.setOnSelectListener(new PickerView.onSelectListener() {
            @Override
            public void onSelect(String text) {
                selectedCalender.set(Calendar.YEAR, Integer.parseInt(text));//根据选择的年份设置selectedCalender
                monthChange();
            }
        });
        month_pv.setOnSelectListener(new PickerView.onSelectListener() {
            @Override
            public void onSelect(String text) {
                selectedCalender.set(Calendar.DAY_OF_MONTH, 1);
                selectedCalender.set(Calendar.MONTH, Integer.parseInt(text) - 1);//月份的特殊性要-1
                dayChange();
            }
        });
        day_pv.setOnSelectListener(new PickerView.onSelectListener() {
            @Override
            public void onSelect(String text) {
                selectedCalender.set(Calendar.DAY_OF_MONTH, Integer.parseInt(text));
                hourChange();
            }
        });
        hour_pv.setOnSelectListener(new PickerView.onSelectListener() {
            @Override
            public void onSelect(String text) {
                selectedCalender.set(Calendar.HOUR_OF_DAY, Integer.parseInt(text));
                minuteChange();
            }
        });
        minute_pv.setOnSelectListener(new PickerView.onSelectListener() {
            @Override
            public void onSelect(String text) {
                selectedCalender.set(Calendar.MINUTE, Integer.parseInt(text));
            }
        });

    }

    /**
     * 像年月日时分的PickerView 设置数据
     * 可以都设置为开始时间,
     * 但如果selectedCalender 不为空时,根据selectedCalender 的年月日 设置PickerView默认显示的年月日时分
     */
    private void loadComponent() {
        //        year_pv.setSelected(0);
//        month_pv.setSelected(0);
//        day_pv.setSelected(0);
//        hour_pv.setSelected(0);
//        minute_pv.setSelected(0);

        year_pv.setData(year);
        month_pv.setData(month);
        day_pv.setData(day);
        hour_pv.setData(hour);
        minute_pv.setData(minute);

        String yearStr = DateUtil.format(selectedCalender.getTime(), YEAR_FORMAT_STR);
        String monthStr = DateUtil.format(selectedCalender.getTime(), MONTH_FORMAT_STR);
        String dayStr = DateUtil.format(selectedCalender.getTime(), DAY_FORMAT_STR);

        if(year.contains(yearStr)){
            int index = year.indexOf(yearStr);
            year_pv.setSelected(index);
        }else{
            year_pv.setSelected(0);
        }
        if(month.contains(monthStr)){
            int index = month.indexOf(monthStr);
            month_pv.setSelected(index);
        }else{
            month_pv.setSelected(0);
        }
        if(day.contains(dayStr)){
            int index = day.indexOf(dayStr);
            day_pv.setSelected(index);
        }else{
            day_pv.setSelected(0);
        }
        hour_pv.setSelected(0);
        minute_pv.setSelected(0);

        excuteScroll();
    }

    private void excuteScroll() {
        year_pv.setCanScroll(year.size() > 1);
        month_pv.setCanScroll(month.size() > 1);
        day_pv.setCanScroll(day.size() > 1);
        hour_pv.setCanScroll(hour.size() > 1 && (scrollUnits & SCROLLTYPE.HOUR.value) == SCROLLTYPE.HOUR.value);
        minute_pv.setCanScroll(minute.size() > 1 && (scrollUnits & SCROLLTYPE.MINUTE.value) == SCROLLTYPE.MINUTE.value);
    }

    /**
     * 处理月份变化的数据
     */
    private void monthChange() {

        month.clear();
        int selectedYear = selectedCalender.get(Calendar.YEAR);
        if (selectedYear == startYear) {//选择的年份是开始年份,则从开始月份重置monthList
            for (int i = startMonth; i <= MAXMONTH; i++) {
                month.add(fomatTimeUnit(i));
            }
        } else if (selectedYear == endYear) {//选择的年份是结束年份,则从开始月份重置monthList
            for (int i = 1; i <= endMonth; i++) {
                month.add(fomatTimeUnit(i));
            }
        } else {//既不是开始年也不是结束年,就按12个月处理
            for (int i = 1; i <= MAXMONTH; i++) {
                month.add(fomatTimeUnit(i));
            }
        }
        selectedCalender.set(Calendar.MONTH, Integer.parseInt(month.get(0)) - 1);//设置selectedCalender的月份,因为Calendar.MONTH的特殊之处,不能直接使用Integer.parseInt(month.get(0)),要-1
        month_pv.setData(month);
        month_pv.setSelected(0);
        excuteAnimator(ANIMATORDELAY, month_pv);

        month_pv.postDelayed(new Runnable() {
            @Override
            public void run() {
                dayChange();//月份变化后再处理日变化
            }
        }, CHANGEDELAY);

    }

    /**
     * 处理日变化的数据
     */
    private void dayChange() {

        day.clear();
        int selectedYear = selectedCalender.get(Calendar.YEAR);
        int selectedMonth = selectedCalender.get(Calendar.MONTH) + 1;
        if (selectedYear == startYear && selectedMonth == startMonth) {//选择的年份是年份\月份都是开始时间,则要从开始日期的天数重置dayList
            for (int i = startDay; i <= selectedCalender.getActualMaximum(Calendar.DAY_OF_MONTH); i++) {
                day.add(fomatTimeUnit(i));
            }
        } else if (selectedYear == endYear && selectedMonth == endMonth) {//选择的年份是年份\月份都是结束时间,则要从结束日期的天数重置dayList
            for (int i = 1; i <= endDay; i++) {
                day.add(fomatTimeUnit(i));
            }
        } else {//既不是开始日也不是结束日,就按正常天数处理
            for (int i = 1; i <= selectedCalender.getActualMaximum(Calendar.DAY_OF_MONTH); i++) {
                day.add(fomatTimeUnit(i));
            }
        }
        selectedCalender.set(Calendar.DAY_OF_MONTH, Integer.parseInt(day.get(0)));
        day_pv.setData(day);
        day_pv.setSelected(0);
        excuteAnimator(ANIMATORDELAY, day_pv);

        day_pv.postDelayed(new Runnable() {
            @Override
            public void run() {
                hourChange();
            }
        }, CHANGEDELAY);
    }

    /**
     * 处理小时变化的数据
     */
    private void hourChange() {
        if ((scrollUnits & SCROLLTYPE.HOUR.value) == SCROLLTYPE.HOUR.value) {
            hour.clear();
            int selectedYear = selectedCalender.get(Calendar.YEAR);
            int selectedMonth = selectedCalender.get(Calendar.MONTH) + 1;
            int selectedDay = selectedCalender.get(Calendar.DAY_OF_MONTH);

            if (selectedYear == startYear && selectedMonth == startMonth && selectedDay == startDay) {//选择的年份是年份\月份\日都是开始时间,则要从开始日期小时数-最晚小时数重置hourList
                for (int i = startHour; i <= MAXHOUR; i++) {//结束时间小时数
                    hour.add(fomatTimeUnit(i));
                }
            } else if (selectedYear == endYear && selectedMonth == endMonth && selectedDay == endDay) {//选择的年份是年份\月份\日都是结束时间,则要从最早小时数-结束日期小时数重置hourList
                for (int i = MINHOUR; i <= endHour; i++) {//开始时间小时数
                    hour.add(fomatTimeUnit(i));
                }
            } else {

                for (int i = MINHOUR; i <= MAXHOUR; i++) {//既不是开始时也不是结束时,就按最小MINHOUR最大MAXHOUR
                    hour.add(fomatTimeUnit(i));
                }

            }
            selectedCalender.set(Calendar.HOUR_OF_DAY, Integer.parseInt(hour.get(0)));
            hour_pv.setData(hour);
            hour_pv.setSelected(0);
            excuteAnimator(ANIMATORDELAY, hour_pv);
        }
        hour_pv.postDelayed(new Runnable() {
            @Override
            public void run() {
                minuteChange();
            }
        }, CHANGEDELAY);

    }

    /**
     * 处理分钟变化的数据
     */
    private void minuteChange() {
        if ((scrollUnits & SCROLLTYPE.MINUTE.value) == SCROLLTYPE.MINUTE.value) {
            minute.clear();
            int selectedYear = selectedCalender.get(Calendar.YEAR);
            int selectedMonth = selectedCalender.get(Calendar.MONTH) + 1;
            int selectedDay = selectedCalender.get(Calendar.DAY_OF_MONTH);
            int selectedHour = selectedCalender.get(Calendar.HOUR_OF_DAY);
            //选择的年份是年份\月份\日\时都是开始时间,则要从开始日期分钟数-最晚分钟数重置minuteList
            if (selectedYear == startYear && selectedMonth == startMonth && selectedDay == startDay && selectedHour == startHour) {
                for (int i = startMininute; i <= MAXMINUTE; i++) {
                    minute.add(fomatTimeUnit(i));
                }
                //选择的年份是年份\月份\日\时都是结束时间,则要从最早分钟数-结束日期分钟数重置minuteList
            } else if (selectedYear == endYear && selectedMonth == endMonth && selectedDay == endDay && selectedHour == endHour) {
                for (int i = MINMINUTE; i <= endMininute; i++) {
                    minute.add(fomatTimeUnit(i));
                }
                //选择的小时数等于开始时间的小时数,则要从开始分钟数-最晚分钟数重置minuteList
            } else if (selectedHour == hour_workStart) {
                for (int i = minute_workStart; i <= MAXMINUTE; i++) {
                    minute.add(fomatTimeUnit(i));
                }
                //选择的小时数等于结束时间的小时数,则要从开始分钟数-最晚分钟数重置minuteList
            } else if (selectedHour == hour_workEnd) {
                for (int i = MINMINUTE; i <= minute_workEnd; i++) {
                    minute.add(fomatTimeUnit(i));
                }
            } else {
                //没有限制,则要从最早分钟数-最晚分钟数重置minuteList
                for (int i = MINMINUTE; i <= MAXMINUTE; i++) {
                    minute.add(fomatTimeUnit(i));
                }
            }
            selectedCalender.set(Calendar.MINUTE, Integer.parseInt(minute.get(0)));
            minute_pv.setData(minute);
            minute_pv.setSelected(0);
            excuteAnimator(ANIMATORDELAY, minute_pv);

        }
        excuteScroll();


    }

    /**
     * 给PickerView 设置渐变、缩放动画(非必要)
     * @param ANIMATORDELAY
     * @param view
     */
    private void excuteAnimator(long ANIMATORDELAY, View view) {
        PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("alpha", 1f,
                0f, 1f);
        PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("scaleX", 1f,
                1.3f, 1f);
        PropertyValuesHolder pvhZ = PropertyValuesHolder.ofFloat("scaleY", 1f,
                1.3f, 1f);
        ObjectAnimator.ofPropertyValuesHolder(view,pvhX, pvhY, pvhZ).setDuration(ANIMATORDELAY).start();
    }

    /**
     * 设置右上角文案
     * @param str
     */
    public void setNextBtTip(String str) {
        tv_select.setText(str);
    }

    /**
     *设置中间文案
     * @param str
     */
    public void setTitle(String str) {
        tv_title.setText(str);
    }

    /**
     * ?????
     * @param scrolltypes
     * @return
     */
    public int disScrollUnit(SCROLLTYPE... scrolltypes) {
        if (scrolltypes == null || scrolltypes.length == 0)
            scrollUnits = SCROLLTYPE.HOUR.value + SCROLLTYPE.MINUTE.value;//3
        for (SCROLLTYPE scrolltype : scrolltypes) {
            scrollUnits ^= scrolltype.value;//scrollUnits基础上-scrolltype.value
        }
        return scrollUnits;
    }

    public void setMode(MODE mode) {
        switch (mode.value) {
            case 1://只显示年月日的控件
                disScrollUnit(SCROLLTYPE.HOUR, SCROLLTYPE.MINUTE);
                hour_pv.setVisibility(View.GONE);
                minute_pv.setVisibility(View.GONE);
                hour_text.setVisibility(View.GONE);
                minute_text.setVisibility(View.GONE);
                break;
            case 2://显示年月日时分的控件
                disScrollUnit();
                hour_pv.setVisibility(View.VISIBLE);
                minute_pv.setVisibility(View.VISIBLE);
                hour_text.setVisibility(View.VISIBLE);
                minute_text.setVisibility(View.VISIBLE);
                break;

        }
    }

    public void setIsLoop(boolean isLoop) {
        this.year_pv.setIsLoop(isLoop);
        this.month_pv.setIsLoop(isLoop);
        this.day_pv.setIsLoop(isLoop);
        this.hour_pv.setIsLoop(isLoop);
        this.minute_pv.setIsLoop(isLoop);
    }
}
R.style.time_dialog:
    <style name="time_dialog" parent="android:style/Theme.Dialog">
        <item name="android:windowFrame">@null</item>
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowIsFloating">true</item>
        <item name="android:windowContentOverlay">@null</item>
        <item name="android:windowBackground">@color/white</item>
    </style>
R.layout.dlg_selector:
<?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:orientation="vertical">

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="#ffffff"
            android:padding="10dp">

            <TextView
                android:id="@+id/tv_cancle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:padding="10dp"
                android:text="@string/cancel"
                android:textColor="@color/main_color"
                android:textSize="16sp" />


            <TextView
                android:id="@+id/tv_title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:text="@string/chosetime"
                android:textColor="@color/main_color"
                android:textSize="20sp" />


            <TextView
                android:id="@+id/tv_select"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:layout_centerVertical="true"
                android:padding="10dp"
                android:text="@string/chose"
                android:textColor="@color/main_color"
                android:textSize="16sp" />

        </RelativeLayout>

        <View
            android:layout_width="fill_parent"
            android:layout_height="0.5dp"
            android:background="#11112233" />

        <RelativeLayout
            android:layout_width="fill_parent"
            android:layout_height="wrap_content">


            <LinearLayout
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="5dp"
                android:background="#ffffff"
                android:orientation="horizontal"
                android:paddingLeft="20dp"
                android:paddingTop="15dp"
                android:paddingRight="20dp"
                android:paddingBottom="15dp">

                <com.sange.cashfloater.cfwidget.timeselector.view.PickerView
                    android:id="@+id/year_pv"
                    android:layout_width="0dp"
                    android:layout_height="160dp"
                    android:layout_weight="3" />


                <TextView
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:gravity="center"
                    android:text="年"
                    android:textColor="#333333"
                    android:textSize="18sp" />

                <com.sange.cashfloater.cfwidget.timeselector.view.PickerView
                    android:id="@+id/month_pv"
                    android:layout_width="0dp"
                    android:layout_height="160dp"
                    android:layout_weight="2"
                    app:isLoop="false" />

                <TextView
                    android:text="月"
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:gravity="center"
                    android:textColor="#333333"
                    android:textSize="18sp" />

                <com.sange.cashfloater.cfwidget.timeselector.view.PickerView
                    android:id="@+id/day_pv"
                    android:layout_width="0dp"
                    android:layout_height="160dp"
                    android:layout_weight="2" />


                <TextView
                    android:text="日"
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:gravity="center"
                    android:textColor="#333333"
                    android:textSize="18sp" />


                <com.sange.cashfloater.cfwidget.timeselector.view.PickerView
                    android:id="@+id/hour_pv"
                    android:layout_width="0dp"
                    android:layout_height="160dp"
                    android:layout_weight="2" />

                <TextView
                    android:id="@+id/hour_text"
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:gravity="center"
                    android:text="时"
                    android:textColor="#333333"
                    android:textSize="18sp" />

                <com.sange.cashfloater.cfwidget.timeselector.view.PickerView
                    android:id="@+id/minute_pv"
                    android:layout_width="0dp"
                    android:layout_height="160dp"
                    android:layout_weight="2" />

                <TextView
                    android:id="@+id/minute_text"
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:text="分"
                    android:gravity="center"
                    android:textColor="#333333"
                    android:textSize="18sp" />
            </LinearLayout>


        </RelativeLayout>

    </LinearLayout>


</LinearLayout>

Related tool classes:
ScreenUtil:

public class ScreenUtil {

	public static int height;
	public static int width;
	private Context context;

	private static ScreenUtil instance;

	public static ScreenUtil getInstance(Context context) {
		if (instance == null) {
			instance = new ScreenUtil(context);
		}
		return instance;
	}

	private ScreenUtil(Context context) {
		this.context = context;
		WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
		DisplayMetrics dm = new DisplayMetrics();
		manager.getDefaultDisplay().getMetrics(dm);
		width = dm.widthPixels;
		height = dm.heightPixels;
	}

	public int getScreenWidth() {
		return width;
	}

}
DateUtil:

public class DateUtil {
    public static Date parse(String strDate, String pattern) {

        if (TextUtil.isEmpty(strDate)) {
            return null;
        }
        try {
            SimpleDateFormat df = new SimpleDateFormat(pattern);
            return df.parse(strDate);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    public static String format(Date date, String pattern) {
        String returnValue = "";
        if (date != null) {
            SimpleDateFormat df = new SimpleDateFormat(pattern);
            returnValue = df.format(date);
        }
        return (returnValue);
    }

}
TextUtil:

public class TextUtil {

    public static boolean isEmpty(String str) {
        if (str == null || "".equals(str) || "null".equalsIgnoreCase(str)) {
            return true;
        }
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            if (c != ' ' && c != '\t' && c != '\r' && c != '\n') {
                return false;
            }
        }
        return true;
    }

}

over~

Guess you like

Origin blog.csdn.net/NewActivity/article/details/123599792