Android 纵向双选日历

这个日历的布局分两部分,一部分是显示星期几的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

  1. 计算数据
  2. 可以扩展日历的点击事件和范围选择事件
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属性操作):

  1. 根据类型,填充月布局和日布局。
  2. 包含点击、范围选择的方法
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,完毕!

RecyclerView 的纵向自动滚动实现可以使用 RecyclerView 自带的 LayoutManager 或者自定义 LayoutManager 实现。 使用 RecyclerView 自带的 LayoutManager 实现纵向自动滚动: 1. 获取 RecyclerView 的 LinearLayoutManager 对象 2. 创建一个 Handler 对象和一个 Runnable 对象 3. 在 Runnable 的 run() 方法中,调用 LinearLayoutManager 的 smoothScrollToPosition() 方法实现自动滚动 4. 在 Handler 中使用 postDelayed() 方法,指定延迟时间和 Runnable 对象,实现定时滚动。 示例代码如下: ``` private LinearLayoutManager mLinearLayoutManager; private Handler mHandler = new Handler(); private Runnable mRunnable = new Runnable() { @Override public void run() { int currentPos = mLinearLayoutManager.findFirstVisibleItemPosition(); mLinearLayoutManager.smoothScrollToPosition(mRecyclerView, null, currentPos + 1); mHandler.postDelayed(this, 3000); // 延迟 3 秒滚动 } }; private void initRecyclerView() { mLinearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); mRecyclerView.setLayoutManager(mLinearLayoutManager); mRecyclerView.setAdapter(mAdapter); } private void startAutoScroll() { mHandler.postDelayed(mRunnable, 3000); // 延迟 3 秒滚动 } private void stopAutoScroll() { mHandler.removeCallbacks(mRunnable); } ``` 使用自定义 LayoutManager 实现纵向自动滚动: 1. 自定义一个继承自 LinearLayoutManager 的 LayoutManager 2. 在 LayoutManager 的 onLayoutChildren() 方法中,调用 super.onLayoutChildren() 方法完成布局,并获取第一个和最后一个可见 item 的位置 3. 在 LayoutManager 的 scrollVerticallyBy() 方法中,判断当前滑动的距离是否大于等于一个屏幕的高度,如果是,则调用 scrollToPosition() 方法实现自动滚动。 示例代码如下: ``` public class AutoScrollLayoutManager extends LinearLayoutManager { private int mScreenHeight; public AutoScrollLayoutManager(Context context) { super(context); mScreenHeight = context.getResources().getDisplayMetrics().heightPixels; } @Override public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { super.onLayoutChildren(recycler, state); int firstVisiblePos = findFirstVisibleItemPosition(); int lastVisiblePos = findLastVisibleItemPosition(); if (lastVisiblePos - firstVisiblePos < state.getItemCount() - 1) { // 如果可见 item 数量小于总 item 数量,强制重新布局 detachAndScrapAttachedViews(recycler); layoutChildren(recycler, state); } } private void layoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { int offsetY = 0; for (int i = 0; i < getItemCount(); i++) { View view = recycler.getViewForPosition(i); addView(view); measureChildWithMargins(view, 0, 0); int width = getDecoratedMeasuredWidth(view); int height = getDecoratedMeasuredHeight(view); layoutDecorated(view, 0, offsetY, width, offsetY + height); offsetY += height; } } @Override public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) { int travel = dy; if (dy > 0) { // 手指向上滑动 View lastVisibleView = getChildAt(getChildCount() - 1); int lastVisiblePos = getPosition(lastVisibleView); if (lastVisibleView.getBottom() - dy < mScreenHeight) { // 如果最后一个可见 item 没有完全显示,就不滑动 travel = 0; } else if (lastVisiblePos == getItemCount() - 1) { // 如果滑到最后一个 item,就不滑动 travel = 0; } } int realScroll = super.scrollVerticallyBy(travel, recycler, state); if (realScroll != travel) { // 如果滑动距离小于手指滑动距离,说明已经滑到边界了,就自动滚动 int currentPos = findFirstVisibleItemPosition(); scrollToPosition(currentPos + 1); } return realScroll; } } ``` 使用自定义的 AutoScrollLayoutManager: ``` private AutoScrollLayoutManager mLayoutManager; private void initRecyclerView() { mLayoutManager = new AutoScrollLayoutManager(this); mRecyclerView.setLayoutManager(mLayoutManager); mRecyclerView.setAdapter(mAdapter); } private void startAutoScroll() { mRecyclerView.smoothScrollToPosition(mAdapter.getItemCount() - 1); // 先滚动到最后一个 item mHandler.postDelayed(mRunnable, 3000); // 延迟 3 秒开始自动滚动 } private void stopAutoScroll() { mHandler.removeCallbacks(mRunnable); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值