You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于ViewPager2的网格日历开发问题:需按月展示而非单日

实现ViewPager2按月展示日历的核心方案

1. 重构数据结构:从单日到按月分组

把原有的单日数据结构,改成按月分组的日期集合,每个组对应一个月的完整日历数据(包含上月末尾、当月、下月开头的补位日期,保证日历排版为完整的6行7列)。

示例数据结构:

// 每个子列表对应一个月的所有日期(含补位)
private List<List<LocalDate>> mMonthDatesList = new ArrayList<>();

2. 完善daysInMonthArray方法,生成单月完整日历数据

修改该方法,让它返回指定月份的所有日期(包括补位),而非单日。示例实现:

public List<LocalDate> daysInMonthArray(LocalDate date) {
    List<LocalDate> daysInMonth = new ArrayList<>();
    YearMonth yearMonth = YearMonth.from(date);
    int daysInMonthCount = yearMonth.lengthOfMonth();
    
    // 获取当月第一天的星期值(周一=1,周日=7,可按需调整)
    LocalDate firstDayOfMonth = yearMonth.atDay(1);
    int firstDayWeekDay = firstDayOfMonth.getDayOfWeek().getValue();
    
    // 添加上月补位日期
    LocalDate prevMonthLastDay = firstDayOfMonth.minusDays(1);
    for (int i = firstDayWeekDay - 1; i > 0; i--) {
        daysInMonth.add(prevMonthLastDay.minusDays(i - 1));
    }
    
    // 添加当月日期
    for (int day = 1; day <= daysInMonthCount; day++) {
        daysInMonth.add(yearMonth.atDay(day));
    }
    
    // 添加下月补位日期,凑够6行×7列=42天
    int remainingSlots = 42 - daysInMonth.size();
    LocalDate nextMonthFirstDay = yearMonth.plusMonths(1).atDay(1);
    for (int i = 0; i < remainingSlots; i++) {
        daysInMonth.add(nextMonthFirstDay.plusDays(i));
    }
    
    return daysInMonth;
}

3. 修改ViewPager2Adapter,适配按月页面

原适配器绑定单日数据,现在改为绑定单月日期列表,每个页面加载一个月的日历布局(示例用RecyclerView网格布局实现)。

示例适配器修改:

public class ViewPager2Adapter extends RecyclerView.Adapter<ViewPager2Adapter.MonthViewHolder> {
    private List<List<LocalDate>> mMonthDates;

    public ViewPager2Adapter(List<List<LocalDate>> monthDates) {
        this.mMonthDates = monthDates;
    }

    @NonNull
    @Override
    public MonthViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_month_calendar, parent, false);
        return new MonthViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull MonthViewHolder holder, int position) {
        List<LocalDate> monthDays = mMonthDates.get(position);
        // 给单月日历网格设置日期适配器
        CalendarGridAdapter gridAdapter = new CalendarGridAdapter(monthDays);
        holder.mCalendarGrid.setAdapter(gridAdapter);
        // 设置月份标题,如"2024年5月"
        holder.mMonthTitle.setText(YearMonth.from(monthDays.get(15)).format(DateTimeFormatter.ofPattern("yyyy年MM月")));
    }

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

    static class MonthViewHolder extends RecyclerView.ViewHolder {
        RecyclerView mCalendarGrid;
        TextView mMonthTitle;

        public MonthViewHolder(@NonNull View itemView) {
            super(itemView);
            mCalendarGrid = itemView.findViewById(R.id.recycler_calendar_grid);
            mMonthTitle = itemView.findViewById(R.id.tv_month_title);
            // 配置网格布局:7列
            GridLayoutManager layoutManager = new GridLayoutManager(itemView.getContext(), 7);
            mCalendarGrid.setLayoutManager(layoutManager);
        }
    }
}

对应的单月日历布局item_month_calendar.xml示例:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_month_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="18sp"
        android:textStyle="bold"
        android:gravity="center"
        android:padding="8dp"/>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_calendar_grid"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</LinearLayout>

4. 在MainActivity中初始化ViewPager2

生成前后N个月的数据(示例默认加载当前月,前后各12个月),并绑定适配器:

public class MainActivity extends AppCompatActivity {
    private ViewPager2 mViewPager;
    private ViewPager2Adapter mAdapter;
    private List<List<LocalDate>> mMonthDatesList = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        mViewPager = findViewById(R.id.view_pager_calendar);
        // 生成前后12个月的日历数据
        generateMonthDates(LocalDate.now(), 12, 12);
        // 设置适配器
        mAdapter = new ViewPager2Adapter(mMonthDatesList);
        mViewPager.setAdapter(mAdapter);
        // 默认跳转到当前月的位置(中间索引)
        mViewPager.setCurrentItem(12, false);
    }

    // 生成指定日期前后n个月的数据
    private void generateMonthDates(LocalDate centerDate, int pastMonths, int futureMonths) {
        // 添加过去的月份
        for (int i = pastMonths; i > 0; i--) {
            mMonthDatesList.add(daysInMonthArray(centerDate.minusMonths(i)));
        }
        // 添加当前月
        mMonthDatesList.add(daysInMonthArray(centerDate));
        // 添加未来的月份
        for (int i = 1; i <= futureMonths; i++) {
            mMonthDatesList.add(daysInMonthArray(centerDate.plusMonths(i)));
        }
    }

    // 此处放置修改后的daysInMonthArray方法
    public List<LocalDate> daysInMonthArray(LocalDate date) {
        // 实现如上述步骤2
    }
}

5. 扩展优化逻辑

  • 日期点击:在CalendarGridAdapter中给每个日期Item添加点击事件,可通过YearMonth.from(date).equals(YearMonth.from(当月中心日期))区分当月和补位日期。
  • 无限滑动:若需类似Google Calendar的无限滑动效果,可让适配器getItemCount()返回极大值(如Integer.MAX_VALUE),在onBindViewHolder中通过取模计算实际月份索引,同时监听ViewPager2滚动事件动态加载新月份数据。

内容的提问来源于stack exchange,提问作者Chris Damianidis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 07:23:23