双折线统计图

记录一次自定义view(需求太奇葩没得办法),合适的同学可以借鉴下。(大佬请绕道)


8多说看效果

record.gif

实际没这么快:)


上代码

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.CornerPathEffect;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

import androidx.annotation.Nullable;

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


/**
 * Created by TU on 2018/11/30.
 * Custom double fold line
 */
public class PolyLine extends View {
    private List<Float> lineDatas;//第一条折线数据
    private List<Float> secondLineDatas;//第二条折线数据

    //获取view宽高
    private int measuredHeight;
    private int measuredWidth;

    private int widthText;//右侧Y轴文字宽度
    private int heightText;//右侧Y轴文字高度
    private float yMax;//最大值 (基准值)

    private float yMin;//最小值 (基准值)

    private int maxDataSize;

    private float[] y_TextArray;//刻度分割后的数据组,六条虚线分6个
    private float split_difference; // 刻度分割差值

    private Paint mTxtPaintY;//Y轴刻度

    private Paint mDashLinePaint;//背景虚线

    private Paint mTimingLinePaint;//折线一

    private Paint mTimingLinePaintSecond;//折线二

    //画笔:实时横线右侧的红色的框和实时数据
    private Paint mTimingTxtBgPaint;//实时数据的背景
    private Paint mTimingTxtPaint;//实时数据文本

    private Paint mTimingTxtPaintSecond;
    private Paint mTimingTxtBgPaintSecond;

    //图形距离顶部距离(防止第一条线被视图覆盖,大概一个状态栏高度)
    private int linePaddingTop;

    //文本框矩形
    private int textRectRight;
    private int textRectBottom;
    private int textRectTop;
    private int textRectLeft;

    public PolyLine(Context context) {
        this(context, null);
    }

    public PolyLine(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PolyLine(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //关闭硬件加速 显示虚线
        this.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

        lineDatas = new ArrayList<>();
        secondLineDatas = new ArrayList<>();
        y_TextArray = new float[6];

        //测试基准值
        yMax = 26.225f;
        yMin = 26.225f;
        split_difference = 0.005f;

        initPaint();
        initDefAttrs();//初始化间距
        getTextBoundsY(); //获取y轴文字宽高
        getOffsetY();   //模拟y轴坐标刻度

    }

    private void initPaint() {
        initYTxPaint();//Y轴刻度
        initDashLinePaint();//背景虚线
        initTimingTxtPaint();//实时文本 和文本 背景
        initTimingLinePaint();//实时折线(包含实时横线)

        //第二条折线
        initTimingTxtPaint_Second();
        initTimingLinePaint_Second();
    }

    /**
     * 初始化相关
     */
    private void initDefAttrs() {
        measuredHeight = 400;
        measuredWidth = 1080;

        maxDataSize = 50;

        linePaddingTop = 45;

        textRectRight = 28;
        textRectBottom = 70;
        textRectTop = 20;
        textRectLeft = 100;
    }

    private void getTextBoundsY() {
        Paint paint = new Paint();
        Rect rect = new Rect();
        String yMaxstr = String.valueOf(yMax);
        paint.getTextBounds(yMaxstr, 0, yMaxstr.length(), rect);
        widthText = rect.width();
        heightText = rect.height();

        //widthText =  paint.measureText(yMaxstr);
    }

    private void getOffsetY() {
        for (int i = 0; i < 6; i++) {
            y_TextArray[i] = yMin;
            yMin = yMin - split_difference;
        }
    }

    private void initTimingLinePaint_Second() {
        mTimingLinePaintSecond = new Paint();
        mTimingLinePaintSecond.setAntiAlias(true);
        mTimingLinePaintSecond.setPathEffect(new CornerPathEffect(10));
        // mTimingLinePaintSecond.setStrokeJoin(Paint.Join.ROUND);
        mTimingLinePaintSecond.setColor(getResources().getColor(R.color.color3));
        mTimingLinePaintSecond.setStyle(Paint.Style.STROKE);
        mTimingLinePaintSecond.setStrokeWidth(3);
    }

    private void initTimingTxtPaint_Second() {
        mTimingTxtPaintSecond = new Paint();
        mTimingTxtPaintSecond.setAntiAlias(true);
        mTimingTxtPaintSecond.setColor(Color.WHITE);
        mTimingTxtPaintSecond.setTextSize(34);
        mTimingTxtPaintSecond.setStrokeWidth(1);
        mTimingTxtPaintSecond.setTextAlign(Paint.Align.CENTER);
        mTimingTxtPaintSecond.setTypeface(Typeface.DEFAULT_BOLD);
        //文本框背景
        mTimingTxtBgPaintSecond = new Paint();
        mTimingTxtBgPaintSecond.setAntiAlias(true);
        mTimingTxtBgPaintSecond.setStrokeWidth(5);
        mTimingTxtBgPaintSecond.setStrokeCap(Paint.Cap.ROUND);
        mTimingTxtBgPaintSecond.setColor(getResources().getColor(R.color.color3));
        mTimingTxtBgPaintSecond.setStyle(Paint.Style.FILL);
    }

    private void initTimingLinePaint() {
        mTimingLinePaint = new Paint();
        mTimingLinePaint.setAntiAlias(true);
        mTimingLinePaint.setPathEffect(new CornerPathEffect(10));
        //mTimingLinePaint.setStrokeJoin(Paint.Join.ROUND);
        mTimingLinePaint.setColor(getResources().getColor(R.color.color4));
        mTimingLinePaint.setStyle(Paint.Style.STROKE);
        mTimingLinePaint.setStrokeWidth(3);
    }

    private void initTimingTxtPaint() {
        mTimingTxtPaint = new Paint();
        mTimingTxtPaint.setAntiAlias(true);
        mTimingTxtPaint.setColor(Color.WHITE);
        mTimingTxtPaint.setTextSize(34);
        mTimingTxtPaint.setStrokeWidth(1);
        mTimingTxtPaint.setTextAlign(Paint.Align.CENTER);
        mTimingTxtPaint.setTypeface(Typeface.DEFAULT_BOLD);
        //文本框背景
        mTimingTxtBgPaint = new Paint();
        mTimingTxtBgPaint.setAntiAlias(true);
        mTimingTxtBgPaint.setStrokeWidth(10);
        mTimingTxtBgPaint.setStrokeCap(Paint.Cap.ROUND);
        mTimingTxtBgPaint.setColor(getResources().getColor(R.color.color4));
        mTimingTxtBgPaint.setStyle(Paint.Style.FILL);
    }

    private void initDashLinePaint() {
        //虚线初始化
        mDashLinePaint = new Paint();
        mDashLinePaint.setAntiAlias(true);
        mDashLinePaint.setStyle(Paint.Style.STROKE);
        mDashLinePaint.setColor(getResources().getColor(R.color.color2));//设置画笔颜色
        mDashLinePaint.setStrokeWidth(2);
        mDashLinePaint.setPathEffect(new DashPathEffect(new float[]{15, 30, 15, 30}, 0));
    }

    private void initYTxPaint() {
        mTxtPaintY = new Paint();
        mTxtPaintY.setAntiAlias(true);
        mTxtPaintY.setTextAlign(Paint.Align.LEFT);
        mTxtPaintY.setTypeface(Typeface.DEFAULT_BOLD);
        mTxtPaintY.setStyle(Paint.Style.FILL);
        mTxtPaintY.setColor(getResources().getColor(R.color.color2));
        mTxtPaintY.setStrokeWidth(1);
        mTxtPaintY.setTextSize(34);
    }

    /**
     * 根据需求 修改
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//        int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
//        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
//
//        if (modeWidth == MeasureSpec.EXACTLY) {
//            measuredWidth = sizeWidth;
//        } else {//默认宽度1080dp
//            measuredWidth = 1080;
//        }
//        Log.e("------------->", "width:" + measuredWidth);
//        setMeasuredDimension(measuredWidth, measuredWidth);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Log.e("TAG", "onDraw: " + measuredWidth + "," + measuredHeight);
        //todo 还可优化计算方式
        //整个view画布背景
        //canvas.drawColor(getResources().getColor(R.color.color1)); //背景
        //画Y文字
        drawTextY(canvas);
        //循环画5条背景虚线 两点一线
        drawDashLine(canvas);
        //点连线(折线) 画点数据源 第一条折线
        drawLine(canvas);
        //第二条折线
        drawSecondLine(canvas);
    }

    @SuppressLint("DefaultLocale")
    private void drawTextY(Canvas canvas) {
        for (int i = 0; i < 6; i++) {
            canvas.drawText(String.format("%.3f", y_TextArray[i]), measuredWidth - widthText - textRectLeft,
                    (measuredHeight + linePaddingTop - (measuredHeight / (y_TextArray[0] - y_TextArray[5])) *
                            (y_TextArray[i] - y_TextArray[5])), mTxtPaintY);
        }
    }

    private void drawDashLine(Canvas canvas) {
        for (int i = 0; i < 6; i++) {
            float[] pts = {0, (measuredHeight + linePaddingTop - (measuredHeight / (y_TextArray[0] - y_TextArray[5])) *
                    (y_TextArray[i] - y_TextArray[5])), measuredWidth - widthText - textRectLeft,
                    (measuredHeight + linePaddingTop - (measuredHeight / (y_TextArray[0] - y_TextArray[5])) *
                            (y_TextArray[i] - y_TextArray[5]))};//数据
            canvas.drawLines(pts, mDashLinePaint); //绘制多条虚线
        }
    }

    @SuppressLint("DefaultLocale")
    private void drawSecondLine(Canvas canvas) {
        if (secondLineDatas.size() != 0) {
            Path pathLine = new Path();
            pathLine.moveTo(0,
                    (measuredHeight + linePaddingTop - (measuredHeight / (yMax - y_TextArray[5])) * (secondLineDatas.get(0) - y_TextArray[5])));
            for (int i = 1; i <= secondLineDatas.size() - 1; i++) {
                pathLine.lineTo(((measuredWidth - widthText - textRectLeft) / maxDataSize) * i,
                        (measuredHeight + linePaddingTop - (measuredHeight / (yMax - y_TextArray[5])) * (secondLineDatas.get(i) - y_TextArray[5])));
            }
            canvas.drawPath(pathLine, mTimingLinePaintSecond);

            //实时横线二
            canvas.drawLine(((measuredWidth - widthText - textRectLeft) / maxDataSize) * (secondLineDatas.size() - 1),
                    (measuredHeight + linePaddingTop - (measuredHeight / (yMax - y_TextArray[5])) * (secondLineDatas.get(secondLineDatas.size() - 1) - y_TextArray[5])),
                    measuredWidth - widthText - textRectLeft,
                    (measuredHeight + linePaddingTop - (measuredHeight / (yMax - y_TextArray[5])) * (secondLineDatas.get(secondLineDatas.size() - 1) - y_TextArray[5])),
                    mTimingLinePaintSecond);

            //绘制文本-矩形框
            canvas.drawRoundRect(measuredWidth - widthText - textRectLeft, (measuredHeight + textRectTop - (measuredHeight / (yMax - y_TextArray[5]))
                            * (secondLineDatas.get(secondLineDatas.size() - 1) - y_TextArray[5]) - heightText + 5),
                    measuredWidth - textRectRight, (measuredHeight + textRectBottom - (measuredHeight / (yMax - y_TextArray[5]))
                            * (secondLineDatas.get(secondLineDatas.size() - 1) - y_TextArray[5])) + heightText - 5,
                    3, 3, mTimingTxtBgPaintSecond);

            //绘制文本-文字
            canvas.drawText(String.format("%.3f", secondLineDatas.get(secondLineDatas.size() - 1)), measuredWidth - widthText - 45,
                    (measuredHeight + linePaddingTop - (measuredHeight / (yMax - y_TextArray[5]))
                            * (secondLineDatas.get(secondLineDatas.size() - 1) - y_TextArray[5])) + 12, mTimingTxtPaintSecond);

        }
//        else{
//            throw(new NullPointerException("数据为空"));
//        }
    }

    @SuppressLint("DefaultLocale")
    private void drawLine(Canvas canvas) {
        if (lineDatas.size() != 0) {
            Path pathLine = new Path();
            pathLine.moveTo(0,
                    (measuredHeight + linePaddingTop - (measuredHeight / (yMax - y_TextArray[5])) * (lineDatas.get(0) - y_TextArray[5])));
            for (int i = 1; i <= lineDatas.size() - 1; i++) {
                pathLine.lineTo(((measuredWidth - widthText - textRectLeft) / maxDataSize) * i,
                        (measuredHeight + linePaddingTop - (measuredHeight / (yMax - y_TextArray[5])) * (lineDatas.get(i) - y_TextArray[5])));
            }
            canvas.drawPath(pathLine, mTimingLinePaint);

            //实时横线一
            canvas.drawLine(((measuredWidth - widthText - textRectLeft) / maxDataSize) * (lineDatas.size() - 1),
                    (measuredHeight + linePaddingTop - (measuredHeight / (yMax - y_TextArray[5])) * (lineDatas.get(lineDatas.size() - 1) - y_TextArray[5])),
                    measuredWidth - widthText - textRectLeft,
                    (measuredHeight + linePaddingTop - (measuredHeight / (yMax - y_TextArray[5])) * (lineDatas.get(lineDatas.size() - 1) - y_TextArray[5])),
                    mTimingLinePaint);
            //绘制文本-矩形框

            canvas.drawRoundRect(measuredWidth - widthText - textRectLeft, (measuredHeight + textRectTop - (measuredHeight / (yMax - y_TextArray[5]))
                            * (lineDatas.get(lineDatas.size() - 1) - y_TextArray[5]) - heightText + 5),
                    measuredWidth - textRectRight, (measuredHeight + textRectBottom - (measuredHeight / (yMax - y_TextArray[5]))
                            * (lineDatas.get(lineDatas.size() - 1) - y_TextArray[5])) + heightText - 5,
                    3, 3, mTimingTxtBgPaint);

            //绘制文本-文字
            canvas.drawText(String.format("%.3f", lineDatas.get(lineDatas.size() - 1)), measuredWidth - widthText - 45,
                    (measuredHeight + linePaddingTop - (measuredHeight / (yMax - y_TextArray[5]))
                            * (lineDatas.get(lineDatas.size() - 1) - y_TextArray[5])) + 12, mTimingTxtPaint);
        }
    }

    public float getyMax() {
        return yMax;
    }

    public void setyMax(float yMax) {
        this.yMax = yMax;
    }

    public float getyMin() {
        return yMin;
    }

    public void setyMin(float yMin) {
        this.yMin = yMin;
    }

    public void setLineDatas(List<Float> lineDatas) {
        this.lineDatas = lineDatas;
    }

    public void addData(float f) {
        if (lineDatas.size() >= maxDataSize) lineDatas.remove(0);
        lineDatas.add(f);
    }

    public void setSecondLineDatas(List<Float> secondLineDatas) {
        this.secondLineDatas = secondLineDatas;
    }

    public void addSecondData(float f) {
        if (secondLineDatas.size() >= maxDataSize) secondLineDatas.remove(0);
        secondLineDatas.add(f);
    }
//    public void invalidateLine() {
//        invalidate();
//    }
}

代码还需改进,参数都基于需求写死的,可以灵活扩展


调用

写的很丑陋,但也能用 ( ' _ ' )

   /**
     * 数据模拟
     */
    final float[] pointArray = {
            26.211f, 26.212f, 26.210f, 26.215f, 26.215f, 26.213f, 26.214f, 26.218f, 26.215f, 26.218f,
            26.211f, 26.212f, 26.210f, 26.215f, 26.215f, 26.213f, 26.214f, 26.218f, 26.215f, 26.218f,
            26.211f, 26.212f, 26.210f, 26.215f, 26.215f, 26.213f, 26.214f, 26.218f, 26.215f, 26.218f,
            26.211f, 26.212f, 26.210f, 26.215f, 26.215f, 26.213f, 26.214f, 26.218f, 26.215f, 26.218f,
            26.211f, 26.212f, 26.210f, 26.215f, 26.215f, 26.213f, 26.214f, 26.218f, 26.215f, 26.218f,
            26.211f, 26.212f, 26.210f, 26.215f, 26.215f, 26.213f, 26.214f, 26.218f, 26.215f, 26.217f};
    final float[] pointArray2 = {
            26.210f, 26.211f, 26.208f, 26.213f, 26.213f, 26.210f, 26.211f, 26.216f, 26.213f, 26.204f,
            26.210f, 26.211f, 26.208f, 26.213f, 26.213f, 26.210f, 26.211f, 26.216f, 26.213f, 26.204f,
            26.210f, 26.211f, 26.208f, 26.213f, 26.213f, 26.210f, 26.211f, 26.216f, 26.213f, 26.204f,
            26.210f, 26.211f, 26.208f, 26.213f, 26.213f, 26.210f, 26.211f, 26.216f, 26.213f, 26.204f,
            26.210f, 26.211f, 26.208f, 26.213f, 26.213f, 26.210f, 26.211f, 26.216f, 26.213f, 26.204f,
            26.210f, 26.211f, 26.208f, 26.213f, 26.213f, 26.210f, 26.211f, 26.216f, 26.213f, 26.204f};

    @Override
    protected void onStart() {
        super.onStart();
        new Thread(new Runnable() {
            @Override
            public void run() {
                int i = 0;
                while (true) {     //在线程中不断往集合中增加数据
                    i++;
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (i < 60) {
                        if (null != pointArray) {
                            polyline.addData(pointArray[i]);
                        }
                        if (null != pointArray) {
                            polyline.addSecondData(pointArray2[i]);
                        }
                        mh.sendEmptyMessage(0);   //发送空消息通知刷新
                    }
                }
            }
        }).start();
    }

    @SuppressLint("HandlerLeak")
    private Handler mh = new Handler() {
        public void handleMessage(android.os.Message msg) {
            if (msg.what == 0) {    //判断接受消息类型
                polyline.invalidate();  //刷新View
            }
        }
    };

如果能帮到你,请点个赞哦~

传送门Github地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,036评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,046评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,411评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,622评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,661评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,521评论 1 304
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,288评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,200评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,644评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,837评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,953评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,673评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,281评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,889评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,011评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,119评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,901评论 2 355

推荐阅读更多精彩内容