这个日历的布局分两部分,一部分是显示星期几的LinearLayout,另外就是一个RecyclerView,负责纵向滚动了。
工具类:
implementation 'com.blankj:utilcode:1.17.3'
上activity_calendar代码:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"><TextViewandroid:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:gravity="center"android:padding="5dp"android:text="日" /><TextViewandroid:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:gravity="center"android:padding="5dp"android:text="一" /><TextViewandroid:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:gravity="center"android:padding="5dp"android:text="二" /><TextViewandroid:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:gravity="center"android:padding="5dp"android:text="三" /><TextViewandroid:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:gravity="center"android:padding="5dp"android:text="四" /><TextViewandroid:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:gravity="center"android:padding="5dp"android:text="五" /><TextViewandroid:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:gravity="center"android:padding="5dp"android:text="六" /></LinearLayout><androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/recycler_view"android:layout_width="match_parent"android:layout_height="match_parent"android:overScrollMode="never" />
</LinearLayout>
接下来是CalendarActivity
- 计算数据
- 可以扩展日历的点击事件和范围选择事件
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;import com.blankj.utilcode.constant.TimeConstants;
import com.blankj.utilcode.util.TimeUtils;import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;public class CalendarActivity extends AppCompatActivity {private RecyclerView recyclerView;private CalendarRangeAdapter calendarRangeAdapter;/*** 最大月份数*/private int maxMonth = 12;private List<DateBean> dateList = new ArrayList<>();private List<DateBean> allDateList = new ArrayList<>();private Handler handler = new Handler(Looper.getMainLooper()) {@Overridepublic void handleMessage(Message msg) {initView();super.handleMessage(msg);}};@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_calendar);recyclerView = findViewById(R.id.recycler_view);new Thread() {@Overridepublic void run() {initData();};}.start();}private void initView() {GridLayoutManager manager = new GridLayoutManager(CalendarActivity.this, 7);manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {@Overridepublic int getSpanSize(int position) {return allDateList.get(position).getType() == 0 ? 7 : 1;}});recyclerView.setLayoutManager(manager);calendarRangeAdapter = new CalendarRangeAdapter(CalendarActivity.this, allDateList);recyclerView.setAdapter(calendarRangeAdapter);calendarRangeAdapter.setOnItemSelect(new CalendarRangeAdapter.OnItemSelect() {@Overridepublic void onItemClick(int position) {}@Overridepublic void onItemRangeSelect(String startDate, String endDate) {System.out.println(startDate + "~" + endDate);}});}private void initData() {for (int i = 0; i < maxMonth; i++) {Calendar calendar = Calendar.getInstance();calendar.add(Calendar.MONTH, i);int year = calendar.get(Calendar.YEAR);int month = calendar.get(Calendar.MONTH) + 1;int maxDays = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.CHINA);String dateString = simpleDateFormat.format(calendar.getTime());DateBean monthBean = new DateBean();monthBean.setDate(dateString.substring(0, 7));monthBean.setCanSelect(false);monthBean.setType(0);dateList.clear();dateList.add(monthBean);calendar.set(Calendar.DAY_OF_MONTH, 1);//当月第一天是周几 0是周日,1是周一 以此类推int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);//填充1号前面的空白for (int j = 0; j < dayOfWeek - 1; j++) {DateBean dateBean = new DateBean();dateBean.setCanSelect(false);dateBean.setType(1);dateBean.setDate("");dateList.add(dateBean);}for (int j = 0; j < maxDays; j++) {DateBean dateBean = new DateBean();dateBean.setType(1);dateBean.setCenterString(String.valueOf(j + 1));dateBean.setSelect(true);dateBean.setDate(year + "-" + addZero(month) + "-" + addZero(j + 1));//今天之前不可选中if (TimeUtils.getTimeSpanByNow(dateBean.getDate(), new SimpleDateFormat("yyyy-MM-dd", Locale.CHINA), TimeConstants.DAY) < 0) {dateBean.setCenterString(String.valueOf(j + 1));dateBean.setCanSelect(false);} else {if (TimeUtils.getTimeSpanByNow(dateBean.getDate(), new SimpleDateFormat("yyyy-MM-dd", Locale.CHINA), TimeConstants.DAY) > 0) {dateBean.setCenterString(String.valueOf(j + 1));} else {dateBean.setCenterString("今天");}dateBean.setCanSelect(true);}dateList.add(dateBean);}allDateList.addAll(dateList);}Message msg = handler.obtainMessage();handler.sendMessage(msg);}private String addZero(int text) {if (text < 10) {return "0" + text;} else {return "" + text;}}
}
上CalendarRangeAdapter代码(根据bean属性操作):
- 根据类型,填充月布局和日布局。
- 包含点击、范围选择的方法
public class CalendarRangeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {private Context context;private List<DateBean> list;private int[] selectRange = new int[2];private final int TYPE_MONTH = 0;private final int TYPE_DAY = 1;public CalendarRangeAdapter(Context context, List<DateBean> list) {this.context = context;this.list = list;initSelect();}public void initSelect() {clearSelect();}public void clearSelect() {for (int i = 0; i < list.size(); i++) {list.get(i).setSelect(false);list.get(i).setSelectRange(false);list.get(i).setBottomString("");}selectRange[0] = -1;selectRange[1] = -1;}public void notifySelect() {notifyDataSetChanged();}public void setData(List<DateBean> list) {this.list = list;notifyDataSetChanged();}@NonNull@Overridepublic RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {RecyclerView.ViewHolder holder = null;if (viewType == 0) {//月份View view = LayoutInflater.from(context).inflate(R.layout.item_month,parent, false);holder = new MonthViewHolder(view);} else {//日期View view = LayoutInflater.from(context).inflate(R.layout.item_day,parent, false);holder = new DayViewHolder(view);}return holder;}@Overridepublic void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {final int fPosition = holder.getAdapterPosition();if (holder instanceof MonthViewHolder) {((MonthViewHolder) holder).tvMonth.setText(list.get(position).getDate().replace("-", "年") + "月");} else {final DayViewHolder viewHolder = (DayViewHolder) holder;viewHolder.tvCenter.setText(list.get(fPosition).getCenterString());viewHolder.tvBottom.setText(list.get(fPosition).getBottomString());if (viewHolder.llDay.getTag() instanceof View.OnClickListener){viewHolder.llDay.setOnClickListener( null);}if (list.get(fPosition).isCanSelect()) {//今天之前(不可选)if (TimeUtils.getTimeSpanByNow(list.get(position).getDate(), new SimpleDateFormat("yyyy-MM-dd", Locale.CHINA), TimeConstants.DAY) < 0) {viewHolder.tvCenter.setTextColor(ContextCompat.getColor(context, R.color.color_calendar_can_not_select));} else {if (TimeUtils.getTimeSpanByNow(list.get(position).getDate(), new SimpleDateFormat("yyyy-MM-dd", Locale.CHINA), TimeConstants.DAY) > 0) {viewHolder.tvCenter.setTextColor(ContextCompat.getColor(context, R.color.color_calendar_can_select));} else {viewHolder.tvCenter.setTextColor(ContextCompat.getColor(context, R.color.color_calendar_today));}if (list.get(fPosition).isSelect()) {viewHolder.llDay.setBackgroundColor(ContextCompat.getColor(context, R.color.color_calendar_background_select));viewHolder.tvCenter.setTextColor(ContextCompat.getColor(context, R.color.color_calendar_select));viewHolder.tvBottom.setTextColor(ContextCompat.getColor(context, R.color.color_calendar_select));} else if (list.get(fPosition).isSelectRange()) {viewHolder.llDay.setBackgroundColor(ContextCompat.getColor(context, R.color.color_calendar_background_select_range));viewHolder.tvCenter.setTextColor(ContextCompat.getColor(context, R.color.color_calendar_select));viewHolder.tvBottom.setTextColor(ContextCompat.getColor(context, R.color.color_calendar_select));} else {viewHolder.llDay.setBackgroundColor(ContextCompat.getColor(context, R.color.color_calendar_background_normal));}if (onItemSelect != null) {View.OnClickListener clickListener = new View.OnClickListener() {@Overridepublic void onClick(View v) {if (selectRange[0] == -1 && selectRange[1] == -1) {selectRange[0] = fPosition;list.get(fPosition).setSelect(true);list.get(fPosition).setBottomString("开始");onItemSelect.onItemClick(fPosition);notifyDataSetChanged();} else if (selectRange[0] != -1 && selectRange[1] == -1) {onItemSelect.onItemClick(fPosition);if (fPosition < selectRange[0]) {clearSelect();selectRange[0] = fPosition;list.get(fPosition).setSelect(true);list.get(fPosition).setBottomString("开始");} else if (fPosition > selectRange[0]) {selectRange[1] = fPosition;list.get(fPosition).setSelect(true);list.get(fPosition).setBottomString("结束");for (int i = selectRange[0] + 1; i < selectRange[1]; i++) {list.get(i).setSelectRange(true);list.get(i).setBottomString("");}onItemSelect.onItemRangeSelect(list.get(selectRange[0]).getDate(), list.get(selectRange[1]).getDate());} else {clearSelect();}notifyDataSetChanged();} else {clearSelect();selectRange[0] = fPosition;list.get(fPosition).setSelect(true);list.get(fPosition).setBottomString("开始");onItemSelect.onItemClick(fPosition);notifyDataSetChanged();}}};viewHolder.llDay.setOnClickListener(clickListener);viewHolder.llDay.setTag(clickListener);}}} else {viewHolder.llDay.setBackgroundColor(ContextCompat.getColor(context, R.color.color_calendar_background_normal));viewHolder.tvCenter.setTextColor(ContextCompat.getColor(context, R.color.color_calendar_can_not_select));}}}public OnItemSelect onItemSelect;public void setOnItemSelect(OnItemSelect onItemSelect) {this.onItemSelect = onItemSelect;}public interface OnItemSelect {void onItemClick(int position);void onItemRangeSelect(String startDate, String endDate);}@Overridepublic int getItemViewType(int position) {return list.get(position).getType() == 0 ? TYPE_MONTH : TYPE_DAY;}@Overridepublic int getItemCount() {return list == null ? 0 : list.size();}class MonthViewHolder extends RecyclerView.ViewHolder {TextView tvMonth;public MonthViewHolder(View itemView) {super(itemView);tvMonth = itemView.findViewById(R.id.tv_month);}}class DayViewHolder extends RecyclerView.ViewHolder {LinearLayout llDay;TextView tvCenter;TextView tvBottom;public DayViewHolder(View itemView) {super(itemView);llDay = itemView.findViewById(R.id.ll_day);tvCenter = itemView.findViewById(R.id.tv_center);tvBottom = itemView.findViewById(R.id.tv_bottom);}}
}
DateBean代码(中间文字,底部文字,是否可选等属性):
public class DateBean {/*** 日期*/private String date;/*** 底部文字*/private String bottomString;/*** 中间文字*/private String centerString;/*** 是否可以选择*/private boolean canSelect;/*** 是否选中*/private boolean isSelect;/**是否在选中范围*/private boolean isSelectRange;/*** item type 0月份 1日期*/private int type;public String getDate() {return date == null ? "" : date;}public void setDate(String date) {this.date = date;}public String getCenterString() {return centerString == null ? "" : centerString;}public void setCenterString(String centerString) {this.centerString = centerString;}public boolean isCanSelect() {return canSelect;}public void setCanSelect(boolean canSelect) {this.canSelect = canSelect;}public boolean isSelect() {return isSelect;}public void setSelect(boolean select) {isSelect = select;}public boolean isSelectRange() {return isSelectRange;}public void setSelectRange(boolean selectRange) {isSelectRange = selectRange;}public int getType() {return type;}public void setType(int type) {this.type = type;}public String getBottomString() {return bottomString == null ? "" : bottomString;}public void setBottomString(String bottomString) {this.bottomString = bottomString;}
}
上color.xml(可以自己换):
<resources><color name="color_calendar_can_not_select">#dedede</color><color name="color_calendar_can_select">#505050</color><color name="color_calendar_select">#ffffff</color><color name="color_calendar_today">#F67332</color><color name="color_calendar_background_select">#F3BE30</color><color name="color_calendar_background_select_range">#7DF3BE30</color><color name="color_calendar_background_normal">#00000000</color>
</resources>
OK,完毕!