自定义打卡日历

先看效果图

实际是圆的,由于录制视频视频尺寸小于实际宽度,所以看着有点像椭圆;
在这里插入图片描述

需求分析

1、需求左右点击可以切换月份,不需要左右滑动切换,时间紧迫就按需求完成功能;
2、打卡日期选中如图,单个选中、左右相邻选中和换行样式如图;
3、当前日期选中,点击也需要选中效果;
4、月份可能会显示5行或6行,控制总高度不变,动态设置单行日期的高度,选中要是的宽高;
5、根据年月获取当月有多少天,再根据第一天在星期几,当月数据从此处开始添加

核心代码

星期的实现

星期根据控件的宽均分显示,高度居中显示

public class WeekBarView extends AppCompatTextView {
    public String[] days = {"日", "一", "二", "三", "四", "五", "六"};
    private TextPaint textPaint;

    public WeekBarView(Context context) {
        this(context, null);
    }

    public WeekBarView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        textPaint = getPaint();
        textPaint.setTextAlign(Paint.Align.CENTER);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();

        int paddingTop = getPaddingTop();
        int paddingBottom = getPaddingBottom();

        int width = getMeasuredWidth() - paddingRight - paddingLeft;
        int height = getMeasuredHeight() - paddingTop - paddingBottom;
        for (int i = 0; i < days.length; i++) {
            Rect rect = new Rect(paddingLeft + (i * width / days.length), paddingTop, paddingLeft + ((i + 1) * width / days.length), paddingTop + height);
            Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
            float top = fontMetrics.top;
            float bottom = fontMetrics.bottom;
            int baseLineY = (int) (rect.centerY() - top / 2 - bottom / 2);
            canvas.drawText(days[i], rect.centerX(), baseLineY, textPaint);
        }
    }
}

日历实现

日期的工具类

/**
     * 获取对应年月的天数
     *
     * @param year
     * @param month
     * @return
     */
    public int getMonthLastDay(int year, int month) {
        Calendar a = Calendar.getInstance();
        a.set(Calendar.YEAR, year);
        a.set(Calendar.MONTH, month - 1);
        a.set(Calendar.DATE, 1);//把日期设置为当月第一天
        a.roll(Calendar.DATE, -1);//日期回滚一天,也就是最后一天
        return a.get(Calendar.DATE);
    }
    //获取日历相关数据
 Calendar calendar = Calendar.getInstance();
 //当天在当月中第几天 2、12、23
 int currDay = calendar.get(Calendar.DAY_OF_MONTH);
 //当月是第几个月 8、11
 int currMouth = calendar.get(Calendar.MONTH) + 1;
 //当前年份 2019
  int currYear = calendar.get(Calendar.YEAR);

/**
     * 对应年月的一号是星期几
     *
     * @param year
     * @param month
     * @return
     */
    public int getFirstDayOfWeek(int year, int month) {
        Calendar calendar = Calendar.getInstance();
        calendar.set(year, month - 1, 0);
        int currYear = calendar.get(Calendar.YEAR);
        int currMouth = calendar.get(Calendar.MONTH) + 1;
        int currDay = calendar.get(Calendar.DAY_OF_MONTH);
        int i1 = calendar.get(Calendar.DAY_OF_WEEK);//这就是星期几
        if (i1 == 7) {
            i1 = 0;
        }
        return i1;
    }

设置日历的数据核心代码:

//获取日历布局的宽高,后面用于item的宽高和选中宽高设置
   @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        totalHeight = getMeasuredHeight();
        totalWidth = getMeasuredWidth();
//        Log.e("snow_m", "=========" + totalHeight);
    }
 /**
     * 重新设置日历数据
     *
     * @param year
     * @param month
     */
    public void resetCalendarData(int year, int month, List<ClockInBean> clockInList) {
        calendarDataList.clear();
        int monthLastDay = CalendarUtils.init().getMonthLastDay(year, month);
        int dayOfWeek = CalendarUtils.init().getFirstDayOfWeek(year, month);
        int dayOfMonth = CalendarUtils.init().getTodayOfMonth();
        //当前页面显示上月的数据,或者空
        for (int i = 0; i < dayOfWeek; i++) {
            CalendarData calendarData = new CalendarData();
            calendarData.setSolarNum("");
            calendarDataList.add(calendarData);
        }
        //设置当月数据
        for (int i = 0; i < monthLastDay; i++) {
            CalendarData calendarData = new CalendarData();
            calendarData.setYearMD(year + "-" + month + "-" + CalendarUtils.init().addZeroBefore(i + 1));
            calendarData.setSolarNum(String.valueOf(i + 1));
            calendarDataList.add(calendarData);
        }
//        calendarAdapter.setTodayPosition(dayOfMonth + dayOfWeek, CalendarUtils.init().getTodayMonth());
        //计算共几行
        int rowNum = (int) Math.ceil((monthLastDay + dayOfWeek) / 7f);
        calendarAdapter.setItemWidthHeight(totalHeight / rowNum, rowNum, totalWidth / 7);

        CalendarUtils.init().addClockInInfoToCalendarInfo(year, month, dayOfWeek, calendarDataList, clockInList);

        calendarAdapter.setDataList(calendarDataList);
    }

适配器实现功能核心代码:

public class AdapterCalendarItem extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private Context mContext;
    private List<CalendarData> dataList;
    private int itemHeight;
    private int itemWidth;
    private int onlySelectWH;
    CalendarItemClickListener itemClickListener;
    //当前点击日期 2019-11-02
    private String currentClickDate;
    //当前日期 2019-11-02
    private String todayDate;


    public AdapterCalendarItem(Context mContext, CalendarItemClickListener itemClickListener) {
        this.mContext = mContext;
        this.itemClickListener = itemClickListener;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(mContext).inflate(R.layout.item_calendar_adapter, null);
        return new ViewHolder(view);
    }

    public void setDataList(List<CalendarData> dataList) {
        this.dataList = dataList;
        notifyDataSetChanged();
    }

    /**
     * 设置今天日期
     *
     * @param todayPosition
     */
    public void setTodayPosition(String todayPosition) {
        this.todayDate = todayPosition;
    }

    /**
     * 设置item的高度  总行数
     *
     * @param itemHeight
     * @param rowNum
     */
    public void setItemWidthHeight(int itemHeight, int rowNum, int itemWidth) {
        this.itemHeight = itemHeight;
        this.itemWidth = itemWidth;
        onlySelectWH = (int) (itemHeight - TextViewUtils.init().dp2px(mContext, 12) * (5 / 6f));
        if (onlySelectWH > itemWidth) {
            onlySelectWH = itemWidth;
        }
//        Log.e("snow_m", "====itemHeight=====" + itemHeight);
//        Log.e("snow_m", "====itemWidth=====" + itemWidth);
    }


    public void setCurrentClickDate(String clickDate) {
        this.currentClickDate = clickDate;
        notifyDataSetChanged();
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, final int position) {
        final CalendarData data = dataList.get(position);
        ViewHolder vh = (ViewHolder) holder;
        //设置item的高
        RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) vh.root_view.getLayoutParams();
        layoutParams.height = itemHeight;
        vh.root_view.setLayoutParams(layoutParams);
        //设置选中的宽高
        RelativeLayout.LayoutParams onlySelectViewParams = (RelativeLayout.LayoutParams) vh.onlySelectView.getLayoutParams();
        onlySelectViewParams.height = onlySelectWH;
        onlySelectViewParams.width = onlySelectWH;
        vh.onlySelectView.setLayoutParams(onlySelectViewParams);

        vh.tv_num.setText(data.getSolarNum());
        //设置选中的===已签到状态
        if (data.getClockInBean() != null) {
            vh.tv_num.setTextColor(mContext.getResources().getColor(R.color.tt_FF8820));
            setSelectedView(vh.selectedView, position);
            vh.ivRemind.setVisibility(View.VISIBLE);
        } else {
            vh.tv_num.setTextColor(mContext.getResources().getColor(R.color.tt_33333));
            vh.ivRemind.setVisibility(View.INVISIBLE);
            vh.selectedView.setBackground(null);
        }
        //设置当前点击的效果
        if (!TextUtils.isEmpty(currentClickDate) && currentClickDate.equals(data.getYearMD())) {
            vh.ivRemind.setColorFilter(mContext.getResources().getColor(R.color.tt_white));
            vh.onlySelectView.setBackgroundResource(R.drawable.bg_ff8820_circle);
            vh.tv_num.setTextColor(mContext.getResources().getColor(R.color.tt_white));
        } else {
            vh.ivRemind.setColorFilter(mContext.getResources().getColor(R.color.tt_FF8820));
            vh.onlySelectView.setBackground(null);
        }
        //设置今天对应的效果
        if (!TextUtils.isEmpty(data.getYearMD()) && data.getYearMD().equals(todayDate)) {
            vh.ivRemind.setColorFilter(mContext.getResources().getColor(R.color.tt_white));
            vh.onlySelectView.setBackgroundResource(R.drawable.bg_ff8820_circle);
            vh.tv_num.setTextColor(mContext.getResources().getColor(R.color.tt_white));
        }
        //item点击
        vh.root_view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (itemClickListener != null) {
                    itemClickListener.itemClick(data, position);
                }
            }
        });
    }

    /**
     * 根据情况显示已选中(打卡)的样式
     *
     * @param selectedView
     * @param position
     */
    private void setSelectedView(View selectedView, int position) {
        if (position % 7 == 0) {//最左边的数字
            drawSelectView(selectedView, nextIsSelected(position, dataList) ? 3 : 0);
        } else if (position % 7 == 6) {//最右边的数字
            drawSelectView(selectedView, lastIsSelected(position, dataList) ? 2 : 0);
        } else {//中间数据
            boolean left = lastIsSelected(position, dataList);
            boolean right = nextIsSelected(position, dataList);
            if (left && right) {
                drawSelectView(selectedView, 1);
            } else if (left) {
                drawSelectView(selectedView, 2);
            } else if (right) {
                drawSelectView(selectedView, 3);
            } else {
                drawSelectView(selectedView, 0);
            }
        }
    }

    /**
     * 绘制选中的样式
     *
     * @param selectedView
     * @param flag
     */
    private void drawSelectView(View selectedView, int flag) {
        //设置选中的宽高
        RelativeLayout.LayoutParams onlySelectViewParams = (RelativeLayout.LayoutParams) selectedView.getLayoutParams();
        onlySelectViewParams.height = onlySelectWH;
        if (flag == 1) {//画全整个矩形
            selectedView.setBackgroundResource(R.color.tt_ffe2c9);
            onlySelectViewParams.width = itemWidth + 5;
        } else if (flag == 2) { //左矩形 右圆==靠左
            selectedView.setBackgroundResource(R.drawable.bg_ffe2c9_right_circle);
            onlySelectViewParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
            onlySelectViewParams.width = itemWidth / 2 + onlySelectWH / 2;
        } else if (flag == 3) {//右矩形 左圆====靠右
            selectedView.setBackgroundResource(R.drawable.bg_ffe2c9_left_circle);
            onlySelectViewParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
            onlySelectViewParams.width = itemWidth / 2 + onlySelectWH / 2;
        } else { //圆形
            selectedView.setBackgroundResource(R.drawable.bg_ffe2c9_circle);
            onlySelectViewParams.width = onlySelectWH;
            onlySelectViewParams.addRule(RelativeLayout.CENTER_IN_PARENT);
        }
        selectedView.setLayoutParams(onlySelectViewParams);
    }

    /**
     * 上一条数据是否被选中
     *
     * @param position
     * @return
     */
    private boolean lastIsSelected(int position, List<CalendarData> dataList) {
        if (dataList == null) {
            return false;
        }
        CalendarData lastData = null;
        if (position > 0) {
            lastData = dataList.get(position - 1);
        }
        if (lastData == null || lastData.getClockInBean() == null) {
            return false;
        }
        return true;
    }

    /**
     * 下一条数据是否被选中
     *
     * @param position
     * @return
     */
    private boolean nextIsSelected(int position, List<CalendarData> dataList) {
        if (dataList == null) {
            return false;
        }
        CalendarData nextData = null;
        if (position < dataList.size() - 1) {
            nextData = dataList.get(position + 1);
        }
        if (nextData == null || nextData.getClockInBean() == null) {
            return false;
        }
        return true;
    }

    @Override
    public int getItemCount() {
        return dataList == null ? 0 : dataList.size();
    }

    class ViewHolder extends RecyclerView.ViewHolder {
        TextView tv_num;
        RelativeLayout root_view;
        //点击或者单个选中
        View onlySelectView;
        //已签到选中
        View selectedView;
        ImageView ivRemind;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            tv_num = itemView.findViewById(R.id.tv_num);
            root_view = itemView.findViewById(R.id.root_view);
            onlySelectView = itemView.findViewById(R.id.only_select_view);
            selectedView = itemView.findViewById(R.id.selected_view);
            ivRemind = itemView.findViewById(R.id.iv_remind);
        }
    }

    public interface CalendarItemClickListener {
        void itemClick(CalendarData data, int position);
    }
}

对应的适配器xml代码:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <RelativeLayout
        android:id="@+id/root_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <View
            android:id="@+id/selected_view"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true" />

        <View
            android:id="@+id/only_select_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true" />

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:gravity="center"
            android:orientation="vertical">

            <ImageView
                android:layout_width="@dimen/margin_08"
                android:layout_height="@dimen/margin_07" />

            <TextView
                android:id="@+id/tv_num"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textSize="@dimen/text_size_18" />

            <ImageView
                android:id="@+id/iv_remind"
                android:layout_width="@dimen/margin_08"
                android:layout_height="@dimen/margin_07"
                android:src="@mipmap/icon_clock_in_complete"
                android:visibility="gone"/>
        </LinearLayout>
    </RelativeLayout>
</RelativeLayout>

其实github也有很多好的日历依赖库,但项目样式和需求问题只能自定义实现,由于时间问题,临时是只实现左右切换,没有做左右滑动切换,可以使用ViewPager进行包装实现左右滑动切换,对数据还需要整理下才能更好适用自己的项目;

demo代码

  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值