自定义时间选择器

自定义时间选择器

这里使用符合控件的形式来编写,过于复杂这难于理解,能够高效实现需求。

一、时间选择器需求分析

这里主要作用是明确却所需要的功能,才能确定所需要的的技术,方便提前阅读相关资料


dialog0.png

二、技术分析

dialog1.png
  1. 创建显示弹窗,采用的DialogFragment,PopWindow的话,太多问题了,对于有虚拟按键的剧中显示,背景透明度等不好控制,而且官方推荐使用DialogFragment
    能够有效关系生命周期
 Bundle bundle = new Bundle();
        bundle.putString(TimerPickDialogFragment.TITLE, "行程时间");
        bundle.putLong(TimerPickDialogFragment.TIME_START, System.currentTimeMillis());
        bundle.putLong(TimerPickDialogFragment.TIME_END, System.currentTimeMillis());
        new TimerPickDialogFragment().setOnTimeClickListener(new TimerPickDialogFragment.OnTimeClickListener() {
            @Override
            public void onClick(View view, TimerPickDialogFragment timerPickDialogFragment) {
                switch (view.getId()) {
                    case R.id.tvTimerPickerCancel:
                        timerPickDialogFragment.dismiss();
                        break;
                    case R.id.tvTimerPickerSure:
                        timerPickDialogFragment.dismiss();
                        break;
                    default:
                        break;
                }
            }
        }).setOnStartCurrentDateListener(new TimerPickDialogFragment.OnStartCurrentDateListener() {
            @Override
            public void onStartCurrentDate(View view, long date, TimerPickDialogFragment timerPickDialogFragment) {

            }
        }).setOnEndCurrentDateListener(new TimerPickDialogFragment.OnEndCurrentDateListener() {
            @Override
            public void onEndCurrentDate(View view, long date, TimerPickDialogFragment timerPickDialogFragment) {

            }
        }).addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {

            }

            @Override
            public void afterTextChanged(Editable s) {

            }
        }).isShowRemark(true)
                .updateText("时间选择器")
                .setBundle(bundle)
                .show(getFragmentManager(), "Time");

这里首先要对DialogFragment控件熟知,不懂得同学可以先移步官方文档,这里也对一些背景设定做下说明

public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
        // 修改显示的背景色
        Window window = getDialog().getWindow();
        window.setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(getActivity(), R.color.c_99000000)));
        // 修改输入法对弹窗的影响
        window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
        View view = inflater.inflate(R.layout.popwin_time_picker, container, false);
        unbinder = ButterKnife.bind(this, view);

        if (bundle != null) {
            timeStart = bundle.getLong(TIME_START);
            timeEnd = bundle.getLong(TIME_END);
            tvTimerPickerTitle.setText(bundle.getString(TITLE));
        } else if (timeStart <= 0 && timeEnd <= 0) {
            timeStart = timeEnd = Calendar.getInstance().getTimeInMillis();
        }
        if (textWatcher != null) {
            edTimerPickerRemark.addTextChangedListener(textWatcher);
        }

        if (isShow) {
            if (!TextUtils.isEmpty(remark)) {
                edTimerPickerRemark.setText(remark);
            }
            tvTimerPickerRemarkTitle.setVisibility(View.VISIBLE);
            edTimerPickerRemark.setVisibility(View.VISIBLE);
        } else {
            tvTimerPickerRemarkTitle.setVisibility(View.GONE);
            edTimerPickerRemark.setVisibility(View.GONE);
        }
        /**
         * 设置不可点击
         */
        //    pvTimerPickerStart.setWheelEnabled(false);
        //时间回调
        pvTimerPickerStart.setOnCurrentDateListener(new PickView.OnCurrentDateListener() {
            @Override
            public void onCurrentDate(View view, long date) {
                if (onStartCurrentDateListener != null) {
                    onStartCurrentDateListener.onStartCurrentDate(view, date, TimerPickDialogFragment.this);
                }
            }
        });
        pvTimerPickerEnd.setOnCurrentDateListener(new PickView.OnCurrentDateListener() {
            @Override
            public void onCurrentDate(View view, long date) {
                if (onEndCurrentDateListener != null) {
                    onEndCurrentDateListener.onEndCurrentDate(view, date, TimerPickDialogFragment.this);
                }
            }
        });

        pvTimerPickerStart.updateDate(timeStart);
        pvTimerPickerEnd.updateDate(timeEnd);
        return view;
    }
  1. 设计具体的滚动Item
    时间选择器有年月日时分,五个大Item 组合而成
package wudiplk.com.mycustomview.custom_view;

import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;

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

/**
 * @author Wudi
 * @date 2018/1/16
 */

public class PickView extends LinearLayout {
    private Context context;
    private WheelView rvYear, rvMonth, rvDay, rvHour, rvMinute;

    private List<String> yearList = new ArrayList<>();
    private List<String> monthList = new ArrayList<>();
    private List<String> dayList = new ArrayList<>();
    private List<String> hourList = new ArrayList<>();
    private List<String> minuteList = new ArrayList<>();
    private Calendar calendar;
    private int yearPosition, monthPosition, dayPosition, hourPosition, minutePosition;

    public PickView(Context context) {
        super(context);
        this.context = context;
    }

    public PickView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
    }

    public PickView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.context = context;
    }


    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if (calendar == null) {
            calendar = Calendar.getInstance();
        }
        Handler handler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                initView();
                return false;
            }
        });
        handler.sendEmptyMessage(1);
    }


    private void initView() {

        setOrientation(HORIZONTAL);

        // 初始化时间
        initDate();
        // 初始化控件
        rvYear = new WheelView(context, calendar, Calendar.YEAR, getMeasuredWidth(), getMeasuredHeight(), yearList, yearPosition);
        rvMonth = new WheelView(context, calendar, Calendar.MONTH, getMeasuredWidth(), getMeasuredHeight(), monthList, monthPosition);
        rvDay = new WheelView(context, calendar, Calendar.DAY_OF_MONTH, getMeasuredWidth(), getMeasuredHeight(), dayList, dayPosition);
        rvHour = new WheelView(context, calendar, Calendar.HOUR_OF_DAY, getMeasuredWidth(), getMeasuredHeight(), hourList, hourPosition);
        rvMinute = new WheelView(context, calendar, Calendar.MINUTE, getMeasuredWidth(), getMeasuredHeight(), minuteList, minutePosition);

        rvYear.setOnCurrentItemListener(new WheelView.OnCurrentItemListener() {
            @Override
            public void onCurrent(int position, String content) {
                if (onCurrentDateListener != null) {
                    int year = Integer.parseInt(content.replace("年", ""));
                    calendar.set(Calendar.YEAR, year);
                    onCurrentDateListener.onCurrentDate(PickView.this, calendar.getTimeInMillis());
                }
            }
        });

        rvMonth.setOnCurrentItemListener(new WheelView.OnCurrentItemListener() {
            @Override
            public void onCurrent(int position, String content) {
                if (onCurrentDateListener != null) {
                    // -1 由于系统计算月份是由0开始
                    int month = Integer.valueOf(content.replace("月", "")) - 1;
                    calendar.set(Calendar.MONTH, month);

                    // 更新这个月的天数
                    dayPosition = 0;
                    getDas(calendar.get(Calendar.YEAR), month + 1, calendar.get(Calendar.DAY_OF_MONTH));
                    rvDay.updateData(dayList, dayPosition);

                    onCurrentDateListener.onCurrentDate(PickView.this, calendar.getTimeInMillis());
                }

            }
        });
        rvDay.setOnCurrentItemListener(new WheelView.OnCurrentItemListener() {
            @Override
            public void onCurrent(int position, String content) {
                if (onCurrentDateListener != null) {
                    int day = Integer.valueOf(content.replace("日", ""));
                    calendar.set(Calendar.DAY_OF_MONTH, day);
                    onCurrentDateListener.onCurrentDate(PickView.this, calendar.getTimeInMillis());
                }

            }
        });
        rvHour.setOnCurrentItemListener(new WheelView.OnCurrentItemListener() {
            @Override
            public void onCurrent(int position, String content) {
                if (onCurrentDateListener != null) {
                    int hour = Integer.valueOf(content.replace("时", ""));
                    calendar.set(Calendar.HOUR_OF_DAY, hour);
                    onCurrentDateListener.onCurrentDate(PickView.this, calendar.getTimeInMillis());
                }

            }
        });
        rvMinute.setOnCurrentItemListener(new WheelView.OnCurrentItemListener() {
            @Override
            public void onCurrent(int position, String content) {
                if (onCurrentDateListener != null) {
                    int minute = Integer.valueOf(content.replace("分", ""));
                    calendar.set(Calendar.MINUTE, minute);
                    onCurrentDateListener.onCurrentDate(PickView.this, calendar.getTimeInMillis());
                }

            }
        });
        // 添加所有初始化后的页面

        addView(rvYear, 0);
        addView(rvMonth, 1);
        addView(rvDay, 2);
        addView(rvHour, 3);
        addView(rvMinute, 4);

        if (!isEnable) {
            rvYear.setWheelEnable(isEnable);
            rvMonth.setWheelEnable(isEnable);
            rvDay.setWheelEnable(isEnable);
            rvHour.setWheelEnable(isEnable);
            rvMinute.setWheelEnable(isEnable);
        }
    }

    /**
     * 设置初始时间
     *
     * @param updateDate
     */
    public void updateDate(long updateDate) {
        if (updateDate != 0) {
            calendar = Calendar.getInstance();
            calendar.setTime(new Date(updateDate));
        }
    }

    /**
     * 获取获取具体年份的天数,如果是初始化 当前的年月日
     * 如果是获取某年某月的天数day 默认为1
     *
     * @param year
     * @param month
     * @return
     */
    public void getDas(int year, int month, int day) {
        Calendar newCalendar = Calendar.getInstance();
        newCalendar.set(Calendar.YEAR, year);
        newCalendar.set(Calendar.MONTH, month - 1);
        newCalendar.set(Calendar.DATE, 1);
        newCalendar.roll(Calendar.DATE, -1);
        int days = newCalendar.get(Calendar.DATE);
        dayList.clear();
        for (int i = 1; i <= days; i++) {
            if (i < 10) {
                dayList.add("0" + i + "日");
            } else {
                dayList.add(i + "日");
            }
            if (i == day) {
                dayPosition = i - 1;
            }
        }
    }

    /**
     * 初始化时间数据
     */
    private void initDate() {

        int year = calendar.get(Calendar.YEAR);
        int month = calendar.get(Calendar.MONTH);
        int day = calendar.get(Calendar.DAY_OF_MONTH);
        int hour = calendar.get(Calendar.HOUR_OF_DAY);
        int minute = calendar.get(Calendar.MINUTE);
        // 初始化年数 和当前年
        for (int i = year - 50; i <= (year + 100); i++) {
            if (i == year) {
                yearPosition = yearList.size();
            }
            yearList.add(i + "年");

        }
        // 初始化月份 和当前月
        for (int i = 1; i <= 12; i++) {
            if (i == month) {
                monthPosition = i;
            }
            monthList.add(i + "月");
        }

        // 初始化天数 和当前天
        getDas(year, month + 1, day);

        // 初始化小时数 和当小时
        for (int i = 0; i < 24; i++) {
            if (hour == i) {
                hourPosition = i;
            }
            if (i < 10) {
                hourList.add("0" + i + "时");
            } else {
                hourList.add(i + "时");
            }
        }
        // 初始化分钟数 和当分钟
        for (int i = 0; i < 12; i++) {

            if (minute / 5 == i) {
                minutePosition = i;
                calendar.set(Calendar.MINUTE, i * 5);
            }
            if (i < 2) {
                minuteList.add("0" + i * 5 + "分");
            } else {
                minuteList.add(i * 5 + "分");
            }
        }

    }

    public interface OnCurrentDateListener {
        /**
         * 获取当前的日期
         *
         * @param view
         * @param date
         */
        void onCurrentDate(View view, long date);
    }

    private OnCurrentDateListener onCurrentDateListener;

    public void setOnCurrentDateListener(OnCurrentDateListener onCurrentDateListener) {
        this.onCurrentDateListener = onCurrentDateListener;
    }

    private boolean isEnable = true;

    public void setWheelEnabled(boolean b) {
        this.isEnable = b;
    }
}

每个Item 都是由一个RecycleView(WheelView),关键代码如下

 /**
     * 初始化内部控件
     */
    private void init() {
        currentPosition = initPosition;
        // 初始化滚动器的宽高
        oneHeight = (parentHeight - DensityUtils.dp2px(context, 4)) / 3;
        oneWidth = parentWidth / 5;
        final int padding = dp2px(3);
        LayoutParams params = new LayoutParams(oneWidth, parentHeight);
        setLayoutParams(params);
        this.setPadding(padding, 0, padding, 0);
        // 设置内部控件左右两边的空隙
        mRecyclerView = new RecyclerView(context);
        mRecyclerView.setOverScrollMode(OVER_SCROLL_NEVER);
        LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
        layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT, TRUE);
        // 指定显示三行数据
        layoutManager = new LinearLayoutManager(context) {
            @Override
            public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
                if (getChildCount() > 0) {
                    View firstChildView = recycler.getViewForPosition(0);
                    measureChild(firstChildView, widthSpec, heightSpec);
                    setMeasuredDimension(MeasureSpec.getSize(widthSpec), firstChildView.getMeasuredHeight() * 3 - DensityUtils.dp2px(context, 4));
                } else {
                    super.onMeasure(recycler, state, widthSpec, heightSpec);
                }
            }
        };
        mRecyclerView.setLayoutManager(layoutManager);
        // 添加数据
        itemAdapter = new ItemAdapter();
        mRecyclerView.setAdapter(itemAdapter);

        // 滑动监听获取当前的item
        mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            RecyclerView.LayoutManager layoutManager;

            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                // now State  0静止,没有滚动  1 正在被外部拖拽,一般为用户正在用手指滚动  2 自动滚动开始
                if (newState == 0) {
                    layoutManager = recyclerView.getLayoutManager();
                    LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
                    if (linearLayoutManager.findLastCompletelyVisibleItemPosition() == currentPosition) {
                        // TODO
                    } else if (onCurrentItemListener != null) {
                        int nowPosition = (linearLayoutManager.findLastCompletelyVisibleItemPosition());
                        String text = stringList.get(nowPosition % stringList.size())
                                .replace("年", "")
                                .replace("月", "")
                                .replace("日", "")
                                .replace("时", "")
                                .replace("分", "");
                        // 判断是否是过去的日期
                        if (wheelType == Calendar.MONTH) {
                            selectCalendar.set(wheelType, Integer.valueOf(text) - 1);
                        } else {
                            selectCalendar.set(wheelType, Integer.valueOf(text));
                        }
                        // 判断是否是小于当前时间
                        if (selectCalendar.getTimeInMillis() >= System.currentTimeMillis()) {
                            onCurrentItemListener.onCurrent(nowPosition % stringList.size(), stringList.get(nowPosition % stringList.size()));
                            layoutManager.scrollToPosition(nowPosition + 1);
                            itemAdapter.notifyItemChanged(nowPosition, 0);
                            itemAdapter.notifyItemChanged(currentPosition, 1);
                            currentPosition = nowPosition;
                        } else {
                            // 判断是向上还是线下 isUpOrDown>向下,isUpOrDown<0向上
                            if (isUpOrDown > 0) {
                                currentPosition = initPosition;
                                // 由于滚动有些偏差,使用scrollToPositionWithOffset 来滚动
                                linearLayoutManager.scrollToPositionWithOffset(currentPosition - 1, -DensityUtils.dp2px(context, 3));
                                itemAdapter.notifyItemChanged(currentPosition, 0);
                            } else {
                                currentPosition = initPosition;
                                linearLayoutManager.scrollToPositionWithOffset(currentPosition - 1, -DensityUtils.dp2px(context, 3));
                                itemAdapter.notifyItemChanged(currentPosition, 0);
                            }
                        }
                    }
                } else {
                    itemAdapter.notifyItemChanged(currentPosition, 1);
                }
            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                isUpOrDown = dy;
            }
        });


        // 添加滚动器的的双杠线
        View topLineView = new View(context);
        View bottomLineView = new View(context);
        topLineView.setBackgroundColor(ContextCompat.getColor(context, R.color.c_e5e5e5));
        bottomLineView.setBackgroundColor(ContextCompat.getColor(context, R.color.c_e5e5e5));

        LayoutParams topLayoutParam = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dp2px(1));
        topLayoutParam.setMargins(0, oneHeight, 0, 0);
        topLayoutParam.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE);

        LayoutParams bottomLayoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dp2px(1));
        bottomLayoutParams.setMargins(0, oneHeight * 2, 0, 0);
        bottomLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE);
        // 滑动到初始位置 -1是为了移动到中间位置
        layoutManager.scrollToPosition(stringList.size() * 1000 + initPosition - 1);
        currentPosition = stringList.size() * 1000 + initPosition;
        // 添加所有初始化后的空间
        addView(mRecyclerView, layoutParams);
        addView(topLineView, topLayoutParam);
        addView(bottomLineView, bottomLayoutParams);
    }
    
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容