Android自定义日期区间选择,类似12306酒店入住的日期选择

时间过的好快,一转眼2019年马上就结束了,在年末最后一天,写一篇与时间有关的文章吧,今天做一个日期区间的选择功能,效果类似一些酒店入住的日期选择,我写的这个类似12306上面的酒店入住日期选择效果,像一些其他APP如美团、携程等酒店入住日期选择效果也大同小异。先看一下效果图吧。

 此功能中的日历是使用RecyclerView+GridLayoutManager来实现的,日历中的日期数据是通过Calendar日历获取的(不用自己再单独计算是平年闰年和每个月多少天了),因为RecyclerView的GridLayoutManager可以实现网格布局的效果。我们看到日历的头部有周日到周一,一行显示7天的日期数据;滑动日历列表开始的年月,下面是对应月份的具体日期。我们需要做的处理是:

(1)如果显示年月,则通过GridLayoutManagere来控制一行展示1个Item,如果显示月份的具体日期,则通过GridLayoutManagere来控制一行展示7个Item;

        GridLayoutManager gridLayoutManager = new GridLayoutManager(context, 7);
        gridLayoutManager.setOrientation(GridLayoutManager.VERTICAL);
        gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
            @Override
            public int getSpanSize(int i) {
                //这个方法返回的是当前位置的 item 跨度大小
                if (DateBean.item_type_month == data.get(i).getItemType()) {
                    return 7;
                } else {
                    return 1;
                }
            }
        });
        recyclerView.setLayoutManager(gridLayoutManager);

(2)处理每个月的月初:每个月从1号开始到月末31号(先拿一个月31天举例),1号如果是周日则将其绘制在第一行的第一个位置;如果是周一,则绘制在第一行的第二个位置,第一个位置的item补空占位,以此类推,如果是周六,则绘制在第一行的最后一个位置,前面留个位置都补空占位;

(3)处理每个月的月末:如果这个月的最后一天的周日,则后面六天都补空占位,如果这个月的最后一天是周六,则正好显示不用补空;

(4)依次类推,处理完一个月的开始日期结束日期,中间的日期照常生成即可,无需特殊处理,最终将数据存储在数组里即可。

//生成日历数据
//    private List<DateBean> days(String startDateStr, String endDateStr)
    private List<DateBean> days(int monthLength){
        List<DateBean> dateBeanList = new ArrayList<>();
        try {
            Calendar calendar = Calendar.getInstance();
            //日期格式化
            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
            SimpleDateFormat formatYYYYMM = new SimpleDateFormat("yyyy-MM");

            //=============================== start 动态设置从本月开始   和时间长度(显示多少个月)======================================//
            //起始日期
            Date startDate = new Date();
            calendar.setTime(startDate);

            //结束日期
            calendar.add(Calendar.MONTH, monthLength);//月份是从0开始的  增加6个月  日历显示7个月的长度
            Date endDate = new Date(calendar.getTimeInMillis());//方法返回此Calendar以毫秒为单位的时间

            Log.e("tag", "startDate:" + format.format(startDate) + "----------endDate:" + format.format(endDate));

            //格式化开始日期和结束日期为 yyyy-mm-dd格式
            String endDateStr = format.format(endDate);//把date转成String
            endDate = format.parse(endDateStr);//把String转成date

            String startDateStr = format.format(startDate);
            startDate = format.parse(startDateStr);
            //================================= end ====================================//


            //--------------------------------- start 动态传值方式设置显示的日历日期区间----------------------------------------//
            //起始日期
//            Date startDate = format.parse(startDateStr);//把String转成date
//            //结束日期
//            Date endDate = format.parse(endDateStr);//把String转成date
            //---------------------------------- end ---------------------------------------//

            calendar.setTime(startDate);//上面的calendar.setTime(startDate)是设置了当前时间,但是后面calendar.add(Calendar.MONTH, 5)结束日期加了5个月,日期就延后了5个月,所以要得到当前日期,需要在此处再设置一次

            Log.e("tag", "startDateStr:" + startDateStr + "---------endDate:" + format.format(endDate));

            calendar.set(Calendar.DAY_OF_MONTH, 1);//设置日期为1
            Calendar monthCalendar = Calendar.getInstance();

            //按月生成日历 每行7个 最多6行 42个
            //每一行有七个日期  日 一 二 三 四 五 六 的顺序
            Log.e("tag","calendar.getTimeInMillis()="+calendar.getTimeInMillis()+"----------endDate.getTime()="+endDate.getTime());
            for (calendar.getTimeInMillis(); calendar.getTimeInMillis() <= endDate.getTime();){//从当前时间开始,如果小于等于最后的时间,则增加一个月
                //月份item
                DateBean monthDateBean = new DateBean();
                monthDateBean.setDate(calendar.getTime());
                monthDateBean.setMonthStr(formatYYYYMM.format(monthDateBean.getDate()));
                monthDateBean.setItemType(DateBean.getItem_type_month());
                dateBeanList.add(monthDateBean);

                //获取一个月结束的日期和开始日期
                monthCalendar.setTime(calendar.getTime());
                monthCalendar.set(Calendar.DAY_OF_MONTH, 1);
                Date startMonthDay = calendar.getTime();

                monthCalendar.add(Calendar.MONTH, 1);//表示加一个月
                monthCalendar.add(Calendar.DAY_OF_MONTH, -1);//表示对日期进行减一天操作
                //从而得到当前月的最后一天
                Date endMonthDay = monthCalendar.getTime();

                //重置为本月开始
                monthCalendar.set(Calendar.DAY_OF_MONTH, 1);
                Log.e("tag", "月份的开始日期:" + format.format(startMonthDay) + "——星期"+getWeekStr(calendar.get(Calendar.DAY_OF_WEEK)+"")+ "---------结束日期:" + format.format(endMonthDay));
                //从月的第一天开始,如果小于等于本月最后一天,则增加一天
                for(monthCalendar.getTimeInMillis();monthCalendar.getTimeInMillis() <= endMonthDay.getTime();){
                    //生成单个月的日历
                    //处理一个月开始的第一天
                    if (monthCalendar.get(Calendar.DAY_OF_MONTH) == 1){
                        //看某个月第一天是周几
                        int weekDay = monthCalendar.get(Calendar.DAY_OF_WEEK);
                        Log.e("tag","dateBeanList="+dateBeanList.size());
                        Log.e("tag","monthDateBean.getMonthStr()="+monthDateBean.getMonthStr());
                        switch (weekDay){
                            case 1://周日  正常顶格显示
                                break;
                            case 2://周一  错后一格显示
                                addDatePlaceholder(dateBeanList, 1, monthDateBean.getMonthStr());
                                break;
                            case 3://周二  错后二格显示
                                addDatePlaceholder(dateBeanList, 2, monthDateBean.getMonthStr());
                                break;
                            case 4://周三  错后三格显示
                                addDatePlaceholder(dateBeanList, 3, monthDateBean.getMonthStr());
                                break;
                            case 5://周四  错后四格显示
                                addDatePlaceholder(dateBeanList, 4, monthDateBean.getMonthStr());
                                break;
                            case 6://周五  错后五格显示
                                addDatePlaceholder(dateBeanList, 5, monthDateBean.getMonthStr());
                                break;
                            case 7://周六  错后六格显示
                                addDatePlaceholder(dateBeanList, 6, monthDateBean.getMonthStr());
                                break;
                        }
                    }

                    //生成某一天日期实体 日item
                    DateBean dayDateBean = new DateBean();
                    dayDateBean.setDate(monthCalendar.getTime());
                    dayDateBean.setMonthStr(monthDateBean.getMonthStr());
                    dayDateBean.setDay(monthCalendar.get(Calendar.DAY_OF_MONTH) + "");
                    dateBeanList.add(dayDateBean);

                    //处理一个月的最后一天
                    if (monthCalendar.getTimeInMillis() == endMonthDay.getTime()){
                        //看某个月最后一天是周几
                        int weekDay = monthCalendar.get(Calendar.DAY_OF_WEEK);
                        switch (weekDay){
                            case 1://周日 添加6个空的日期占位
                                addDatePlaceholder(dateBeanList, 6, monthDateBean.getMonthStr());
                                break;
                            case 2://周一 添加5个空的日期占位
                                addDatePlaceholder(dateBeanList, 5, monthDateBean.getMonthStr());
                                break;
                            case 3://周二 添加4个空的日期占位
                                addDatePlaceholder(dateBeanList, 4, monthDateBean.getMonthStr());
                                break;
                            case 4://周三 添加3个空的日期占位
                                addDatePlaceholder(dateBeanList, 3, monthDateBean.getMonthStr());
                                break;
                            case 5://周四 添加2个空的日期占位
                                addDatePlaceholder(dateBeanList, 2, monthDateBean.getMonthStr());
                                break;
                            case 6://周五 添加1个空的日期占位
                                addDatePlaceholder(dateBeanList, 1, monthDateBean.getMonthStr());
                                break;
                            case 7://周六
                                break;
                        }
                    }
                    //天数加1
                    monthCalendar.add(Calendar.DAY_OF_MONTH, 1);
                }
                Log.e("tag", "日期:" + format.format(calendar.getTime()) + "----周" + getWeekStr(calendar.get(Calendar.DAY_OF_WEEK) + ""));
                //月份加1
                calendar.add(Calendar.MONTH, 1);
            }
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return dateBeanList;
    }

    //添加空的日期占位
    private void addDatePlaceholder(List<DateBean> dateBeans, int count, String monthStr) {
        for (int i = 0; i < count; i++) {
            DateBean dateBean = new DateBean();
            dateBean.setMonthStr(monthStr);
            dateBeans.add(dateBean);
        }
    }

    //获取星期几
    private String getWeekStr(String mWay) {
        if ("1".equals(mWay)) {
            mWay = "日";
        } else if ("2".equals(mWay)) {
            mWay = "一";
        } else if ("3".equals(mWay)) {
            mWay = "二";
        } else if ("4".equals(mWay)) {
            mWay = "三";
        } else if ("5".equals(mWay)) {
            mWay = "四";
        } else if ("6".equals(mWay)) {
            mWay = "五";
        } else if ("7".equals(mWay)) {
            mWay = "六";
        }
        return mWay;
    }

在生成日历的时候还有一点需要注意下,就是今天之前的日期是不可选的,置灰 ,今天的日期以特殊样式标记出来,方便一眼就看到今天。这个要在adapter的item上设置

            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
            Date todayDate = new Date();
            String todayStr = format.format(todayDate);//获取今天日期
            String date = data.get(position).getMonthStr()+"-"+data.get(position).getDay();//获取得到的日期
            Date beforeToday = new Date();
            try {
                beforeToday = format.parse(date);
            } catch (ParseException e) {
                e.printStackTrace();
            }
            if (date.equals(todayStr)){
                //如果是今天的日期  则把显示的日期号改为“今天”两个字
                viewHolder.tv_day.setText("今天");
                viewHolder.tv_day.setTextColor(Color.parseColor("#2196F3"));
            } else if (beforeToday.getTime() < todayDate.getTime()){
                //今天之前的日期  设置成灰色
                viewHolder.tv_day.setText(data.get(position).getDay());
                viewHolder.tv_day.setTextColor(Color.parseColor("#dadada"));
            } else {
                viewHolder.tv_day.setText(data.get(position).getDay());
                viewHolder.tv_day.setTextColor(Color.BLACK);
            }

            DateBean dateBean = data.get(position);
            //设置item状态
            if (dateBean.getItemState() == DateBean.ITEM_STATE_BEGIN_DATE || dateBean.getItemState() == DateBean.ITEM_STATE_END_DATE){
                //开始日期或结束日期
                viewHolder.itemView.setBackgroundColor(context.getResources().getColor(R.color.blue));
                viewHolder.tv_day.setTextColor(Color.WHITE);
                viewHolder.tv_check_in_check_out.setVisibility(VISIBLE);
                if (dateBean.getItemState() == DateBean.ITEM_STATE_BEGIN_DATE){
                    viewHolder.tv_check_in_check_out.setText("开始");
                }else {
                    viewHolder.tv_check_in_check_out.setText("结束");
                }
            }else if (dateBean.getItemState() == DateBean.ITEM_STATE_SELECTED){
                //选中状态
                viewHolder.itemView.setBackgroundColor(context.getResources().getColor(R.color.blue1));
                viewHolder.tv_day.setTextColor(Color.WHITE);
            }else {
                //正常状态
                viewHolder.itemView.setBackgroundColor(Color.WHITE);
                viewHolder.tv_check_in_check_out.setVisibility(GONE);
            }

选择日期时需要判断,今天之前的如期不可选

adapter.setOnRecyclerviewItemClick(new CalendarAdapter.OnRecyclerviewItemClick() {
            @Override
            public void onItemClick(View v, int position) {
                Date todayDate = new Date();//今天
                String date = data.get(position).getMonthStr()+"-"+data.get(position).getDay();//获取得到的日期
                Date beforeToday = new Date();
                try {
                    beforeToday = simpleDateFormat.parse(date);
                } catch (ParseException e) {
                    e.printStackTrace();
                }
                if (beforeToday.getTime() < todayDate.getTime()-1000*60*60*24){//-1000*60*60*24  得到的是昨天的时间  不然今天也不可选
                    //今天之前的日期不可选
                    Toast.makeText(context,"当前日期不可选",Toast.LENGTH_SHORT).show();
                }else {
                    onClick(data.get(position));
                }
                Log.e("tag","date="+date);
            }
        });

对日期的选中与否做处理,如果没有选中开始日期则此次操作选中开始日期;如果选中了开始日期但没有选中结束日期,本次操作选中结束日期;如果结束日期和开始日期都已选中,则重新选择开始日期。具体细节下面代码里都有注释。

private void onClick(DateBean dateBean){
        if (dateBean.getItemType() == DateBean.item_type_month || TextUtils.isEmpty(dateBean.getDay())) {
            return;
        }

        //这个是在Dialog显示的情况下会用到,来判断如期是否已选完,来改变Dialog里面确定按钮的选中状态
        if(onDateSelected!=null){
            onDateSelected.hasSelect(false);
        }
        //如果没有选中开始日期则此次操作选中开始日期
        if (startDate == null){
            startDate = dateBean;
            dateBean.setItemState(DateBean.ITEM_STATE_BEGIN_DATE);
        }else if (endDate == null){
            //如果选中了开始日期但没有选中结束日期,本次操作选中结束日期

            //如果当前点击的结束日期跟开始日期一致 则不做操作
            if (startDate == dateBean){

            }else if (dateBean.getDate().getTime() < startDate.getDate().getTime()){
                //如果当前点选的日期小于当前选中的开始日期,则本次操作重新选中开始日期
                startDate.setItemState(DateBean.ITEM_STATE_NORMAL);
                startDate = dateBean;
                startDate.setItemState(DateBean.ITEM_STATE_BEGIN_DATE);
            }else {
                //当前点选的日期大于当前选中的开始日期  此次操作选中结束日期
                endDate = dateBean;
                endDate.setItemState(DateBean.ITEM_STATE_END_DATE);
                setState();//选中中间的日期

                if(onDateSelected!=null){
                    onDateSelected.hasSelect(true);
                    onDateSelected.selected(simpleDateFormat.format(startDate.getDate()),simpleDateFormat.format(endDate.getDate()));
                }
            }
        }else if (startDate != null && endDate != null){
            //结束日期和开始日期都已选中
            clearState();//取消选中状态

            /**
             * 一定要先清除结束日期,再重新选择开始日期,不然会有一个bug,当开始日期和结束日期都选中的时候,如果此次点选开始日期,则选中开始日期,
             * 如果点结束日期,则全都清除了,再点结束日期没有反应,应该是结束日期变为开始日期才对
             * 因此要先清除结束位置,再重新选中开始日期
             */
            //一定要先清除结束日期,再重新选择开始日期
            endDate.setItemState(DateBean.ITEM_STATE_NORMAL);
            endDate = null;
            startDate.setItemState(DateBean.ITEM_STATE_NORMAL);
            startDate = dateBean;
            startDate.setItemState(DateBean.ITEM_STATE_BEGIN_DATE);
        }
        adapter.notifyDataSetChanged();
    }

    //选中中间的日期
    private void setState(){
        if (endDate != null && startDate != null){
            int start = data.indexOf(startDate);
            start += 1;
            int end = data.indexOf(endDate);
            for (; start < end; start++){
                DateBean dateBean = data.get(start);
                if (!TextUtils.isEmpty(dateBean.getDay())) {
                    dateBean.setItemState(DateBean.ITEM_STATE_SELECTED);
                }
            }
        }
    }

    //取消选中状态
    private void clearState(){
        if (endDate != null && startDate != null){
            int start = data.indexOf(startDate);
            start += 1;
            int end = data.indexOf(endDate);
            for (; start < end; start++){
                DateBean dateBean = data.get(start);
                dateBean.setItemState(DateBean.ITEM_STATE_NORMAL);
            }
        }
    }

RecyclerView对应的adapter

/**
 * Created by wjy.
 * Date: 2019/12/26
 * Time: 11:57
 * Describe: 日历adapter
 */
public class CalendarAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private Context context;
    public ArrayList<DateBean> data = new ArrayList<>();
    private OnRecyclerviewItemClick onRecyclerviewItemClick;

    public OnRecyclerviewItemClick getOnRecyclerviewItemClick() {
        return onRecyclerviewItemClick;
    }

    public void setOnRecyclerviewItemClick(OnRecyclerviewItemClick onRecyclerviewItemClick) {
        this.onRecyclerviewItemClick = onRecyclerviewItemClick;
    }

    public CalendarAdapter(Context context,ArrayList<DateBean> data){
        this.context = context;
        this.data = data;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == DateBean.item_type_day){
            final View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_day,parent,false);
            final DayViewHolder dayViewHolder = new DayViewHolder(view);
            dayViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (onRecyclerviewItemClick != null){
                        onRecyclerviewItemClick.onItemClick(v,dayViewHolder.getLayoutPosition());
                    }
                }
            });
            return dayViewHolder;
        }else if (viewType == DateBean.item_type_month){
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_month,parent,false);
            final MonthViewHolder monthViewHolder = new MonthViewHolder(view);
            monthViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (onRecyclerviewItemClick != null){
                        onRecyclerviewItemClick.onItemClick(v,monthViewHolder.getLayoutPosition());
                    }
                }
            });
            return monthViewHolder;
        }
        return null;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof MonthViewHolder){
            MonthViewHolder viewHolder = (MonthViewHolder) holder;
            viewHolder.tv_month.setText(data.get(position).getMonthStr());
        }else {
            DayViewHolder viewHolder = (DayViewHolder) holder;

            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
            Date todayDate = new Date();
            String todayStr = format.format(todayDate);//获取今天日期
            String date = data.get(position).getMonthStr()+"-"+data.get(position).getDay();//获取得到的日期
            Date beforeToday = new Date();
            try {
                beforeToday = format.parse(date);
            } catch (ParseException e) {
                e.printStackTrace();
            }
            if (date.equals(todayStr)){
                //如果是今天的日期  则把显示的日期号改为“今天”两个字
                viewHolder.tv_day.setText("今天");
                viewHolder.tv_day.setTextColor(Color.parseColor("#2196F3"));
            } else if (beforeToday.getTime() < todayDate.getTime()){
                //今天之前的日期  设置成灰色
                viewHolder.tv_day.setText(data.get(position).getDay());
                viewHolder.tv_day.setTextColor(Color.parseColor("#dadada"));
            } else {
                viewHolder.tv_day.setText(data.get(position).getDay());
                viewHolder.tv_day.setTextColor(Color.BLACK);
            }

            DateBean dateBean = data.get(position);
            //设置item状态
            if (dateBean.getItemState() == DateBean.ITEM_STATE_BEGIN_DATE || dateBean.getItemState() == DateBean.ITEM_STATE_END_DATE){
                //开始日期或结束日期
                viewHolder.itemView.setBackgroundColor(context.getResources().getColor(R.color.blue));
                viewHolder.tv_day.setTextColor(Color.WHITE);
                viewHolder.tv_check_in_check_out.setVisibility(VISIBLE);
                if (dateBean.getItemState() == DateBean.ITEM_STATE_BEGIN_DATE){
                    viewHolder.tv_check_in_check_out.setText("开始");
                }else {
                    viewHolder.tv_check_in_check_out.setText("结束");
                }
            }else if (dateBean.getItemState() == DateBean.ITEM_STATE_SELECTED){
                //选中状态
                viewHolder.itemView.setBackgroundColor(context.getResources().getColor(R.color.blue1));
                viewHolder.tv_day.setTextColor(Color.WHITE);
            }else {
                //正常状态
                viewHolder.itemView.setBackgroundColor(Color.WHITE);
                viewHolder.tv_check_in_check_out.setVisibility(GONE);
            }
        }
    }

    @Override
    public int getItemCount() {
        return data.size();
    }

    @Override
    public int getItemViewType(int position) {
        return data.get(position).getItemType();
    }

    public class DayViewHolder extends RecyclerView.ViewHolder {
        public TextView tv_day;
        public TextView tv_check_in_check_out;

        public DayViewHolder(@NonNull View itemView) {
            super(itemView);
            tv_day = itemView.findViewById(R.id.tv_day);
            tv_check_in_check_out = itemView.findViewById(R.id.tv_check_in_check_out);
        }
    }

    public class MonthViewHolder extends RecyclerView.ViewHolder {
        public TextView tv_month;

        public MonthViewHolder(@NonNull View itemView) {
            super(itemView);
            tv_month = itemView.findViewById(R.id.tv_month);
        }
    }

    public interface OnRecyclerviewItemClick {
        void onItemClick(View v, int position);
    }
}

以Dialog弹窗形式显示,需要自定义一个CalendarDialog,并将Dialog设置以全屏形式显示

/**
 * Created by wjy.
 * Date: 2019/12/30
 * Time: 12:06
 * Describe: Dialog弹窗显示日历
 */
public class CalendarDialog extends Dialog {

    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
    ImageView img_close;
    Button btn_ok;
    MyCalendarList calendarList;
    private OnDialogCalendarListener calendarListener;
    private String startDates,endDates;

    public static DisplayMetrics metrics;
    public static int screenWidth;//屏幕宽
    public static int screenHeigh;//屏幕高

    public CalendarDialog(@NonNull Context context) {
        super(context,R.style.CalendarDialog);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        setContentView(R.layout.calendarpopupwindow);
        LayoutInflater inflater = LayoutInflater.from(getContext());
        View viewDialog = inflater.inflate(R.layout.calendarpopupwindow, null);
        metrics = getContext().getResources().getDisplayMetrics();
        screenWidth = metrics.widthPixels;//屏幕宽
        screenHeigh = metrics.heightPixels;//屏幕高
        //设置dialog的宽高为屏幕的宽高
        ViewGroup.LayoutParams layoutParams = new  ViewGroup.LayoutParams(screenWidth, screenHeigh-100);
        setContentView(viewDialog, layoutParams);
        initView();
    }

    @Override
    public void show() {
        super.show();
    }

    private void initView(){
        img_close = findViewById(R.id.img_close);
        img_close.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dismiss();
            }
        });

        calendarList = findViewById(R.id.calendarList);
        calendarList.setOnDateSelected(new MyCalendarList.OnDateSelected() {
            @Override
            public void selected(String startDate, String endDate) {
                startDates = startDate;
                endDates = endDate;
                try {
                    Date sDate = format.parse(startDate);
                    Date eDate = format.parse(endDate);
                    Toast.makeText(getContext(),"共"+ CommonTools.getDayCount(sDate,eDate) +"晚",Toast.LENGTH_LONG).show();
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void hasSelect(boolean select) {
                if (select){
                    btn_ok.setSelected(true);
                }else {
                    btn_ok.setSelected(false);
                }
            }
        });

        btn_ok = findViewById(R.id.btn_ok);
        btn_ok.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (calendarListener != null){
                    calendarListener.OnDialogCalendarListener(startDates,endDates);
                    dismiss();
                }
            }
        });
    }

    public interface OnDialogCalendarListener{
        void OnDialogCalendarListener(String startDate, String endDate);
    }

    public void setOnDialogCalendarListener(OnDialogCalendarListener calendarListener) {
        this.calendarListener = calendarListener;
    }
}

Dialog的style在res->values->styles.xml文件里创建

    <style name="CalendarDialog" parent="android:style/Theme.Dialog">
        <!--背景颜色及和透明程度-->
        <item name="android:windowBackground">@android:color/transparent</item>
        <!--是否去除标题 -->
        <item name="android:windowNoTitle">true</item>
        <!--是否去除边框-->
        <item name="android:windowFrame">@null</item>
        <!--是否浮现在activity之上-->
        <item name="android:windowIsFloating">true</item>
        <!--是否模糊-->
        <item name="android:backgroundDimEnabled">true</item>
    </style>

CalendarDialog的布局文件calendarpopupwindow.xml

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

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="60dp"
        android:background="@color/transparent"
        android:orientation="vertical">
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:background="@drawable/bg_gray_top_corner">
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:text="选择日期"
                android:textSize="15sp"
                android:textColor="@color/black"/>

            <ImageView
                android:id="@+id/img_close"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginRight="15dp"
                android:src="@mipmap/com_btn_guanbibutton_press_01"
                android:layout_alignParentRight="true"
                android:layout_centerVertical="true"/>

        </RelativeLayout>
        <com.junto.text.Calendar.MyCalendarList
            android:id="@+id/calendarList"
            android:layout_width="match_parent"
            android:layout_height="450dp"
            android:background="@color/white">

        </com.junto.text.Calendar.MyCalendarList>
    </LinearLayout>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:padding="10dp"
        android:background="@color/white">
        <Button
            android:id="@+id/btn_ok"
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:background="@drawable/btn_ok"
            android:text="完成"
            android:textColor="@color/white"
            android:textSize="15sp"/>
    </RelativeLayout>

</RelativeLayout>

在Activity类里面使用

/**
 * Created by wjy.
 * Date: 2019/12/26
 * Time: 10:21
 * Describe: 类似美团携程选择酒店入住日期和离店日期的日历效果
 */
public class CalendarActivity extends Activity implements CalendarPopupWindow.CalendarListener {

    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
    MyCalendarList calendarList;
    TextView tv_selectDate;
    CalendarPopupWindow calendarPopupWindow;
    CalendarDialog calendarDialog;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_calendar);
        calendarDialog = new CalendarDialog(CalendarActivity.this);
        initView();
    }

    private void initView(){
        calendarPopupWindow = new CalendarPopupWindow(CalendarActivity.this);
        calendarPopupWindow.setCalendarListener(this);
        tv_selectDate = findViewById(R.id.tv_selectDate);
        tv_selectDate.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //以Dialog弹窗形式显示
                calendarDialog.getWindow().setGravity(Gravity.BOTTOM);
                calendarDialog.getWindow().setWindowAnimations(R.style.mystyle);
                calendarDialog.show();

                //以PopupWindow弹窗形式显示
//                calendarPopupWindow.showAtLocation(CalendarActivity.this.findViewById(R.id.ll_parent), Gravity.BOTTOM,0,0);
//                calendarPopupWindow.setAnimationStyle(R.style.mystyle   );
            }
        });

        calendarDialog.setOnDialogCalendarListener(new CalendarDialog.OnDialogCalendarListener() {
            @Override
            public void OnDialogCalendarListener(String startDate, String endDate) {
                Log.e("tag","OnDialogCalendarListener  startDate="+startDate);
                Log.e("tag","OnDialogCalendarListener  endDate="+endDate);
            }
        });

        calendarList = findViewById(R.id.calendarList);
        calendarList.setOnDateSelected(new MyCalendarList.OnDateSelected() {
            @Override
            public void selected(String startDate, String endDate) {
                Toast.makeText(CalendarActivity.this,"开始日期:"+startDate+"\n结束日期:"+endDate,Toast.LENGTH_LONG).show();
                try {
                    Date sDate = format.parse(startDate);
                    Date eDate = format.parse(endDate);
                    tv_selectDate.setText(CommonTools.getDateForStandard(startDate).substring(5)+"    "+CommonTools.DateToWeek(sDate)+"——"
                            +CommonTools.getDateForStandard(endDate).substring(5)+"    "+CommonTools.DateToWeek(eDate)+"    共"+ CommonTools.getDayCount(sDate,eDate) +"晚");
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void hasSelect(boolean select) {

            }
        });
    }

    @Override
    public void onCalendarListenerResult(String startDate, String endDate) {
        Log.e("tag","onCalendarListenerResult  startDate="+startDate);
        Log.e("tag","onCalendarListenerResult  endDate="+endDate);
    }
}

设置弹窗显示位置,并且给Dialog的弹出与关闭设置了滑动动画效果

//以Dialog弹窗形式显示
calendarDialog.getWindow().setGravity(Gravity.BOTTOM);
calendarDialog.getWindow().setWindowAnimations(R.style.mystyle);
calendarDialog.show();

动画的style在res->values->styles.xml文件里创建

    <!-- 进出场动画都用到的anim style-->
    <style name="mystyle" parent="android:Animation">
    <!--进入时的动画-->
    <item name="android:windowEnterAnimation">@anim/calendarpopupwindow_enter</item>
    <!--退出时的动画-->
    <item name="android:windowExitAnimation">@anim/calendarpopupwindow_exit</item>
    </style>

进入退出的动画文件:在res下新建anim文件夹,在里面创建进入时动画文件calendarpopupwindow_enter.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="600"
        android:fromYDelta="100%p" />
</set>

退出时动画文件calendarpopupwindow_exit.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="600"
        android:toYDelta="100%p" />
</set>

此功能里还用到一个实体类DateBean,记录item类型,item状态等

public class DateBean {

    //item类型
    public static int item_type_day = 1;//日期item
    public static int item_type_month = 2;//月份item
    int itemType = 1;//默认是日期item

    //item状态
    public static int ITEM_STATE_BEGIN_DATE = 1;//开始日期
    public static int ITEM_STATE_END_DATE = 2;//结束日期
    public static int ITEM_STATE_SELECTED = 3;//选中状态
    public static int ITEM_STATE_NORMAL = 4;//正常状态
    public int itemState = ITEM_STATE_NORMAL;

    Date date;//具体日期
    String day;//一个月的某天
    String monthStr;//月份

    public static int getItem_type_day() {
        return item_type_day;
    }

    public static void setItem_type_day(int item_type_day) {
        DateBean.item_type_day = item_type_day;
    }

    public static int getItem_type_month() {
        return item_type_month;
    }

    public static void setItem_type_month(int item_type_month) {
        DateBean.item_type_month = item_type_month;
    }

    public int getItemType() {
        return itemType;
    }

    public void setItemType(int itemType) {
        this.itemType = itemType;
    }

    public int getItemState() {
        return itemState;
    }

    public void setItemState(int itemState) {
        this.itemState = itemState;
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    public String getDay() {
        return day;
    }

    public void setDay(String day) {
        this.day = day;
    }

    public String getMonthStr() {
        return monthStr;
    }

    public void setMonthStr(String monthStr) {
        this.monthStr = monthStr;
    }
}

到此日期区间选择就完成了。下面再添加一个效果:实现月份标题悬停的效果

我们仔细看效果图可以发现,月份标题是有一个悬停和慢慢推走的效果的。这个效果可以用ItemDecoration装饰来实现。具体实现是继承ItemDecoration 重写OnDrawOver方法在这个方法要做这么几件事

绘制出当前月份标题
如何获取当前要绘制的月份标题是几月份呢?我们RecyclerView的adapter中的数据源DataBean每个日期item都存储了他对应的日期标题,这个日期对应的月份,可以通过 RecyclerView的getAdapter()方法获取Adapter然后通过RecyclerView 的getChildAdapterPosition(fistView)来获取某个itemView在adapter对应的位置 然后从Adapter的数据源中获取每个item的对应的月份。
如何实现月份标题推走的效果
逻辑是首先找出当前所有可见的Item的第一个月份标题类型的Item这个Item是当我们滑动列表时下一个悬停的月份标题。然后我们获取这个Item距离顶部的距离view.getTop()当它距离顶部的距离小于等于我们月份标题的高度时,假如标题的高度是150,我们绘制顶部的月份标题顶部的位置就是 150-view.getTop()这样随着位置的推移就会有一个慢慢推走的效果。代码如下

/**
 * Created by wjy.
 * Date: 2019/12/31
 * Time: 12:02
 * Describe: 实现月份标题悬停的效果
 */
public class MyItemDecoration extends RecyclerView.ItemDecoration {

    Paint paint=new Paint();
    Paint colorPaint=new Paint();
    Paint linePaint=new Paint();

    public MyItemDecoration(){
        paint.setColor(Color.parseColor("#ffffff"));
        paint.setStyle(Paint.Style.FILL);
        colorPaint.setColor(Color.parseColor("#2196F3"));
        colorPaint.setAntiAlias(true);
        linePaint.setAntiAlias(true);
        linePaint.setColor(Color.parseColor("#dddddd"));
    }

    @Override
    public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.onDrawOver(c, parent, state);
        if(parent.getChildCount()<=0){
            return;
        }

        //头部的高度
        int height=50;
        final float scale = parent.getContext().getResources().getDisplayMetrics().density;
        height= (int) (height*scale+0.5f);

        //获取第一个可见的view,通过此view获取其对应的月份
        CalendarAdapter adapter=(CalendarAdapter) parent.getAdapter();
        View fistView=parent.getChildAt(0);
        String text=adapter.data.get(parent.getChildAdapterPosition(fistView)).getMonthStr();

        String fistMonthStr="";
        int fistViewTop=0;
        //查找当前可见的itemView中第一个月份类型的item
        for(int i=0;i<parent.getChildCount();i++){
            View v=parent.getChildAt(i);
            if(2==parent.getChildViewHolder(v).getItemViewType()){
                fistMonthStr=adapter.data.get(parent.getChildAdapterPosition(v)).getMonthStr();
                fistViewTop=v.getTop();
                break;
            }
        }

        //计算偏移量
        int topOffset=0;
        if(!fistMonthStr.equals(text)&&fistViewTop<height){
            //前提是第一个可见的月份item不是当前显示的月份和距离的顶部的距离小于头部的高度
            topOffset=height-fistViewTop;
        }
        int t=0-topOffset;

        //绘制头部区域
        c.drawRect(parent.getLeft(),t,parent.getRight(),t+height,paint);

        colorPaint.setTextAlign(Paint.Align.CENTER);
        colorPaint.setTextSize(15*scale+0.5f);
        //绘制头部月份文字
        c.drawText(text,parent.getRight()/2,(t+height)/2,colorPaint);

        //绘制分割线
//        if(fistViewTop!=height) {
//            linePaint.setStrokeWidth(scale * 0.5f + 0.5f);
//            c.drawLine(parent.getLeft(), t + height, parent.getRight(), t + height, linePaint);
//        }

    }
}

设置到recyclerView里

//实现月份标题悬停的效果
MyItemDecoration myItemDecoration = new MyItemDecoration();
recyclerView.addItemDecoration(myItemDecoration);

感谢  参考文章:https://blog.csdn.net/qifengdeqingchen/article/details/85233379

源码地址

CSDN地址:https://download.csdn.net/download/u013184970/12068611

github地址:https://github.com/WangJinyong/MyCalendarSelect

发布了92 篇原创文章 · 获赞 38 · 访问量 14万+

猜你喜欢

转载自blog.csdn.net/u013184970/article/details/103780404