Android Gantt View 安卓实现项目甘特图

需要做一个项目管理工具,其中使用到了甘特图。发现全网甘特图解决方案比较少,于是自动动手丰衣足食。

前面我用 Python和 Node.js 前端都做过,这次仅仅是移植到 Android上面。

其实甘特图非常简单,开发也不难,如果我专职去做,能做出一个非常棒产品。我写这个只是消遣,玩玩,闲的蛋痛,所以不怎么上心,就搞成下面这德行吧。仅仅供大家学习,参考。

那天心情好了,完善一下。

屏幕布局文件

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"

    android:layout_width="match_parent"
    android:layout_height="match_parent">


    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    </ScrollView>

    <HorizontalScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">


        <cn.netkiller.gantt.ui.GanttView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:background="#DEDEDE"
            android:keepScreenOn="true"
            android:padding="15dp"
            android:text="TextView" />
    </HorizontalScrollView>


</androidx.constraintlayout.widget.ConstraintLayout>

View 代码

package cn.netkiller.gantt.ui;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * TODO: document your custom view class.
 */
public class GanttView extends View {
    private final String TAG = GanttView.class.getSimpleName();
    private Drawable mExampleDrawable;

    private int contentWidth, contentHeight;
    private int paddingLeft, paddingTop, paddingRight, paddingBottom;
    private int canvasLeft, canvasTop, canvasRight, canvasBottom;
    private Canvas canvas;
    private int textSize;
    private Map<Date, Coordinate> coordinates = new HashMap<Date, Coordinate>();


    public static class Coordinate {
        public int x, y;

        public Coordinate(int x, int y) {
            this.x = x;
            this.y = y;
        }

        public int getX() {
            return x;
        }

        public void setX(int x) {
            this.x = x;
        }

        public int getY() {
            return y;
        }

        public void setY(int y) {
            this.y = y;
        }

        @Override
        public String toString() {
            return "Coordinate{" +
                    "x=" + x +
                    ", y=" + y +
                    '}';
        }
    }

    public GanttView(Context context) {
        super(context);
        init(null, 0);
    }

    public GanttView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs, 0);
    }

    public GanttView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(attrs, defStyle);
    }

    private void init(AttributeSet attrs, int defStyle) {

        paddingLeft = getPaddingLeft();
        paddingTop = getPaddingTop();
        paddingRight = getPaddingRight();
        paddingBottom = getPaddingBottom();

        contentWidth = getWidth() - paddingLeft - paddingRight;
        contentHeight = getHeight() - paddingTop - paddingBottom;

        // Load attributes
//        final TypedArray a = getContext().obtainStyledAttributes(
//                attrs, R.styleable.MyView, defStyle, 0);
//
        mExampleString = a.getString(
                R.styleable.MyView_exampleString);
//        mExampleString = "AAAA";
//        mExampleColor = a.getColor(
//                R.styleable.MyView_exampleColor,
//                mExampleColor);
//        // Use getDimensionPixelSize or getDimensionPixelOffset when dealing with
//        // values that should fall on pixel boundaries.
//        mExampleDimension = a.getDimension(
//                R.styleable.MyView_exampleDimension,
//                mExampleDimension);
//
//        if (a.hasValue(R.styleable.MyView_exampleDrawable)) {
//            mExampleDrawable = a.getDrawable(
//                    R.styleable.MyView_exampleDrawable);
//            mExampleDrawable.setCallback(this);
//        }
//
//        a.recycle();

    }

//    @Override
//    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
//        super.onSizeChanged(w, h, oldw, oldh);
//
        if (h < computeVerticalScrollRange()) {
            canScroll = true;
        } else {
            canScroll = false;
        }
//    }

    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(contentWidth, contentHeight);

    }
//
//    private boolean canScroll = true;
//
//    @Override
//    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
//        super.onSizeChanged(w, h, oldw, oldh);
//
//        if (h < computeVerticalScrollRange()) {
//            canScroll = true;
//        } else {
//            canScroll = false;
//        }
//    }

    List<Data> taskList = List.of(
            new Data("AAA", "2024-06-28", "2024-07-04", "1", "Neo"),
            new Data("AAABBB", "2024-06-15", "2024-06-20", "10", "Neo"),
            new Data("AAACC", "2024-06-25", "2024-06-27", "1", "Neo"),
            new Data("AAABBCCD", "2024-06-25", "2024-06-28", "1", "Neo"),
            new Data("消息推送", "2024-01-15", "2024-06-30", "1", "Neo"),
            new Data("AAA", "2024-06-05", "2024-07-12", "1", "Neo"),
            new Data("AAA", "2024-06-05", "2024-07-17", "1", "Neo"),
            new Data("AAA", "2024-06-15", "2024-07-07", "1", "Neo"),
            new Data("AAA", "2024-06-18", "2024-07-02", "1", "Neo"),
            new Data("AAA", "2024-06-05", "2024-07-05", "1", "Neo")
    );

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

        canvasTop = paddingTop;
        canvasLeft = paddingLeft;
        canvasRight = canvas.getWidth() - paddingRight;
        canvasBottom = canvas.getHeight() - paddingBottom;


        Paint paint = new Paint();
        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(Color.BLUE);
        paint.setStrokeWidth(2);

        canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
        paint.setColor(Color.DKGRAY);
        paint.setStyle(Paint.Style.FILL_AND_STROKE);
        paint.setAlpha(30);
        canvas.drawRect(canvasLeft, canvasTop, canvasRight, canvasBottom, paint);

        title("canvas");
        try {
            calendar("2024-06-12", "2024-07-20");
            tasks(taskList);
        } catch (Exception e) {

        }

        // Draw the example drawable on top of the text.
        if (mExampleDrawable != null) {
            mExampleDrawable.setBounds(paddingLeft, paddingTop,
                    paddingLeft + contentWidth, paddingTop + contentHeight);
            mExampleDrawable.draw(canvas);
        }


    }


    public Map<String, Float> colume(List<Data> taskList) {
        TextPaint textPaint = new TextPaint();
        textPaint.setTextSize(sp2px(20));


        float name = textPaint.measureText("任务");
        float start = textPaint.measureText("开始日期");
        float finish = textPaint.measureText("截止日期");
        float day = textPaint.measureText("工时");
        float resource = textPaint.measureText("资源");
        for (Data data : taskList) {
            float textWidth = textPaint.measureText(data.name);
            if (textWidth > name) {
                name = textWidth;
            }

            if (textPaint.measureText(data.start) > start) {
                start = textPaint.measureText(data.start);
            }
            if (textPaint.measureText(data.finish) > finish) {
                finish = textPaint.measureText(data.finish);
            }
            if (textPaint.measureText(data.day) > day) {
                day = textPaint.measureText(data.day);
            }
            if (textPaint.measureText(data.resource) > resource) {
                resource = textPaint.measureText(data.resource);
            }
        }

        float finalName = name;
        float finalStart = start;
        float finalResource = resource;
        float finalFinish = finish;
        float finalDay = day;
        return new LinkedHashMap<String, Float>() {{
            put("任务", finalName);
            put("开始日期", finalStart);
            put("截止日期", finalFinish);
            put("工时", finalDay);
            put("资源", finalResource);
        }};

    }

    private int titleHeight;

    public float sp2px(float spValue) {
        //fontScale (DisplayMetrics类中属性scaledDensity)
        final float fontScale = getResources().getDisplayMetrics().scaledDensity;
        return (spValue * fontScale + 0.5f);
    }

    private void title(String value) {
        // Draw the text.
        TextPaint textPaint = new TextPaint();
        textPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
        textPaint.setTextAlign(Paint.Align.LEFT);

        textPaint.setTextSize(sp2px(25));
        float textWidth = textPaint.measureText(value);

        Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
//        float textHeight = fontMetrics.bottom;
        float textHeight = textPaint.getFontSpacing();
        canvas.drawText(value,
                canvasLeft + (getWidth() - textWidth) / 2,
                canvasTop + textHeight,
                textPaint);

        textPaint.setTextSize(sp2px(18));

        String copyright = "https://www.netkiller.cn - design by netkiller";
        textWidth = textPaint.measureText(copyright);
        textHeight += textPaint.getFontSpacing();
        canvas.drawText(copyright,
                getWidth() - textWidth - canvasLeft,
                canvasTop + textHeight,
                textPaint);

        titleHeight = (int) textHeight;

    }

    private void process(String string, int x, int y, int size) {
        TextPaint mTextPaint = new TextPaint();
        mTextPaint.setTextSize(sp2px(size));
//        mTextWidth = mTextPaint.measureText(string);
//        Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
//        mTextHeight = fontMetrics.bottom;

    }

    private int tableEnd = 0;

//    private void table() {
//
//
        TextPaint textPaint = new TextPaint();
//        calendarTextPaint.setTextSize(sp2px(20));
//
//        Paint.FontMetrics fontMetrics = calendarTextPaint.getFontMetrics();
//        float fontSpacing = calendarTextPaint.getFontSpacing();
        mTextHeight = fontMetrics.bottom;
//        int textX = canvasLeft + (int) fontSpacing / 2;
//        int textY = canvasTop + (int) fontSpacing * 3 + titleHeight;
//
//        int startX = 0;
//        int startY = (int) (canvasTop + titleHeight + fontSpacing * 2);
//        int stopX = startX;
//        int stopY = canvasBottom;
//
//        for (String text : List.of("任务", "开始日期", "截止日期", "工时", "资源")) {
//            canvas.drawText(text, textX, textY, calendarTextPaint);
//            textX += (int) (calendarTextPaint.measureText(text) + fontSpacing);
//            startX = stopX = textX;
//            canvas.drawLine(startX - (fontSpacing / 2), startY, stopX - (fontSpacing / 2), stopY, calendarPaint);
//        }
//        tableEnd = (int) (startX - (int) fontSpacing - fontSpacing / 2);
//
//    }

    private void table() {


//        TextPaint textPaint = new TextPaint();
        calendarTextPaint.setTextSize(sp2px(20));

//        Paint.FontMetrics fontMetrics = calendarTextPaint.getFontMetrics();
        float fontSpacing = calendarTextPaint.getFontSpacing();
//        mTextHeight = fontMetrics.bottom;

        int tableLeft = canvasLeft;
        int tableTop = canvasTop + (int) fontSpacing * 2 + titleHeight;
        int tableRight = canvasRight;
        int tableBottom = canvasBottom;


        int textX = tableLeft;
        int textY = tableTop + calendarFontSpacing;

        int startX = tableLeft;
        int startY = tableTop;
        int stopX = startX;
        int stopY = tableBottom;


        for (Map.Entry<String, Float> entry : colume(taskList).entrySet()) {
            String text = entry.getKey();
            Float textWidth = entry.getValue();
            canvas.drawText(text, textX + (int) fontSpacing / 2, textY, calendarTextPaint);

            textX += (int) (textWidth + calendarFontSpacing);
            startX += (int) (textWidth + calendarFontSpacing);
            stopX = startX;
            canvas.drawLine(startX, startY, stopX, stopY, calendarPaint);
        }
        tableEnd = tableRight = (int) (startX - calendarFontSpacing / 2);

    }

    private void tasks(List<Data> taskList) throws ParseException {

        Paint paint = new Paint();
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.BLUE);
        paint.setStrokeWidth(2);

//        TextPaint textPaint = new TextPaint();
//        calendarPaint.setColor(Color.DKGRAY);
        calendarPaint.setColor(Color.GRAY);
        calendarTextPaint.setTextSize(sp2px(20));

        int taskTop = calendarTop + calendarFontSpacing * 3;
        int taskLeft = canvasLeft;
        int taskRight = tableEnd;
        int taskBottom = canvasBottom;

//        Paint.FontMetrics fontMetrics = calendarTextPaint.getFontMetrics();
//        float fontSpacing = calendarTextPaint.getFontSpacing();

        int textX = 0;
        int textY = taskTop + calendarFontSpacing;

        int startX = 0;
        int startY = taskTop;
        int stopX = startX;
        int stopY = taskTop + calendarFontSpacing;

//        canvas.drawLine(startX, calendarTop + calendarFontSpacing * 1, calendarRight, calendarTop + calendarFontSpacing * 1, calendarPaint);
//        canvas.drawLine(startX, startY - calendarFontSpacing, startX, stopY, calendarTextPaint);
        Map<String, Float> col = colume(taskList);
        Log.d(TAG, col.toString());

        for (Data task : taskList) {
            textX = taskLeft + (int) calendarFontSpacing / 2;
            Iterator<Float> aa = col.values().iterator();
            for (String text : List.of(task.name, task.start, task.finish, task.day, task.resource)) {

                Float textWidth = aa.next();
                canvas.drawText(text, textX, textY, calendarTextPaint);
                textX += (int) (textWidth + calendarFontSpacing);
                startX = stopX = textX;
            }
            textY += (int) (calendarFontSpacing);

            try {


                Date startData = new SimpleDateFormat("yyyy-MM-dd").parse(task.start);
                Date finishData = new SimpleDateFormat("yyyy-MM-dd").parse(task.finish);
                Log.e(TAG, "Start: " + String.valueOf(startData) + " Finish: " + finishData);
                Coordinate startCoordinates = coordinates.get(startData);
                Coordinate finishCoordinates = coordinates.get(finishData);
                Log.e(TAG, "Start: " + startCoordinates.toString() + "Finish: " + finishCoordinates);
                canvas.drawRect(startCoordinates.x + 5, startY + 5, finishCoordinates.x + calendarFontSpacing - 5, stopY - 5, paint);

            } catch (Exception e) {

            }
//            canvas.drawText(task.name, textX, textY, calendarTextPaint);
            startY += (int) (calendarFontSpacing);

            canvas.drawLine(canvasLeft, startY, calendarRight, stopY, calendarPaint);
            stopY += (int) (calendarFontSpacing);
        }

    }


    private Paint calendarPaint = new Paint();
    private TextPaint calendarTextPaint = new TextPaint();
    private int calendarFontSpacing;
    private int calendarLeft, calendarTop, calendarRight, calendarBottom;

    private void calendar(String startDate, String endDate) throws ParseException {


        calendarPaint.setStyle(Paint.Style.STROKE);
        calendarPaint.setColor(Color.DKGRAY);
        calendarPaint.setStrokeWidth(2);
//        calendarPaint.setTextSize(sp2px(20));
//        paint.setAlpha(50);
        calendarTextPaint.setTextSize(sp2px(20));
        calendarFontSpacing = (int) calendarTextPaint.getFontSpacing();

//
//        Paint paint = new Paint();
//        paint.setTypeface(Typeface.DEFAULT);
//        paint.setTextSize(getTextSize());
//        Paint.FontMetrics fontMetrics = paint.getFontMetrics();
//        float textHeight = fm.getAscent() + fm.getDescent();

//        float calendarFontSpacing = fontMetrics.descent - fontMetrics.ascent;
//        float calendarFontSpacing = fontMetrics.bottom - fontMetrics.top;


//        Paint.FontMetrics fontMetrics = calendarPaint.getFontMetrics();
//        float textHeight = fontMetrics.getAscent() + fontMetrics.getDescent();
        // 边框
        canvas.drawRect(canvasLeft, canvasTop + titleHeight, canvasRight, canvasBottom, calendarPaint);
        table();

        calendarLeft = canvasLeft + tableEnd;
        calendarTop = canvasTop + titleHeight;
        calendarRight = canvasRight;
        calendarBottom = canvasBottom;


        int textX = calendarLeft;
        int textY = calendarTop + (int) calendarFontSpacing * 2;

        int startX = calendarLeft;
        int startY = calendarTop + (int) calendarFontSpacing * 1;
        int stopX = 0;
        int stopY = calendarBottom;
        canvas.drawLine(startX, calendarTop + calendarFontSpacing * 1, calendarRight, calendarTop + calendarFontSpacing * 1, calendarPaint);
        canvas.drawLine(startX, startY - calendarFontSpacing, startX, stopY, calendarTextPaint);

        canvas.drawLine(canvasLeft, calendarTop + calendarFontSpacing * 2, calendarRight, calendarTop + calendarFontSpacing * 2, calendarPaint);
        canvas.drawLine(canvasLeft, calendarTop + calendarFontSpacing * 3, canvasRight, calendarTop + calendarFontSpacing * 3, calendarPaint);

//        Paint paint = new Paint();
        calendarPaint.setStyle(Paint.Style.FILL);
        calendarPaint.setColor(Color.BLUE);
        calendarPaint.setStrokeWidth(2);

        startY = calendarTop + (int) calendarFontSpacing * 2;

        int measureWeek = (int) calendarTextPaint.measureText("六");
        int measureDay = (int) calendarTextPaint.measureText("30");

        int measureText = measureWeek > measureDay ? measureWeek : measureDay;
        List<String> weeks = List.of("日", "一", "二", "三", "四", "五", "六");

        Calendar calendar = Calendar.getInstance();
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM");

        Date d1 = new SimpleDateFormat("yyyy-MM-dd").parse(startDate);//定义起始日期
        Date d2 = new SimpleDateFormat("yyyy-MM-dd").parse(endDate);//定义结束日期

        calendar.setTime(d2);
        calendar.add(Calendar.DATE, 1);
        d2 = calendar.getTime();

        calendar.setTime(d1);//设置日期起始时间

        while (calendar.getTime().before(d2)) {//判断是否到结束日期
            String month = new SimpleDateFormat("yyyy-MM").format(calendar.getTime());
            String day = new SimpleDateFormat("d").format(calendar.getTime());
            coordinates.put(calendar.getTime(), new Coordinate(startX, startY));
//            if (dateRange.containsKey(month)) {
//                List<Date> tmp = dateRange.get(month);
//                tmp.add(calendar.getTime());
//            } else {
//                dateRange.put(month, List.of(calendar.getTime()));
//            }
            int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
            String week = weeks.get(dayOfWeek - 1);


            if (Set.of("六", "日").contains(week)) {
                calendarPaint.setColor(Color.WHITE);
            } else {
                calendarPaint.setColor(Color.GRAY);
            }
//            Log.d(TAG, String.valueOf());
//            Log.d(TAG, String.valueOf());
            stopX = (int) (startX + measureText);
            canvas.drawRect(startX, startY, stopX, stopY, calendarPaint);
            canvas.drawText(week, textX, textY, calendarTextPaint);
            if (calendarTextPaint.measureText(day) < calendarTextPaint.measureText(week)) {
                canvas.drawText(day, textX + calendarTextPaint.measureText(day) / 2, textY + calendarFontSpacing, calendarTextPaint);
            } else {
                canvas.drawText(day, textX, textY + calendarFontSpacing, calendarTextPaint);
            }
            if (day.equals("1")) {
                canvas.drawText(month, textX, textY - calendarFontSpacing, calendarTextPaint);
                canvas.drawLine(startX, startY - calendarFontSpacing * 2, startX, stopY, calendarTextPaint);
            }

            if (week.equals("日")) {
                canvas.drawLine(stopX, startY - calendarFontSpacing, stopX, stopY, calendarTextPaint);
            }

            textX += measureText + 2;
            startX = textX;


            calendar.add(Calendar.DATE, 1);//进行当前日期月份加1
        }
        calendarPaint.setColor(Color.GRAY);
        canvas.drawLine(canvasLeft, calendarTop + calendarFontSpacing * 2, calendarRight, calendarTop + calendarFontSpacing * 2, calendarPaint);
        canvas.drawLine(canvasLeft, calendarTop + calendarFontSpacing * 3, canvasRight, calendarTop + calendarFontSpacing * 3, calendarPaint);


        calendarLeft = stopX;

    }


    public class Data {
        public Data(String name, String start, String finish, String day, String resource) {
            this.name = name;
            this.start = start;
            this.finish = finish;
            this.day = day;
            this.resource = resource;
        }

        public String name;
        public String start;
        public String finish;
        public String day;
        public String resource;
    }
//    public class Coordinate{
//
//    }

    public void setTextSize(int textSize) {
        this.textSize = textSize;
    }

    public int getTextSize() {
        return textSize;
    }

    /**
     * Gets the example dimension attribute value.
     *
     * @return The example dimension attribute value.
     */
//    public float getExampleDimension() {
//        return mExampleDimension;
//    }

    /**
     * Sets the view"s example dimension attribute value. In the example view, this dimension
     * is the font size.
     *
     * @param exampleDimension The example dimension attribute value to use.
     */
//    public void setExampleDimension(float exampleDimension) {
//        mExampleDimension = exampleDimension;
//        invalidateTextPaintAndMeasurements();
//    }

    /**
     * Gets the example drawable attribute value.
     *
     * @return The example drawable attribute value.
     */
//    public Drawable getExampleDrawable() {
//        return mExampleDrawable;
//    }

    /**
     * Sets the view"s example drawable attribute value. In the example view, this drawable is
     * drawn above the text.
     *
     * @param exampleDrawable The example drawable attribute value to use.
     */
//    public void setExampleDrawable(Drawable exampleDrawable) {
//        mExampleDrawable = exampleDrawable;
//    }
    private void week() {

        Paint paint = new Paint();
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.BLUE);
        paint.setStrokeWidth(2);


//        Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
//        float fontSpacing = calendarTextPaint.getFontSpacing();

        int textX = calendarLeft;
        int textY = calendarTop + (int) calendarFontSpacing * 2;

        int startX = calendarLeft;
        int startY = calendarTop + (int) calendarFontSpacing * 2;
        int stopX = 0;
        int stopY = calendarBottom;

        int measureWeek = (int) calendarTextPaint.measureText("六");
        int measureDay = (int) calendarTextPaint.measureText("30");

        int measureText = measureWeek > measureDay ? measureWeek : measureDay;
        List<String> weeks = List.of("一", "二", "三", "四", "五", "六", "日");
        int w = 0;
        for (int i = 1; i <= 31; i++) {

//            for (String week : List.of("一", "二", "三", "四", "五", "六", "日")) {
            String week = weeks.get(w);
            w++;
            if (w >= weeks.size()) {
                w = 0;
            }
            String day = String.valueOf(i);

            if (Set.of("六", "日").contains(week)) {
                paint.setColor(Color.WHITE);

            } else {
                paint.setColor(Color.GRAY);
            }
//            Log.d(TAG, String.valueOf());
//            Log.d(TAG, String.valueOf());
            stopX = (int) (startX + measureText);
            canvas.drawRect(startX, startY, stopX, stopY, paint);
            canvas.drawText(week, textX, textY, calendarTextPaint);
            if (calendarTextPaint.measureText(day) < calendarTextPaint.measureText(week)) {
                canvas.drawText(day, textX + calendarTextPaint.measureText(day) / 2, textY + calendarFontSpacing, calendarTextPaint);
            } else {
                canvas.drawText(day, textX, textY + calendarFontSpacing, calendarTextPaint);
            }
//                if (week.equals("一")) {
//                    canvas.drawLine(startX, startY - calendarFontSpacing, startX, stopY, calendarTextPaint);
//                }

            if (week.equals("日")) {
                canvas.drawLine(stopX, startY - calendarFontSpacing, stopX, stopY, calendarTextPaint);
            }

            textX += measureText + 2;
            startX = textX;
//            stopX = (int) (startX + fontSpacing);

        }


        calendarLeft = stopX;

    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/796018.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

代码随想录-暑假算法第一天(数组篇)

代码随想录-暑假算法第一天(数组篇) 1. 二分查找 力扣题目链接(opens new window) 给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &#xff0c;写一个函数搜索 nums 中的 target&#xff0c;如果目标值存在返回下标&#xff0c;否…

3d为什么删掉模型不能返回?---模大狮模型网

在展览3D模型设计行业中&#xff0c;设计师们经常面临一个关键问题&#xff1a;一旦删除了模型的某些部分&#xff0c;为什么很难或者无法恢复原始状态?这不仅是技术上的挑战&#xff0c;更是设计过程中需要深思熟虑的重要考量。本文将探讨这一问题的原因及其在实际工作中的影…

css画半圆画圆弧

利用border-radius和border完成&#xff1a; <!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>test</title> <style> .semicircle {width: 100px;height: 50px;border-radius: 0 0 50px 50px;background:…

更新商品前端接口编写

文章目录 新增页面书写写表单价格符号的显示然后状态的书写后端枚举书写时间书写使用组件 新增页面书写 书写直接复制页面 写表单的绑定信息 然后绑定表单 表单绑定还有表单数据的绑定 标签中ref的作用就是将 该组件注册到vue对象的ref属性中 那么在vue运行的时候,会加载所…

【人工智能】Transformers之Pipeline(一):音频分类(audio-classification)

​​​​​​​ 目录 一、引言 二、音频分类&#xff08;audio-classification&#xff09; 2.1 概述 2.2 技术原理 2.2.1 Wav2vec 2.0模型 2.2.1 HuBERT模型 2.3 pipeline参数 2.3.1 pipeline对象实例化参数 2.3.2 pipeline对象使用参数 2.4 pipeline实战 2.4.1 …

访问 Postman OAuth 2.0 授权的最佳实践

OAuth 2.0 代表了 web 安全协议的发展&#xff0c;便于在多个平台上进行授权服务&#xff0c;同时避免暴露用户凭据。它提供了一种安全的方式&#xff0c;让用户可以授权应用程序访问服务。 在 Postman 中开始使用 OAuth 2.0 Postman 是一个流行的API客户端&#xff0c;支持 …

19 元服务使用心得

Atomic 原子 元数据描述数据的数据 可以理解为鸿蒙版小程序 轻量化 免安装(严格来说需要安装但是较小无感) 独立入口 能够为用户提供一个或者多个便捷的新型应用形态所有文件不超过2M 元服务与应用对比 首包和分包 首包&#xff1a;hap 里面放 首次打开首页和用到的资源 分…

Java常用的API_02(正则表达式、爬虫)

Java正则表达式 七、正则表达式7.1 格式7.1.1 字符类注意字符类示例代码1例2 7.1.2 预定义字符预定义字符示例代码例2 7.1.3 区别总结 7.2 使用Pattern和Matcher类与直接使用String类的matches方法的区别。&#xff08;1&#xff09; 使用Pattern和Matcher类示例代码 &#xff…

oslo_i18n学习小结

背景 代码均为开源代码 基于yoga版本&#xff0c;需要对openstack某服务做翻译&#xff0c;了解到oslo_i18n有翻译功能&#xff0c;配置oslo_i18n来给组件进行翻译 用法 用法 每个服务自己会带一个i18n.py的文件&#xff0c;如果要对日志进行翻译&#xff0c;从i18n导入_&…

AI人工智能作词,为音乐注入未来之力

在当今的音乐世界中&#xff0c;创新的力量不断推动着边界的拓展&#xff0c;而人工智能作词正以其独特的魅力&#xff0c;成为引领音乐走向未来的强大动力。 “妙笔生词智能写歌词软件&#xff08;veve522&#xff09;”无疑是这股浪潮中的璀璨明星。它利用先进的人工智能技术…

Spring Security学习笔记(一)Spring Security架构原理

前言&#xff1a;本系列博客基于Spring Boot 2.6.x依赖的Spring Security5.6.x版本 Spring Security中文文档&#xff1a;https://springdoc.cn/spring-security/index.html 一、什么是Spring Security Spring Security是一个安全控制相关的java框架&#xff0c;它提供了一套全…

某某会员小程序后端性能优化

背景 某某会员小程序后台提供开放平台能力&#xff0c;为三方油站提供会员积分、优惠劵等api。当用户在油站加油&#xff0c;油站收银会调用我们系统为用户发放积分、优惠劵等。用户反馈慢&#xff0c;三方调用发放积分接口性能极低&#xff0c;耗时30s&#xff1b; 接口情况…

在uniapp中如何使用地图

1&#xff0c;技术选择 最好是使用webview html形式加载&#xff0c;避免打包app时的地图加载问题 2&#xff0c;webview使用 使用webview必须按照官方文档,官网地址&#xff1a;https://uniapp.dcloud.net.cn/component/web-view.html <template><view><!…

LabVIEW比例压力控制阀自动测试系统

开发了一套基于LabVIEW编程和PLC控制的比例控制阀自动测试系统。该系统能够实现共轨管稳定的超高压供给&#xff0c;自动完成比例压力控制阀的耐久测试、流量滞环测试及压力-流量测试。该系统操作简便&#xff0c;具有高精度和高可靠性&#xff0c;完全满足企业对自动化测试的需…

80. UE5 RPG 实现UI显示技能冷却进度功能

在上一篇文章里&#xff0c;我们实现了通过GE给技能增加资源消耗和技能冷却功能。UI也能够显示角色能够使用的技能的UI&#xff0c;现在还有一个问题&#xff0c;我们希望在技能释放进去冷却时&#xff0c;技能变成灰色&#xff0c;并在技能冷却完成&#xff0c;技能可以再次使…

Linux安全技术与防火墙

一、安全技术和防火墙 1.1 安全技术 入侵检测系统&#xff1a;特点是不阻断网络访问&#xff0c;主要是提供报警和时候报警&#xff0c;不主动介入。 入侵防御系统&#xff1a;透明模式工作&#xff0c;对数据包、网络监控、服务攻击、木马蠕虫、系统漏洞等等进行准确的分析和…

两个视频怎么剪辑成一个视频?3个方法分享

两个视频怎么剪辑成一个视频&#xff1f;将两个视频剪辑成一个视频&#xff0c;是现代数字内容创作中的高频需求&#xff0c;它不仅简化了素材管理&#xff0c;还能通过创意剪辑提升作品连贯性与表现力。通过精心编排&#xff0c;两个视频片段可以无缝融合&#xff0c;讲述更完…

如何通过兔子和窝窝的故事理解“在机器人学习和研究中的获得成本与维护成本”(节选)

获得成本 掌握一门课程&#xff0c;以最为简单的学校成绩过60为例&#xff0c;需要按要求提交材料&#xff0c;包括作业、报告、实验和考试等&#xff0c;依据学分和考核要求的不同&#xff0c;需要对于花费时间和经历进行完成。 维护成本 考完了&#xff0c;如果被动学习那…

Django 删除单行数据

1&#xff0c;添加模型 from django.db import modelsclass Post(models.Model):title models.CharField(max_length200)content models.TextField()pub_date models.DateTimeField(date published)class Book(models.Model):title models.CharField(max_length100)author…

FastAPI 学习之路(四十七)WebSockets(三)登录后才可以聊天

之前我们是通过前端自动生成的token信息&#xff0c;这次我们通过注册登录&#xff0c;保存到本地去实现。首先&#xff0c;我们实现一个登录页面&#xff0c;放在templates目录下。 <!DOCTYPE html> <html lang"en"> <head><meta charset&quo…