当前位置:   article > 正文

Android自定义View,实现日历展示事件(java)_android 周计划 自定义view

android 周计划 自定义view

自定义View,实现日历展示事件,周和月两种。先上个效果图:

5674ba0db984d414e61654e4d04fa70.jpg
9831a74954d003103f1e675dfb3545a.jpg
实现相对简单,没有什么复杂的效果,支持滚动、点击item项目。

下面说下自定义流程,月和周的类似,就说个周的,月的基本就在周的基础上进行修改即可。

开始着手

首先进行分析界面

界面包含以下元素:

周期一行

周期对应的当前日期一行

每个时间点为一行

1、每一列的宽度是固定的,左侧的时间栏宽度也是固定的

2、内容区域每一行具有默认高度,当超过默认高度之后会继续向下撑,让这一行高度进行适配内容总高度

3、如果总的表列宽度大于屏幕宽度,高度大于屏幕高度,需要进行滑动适配

4、每个内容item项目支持点击

一、基础属性设置

private int mWidth, mHeight, mTotalWidth, mTotalHeight; // 整个试图的宽高,UI全绘制后总的宽高
private int mAxisWidth = 120; // 左侧坐标轴列的宽度
private float mItemHorizontalCount = 2.8f; // 横向显示多少个方格item
private int mItemWidth, mItemHeight = 150; // 每一个方格item的宽高,宽度取决于mItemHorizontalCount,高度存在最小值固定
private int mItemPadding = 2; // 方格item内间距
private int mWorkWidth, mWorkHeight; // 内容的宽高,宽度取决于ItemWidth,高度取决于文字高度+mWorkPaddingVertical * 2
private int mWorkPaddingHorizontal = 6; // 内容的横向内间距
private int mWorkPaddingVertical = 6; // 内容的纵向间距
private final int mStrokeWidth = 2; // 线宽
private int mAxisTextSize = 33; // 坐标轴文字大小
private int mWorkTextSize = 33; // 内容文字大小
private boolean isVerticalLineToTop = true;
private int mWeekMarginVertical = 30;
private int mDateCircleMarginVertical = 20;
private int mWeekHeight; // 顶部第一行,周和日期行的高度
private int mCircleRadius = mAxisTextSize;
private boolean isShowEllipsis, isShowWholeDay;
private static final int TITLE_INTERVAL_TO_SUB_TITLE = 10;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

以上属性都相应会在attrs进行设置,做成自定义属性

二、数据配置

```
public String[] setDate(String date) {
    date = date.replace("-", "/");
    String[] split = date.split("/");
    if (split.length == 3) {
        try {
            int year = Integer.parseInt(split[0]);
            int month = Integer.parseInt(split[1]);
            int day = Integer.parseInt(split[2]);
            return setDate(year, month, day);
        } catch (Exception ignored) {
        }
    }
    return null;
}
```
// 返回第一天和最后一天的日期
public String[] setDate(int year, int month, int day) {
    String[] firstLastDate = new String[2];
    mYear = year;
    mMonth = month;
    mDay = day;
    List<String> weekDayList = TimeUtils.getWeekDayList(String.format(Locale.getDefault(), "%4d-%02d-%02d", year, month, day));
    firstLastDate[0] = weekDayList.get(0);
    firstLastDate[1] = weekDayList.get(weekDayList.size() - 1);
    for (int i = 0; i < weekDayList.size(); i++) {
        String date = weekDayList.get(i);
        String[] split = date.split("/");
        if (split.length == 3) {
            try {
                mDate[i + 1] = Integer.parseInt(split[2]);
            } catch (NumberFormatException ignored) {
            }
        }
    }
    return firstLastDate;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

由外部传递一个日期,然后在内部进行计算获取一周的所有日期,用于绘制

数据模型至少需要包含坐标轴左侧需要的分类信息-时间、列分类信息-日期、内容标题

至于内容item的背景颜色之类的,需要进行区分等则同样可以由数据模型传递进行设置

// 数据模型
public class TimeWork {
    private String mId;
    private int mBgColor;
    private String mTitle;
    private String mDate; // 年月日
    private String mTime; // 00  00:00   00:00:00  三种都可以,只使用到小时,月日历可以不进行设置
    // UI绘制使用
    private RectF rectF; // 对应的坐标
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

三、计算各种路径位置

传递数据之后,就需要对数据进行计算对应拜访的位置了:

首先计算所有XY轴线的位置:
private Map<String, Integer> mPathXMap; // 记录每一根竖线的X轴,用于work绘制时设置X轴起点,key位mDate
private Map<String, Integer> mPathYMap; // 记录每一根横线的Y轴,用于work绘制时设置Y轴起点,key位mTime
  • 1
  • 2

每一列宽度位置:因为每一列都是固定宽度的,只需要宽度进行相加,就可以获取到相应的宽度,然后将宽度对应保存进mPathYMap内,以方便后续绘制读取使用。

每一行高度位置:由TimeWork的mDate和mTime可以计算出位于表格的哪个item项目内,高度则需要计算总的有多少个在item内,然后进行叠加,计算后和默认的高度对比,取大值为item的最终高度。最后这一行的高度,则需要取这一行所有item的最大高度。然后保存在mPathXMap内,以方便后续绘制读取使用。

同时在遍历计算XY轴的时候,也把每个TimeWork摆放的位置也计算出来,存储到TimeWork的RectF内,后续绘制可以直接使用

四、开始布局绘制

按步骤由底部开始向上绘制

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

    // 绘制工作项的背景
    mPaint.setStyle(Paint.Style.FILL);
    mPaint.setColor(mBackgroundColor);
    canvas.drawRect(mAxisWidth + mStrokeWidth, mWeekHeight + mStrokeWidth / 2f, mWidth, mHeight, mPaint);
    // 绘制工作项
    drawWork(canvas);
    // 盖一层白色背景在时间后面,遮住下面的内容
    mPaint.setStyle(Paint.Style.FILL);
    mPaint.setColor(mAxisBackgroundColor);
    canvas.drawRect(0, mWeekHeight + mStrokeWidth / 2f, mAxisWidth, mHeight, mPaint);
    // 盖一层白色背景在周和日期后面,遮住下面的内容
    mPaint.setStyle(Paint.Style.FILL);
    mPaint.setColor(mAxisBackgroundColor);
    canvas.drawRect(mAxisWidth + mStrokeWidth, 0, mWidth, mWeekHeight - mStrokeWidth / 2f, mPaint);
    // 绘制横线
    drawHorizontalAxis(canvas);
    // 绘制竖线
    drawVerticalAxis(canvas);
    // 绘制左侧时间
    drawTime(canvas);
    // 绘制周和日期
    drawWeek(canvas);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

首先绘制出背景,之后绘制工作项,因为考虑后续移动,会导致工作项移动到坐标轴顶部和左边的日期时间区域,所以工作项需要先进行绘制,之后再绘制坐标轴和顶部左边的日期时间,层级才不会乱。

对于绘制的位置,基本在第三步已经计算完毕,这里只需要根据计算的位置,进行绘制即可,内容标题则需要根据列item宽度进行缩短显示:

subTitleWidth = mPaint.measureText(subTitle);
int maxWidth = mWorkWidth - 2 * mWorkPaddingHorizontal;
boolean isOver = false;
while (subTitleWidth > maxWidth && subTitle.length() > 0) {
    isOver = true;
    subTitle = subTitle.substring(0, subTitle.length() - 1);
    subTitleWidth = mPaint.measureText(subTitle);
}
if (isShowEllipsis && isOver && subTitle.length() >= 2) {
    subTitle = subTitle.substring(0, subTitle.length() - 1) + "...";
    subTitleWidth = mPaint.measureText(subTitle);
}
canvas.drawText(subTitle, left + mWorkWidth - mWorkPaddingHorizontal - subTitleWidth / 2f, titleY, mPaint);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

五、滑动和点击支持

让自定义View进行实现GestureDetector.OnGestureListener接口:
`

@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
    if (gestureDetector.onTouchEvent(event))
        return true;
    else
        return super.onTouchEvent(event);
}

@Override
public boolean onDown(@NonNull MotionEvent e) {
    scroller.forceFinished(true);
    return true;
}

@Override
public void onShowPress(@NonNull MotionEvent e) {

}

@Override
public boolean onSingleTapUp(@NonNull MotionEvent e) {
    // 点击事件处理
    ...
    return true;
}

@Override
public boolean onScroll(@NonNull MotionEvent e1, @NonNull MotionEvent e2, float distanceX, float distanceY) {
    mXOffset -= distanceX;
    mYOffset -= distanceY;
    checkOffset();
    refresh();
    return false;
}

@Override
public void onLongPress(@NonNull MotionEvent e) {

}

@Override
public boolean onFling(@NonNull MotionEvent e1, @NonNull MotionEvent e2, float velocityX, float velocityY) {
    Log.i("TimeTable", "onFling mXOffset: " + mXOffset + " mYOffset: " + mYOffset +
            "  minX: " + (mWidth - mTotalWidth) + " maxX: " + 0 + " velocityX:" + velocityX +
            "  minY: " + (mHeight - mTotalHeight) + " maxY: " + 0 + " velocityY:" + velocityY);
    scroller.fling(
            mXOffset, mYOffset, (int) velocityX, (int) velocityY,
            mWidth - mTotalWidth, 0,
            mHeight - mTotalHeight, 0);
    invalidate();
    return true;
}

@Override
public void computeScroll() {
    super.computeScroll();
    Log.i("TimeTable", "currY: " + scroller.getCurrY());
    if (scroller.computeScrollOffset()) {
        mXOffset = scroller.getCurrX();
        mYOffset = scroller.getCurrY();
        checkOffset();
        refresh();
    }
}

private boolean checkOffset() {
    Log.i("TimeTable", "mXOffset pre: " + mXOffset + " mYOffset pre: " + mYOffset);
    boolean isOver = false;
    if (mXOffset > 0) {
        mXOffset = 0;
        isOver = true;
    } else if (mXOffset < mWidth - mTotalWidth) {
        mXOffset = mWidth - mTotalWidth;
        isOver = true;
    }

    if (mYOffset > 0) {
        mYOffset = 0;
        isOver = true;
    } else if (mYOffset < mHeight - mTotalHeight) {
        mYOffset = mHeight - mTotalHeight;
        isOver = true;
    }
    Log.i("TimeTable", "mXOffset: " + mXOffset + " mYOffset: " + mYOffset);
    return isOver;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87

1、滚动事件:

基于接口的onScroll方法,进行设置XY轴的偏移量进行绘制,关键在于偏移量设置之后,需要去检测一次边界,避免偏移量超出试图边界。而onFling方法用于快速滑动后手指离开界面的持续滚动,同样计算好XY轴偏移量即可。

设置完XY轴偏移量之后进行刷新重新从三开始计算各种属性然后重新绘制。

2、点击事件:

基于接口的onSingleTapUp方法,可以获取到点击的XY轴坐标位置,再根据位置,可以通过
mPathXMap、mPathYMap判断是在哪一列、哪一行,还可以通过TimeWork带有的坐标信息RectF判断是点了哪个项目

完结啦

整体来说,实现相对简单,没有什么花里胡哨的功能

项目源码:Calendar: 周日历、月日历 用于记录展示事件列表 (gitee.com)

项目里面的ncalendar模块,使用的是GitHub - yannecer/NCalendar: 一款安卓日历,仿miui,钉钉,华为的日历,万年历、365、周日历,月日历,月视图、周视图滑动切换,农历,节气,Andriod Calendar , MIUI Calendar,小米日历

微信搜索“A查佣利小助手”,获取支付宝红包、TB/JD/PDD返利最新优惠资讯
请添加图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/534772
推荐阅读
相关标签
  

闽ICP备14008679号