自定义View合辑(9)-计划表

为了加强对自定义 View 的认知以及开发能力,我计划这段时间陆续来完成几个难度从易到难的自定义 View,并简单的写几篇博客来进行介绍,所有的代码也都会开源,也希望读者能给个 star 哈 GitHub 地址:github.com/leavesC/Cus… 也可以下载 Apk 来体验下:www.pgyer.com/CustomView

先看下效果图:

一、思路解析

这是一个类似于课程表的自定义 View,横向和纵向均是以时间作为计量单位,通过设置当前计划处于哪个星期数下以及跨度时间,在该范围内绘制出相应的背景以及文本

PlanBean 中有两个比较重要字段,一个是该计划的绘制范围,即坐标系 rectF,另一个字段 isEllipsis 是用于标记当前计划的文本是否以省略的形式出现

public class PlanBean {

    private String planId;
    private String planName;
    private String planStartTime;
    private String planEndTime;
    private String color;
    private int dayIndex;
    
    //计划的坐标
    private RectF rectF;
    //文本是否被省略
    private boolean isEllipsis;

}
复制代码

边框以及时间文本的绘制比较简单,只需要计算出各个起始点和终点的坐标系即可

@Override
    protected void onDraw(Canvas canvas) {
        //先画背景
        bgPaint.setStyle(Paint.Style.FILL);
        bgPaint.setColor(Color.WHITE);
        canvas.drawRect(0, 0, width, realHeight, bgPaint);

        //画左边和上边的边框
        bgPaint.setColor(rectColor);
        bgPaint.setStyle(Paint.Style.FILL);
        canvas.drawRect(0, 0, leftTimeWidth, height, bgPaint);
        canvas.drawRect(leftTimeWidth, 0, width, headerHeight, bgPaint);

        //画线
        canvas.save();
        canvas.translate(leftTimeWidth, 0);
        bgPaint.setColor(lineColor);
        bgPaint.setStrokeWidth(getResources().getDisplayMetrics().density);
        for (int i = 0; i < 7; i++) {
            canvas.drawLine(itemWidth * i, 0, itemWidth * i, height, bgPaint);
        }
        canvas.translate(0, headerHeight);
        for (int i = 0; i < 20; i++) {
            canvas.drawLine(0, i * itemHeight, width - leftTimeWidth + 2, i * itemHeight, bgPaint);
        }
        canvas.restore();

        //画星期数
        canvas.save();
        canvas.translate(leftTimeWidth, 0);
        bgPaint.setTextSize(sp2px(DAY_TEXT_SIZE));
        bgPaint.setColor(Color.BLACK);
        bgPaint.setTextAlign(Paint.Align.CENTER);
        for (String day : DAYS) {
            bgPaint.getTextBounds(day, 0, day.length(), textBounds);
            float offSet = (textBounds.top + textBounds.bottom) >> 1;
            canvas.drawText(day, itemWidth / 2, headerHeight / 2 - offSet, bgPaint);
            canvas.translate(itemWidth, 0);
        }
        canvas.restore();

        //画时间
        for (int i = 0; i < TIMES.length; i++) {
            String time = TIMES[i];
            bgPaint.getTextBounds(time, 0, time.length(), textBounds);
            float offSet = (textBounds.top + textBounds.bottom) >> 1;
            canvas.drawText(time, leftTimeWidth / 2, headerHeight + itemHeight * i - offSet, bgPaint);
        }

       ···
    }
复制代码

难点在于需要判断计划名的文本高度是否超出了其本身的高度,如果超出了则截断文本,并用省略号结尾 。可是 canvas.drawText() 方法本身是无法获取到文本高度以及自动换行的,此时就需要用到 StaticLayout 了,可以设置最大文本宽度,其内部实现了文本自动换行的功能,并且可以获取到文本换行后的整体高度,通过这个就可以完成想要的效果

    if (planListBeanList != null && planListBeanList.size() > 0) {
            for (PlanBean bean : planListBeanList) {
                bgPaint.setColor(Color.parseColor(bean.getColor()));
                measurePlanBound(bean, planRectF);
                canvas.drawRect(planRectF, bgPaint);
                String planName = bean.getPlanName();
                if (TextUtils.isEmpty(planName)) {
                    continue;
                }
                float planItemHeight = planRectF.bottom - planRectF.top;
                StaticLayout staticLayout = null;
                for (int length = planName.length(); length > 0; length--) {
                    staticLayout = new StaticLayout(planName, planTextPaint, (int) (itemWidth - 4 * getResources().getDisplayMetrics().density),
                            Layout.Alignment.ALIGN_CENTER, 1.1f, 1.1f, true);
                    if (staticLayout.getHeight() > planItemHeight) {
                        planName = planName.substring(0, length) + "...";
                        bean.setEllipsis(true);
                    }
                }

                if (staticLayout == null) {
                    staticLayout = new StaticLayout(planName, planTextPaint, (int) (itemWidth - 4 * getResources().getDisplayMetrics().density),
                            Layout.Alignment.ALIGN_CENTER, 1.1f, 1.1f, true);
                }

                if (staticLayout.getHeight() > planItemHeight) {
                    continue;
                }

                canvas.save();

                canvas.translate(planRectF.left + (itemWidth - staticLayout.getWidth()) / 2, planRectF.top + (planItemHeight - staticLayout.getHeight()) / 2);

                staticLayout.draw(canvas);
                canvas.restore();
            }
        }
复制代码

由于 StaticLayout 并没有向外提供设置整体最大高度的 API ,所以需要自己来循环判断文本的整体高度是否已经超出最大高度,是的话则对文本进行截取。如果最大高度太小,无法容纳一行文本,则直接不绘制文本

               for (int length = planName.length(); length > 0; length--) {
                    staticLayout = new StaticLayout(planName, planTextPaint, (int) (itemWidth - 4 * getResources().getDisplayMetrics().density),
                            Layout.Alignment.ALIGN_CENTER, 1.1f, 1.1f, true);
                    if (staticLayout.getHeight() > planItemHeight) {
                        planName = planName.substring(0, length) + "...";
                        bean.setEllipsis(true);
                    }
                }

                if (staticLayout == null) {
                    staticLayout = new StaticLayout(planName, planTextPaint, (int) (itemWidth - 4 * getResources().getDisplayMetrics().density),
                            Layout.Alignment.ALIGN_CENTER, 1.1f, 1.1f, true);
                }

                if (staticLayout.getHeight() > planItemHeight) {
                    continue;
                }
复制代码

另外一个比较重要的点是需要通过计划的时间跨度来计算其坐标系,并将坐标系存储下来,方便判断点击事件

    private void measurePlanBound(PlanBean bean, RectF rect) {
        measurePlanBound(bean.getDayIndex(), bean.getPlanStartTime(), bean.getPlanEndTime(), rect);
        RectF rectF = new RectF(rect);
        bean.setRectF(rectF);
    }

    private void measurePlanBound(int day, String startTime, String endTime, RectF rect) {
        try {
            float left = leftTimeWidth + itemWidth * (day - 1);
            float right = left + itemWidth;
            String[] split = startTime.split(":");
            int startHour = Integer.parseInt(split[0]);
            int startMinute = Integer.parseInt(split[1]);
            float top = ((startHour - START_TIME) * 60 + startMinute) * singleMinuteHeight + headerHeight;
            split = endTime.split(":");
            int endHour = Integer.parseInt(split[0]);
            int endMinute = Integer.parseInt(split[1]);
            float bottom = ((endHour - START_TIME) * 60 + endMinute) * singleMinuteHeight + headerHeight;

            float offset = 1;

            rect.set(left + offset, top + offset, right - offset, bottom - offset);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
复制代码

转载于:https://juejin.im/post/5cfeff05e51d4510835e027b

内容概要:《中文大模型基准测评2025年上半年报告》由SuperCLUE团队发布,详细评估了2025年上半年中文大模型的发展状况。报告涵盖了大模型的关键进展、国内外大模型全景图及差距、专项测评基准介绍等。通过SuperCLUE基准,对45个国内外代表性大模型进行了六大任务(数学推理、科学推理、代码生成、智能体Agent、精确指令遵循、幻觉控制)的综合测评。结果显示,海外模型如o3、o4-mini(high)在推理任务上表现突出,而国内模型如Doubao-Seed-1.6-thinking-250715在智能体Agent和幻觉控制任务上表现出色。此外,报告还分析了模型性价比、效能区间分布,并对代表性模型如Doubao-Seed-1.6-thinking-250715、DeepSeek-R1-0528、GLM-4.5等进行了详细介绍。整体来看,国内大模型在特定任务上已接近国际顶尖水平,但在综合推理能力上仍有提升空间。 适用人群:对大模型技术感兴趣的科研人员、工程师、产品经理及投资者。 使用场景及目标:①了解2025年上半年中文大模型的发展现状与趋势;②评估国内外大模型在不同任务上的表现差异;③为技术选型和性能优化提供参考依据。 其他说明:报告提供了详细的测评方法、评分标准及结果分析,确保评估的科学性和公正性。此外,SuperCLUE团队还发布了多个专项测评基准,涵盖多模态、文本、推理等多个领域,为业界提供全面的测评服务。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值