android Custom View and ViewGroup (6) (Code, Simple Trend Chart, Column Chart Same)

Keywords: Android REST Attribute

The project needs a trend map, which bar chart, trend map, there are a lot of ready-made frameworks on the Internet, but I only need a trend map in the project, I use a third-party framework is simply cumbersome, after all, the size, and the trend map is simple, so I wrote it myself, long time no blog, write it down, for your reference. In fact, it's very simple. Don't spray it. Let's not talk nonsense first. Look at the picture.

The trend of seven days has been recorded. The x coordinate of the origin is not 0, because we are not used to make the coordinate axis, but to record data. The coordinate of the origin is determined according to the actual demand.

Well, the nonsense is over. Let's start. Before we start, we have to think about how to realize it and how to think about it.

First, we need to know what we need to draw.
1. We need to draw the X and Y axes.
2. We need to draw the scale and scale values of X and Y axes.
3. We need to draw the horizontal lines of each scale on the y-axis.
4. We need to draw data points.
5. Data points need to be connected.
That's roughly all. There are also some text sizes, colors and so on, which can be set by themselves.

Then we need to know how to draw.
We all know that in the view coordinate system, a view, the origin is its upper left corner, so we need the actual origin, the real origin, that is, the intersection point of the X and y axes in the trend graph. So we have to first determine the actual origin, assuming that the coordinates of the actual origin are (xo,yo), average X represents the distance between each scale of the x-axis, then the coordinates of the x-axis 11.2 in the picture are relative to the origin (average X, 0), so the actual coordinates are (xo + average X, yo + 0). So the coordinates of the y axis 1000 are relative to the origin (0, - average y), so the actual coordinates are (0 + xo, - average y + yo). Note that in android, the X axis is positive on the right and the y axis is positive, so the coordinates of 1000 are negative. Hey, I'm not good at Chinese. Maybe it's a little unclear? In short, it is the algorithm that determines each point that calculates in this way.

First, we customize the View to inherit from the View, and then related properties:

private Context context;
    //The brush of X and y axis, the brush of scale text, the brush of data point, the brush of data connection, the brush of y axis scale horizontal line, the brush of data point text
    private Paint xyPaint, textPaint, pointPaint, pointslinePaint, yLinePaint, pointTextPaint;
    private int padding;  //Internal distance, set a default padding value
    private int width, height;  //Control actual height and width
    private float xSize, ySize;//The length of the x and y axes (because the control keeps internal distance, otherwise the x and y axes are close to the edge of the control, leading to unclear, internal distance, so the length and width of the control are different)
    private int defaultWidth;  //Default width
    private int defaultHeight;  //Default height
    private int xOrigin, yOrigin;  //Coordinates of relative origin
    private String[] xPoints;  //x-axis coordinates
    private String[] yPoints;  //y-axis coordinates
    private List<Float> xPointList, yPointList; //x coordinate set and y coordinate set of data points;

Then initialize the brush, default width, etc.

private void init() {
        xPointList = new ArrayList<>();
        yPointList = new ArrayList<>();
        defaultWidth = (DisplayUtils.width > 480 ? 480 : DisplayUtils.width);
        defaultHeight = 320;
        padding = DisplayUtils.dip2px(context, 35);

        xyPaint = new Paint();
        xyPaint.setStyle(Paint.Style.FILL);
        xyPaint.setAntiAlias(true);

        textPaint = new Paint();
        textPaint.setStyle(Paint.Style.FILL);
        textPaint.setAntiAlias(true);
        textPaint.setTextSize(DisplayUtils.dip2px(context, 10));

        pointPaint = new Paint();
        pointPaint.setStyle(Paint.Style.FILL);
        pointPaint.setAntiAlias(true);

        pointslinePaint = new Paint();
        pointslinePaint.setStyle(Paint.Style.FILL);
        pointslinePaint.setAntiAlias(true);
        pointslinePaint.setStrokeWidth(6);

        yLinePaint = new Paint();
        yLinePaint.setStyle(Paint.Style.FILL);
        yLinePaint.setAntiAlias(true);

        pointTextPaint = new Paint();
        pointTextPaint.setStyle(Paint.Style.FILL);
        pointTextPaint.setAntiAlias(true);
        pointTextPaint.setTextSize(DisplayUtils.dip2px(context, 6));
    }

For simplicity, I will no longer set attr attributes, directly set color values in the code and so on, you know the idea is good.

Setting up a public method for external calls to set the coordinates of data points, we use Map to save the coordinates:

public void setData(String[] xPoints, String[] yPoints, Map<Float, Float> showPointsMap) {
        this.xPoints = xPoints;
        this.yPoints = yPoints;
        for (float x : showPointsMap.keySet()) {
            xPointList.add(x);
            yPointList.add(showPointsMap.get(x));
        }
    }

Set a public method for external calls to set the brush color:

//Brushes for x,y axis and scale text, brushes for data points, brushes for data connections, brushes for y axis scale horizontal lines, brushes for data points text
    public void setColor(int xyColor, int pointColor, int lineColor, int yLineColor, int pointTextColor) {
        xyPaint.setColor(xyColor);
        textPaint.setColor(xyColor);
        pointPaint.setColor(pointColor);
        pointslinePaint.setColor(lineColor);
        yLinePaint.setColor(yLineColor);
        pointTextPaint.setColor(pointTextColor);
    }

Then we begin to customize the method. First, the onMeasure() method determines the size. If the measurement mode is not EXACYLY, then the default width and height are used:

if (widthMode == MeasureSpec.EXACTLY) {
            width = widthSize;
        } else {
            width = defaultWidth;
        }
        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else {
            height = defaultHeight;
        }
        ySize = height - padding - 60;  //The total length of the y-axis, the padding Top of the y-axis, and the padding Buttom, the padding size, can be defined by myself.
        xSize = width - padding * 2;  //The total length of the x-axis, the left and right padding of the x-direction are set to the padding size.
        xOrigin = padding;  //x coordinates of the actual origin
        yOrigin = height - padding;  //y coordinates of the actual origin
        setMeasuredDimension(width, height);

Then the important onDraw() method:

float averageHeight = ySize / 5;  //The length of the y-axis between each scale is set to six scales.
        float averageWidth = xSize / 6;  //The length of the x-axis between each scale, set seven scales
        float averagePx = ySize / 5000;
        canvas.drawLine(xOrigin, yOrigin, xOrigin + xSize + 15, yOrigin, xyPaint);  //Draw the x-axis. Draw 15 PX more so that the right side of the x-axis can come out. Otherwise, the top just happens to be the last scale line.
        canvas.drawLine(xOrigin, yOrigin, xOrigin, yOrigin - ySize - 15, xyPaint);  //Draw the y axis. Draw 15 PX more so that the top of the y axis can come out, otherwise the top is the last scale line.
        for (int i = 0; i < yPoints.length; i++) {  //Draw the scale on the y-axis, the horizontal line on the y-axis, and the scale text on the y-axis.
            canvas.drawLine(xOrigin, yOrigin - averageHeight * (i + 1), xOrigin - 15, yOrigin - averageHeight * (i + 1), xyPaint);
            canvas.drawLine(xOrigin, yOrigin - averageHeight * (i + 1), xOrigin + xSize + 15, yOrigin - averageHeight * (i + 1), yLinePaint);
            canvas.drawText(yPoints[i], 10, yOrigin - averageHeight * (i + 1), textPaint);
        }
        for (int i = 0; i < xPoints.length; i++) {  //Draw scales and text on the x-axis
            canvas.drawLine(xOrigin + averageWidth * i, yOrigin, xOrigin + averageWidth * i, yOrigin + 15, xyPaint);
            canvas.drawText(xPoints[i], xOrigin + averageWidth * (i), height - 30, textPaint);
        }
        for (int i = 0; i < xPointList.size(); i++) {  //Drawing Trends and Connecting Lines
            if (i < xPointList.size() - 1) {
                canvas.drawLine(xOrigin + averageWidth * (i), yOrigin - averagePx * yPointList.get(i),
                        xOrigin + averageWidth * (i + 1), yOrigin - averagePx * yPointList.get(i + 1), pointslinePaint);
            }
        }
        for (int i = 0; i < xPointList.size(); i++) {  //Draw data points. Draw lines first so that points can cover lines.
            canvas.drawCircle(xOrigin + averageWidth * (i), yOrigin - averagePx * yPointList.get(i), 10, pointPaint);  //data point
            canvas.drawText(yPointList.get(i) + "", xOrigin + averageWidth * (i) + 20, yOrigin - averagePx * yPointList.get(i), pointTextPaint); //Data Text
        }

In fact, the idea is very simple, and there is no difficulty, that is, to draw points, lines and scales. All that needs to be said is written in the code, so it's not wordy. The main thing is to know the train of thought, and the rest is simple.

In fact, there are many other areas that can be optimized, such as custom attribute color values, font size, line thickness, data point size, scale value location, etc., can be customized, these are relatively simple, I will not go verbose, interested friends can try. For histograms, the same principle is true.

Posted by JCF22Lyoko on Sun, 07 Jul 2019 18:09:51 -0700