这个日历的布局分两部分,一部分是显示星期几的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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:padding="5dp"
android:text="日" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:padding="5dp"
android:text="一" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:padding="5dp"
android:text="二" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:padding="5dp"
android:text="三" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:padding="5dp"
android:text="四" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:padding="5dp"
android:text="五" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:padding="5dp"
android:text="六" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android: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()) {
@Override
public void handleMessage(Message msg) {
initView();
super.handleMessage(msg);
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_calendar);
recyclerView = findViewById(R.id.recycler_view);
new Thread() {
@Override
public void run() {
initData();
}
;
}.start();
}
private void initView() {
GridLayoutManager manager = new GridLayoutManager(CalendarActivity.this, 7);
manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public 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() {
@Override
public void onItemClick(int position) {
}
@Override
public 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
@Override
public 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;
}
@Override
public 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() {
@Override
public 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);
}
@Override
public int getItemViewType(int position) {
return list.get(position).getType() == 0 ? TYPE_MONTH : TYPE_DAY;
}
@Override
public 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,完毕!