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

news2024/9/22 23:26:55

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

前面我用 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;

    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PCL从理解到应用【04】Octree 原理分析 | 案例分析 | 代码实现

前言 Octree 作为一种高效的空间分割数据结构&#xff0c;具有重要的应用价值。 本文将深入分析 Octree 的原理&#xff0c;通过多个实际案例帮助读者全面理解其功能和应用&#xff0c;包括最近邻搜索、半径搜索、盒子搜索以及点云压缩&#xff08;体素化&#xff09;。 特性…

MongoDB - 查询操作符:比较查询、逻辑查询、元素查询、数组查询

文章目录 1. 构造数据2. MongoDB 比较查询操作符1. $eq 等于1.1 等于指定值1.2 嵌入式文档中的字段等于某个值1.3 数组元素等于某个值1.4 数组元素等于数组值 2. $ne 不等于3. $gt 大于3.1 匹配文档字段3.2 根据嵌入式文档字段执行更新 4. $gte 大于等于5. $lt 小于6. $lte 小于…

(Vue+SpringBoot+elementUi+WangEditer)仿论坛项目

项目使用到的技术与库 1.前端 Vue2 elementUi Cookie WangEditer 2.后端 SpringBoot Mybatis-Plus 3.数据库 MySql 一、效果展示 1.1主页效果&#xff1a; 1.2 文章编辑页面&#xff1a; 1.3 成功发布文章 1.4 文章关键字搜索提示 1.5 文章查询结果展示 1.6 文章内容及交互展示…

统信UOS服务器操作系统离线安装postgresql数据库

原文链接&#xff1a;统信UOS服务器离线安装postgresql数据库 Hello&#xff0c;大家好啊&#xff01;今天给大家带来一篇关于在统信UOS服务器操作系统上离线安装PostgreSQL数据库的文章。PostgreSQL是一款功能强大的开源对象-关系型数据库管理系统。由于某些环境中无法直接访问…

免费开源的工业物联网(IoT)解决方案

什么是 IoT&#xff1f; 物联网 (IoT) 是指由实体设备、车辆、电器和其他实体对象组成的网络&#xff0c;这些实体对象内嵌传感器、软件和网络连接&#xff0c;可以收集和共享数据。 IoT 设备&#xff08;也称为“智能对象”&#xff09;范围广泛&#xff0c;包括智能恒温器等…

SpringBoot+Vue(2)excel后台管理页面

一、需求 SpringBootVue写excel后台管理页面&#xff08;二级页面打开展示每一个excel表&#xff0c;数据库存储字段为“下载、删除、文件详情、是否共享、共享详情”&#xff09; 二、解答 后端(Spring Boot) 1. 项目设置 使用Spring Initializr创建一个新的Spring Boot项目…

深入理解 Elasticsearch 分页技术

原文链接&#xff1a;https://zhuanlan.zhihu.com/p/609576187 Elasticsearch 是一款分布式的搜索引擎&#xff0c;提供了灵活的分页技术。本文主要介绍 Elasticsearch&#xff08;简称 ES&#xff09; 的几种分页技术&#xff0c;并深入分析各种分页技术的优缺点及应用场景。 …

基于AT89C51单片机篮球计时计分器的设计(含文档、源码与proteus仿真,以及系统详细介绍)

本篇文章论述的是基于AT89C51单片机篮球计时计分器的设计的详情介绍&#xff0c;如果对您有帮助的话&#xff0c;还请关注一下哦&#xff0c;如果有资源方面的需要可以联系我。 目录 绪论 原理图 ​编辑 仿真图 系统总体设计图 代码实现 系统论文 资源下载 绪论 本次…

内网服务器通过squid代理访问外网

一、背景 现在要对172.16.58.158服务器进行openssh升级操作,我用之前写好的升级脚本执行后,发现没有备份旧的ssh程序文件,然后还卸载了oenssl-devel,然后我发现其他服务器ssh该服务器失败。同时脚本执行时报错“ configure: error: *** zlib.h missing - please install first …

windows查看局域网所有设备ip

windows如何查看局域网所有设备ip 操作方法 一 . 在搜索栏里输入cmd 二 .在命令行黑窗口输入arp -a 三 . 最上面显示的动态地址就是所有设备ip

day20、21、22补卡

235. 二叉搜索树的最近公共祖先 这道题的解题思路&#xff0c;我想了一会都没想出来&#xff0c;看了题解想&#xff1a;对于二叉搜索树&#xff0c;当我们从上向下去递归遍历&#xff0c;第一次遇到 cur节点是数值在[q, p]区间中&#xff0c;那么cur就是 q和p的最近公共祖先。…

Database数据库 vs Data Warehouse数据仓库 vs Data Mart数据集市 vs Data Lake数据湖

1.DATABASE 数据库 数据库是一个结构化的数据集合&#xff0c;用于存储、管理和检索数据。数据库设计用于支持事务处理&#xff08;OLTP&#xff0c;Online Transaction Processing&#xff09;和日常操作。 数据库通常由数据库管理系统&#xff08;DBMS&#xff09;控制&…

webRtc架构与目录结构

整体架构 目录结构 webrtc Modules目录

基于PCIe总线架构的2路1GSPS AD、4路1GSPS DA信号处理平台(100%国产化)

板卡概述 PCIE723-165是基于PCIE总线架构的2通道1GSPS采样率14位分辨率、4通道1GSPS采样率16位分辨率信号处理平台&#xff0c;该板卡采用国产16nm FPGA作为实时处理器&#xff0c;支持2路高速采集以及4路高速数据回放&#xff0c;板载2组DDR4 SDRAM大容量数据缓存&#xff0c;…

宝兰德参编金融智能体标准,深耕大模型场景化落地

随着数智化浪潮的不断推进&#xff0c;人工智能技术正深刻影响着金融服务的模式和流程&#xff0c;金融智能体在大模型的加持下&#xff0c;业务场景的应用能力得到强化。然而&#xff0c;作为新型技术&#xff0c;金融智能体在隐私保护、透明性、数据泄露等方面仍存在诸多风险…

图片存储问题总结

参考博客&#xff1a; https://blog.csdn.net/BUPT_Kwong/article/details/100972964 今天发现图片保存的一个神奇的问题&#xff0c;就是说原始的jpg图片打开后&#xff0c;重新保存成jpg格式&#xff0c;会发现这个结果不是很对的 example from PIL import Image import n…

房屋出租管理系统小程序需求分析及功能介绍

房屋租赁管理系统适用于写字楼、办公楼、厂区、园区、商城、公寓等商办商业不动产的租赁管理及租赁营销&#xff1b;提供资产管理&#xff0c;合同管理&#xff0c;租赁管理&#xff0c; 物业管理&#xff0c;门禁管理等一体化的运营管理平台&#xff0c;提高项目方管理运营效率…

【qt】QTcpSocket相关的信号

QTcpSocket可以在这里找到相关的信号 进行信号槽的关联 connect():这个信号在connectToHost()被调用并且连接已经成功建立之后发出 disconnected():该信号在套接字断开连接时发出 stateChanged(QAbstractSocket::SocketState socketState):每当QAbstractSocket的状态发生变化…

基于Adaboost的数据分类算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 基于Adaboost的数据分类算法matlab仿真,分别对比线性分类和非线性分类两种方式。 2.测试软件版本以及运行结果展示 MATLAB2022A版本运行 &#xff08;完整程序…

Python - Word转TXT文本,或TXT文本转Word

Word文档&#xff08;.doc或.docx&#xff09;和纯文本文件&#xff08;.txt&#xff09;是两种常用的文件格式。Word文档通常用于复杂的文档处理和排版&#xff0c;而纯文本文件则用于存储和传输纯文本信息。了解如何在这两种格式之间进行转换能提高工作效率&#xff0c;并便于…