折线图(六)绘制真正可用的折线图

时间:2022-11-14 18:03:44

之前那几遍都是为了展示实现思路的,并不是真正的图实现。看过的人大致都知道接下来怎么做了,只不过是测量下折线图然后设置合理的大小。

折线图(六)绘制真正可用的折线图


这个下面本人实现的折线图是测试数据的图,随机点测试


package sam.android.utils.widget;

import java.util.List;

/**
 * 折线信息
 */
public class LineChartInfo
{
    /**
     *    折线名字
     */
    private String name;


    /**
     * 折线颜色
     */
    private int color;

    /**
     * 折线所有的折线点
     */
    private List<LineChartPoint> points;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getColor() {
        return color;
    }

    public void setColor(int color) {
        this.color = color;
    }



    public List<LineChartPoint> getPoints() {
        return points;
    }

    public void setPoints(List<LineChartPoint> points) {
        this.points = points;
    }

}


/**
 * 折线点
 */
public class LineChartPoint {

    /**
     * y坐标轴的实际值
     */
    private float yValue;

    /**
     * x坐标轴的实际值
     */
    private float xValue;

    public float getxValue() {
        return xValue;
    }



    public void setxValue(float xValue) {
        this.xValue = xValue;
    }

    public float getyValue() {
        return yValue;
    }

    public void setyValue(float yValue) {
        this.yValue = yValue;
    }
}



package sam.android.utils.widget;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;

import java.util.ArrayList;
import java.util.List;


/**
 * 绘制折线图详情
 */
public class LineChartView extends View

{
    /*
    **
    * x 坐标最大值的文字长度
    */
    private int maxXChartValueLen = 0;

    /**
     * y 坐标最大值的文字长度
     */
    private int maxYChartValueLen = 0;


    /**
     * x轴每一单位多少px, 默认为2px/单位
     */
    private float xUnit = 16;


    /**
     * y轴每一单位多少px,默认为10px/单位
     */
    private float yUnit = 16;
    /**
     * x 轴最大单位值(非x位置值,其x位置值等于maxXValue*xUnit), 默认为200个单位
     */
    private float maxXValue = 30;

    /**
     * y 轴最大单位值 (非y位置值,其y位置值等于maxYValue*yUnit).默认为300个单位
     */
    private float maxYValue = 45;


    /**
     * 折线图距离左边或右边多少距离,默认为100px
     */
    private float chartViewMarginX = 100;


    /**
     * 折线图距离上班边或下边多少距离, ,默认为100px
     */
    private float chartViewMarginY = 100;


    /**
     * 折线图x坐标名字
     */
    private String xName = "x";


    /**
     * 折线图y坐标名字
     */
    private String yName = "y";


    /**
     * 坐标轴字体大小,默认32px
     */
    private float coordinateValueTextSize = 32;

    /**
     * 坐标轴文字到坐标轴距离
     */
    private float charToCoordinateAxisDistance = 15;


    /**
     * 箭头到最大单位值的距离
     */
    private float arrowToTextDistance = 100;

    /**
     * 表示设置的视图最小宽度
     */
    private float configWith = 480;

    /**
     * 表示设置的视图最小高度
     */
    private float configHeigh = 800;

    private int lineNamesHeight;

    /**
     * y轴最大的c长度
     */
    float realYAxisLen;


    /**
     * x轴最大的长度
     */
    float realXAxisLen;

    /**
     * x坐标轴到底部距离
     */
    float marginXAxisDistance;


    /**
     * y坐标轴到左边距离
     */
    float marginYAxisDistance;

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        for(LineChartInfo lineChartInfo : lineChartInfos)
        {
            List<LineChartPoint> points = lineChartInfo.getPoints();
            for(LineChartPoint point : points)
            {
                maxXValue = Math.max(maxXValue, point.getxValue());
                maxYValue = Math.max(maxYValue, point.getyValue());
            }
        }

        if (MeasureSpec.AT_MOST == heightMode) {
            heightSize = heightSize > configHeigh ? heightSize : (int) configHeigh;
        } else if (MeasureSpec.UNSPECIFIED == heightMode) {
            heightSize = (int) configHeigh;
        }

        if (MeasureSpec.AT_MOST == widthMode) {
            widthSize = widthSize > configWith ? widthSize : (int) configWith;
        } else if (MeasureSpec.UNSPECIFIED == widthMode) {
            widthSize = (int) configWith;
        }


       //x轴名字 CharObj
        CharObj xNameCharObj = getCharObj(xName, coordinateValueTextSize);

        //y轴名字 CharObj
        CharObj yNameCharObj = getCharObj(yName, coordinateValueTextSize);

        // x 轴最大单位值 CharObj
        CharObj maxXValueCharObj = getCharObj(maxXValue + "", coordinateValueTextSize);

        // y 轴最大单位值 CharObj
        CharObj maxYValueCharObj = getCharObj(maxYValue + "", coordinateValueTextSize);

        //计算x坐标轴到底部距离 , 文字到坐标轴距离+ 文字高度+marginY+上方名字长度
        marginXAxisDistance = charToCoordinateAxisDistance + (xNameCharObj.heightLen > maxXValueCharObj.heightLen ? xNameCharObj.heightLen : maxXValueCharObj.heightLen) + chartViewMarginY + lineNamesHeight;

        //计算y坐标轴到左边距离 , 文字到坐标轴距离+ 文字宽度+marginX
        marginYAxisDistance = charToCoordinateAxisDistance + (yNameCharObj.withLen > maxYValueCharObj.withLen ? yNameCharObj.withLen : maxYValueCharObj.withLen) + chartViewMarginX;

        float maxYValuePX = maxYValue * yUnit; //最大y轴的单位值的实际位置


        //显示视图需要的高度
        int needHeight = (int) (configHeigh > maxYValuePX ? configHeigh : maxYValuePX ) + (int) (arrowToTextDistance+marginXAxisDistance * 2);

        float maxXValuePX = maxXValue * xUnit;//最大的x轴单位值的实际位置

        //显示视图需要的宽度
        int needWidth = (int) (configWith > maxXValuePX ? configWith : maxXValuePX ) + (int)(arrowToTextDistance+marginYAxisDistance * 2);

        heightSize = heightSize > needHeight ? heightSize : needHeight;
        widthSize = widthSize > needWidth ? widthSize : needWidth;
        realXAxisLen = widthSize - marginYAxisDistance * 2;
        realYAxisLen = heightSize - marginXAxisDistance * 2;
        super.setMeasuredDimension(widthSize, heightSize);
    }

    private float originY;
    private float originX;
    /**
     * 绘制坐标轴
     */
    private void drawAxis(Canvas canvas) {
        //计算原点y坐标
        originY = marginXAxisDistance + realYAxisLen;
        //计算原点x坐标相对手机的实际位置 = 文字到y坐标轴距离+ 文字的宽度
        originX = marginYAxisDistance;
        Paint paint = new Paint();
        paint.setStrokeWidth(5);
        paint.setTextSize(coordinateValueTextSize);
        canvas.drawLine(originX, originY, originX, marginXAxisDistance, paint);//画出y轴
        canvas.drawLine(originX, originY, originX + realXAxisLen, originY, paint);//画出x轴
        canvas.drawLine(originX, marginXAxisDistance, originX - 12, marginXAxisDistance + 12, paint);//画出y轴左半箭头
        canvas.drawLine(originX, marginXAxisDistance, originX + 12, marginXAxisDistance + 12, paint);//画出y轴右半箭头
        canvas.drawLine(originX + realXAxisLen, originY, originX + realXAxisLen - 12, originY + 12, paint);//画出x轴上半箭头
        canvas.drawLine(originX + realXAxisLen, originY, originX + realXAxisLen - 12, originY - 12, paint);//画出x轴下半箭头

        Rect rect1 = new Rect();
        paint.setTextSize(coordinateValueTextSize+12);
        paint.getTextBounds(xName, 0, xName.length(), rect1);
        canvas.drawText(xName, originX + realXAxisLen-rect1.width()/2, originY + charToCoordinateAxisDistance + rect1.height()*2, paint);

        paint.getTextBounds(yName, 0, yName.length(), rect1);

        canvas.drawText(yName, originX - charToCoordinateAxisDistance-rect1.width()*2, marginXAxisDistance+ rect1.height()/2, paint);

        Paint oPaint = new Paint();
        oPaint.setStrokeWidth(2);

        for (int i = (int) xUnit, j = 1; i <= realXAxisLen - arrowToTextDistance; i += xUnit, j++) {
            int o = 0; //标志物的高度
            if (0 == j % 10)
            {
                o = 20;
                String value = "" + j;
                Rect rect = new Rect();
                paint.setTextSize(coordinateValueTextSize);
                paint.getTextBounds(value, 0, value.length(), rect);
                canvas.drawText("" + j, originX + i-rect.width()/2, originY+charToCoordinateAxisDistance+rect.height(), paint);
            }
            else if (0 == j % 5)
            {
                String value = "" + j;
                Rect rect = new Rect();
                paint.setTextSize(coordinateValueTextSize);
                paint.getTextBounds(value, 0, value.length(), rect);
                canvas.drawText("" + j, originX + i-rect.width()/2, originY+charToCoordinateAxisDistance+rect.height(), paint);
                o = 15;
            }
            else
                o = 10;
            canvas.drawLine(originX + i, originY, originX + i, originY - o, oPaint);
        }

        for (int i = (int) yUnit, j = 1; i <= realYAxisLen - arrowToTextDistance; i += yUnit, j++) {
            int o = 0; //标志物的高度
            if (0 == j % 10)
            {
                String value = "" + j;
                Rect rect = new Rect();
                paint.setTextSize(coordinateValueTextSize);
                paint.getTextBounds(value, 0, value.length(), rect);
                canvas.drawText(value,originX-charToCoordinateAxisDistance-rect.width(), originY - i+rect.height()/2, paint);
                o = 20;
            }
            else if (0 == j % 5)
            {
                String value = "" + j;
                Rect rect = new Rect();
                paint.setTextSize(coordinateValueTextSize);
                paint.getTextBounds(value, 0, value.length(), rect);
                canvas.drawText(value,originX-charToCoordinateAxisDistance-rect.width(), originY - i+rect.height()/2 , paint);
                o = 15;
            }
            else
                o = 10;
            canvas.drawLine(originX, originY - i, originX + o, originY - i, oPaint);//画y标志物

        }

    }


    private List<LineChartInfo> lineChartInfos = new ArrayList<LineChartInfo>();

    public void addLineCharInfo(LineChartInfo info)
    {
        if(null != info)
            lineChartInfos.add(info);
    }

    //刷新界面
    public void update()
    {
        invalidate();
    }


    private void drawLineChartInfo(Canvas canvas)
    {
        if(lineChartInfos.isEmpty())
            return;

        for(LineChartInfo lineChartInfo : lineChartInfos)
        {
            List<LineChartPoint> points = lineChartInfo.getPoints();
            if(null == points || points.isEmpty())
                continue;
            Paint linePaint = new Paint();
            linePaint.setStrokeWidth(5);
            linePaint.setColor(lineChartInfo.getColor());
            Paint pointPaint = new Paint();
            pointPaint.setStrokeWidth(10);
            for(int i = 0; i < points.size()-1; i ++)
            {
                LineChartPoint point = points.get(i);
                LineChartPoint point2 = points.get(i+1);
                int x = (int) (originX+point.getxValue()*xUnit);
                int y = (int) (originY-point.getyValue()*yUnit);
                int x1 = (int) (originX+point2.getxValue()*xUnit);
                int y1 = (int) (originY-point2.getyValue()*yUnit);
                canvas.drawLine( x,y,x1,y1,linePaint);
                canvas.drawPoint(x1, y1, pointPaint);
            }
        }

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawAxis(canvas);
        drawLineChartInfo(canvas);
    }

    /**
     * 获取文字会之后的CharObj
     *
     * @param value 文字
     * @param textSize 文字大小
     * @return CharObj
     */
    private CharObj getCharObj(String value, float textSize) {
        Paint paint = new Paint();
        paint.setTextSize(textSize);
        Rect rect = new Rect();
        paint.getTextBounds(value, 0, value.length(), rect);
        return new CharObj(rect.width(), rect.height());
    }

    /**
     * 记录绘制文字后,其文字高度,宽度。
     */
    class CharObj {
        float withLen;//测量文字的实际宽度
        float heightLen;//测量文字的实际高度

        CharObj(float withLen, float heightLen) {
            this.withLen = withLen;
            this.heightLen = heightLen;
        }
    }

    public LineChartView(Context context) {
        super(context);
    }

    public LineChartView(Context context, AttributeSet attrs) {
        super(context, attrs);

    }


}


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

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

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

            <sam.android.utils.widget.LineChartView
                android:id="@+id/lineChartView"
                android:layout_width="match_parent"
                android:layout_height="match_parent" />
        </HorizontalScrollView>
    </ScrollView>
</LinearLayout>


如果折线点太大看会看不到,所以添加了滚动条


public class MainActivity extends AppCompatActivity {
    private LineChartView lineChartView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        lineChartView = (LineChartView) findViewById(R.id.lineChartView);
        Random random = new Random();
        int[] colors ={Color.RED, Color.BLACK, Color.BLUE, Color.CYAN, Color.GREEN};
        for(int i =  0; i <  5; i ++)
        {
            LineChartInfo info = new LineChartInfo();
            info.setColor(colors[i]);
            int x =0;
            int y =0;
            List<LineChartPoint> pointList = new ArrayList<>();
            for(int j =0 ; j < 4; j ++)
            {
                if(0 != j)
                {
                    x += random.nextInt(10)+1;//随机设置实际值
                    y += random.nextInt(10)+1;
                }
                LineChartPoint point = new LineChartPoint();
                point.setxValue(x);
                point.setyValue(y);
                pointList.add(point);//添加折线点
            }
            info.setPoints(pointList);//将对应的折线点添加到对应的折线信息上
            lineChartView.addLineCharInfo(info);
        }
        lineChartView.update();//刷新界面
    }
}

待更新7