如何在Android Studio中实现图示样式的日历?
在Android Studio中实现指定样式日历的方案
根据你提供的日历样式(带月份切换、日期网格、选中状态及周末高亮),可以通过两种方式实现:第三方库快速搭建,或自定义实现高度定制。
一、使用第三方库(推荐,高效快捷)
以Material Calendar View为例,它适配Material Design风格,能快速实现你要的效果:
- 添加依赖
在Module级别的build.gradle(或build.gradle.kts)中添加依赖:
implementation 'com.prolificinteractive:material-calendarview:1.4.3'
- 布局中添加日历控件
在XML布局文件里配置日历的基础样式:
<com.prolificinteractive.materialcalendarview.MaterialCalendarView android:id="@+id/calendarView" android:layout_width="match_parent" android:layout_height="wrap_content" <!-- 显示其他月份的日期 --> app:mcv_showOtherDates="all" <!-- 选中日期的颜色 --> app:mcv_selectionColor="#FF4081" <!-- 星期标题的文字样式 --> app:mcv_weekDayTextAppearance="@style/WeekDayTextStyle" <!-- 日期文字样式 --> app:mcv_dateTextAppearance="@style/DateTextStyle" <!-- 月份切换箭头颜色 --> app:mcv_arrowColor="#3F51B5"/>
- 自定义周末高亮效果
创建DayViewDecorator子类来标记周末日期:
public class WeekendHighlightDecorator implements DayViewDecorator { private final Calendar calendar = Calendar.getInstance(); private final Drawable highlightBg; public WeekendHighlightDecorator(Context context) { highlightBg = ContextCompat.getDrawable(context, R.drawable.weekend_bg); } @Override public boolean shouldDecorate(CalendarDay day) { day.copyTo(calendar); int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); return dayOfWeek == Calendar.SATURDAY || dayOfWeek == Calendar.SUNDAY; } @Override public void decorate(DayViewFacade view) { view.setBackgroundDrawable(highlightBg); } }
在Activity/Fragment中给日历添加装饰器:
MaterialCalendarView calendarView = findViewById(R.id.calendarView); // 添加周末高亮装饰 calendarView.addDecorator(new WeekendHighlightDecorator(this)); // 设置日期选中监听 calendarView.setOnDateChangedListener((widget, selectedDate, isSelected) -> { // 处理选中日期的逻辑,比如显示选中日期 String date = selectedDate.getYear() + "-" + (selectedDate.getMonth()+1) + "-" + selectedDate.getDay(); Toast.makeText(this, "选中日期:" + date, Toast.LENGTH_SHORT).show(); });
- 自定义样式补充
可以在styles.xml中定义星期和日期的文字样式:
<style name="WeekDayTextStyle" parent="TextAppearance.MaterialComponents.Caption"> <item name="android:textColor">#757575</item> <item name="android:textSize">12sp</item> </style> <style name="DateTextStyle" parent="TextAppearance.MaterialComponents.Body2"> <item name="android:textColor">#212121</item> <item name="android:textSize">14sp</item> </style>
二、自定义实现(高度定制)
如果需要更个性化的样式,可自行搭建日历组件:
- 搭建布局结构
顶部放置月份标题和左右切换按钮,下方用RecyclerView(或GridView)展示日期网格:
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="16dp" android:gravity="center" android:orientation="horizontal"> <ImageView android:id="@+id/btn_prev_month" android:layout_width="24dp" android:layout_height="24dp" android:src="@drawable/ic_arrow_left"/> <TextView android:id="@+id/tv_current_month" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="2024年6月" android:textSize="18sp" android:gravity="center"/> <ImageView android:id="@+id/btn_next_month" android:layout_width="24dp" android:layout_height="24dp" android:src="@drawable/ic_arrow_right"/> </LinearLayout> <RecyclerView android:id="@+id/rv_calendar_dates" android:layout_width="match_parent" android:layout_height="wrap_content" android:numColumns="7"/> </LinearLayout>
- 计算日历数据
编写工具类计算指定月份的第一天星期数、当月天数,生成包含空白占位(月初前的非当月日期)的日期列表:
public class CalendarUtils { public static List<Date> getMonthDates(int year, int month) { List<Date> dates = new ArrayList<>(); Calendar calendar = Calendar.getInstance(); calendar.set(year, month, 1); // 获取当月第一天是星期几(周日为1,周一为2...) int firstDayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); // 添加入月初的空白占位 for (int i = 1; i < firstDayOfWeek; i++) { dates.add(null); } // 添加当月天数 int daysInMonth = calendar.getActualMaximum(Calendar.DAY_OF_MONTH); for (int i = 1; i <= daysInMonth; i++) { Calendar dateCal = Calendar.getInstance(); dateCal.set(year, month, i); dates.add(dateCal.getTime()); } return dates; } }
- 实现日期Adapter
自定义RecyclerView.Adapter,绑定日期数据,处理选中状态、周末高亮:
public class CalendarDateAdapter extends RecyclerView.Adapter<CalendarDateAdapter.DateViewHolder> { private List<Date> mDates; private Date mSelectedDate; private OnDateSelectedListener mListener; public interface OnDateSelectedListener { void onDateSelected(Date date); } public CalendarDateAdapter(List<Date> dates, OnDateSelectedListener listener) { mDates = dates; mListener = listener; mSelectedDate = new Date(); } @Override public DateViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_calendar_date, parent, false); return new DateViewHolder(view); } @Override public void onBindViewHolder(DateViewHolder holder, int position) { Date date = mDates.get(position); if (date == null) { holder.tvDate.setText(""); holder.itemView.setEnabled(false); return; } Calendar calendar = Calendar.getInstance(); calendar.setTime(date); int day = calendar.get(Calendar.DAY_OF_MONTH); holder.tvDate.setText(String.valueOf(day)); // 标记周末 int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); if (dayOfWeek == Calendar.SATURDAY || dayOfWeek == Calendar.SUNDAY) { holder.tvDate.setTextColor(Color.parseColor("#FF4081")); } else { holder.tvDate.setTextColor(Color.parseColor("#212121")); } // 标记选中状态 boolean isSelected = isSameDate(date, mSelectedDate); if (isSelected) { holder.itemView.setBackgroundResource(R.drawable.selected_date_bg); } else { holder.itemView.setBackgroundResource(0); } holder.itemView.setOnClickListener(v -> { mSelectedDate = date; notifyDataSetChanged(); mListener.onDateSelected(date); }); } private boolean isSameDate(Date date1, Date date2) { Calendar cal1 = Calendar.getInstance(); cal1.setTime(date1); Calendar cal2 = Calendar.getInstance(); cal2.setTime(date2); return cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) && cal1.get(Calendar.MONTH) == cal2.get(Calendar.MONTH) && cal1.get(Calendar.DAY_OF_MONTH) == cal2.get(Calendar.DAY_OF_MONTH); } @Override public int getItemCount() { return mDates.size(); } static class DateViewHolder extends RecyclerView.ViewHolder { TextView tvDate; public DateViewHolder(View itemView) { super(itemView); tvDate = itemView.findViewById(R.id.tv_date); } } }
- 实现月份切换逻辑
在Activity中处理左右按钮点击,切换月份并刷新日历数据:
public class CalendarActivity extends AppCompatActivity { private int mCurrentYear; private int mCurrentMonth; private TextView tvCurrentMonth; private RecyclerView rvCalendarDates; private CalendarDateAdapter mAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_calendar); initViews(); initCurrentDate(); loadCalendarData(); } private void initViews() { tvCurrentMonth = findViewById(R.id.tv_current_month); rvCalendarDates = findViewById(R.id.rv_calendar_dates); rvCalendarDates.setLayoutManager(new GridLayoutManager(this, 7)); findViewById(R.id.btn_prev_month).setOnClickListener(v -> { mCurrentMonth--; if (mCurrentMonth < 0) { mCurrentMonth = 11; mCurrentYear--; } loadCalendarData(); }); findViewById(R.id.btn_next_month).setOnClickListener(v -> { mCurrentMonth++; if (mCurrentMonth > 11) { mCurrentMonth = 0; mCurrentYear++; } loadCalendarData(); }); } private void initCurrentDate() { Calendar calendar = Calendar.getInstance(); mCurrentYear = calendar.get(Calendar.YEAR); mCurrentMonth = calendar.get(Calendar.MONTH); } private void loadCalendarData() { List<Date> dates = CalendarUtils.getMonthDates(mCurrentYear, mCurrentMonth); tvCurrentMonth.setText(mCurrentYear + "年" + (mCurrentMonth + 1) + "月"); mAdapter = new CalendarDateAdapter(dates, date -> { // 处理日期选中逻辑 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.CHINA); Toast.makeText(this, "选中:" + sdf.format(date), Toast.LENGTH_SHORT).show(); }); rvCalendarDates.setAdapter(mAdapter); } }
注意事项
- 自定义实现时需处理闰年2月的天数差异,避免日期计算错误。
- 适配不同屏幕尺寸,确保日历网格的每个单元格大小一致。
- 第三方库版本可能更新,使用时注意查看官方文档适配最新版本。
内容的提问来源于stack exchange,提问作者Raj Subh
相关产品推荐
相关产品推荐

