【笔记】ViewPager+自定义控件实现的日历控件CalenderView

接入OEM模块,产品想要人家App上的日历控件,只能乖乖自己写一个。。。

需求

实现一个能左右滑动的,带选中日期效果的日历控件。

看到左右滑动,第一反应就是ViewPager,现成的就是方便。接下来要实现的就是每月的日历了,并且可以点击选中某一天。


无限滑动ViewPager

先把简单的实现了,理论上来说,日历应该可以一直往前翻,或者一直向后翻,就是一个可以无限滚动的ViewPager。

1、设置ViewPager的总量为Integer.MAX_VALUE;

2、设置ViewPager最初页数为中间位置Integer.MAX_VALUE/2;

3、为了不浪费太多内存,实际只生成3页,当前页、前一页和后一页;

简单粗暴的代码块

public class CalenderViewAdapter extends PagerAdapter {
    
    private Context context;
    private List<CalenderBean> list;
    private List<CalenderItemView> viewList;

    public CalenderViewAdapter(Context context, List<CalenderBean> list) {
        this.context = context;
        this.list = list;
        initCalenderView();
    }

    private void initCalenderView() {
        //只创建当前页、前一页和后一页
        if (viewList == null) {
            viewList = new ArrayList<>();
            viewList.add(new CalenderItemView(context));
            viewList.add(new CalenderItemView(context));
            viewList.add(new CalenderItemView(context));
        }
        for (int i = 0; i < list.size(); i++) {
            CalenderBean calenderBean = viewList.get(i).getCalenderBean();
            viewList.get(i).setDate(calenderBean.getYear(), calenderBean.getMonth());
        }
    }

    @Override
    public int getCount() {
        return (list == null || list.size() == 0) ? 0 : Integer.MAX_VALUE;
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view == object;
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        position = position % 3;
        CalenderItemView calenderItemView = viewList.get(position);
        calenderItemView.setDate(list.get(position).getYear(), list.get(position).getMonth());
        try {
            if (calenderItemView.getParent() != null) {
                container.removeView(calenderItemView);
            }
            container.addView(calenderItemView);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return calenderItemView;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
//        super.destroyItem(container, position, object);
//        container.removeView((View) object);
    }

    public void setList(List<CalenderBean> list) {
        this.list = list;
        notifyDataSetChanged();
    }

    /**
     * 设置当前页
     * @param position
     */
    public void setCurrentPosition(int position) {
        //当前页改变后,把其他两页的数据设置成当前页的前一页和后一页
        position = position % 3;
        CalenderBean calenderBean = list.get(position);
        int tmpPosition = (position + 1) % 3;
        list.remove(tmpPosition);
        list.add(tmpPosition, CalenderUtil.getNextCalender(calenderBean.getYear(), calenderBean.getMonth()));
        tmpPosition = (position - 1 + 3) % 3;
        list.remove(tmpPosition);
        list.add(tmpPosition, CalenderUtil.getPreCalender(calenderBean.getYear(), calenderBean.getMonth()));
        initCalenderView();
        notifyDataSetChanged();
    }
}

每月日历

一个显示每月数据的日历控件。

1、第一行星期,周日到周六

2、六行具体天数,根据每月1号的星期和每月总天数,每行7天,可能需要5+1(空行)或6行数据

3、点击某日期选中

绘制头部星期和具体天数

    /**
     * 头部绘制
     *
     * @param canvas
     */
    private void drawHeader(Canvas canvas) {
        drawOneText(canvas, "日", itemWidth / 2 + itemWidth * 0, itemWidth / 2, headerPaint);
        drawOneText(canvas, "一", itemWidth / 2 + itemWidth * 1, itemWidth / 2, headerPaint);
        drawOneText(canvas, "二", itemWidth / 2 + itemWidth * 2, itemWidth / 2, headerPaint);
        drawOneText(canvas, "三", itemWidth / 2 + itemWidth * 3, itemWidth / 2, headerPaint);
        drawOneText(canvas, "四", itemWidth / 2 + itemWidth * 4, itemWidth / 2, headerPaint);
        drawOneText(canvas, "五", itemWidth / 2 + itemWidth * 5, itemWidth / 2, headerPaint);
        drawOneText(canvas, "六", itemWidth / 2 + itemWidth * 6, itemWidth / 2, headerPaint);
    }

    /**
     * 绘制一个文字
     *
     * @param canvas
     * @param text
     * @param centerX
     * @param centerY
     * @param paint
     */
    private void drawOneText(Canvas canvas, String text, int centerX, int centerY, Paint paint) {
        float textWidth = paint.measureText(text);
        canvas.drawText(text, centerX - textWidth / 2, centerY - (paint.descent() + paint.ascent()) / 2, paint);
    }
    /**
     * 日期绘制
     *
     * @param canvas
     */
    private void drawDayItem(Canvas canvas) {
        for (int i = 0; i < cols; i++) {
            for (int j = 0; j < rows; j++) {
                if (dates[i][j] == preSelectDate) {
                    drawSelectItem(canvas, String.valueOf(dates[i][j]), itemWidth * j + itemWidth / 2, itemHeight * (i + 1) + itemHeight / 2, false);
                }
                if (dates[i][j] == selectDate) {
                    drawSelectItem(canvas, String.valueOf(dates[i][j]), itemWidth * j + itemWidth / 2, itemHeight * (i + 1) + itemHeight / 2, true);
                } else if (dates[i][j] > 0) {
                    drawOneText(canvas, String.valueOf(dates[i][j]), itemWidth * j + itemWidth / 2, itemHeight * (i + 1) + itemHeight / 2, datePaint);
                }
            }
        }
    }

绘制选中和未选中日期

    /**
     * 绘制选中项选中和未选中状态
     *
     * @param canvas
     * @param text
     * @param centerX
     * @param centerY
     * @param isSelect
     */
    private void drawSelectItem(Canvas canvas, String text, int centerX, int centerY, boolean isSelect) {
        selectItemPaint.setColor(isSelect ? selectBackColor : backColor);
        canvas.drawCircle(centerX, centerY, Math.min(itemWidth, itemHeight) / 2, selectItemPaint);
        if (isSelect) {
            selectItemPaint.setColor(selectTextColor);
            drawOneText(canvas, text, centerX, centerY, selectItemPaint);
        }
    }

点击选中具体日期

根据点击位置是否在某具体日期范围内,绘制选中日期

    private PointF startPoint;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            startPoint = new PointF(event.getX(), event.getY());
//            Log.i(TAG,startPoint.toString());
            return true;
        } else if (event.getAction() == MotionEvent.ACTION_UP) {
            float x = event.getX();
            float y = event.getY();
//            Log.i(TAG,x+","+y);
            //选中日期
            if (Math.abs(startPoint.x - x) < 20 && Math.abs(startPoint.y - y) < 20) {
                for (int i = 0; i < cols; i++) {
                    for (int j = 0; j < rows; j++) {
                        if (dates[i][j] > 0 && x > itemWidth * j && x < itemWidth * (j + 1) && y > itemHeight * (i + 1) && y < itemHeight * (i + 2)) {
                            preSelectDate = selectDate;
                            selectDate = dates[i][j];
                            if (onItemSelectListener != null) {
                                onItemSelectListener.onSelect(CalenderUtil.getCalender(year, month, selectDate));
                            }
                            invalidate();
                        }
                    }
                }
                return true;
            }
        }
        return super.onTouchEvent(event);
    }


ViewPager+日历控件 合体

实现一个继承自ViewPager的自定义控件,具体实现就是将上面两个的代码整合,在继承ViewPager的CalenderView控件中setAdapter无限滚动,每一页都返回一个具体月份的CalenderItemView控件。


源码Demo


猜你喜欢

转载自blog.csdn.net/q1113225201/article/details/73691555