自定義View合輯(9)-計劃表

爲了增強對自定義 View 的認知以及開發能力,我計劃這段時間陸續來完成幾個難度從易到難的自定義 View,並簡單的寫幾篇博客來進行介紹,全部的代碼也都會開源,也但願讀者能給個 star 哈 GitHub 地址:github.com/leavesC/Cus… 也能夠下載 Apk 來體驗下:www.pgyer.com/CustomViewjava

先看下效果圖:git

1、思路解析

這是一個相似於課程表的自定義 View,橫向和縱向均是以時間做爲計量單位,經過設置當前計劃處於哪一個星期數下以及跨度時間,在該範圍內繪製出相應的背景以及文本github

PlanBean 中有兩個比較重要字段,一個是該計劃的繪製範圍,即座標系 rectF,另外一個字段 isEllipsis 是用於標記當前計劃的文本是否以省略的形式出現canvas

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;

}
複製代碼

邊框以及時間文本的繪製比較簡單,只須要計算出各個起始點和終點的座標系便可bash

@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 了,能夠設置最大文本寬度,其內部實現了文本自動換行的功能,而且能夠獲取到文本換行後的總體高度,經過這個就能夠完成想要的效果ide

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 ,因此須要本身來循環判斷文本的總體高度是否已經超出最大高度,是的話則對文本進行截取。若是最大高度過小,沒法容納一行文本,則直接不繪製文本ui

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;
                }
複製代碼

另一個比較重要的點是須要經過計劃的時間跨度來計算其座標系,並將座標系存儲下來,方便判斷點擊事件spa

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();
        }
    }
複製代碼
相關文章
相關標籤/搜索