时间过的好快,一转眼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() {@Overridepublic 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转成StringendDate = format.parse(endDateStr);//把String转成dateString 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);//设置日期为1Calendar monthCalendar = Calendar.getInstance();//按月生成日历 每行7个 最多6行 42个//每一行有七个日期  日 一 二 三 四 五 六 的顺序Log.e("tag","calendar.getTimeInMillis()="+calendar.getTimeInMillis()+"----------endDate.getTime()="+endDate.getTime());for (calendar.getTimeInMillis(); calendar.getTimeInMillis() <= endDate.getTime();){//从当前时间开始,如果小于等于最后的时间,则增加一个月//月份itemDateBean 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;}}//生成某一天日期实体 日itemDateBean 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;}}//天数加1monthCalendar.add(Calendar.DAY_OF_MONTH, 1);}Log.e("tag", "日期:" + format.format(calendar.getTime()) + "----周" + getWeekStr(calendar.get(Calendar.DAY_OF_WEEK) + ""));//月份加1calendar.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() {@Overridepublic 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;}@Overridepublic 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() {@Overridepublic 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() {@Overridepublic void onClick(View v) {if (onRecyclerviewItemClick != null){onRecyclerviewItemClick.onItemClick(v,monthViewHolder.getLayoutPosition());}}});return monthViewHolder;}return null;}@Overridepublic 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);}}}@Overridepublic int getItemCount() {return data.size();}@Overridepublic 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);}@Overrideprotected 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();}@Overridepublic void show() {super.show();}private void initView(){img_close = findViewById(R.id.img_close);img_close.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {dismiss();}});calendarList = findViewById(R.id.calendarList);calendarList.setOnDateSelected(new MyCalendarList.OnDateSelected() {@Overridepublic 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();}}@Overridepublic 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() {@Overridepublic 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"?>
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_alignParentBottom="true"android:layout_marginBottom="60dp"android:background="@color/transparent"android:orientation="vertical"><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="40dp"android:background="@drawable/bg_gray_top_corner"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:text="选择日期"android:textSize="15sp"android:textColor="@color/black"/><ImageViewandroid: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.MyCalendarListandroid:id="@+id/calendarList"android:layout_width="match_parent"android:layout_height="450dp"android:background="@color/white"></com.junto.text.Calendar.MyCalendarList></LinearLayout><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_alignParentBottom="true"android:padding="10dp"android:background="@color/white"><Buttonandroid: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;@Overrideprotected 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() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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();}}@Overridepublic void hasSelect(boolean select) {}});}@Overridepublic 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"><translateandroid: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"><translateandroid:duration="600"android:toYDelta="100%p" />
</set>

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

public class DateBean {//item类型public static int item_type_day = 1;//日期itempublic static int item_type_month = 2;//月份itemint 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"));}@Overridepublic 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中第一个月份类型的itemfor(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

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

  1. android 画布叠加,Android自定义图形,图形的拼接、叠加、相容

    直接上Xfermode子类: AvoidXfermode  指定了一个颜色和容差,强制Paint避免在它上面绘图(或者只在它上面绘图). PixelXorXfermode  当覆盖已有的颜色时,应用一 ...

  2. Android自定义日期区间选择日期范围选则的日历,实现类似美团携程选择酒店入住日期和离店日期的日历效果

    前言 有时候我们会有需要在日历上选择一个日期范围的这种需求,先选一个开始日期,然后再选结束日期,如酒店入住日期和离店日期选择,美团携程这种预定酒店的app都有这种操作.那么这种需求该如何实现呢?先看一 ...

  3. android自定义滚动日期,Android基于wheelView实现自定义日期选择器

    本文实例为大家分享了Android实现自定义日期选择器的具体代码,供大家参考,具体内容如下 项目要求效果图: 要求 "6月20 星期五" 这一项作为一个整体可以滑动,"7 ...

  4. Android自定义DataTimePicker(日期选择器)

    Android自定义DataTimePicker(日期选择器)  笔者有一段时间没有发表关于Android的文章了,关于Android自定义组件笔者有好几篇想跟大家分享的,后期会记录在博客中.本篇博客 ...

  5. android身高控件_RuleView Android 自定义标尺控件(选择身高、体重等) @codeKK Android开源站...

    尺子刻度 -- 自定义 view 自定义 view 学习(第一章) 1.自定义刻度尺控件 在我们想要获取用户的身高体重等信息时,直接让他们输入显然不够友好偶然看到一款 App 用了类似刻度尺的界面让用 ...

  6. Android自定义时间控件不可选择未来时间

    本文出自:http://blog.csdn.net/dt235201314/article/details/78718066 Android自定义时间控件选择开始时间到结束时间 Android自定义时 ...

  7. android 立体 流量球,Android自定义View——实现水波纹效果类似剩余流量球

    Android自定义View--实现水波纹效果类似剩余流量球 三个点   pre   ber   block   span   初始化   move   理解最近突然手痒就想搞个贝塞尔曲线做个水波纹效 ...

  8. Android自定义类似ProgressDialog效果的Dialog

    2019独角兽企业重金招聘Python工程师标准>>> Android自定义类似ProgressDialog效果的Dialog. 方法如下: 1.首先准备两张自己要定义成哪样子的效果 ...

  9. 微信小程序—仿美团酒店入住日期时段选择

    美团市值突飞猛进,确实霸占了我们的吃喝玩乐,所以想做什么东西,研究一下美团也是不错的.最近需要用到一个酒店入住日期选择的组件,看了一下美团的设计,很好用,在此实现一下. 效果图如下: 体验路径(并获取 ...

最新文章

  1. java basicstroke_使用java.awt.BasicStroke动画化虚线
  2. 波士顿动力机器狗入驻庞贝古城,还要钻盗洞打击违法犯罪
  3. Linux中用mkdir同时创建多个文件夹
  4. 计算机常考应用分析题,计算机常见故障及排除
  5. 2017 ACM-ICPC西安网赛B-Coin
  6. 从A页面跳转到B页面动态路由参数
  7. 4 合并grid列_Grid教程
  8. 家庭医生后台管理系统高保真Axure原型模板
  9. 图书馆管理系统的c语言,图书馆管理系统 c语言.doc
  10. linux微软雅黑字体库_Linux_Debian系统中安装微软雅黑字体的教程,想在linux下添加微软雅黑的字 - phpStudy...
  11. 汽车诊断工程师的黎明:并行刷写策略与实现
  12. 项目“爱心雨伞”构建(一)
  13. 破解Excel的方式
  14. HTML网页设计: 一 HTML的基本结构
  15. 舰c2018换html5,[ 转] HTML/HTML5 download属性及其兼容性的探讨
  16. js:如何监听history的pushState方法和replaceState方法。(高阶函数封装+自定义事件)
  17. W25QXX芯片使用
  18. win10上面此电脑图标没有了
  19. 字符串与数字相加的问题
  20. Word 人人都要会的打印攻略(书籍打印,方便翻页,统一格式,缩放打印省纸又方便)

热门文章

  1. PHP Web开发框架Laravel如何配置
  2. matlab 保存色图,如何在matlab中制作“色图”图?
  3. VMware vSphere6.0 服务器虚拟化部署安装图解(转载火星小编)
  4. 基于卷积神经网络的图像分类
  5. Stagefright框架解读(—)音视频Playback流程
  6. Head First Java: Chapter6 JavaCross7.0 答案整理
  7. python 实现抽样分布(T分布、F分布、卡方分布)
  8. 从带宏密码保护的Excel文件中导出VBA代码和Sheet
  9. pinia 介绍与安装
  10. catia建模圆柱直齿轮和斜齿