Android简易折线图的实现思路

Android简易折线图的实现方案

万事开头难,不说内容如何,关键在于我终于迈出这一步了!

对于折线图来说,github已经有太多优秀的开源框架,如MPAndroidCharts,使用起来很方便,简单的调用其API方法即可。然在懂得运用的情况下我们更要理解折线图表实现的原理。
最近写了一个简单的折线图,在此分享一下,接下来会逐渐完善与优化,主要就是模仿一些APP已经实现的简易功能。说了那么多的废话,开始行动吧!
说到折线图,我相信大家都不陌生,在很多地方都遇到过如Excel.还有股票走势等等,所以说大家也一定知道折现图长什么样子。那么接下来我们会才想,它是怎么实现的呢??
构思:
折现图的形式:
简而言之,一条X轴,一条Y轴,X轴与Y轴组成区域内的一些点,线,以及这些点、线或坐标轴的文字描述。
猜想:应该怎么实现呢?既然知道了折线图的表现形式,由两条坐标轴,若干点与线组成,那应该就会想到,怎么画坐标轴?怎么画点?怎么画线?怎么把它们给链接起来?

-13_19h48_51.png

实现步骤

  1. 画坐标轴
  2. 画点
  3. 画线
    Action:
    假如现在有一个2维数组,
protected float[][] points = new float[][]{{1,10}, {2,47}, {3,11}, {4,38}, {5,9},{6,52}, {7,14}, {8,37}, {9,29}, {10,31}};

我们将通过拆线图的形式把每个点绘制出来。
执行自定义View的3个常用方法
OnMeassure OnLayout OnDraw ,在这主要主是OnDraw.

  • 第一步,平移坐标原点:
//平移坐标原点
canvas.translate(50,height-50);
  • 第二步,画X轴及X轴上显示的文本描述:
    注意
    具体怎么画有很多种方法,这里采用的是每次画一小段,一段连接着下一段,最终形成一条完整的线段,
    也可以直接先画一条直线(宽度有限,也可以叫线段,怎么叫无所谓了,实现才是最关键的)。
    当然在画线的时候会画一个点,及关于这个点的描述,具体也可偏移一些以方便显示文本。
private void drawLineXAxis(Canvas canvas) {
    int startX = 0;
    int startY = 0;
    int spaceing = (width-100)/points.length;
    //每次画一小段 也可以直接画一条线,然后再线上绘制具体的各个点,其原理一样,都是等分每次变化一个spaceing
    for (int i = 0;(startX+spaceing*i)<width-50;i++){
     //绘制X轴上的线段了,0-1 1-2 2-3.。。这个地方应该搞个中间变量来表示 要不重复画了0-1 0-2 0-3 代码问题
        canvas.drawLine(startX,startY,startX+spaceing*i ,startY,linePaint);    
         //绘制X轴各坐标点 小圆
        canvas.drawCircle(startX+spaceing*i, startY, 5, linePaint);
       //绘制X轴各坐标点的描述,在这偷懒了直接就用0 1 2 3...表示了,实际运用可灵活变化 取真正有意义应该显示的内容
        canvas.drawText(i+0+"",startX+spaceing*i,startY+30,textPaint);
    }
}
  • 第三步,画Y轴及Y轴上对应显示的文本描述:
    注意
    Y轴相对X轴来说相对稍微复杂一点,因为Y轴上的坐标点刻度不是像X轴一样有什么显示什么,而是显示的一个区间,
    这个区间可以直接用数据源中的最大值最小值表示,也可以通过动态设定。在这里也偷懒了(人为的分成了6份,
    因为可以直观的知道数据的大小,真实应用时应该根据前面所说,用数据源中最大值最小值表示,还是其他设定)。
    这一点我们搞明白了,Y轴也就简单了。同理X轴,要多少个坐标点 就分成多少份,这里也是height/数据长度代表每一区间的高度
    ,绘制Y坐标文本时应该是动态按比例计算,此处直接写6不合理,
    举个例子按照最大最小分6份:
    最小值+(最大值-最小值)/6i //i表示第几段了 或者这么说 起始值 + 段值段数 来表示Y坐标轴点的描述
private void drawLineYAxis(Canvas canvas) {
    int startX = 0;
    int startY = 0;
    int spaceing = (height-100)/points.length;
    //每次画一小段
    for (int i = 0;(startY+spaceing*i)<height-50;i++){

        canvas.drawLine(startX,startY,startX ,startY-spaceing*i,linePaint);
        canvas.drawCircle(startX, startY-spaceing*i, 5, linePaint);
     //画Y轴关键点就在于Y轴上的坐标点怎么计算与显示
        canvas.drawText(6*i+"",startX-30,startY-spaceing*i,textPaint);
    }
}
  • 第四步,画点与点与点连接成的线
    注意 最关键一最后一步了。
    根据数据源,计算出每个点应该在坐标轴上的哪个地方,然后绘制点再连接成线。
    在这里每个点的横坐标简单,同理这也有2各思路,但实质是一样的。
    横坐标是多少 就乘以sapceingWith(X轴分成了多少份),这样就能计算出横坐标点的位置
    纵坐标的话大体相同,因为纵坐标值表示与X不一样,应做一些改进。
spaceingHeightUnit = height/(最大值-最小值)或者height/(终值-起始值)

,这样可以得到每单位的值占有多少像素
然后spaceHeightUnit*Y值(数据源中每条数据的值)
另外就是按比例了 和每单位值多少一样。
代码中直接写了60也是直接写了偷懒原因。可做改进

float pointX = 0;
    float pointY = 0;
    private void drawLinePoints(Canvas canvas) {
        float pointXTemp = 0;
        float pointYTemp = 0;

        for(int i =0;i<points.length;i++){

            float temp =  points[i][0]%points.length;

            if(temp==0){
                pointX = 0+(points[i][0])*((width-100)/points.length);
            }else{
                pointX = 0+(points[i][0]%points.length)*((width-100)/points.length);
            }

//            pointY = pointY - points[i][1]*((height-100)/points.length);
            pointY = 0-(points[i][1]/60)*((height-100));
//            canvas.drawPoint(pointX,pointY,pointPaint);
            //pointY = 0-points[i][1]*6;
            canvas.drawCircle(pointX, pointY, 5, pointPaint);
            canvas.drawText(i+1+"",pointX-10,pointY-10,pointPaint);
            canvas.drawText("("+((int)points[i][0]+","+(int)points[i][1])+")",pointX-20,pointY-20,textPaint);
            if( i!=0) {
                canvas.drawLine(pointXTemp, pointYTemp, pointX, pointY, linePaint);
            }
            pointXTemp = pointX;
            pointYTemp = pointY;

        }
    }

主要就是以上4步,画X轴、Y轴、点及点点之间连接形成的线。
这是最终形成的效果图。


-13_19h48_51.png

当然这只是自己之前第一次写的,测试demo,但最终做成的是这样,
数据全部动态了,代码也优化成更灵活的了,估计后期还可能加上各种事件或者其他效果,慢慢改进吧.

一直阅读别人写的博客,阅读起来感觉很简单,但是自己执行时才发现,代码写起来容易,写这个难啊。
自己头一次 写东西,搞了好久才勉强弄出来了,虽说仍有各种问题,但我终于迈出了这一步!

package com.example.demochart.chart;

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

import com.example.demochart.util.Tool;

public class SelfLineChartView extends View {

    private int width;// 宽
    private int height;// 高
    private Paint alphaLinePaint;// 半透明画笔
    private Paint linePaint;// 线条画笔
    private Paint textPaint;// 横坐标画笔
    private Paint pointPaint;//点的坐标
    private Paint textEmptyPaint;// 空提示
    private Context context;
    public SelfLineChartView(Context context) {
        super(context);
    }

    public SelfLineChartView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        init();
    }

    private void init() {
        //画笔 X轴
        linePaint = new Paint();
        linePaint.setStyle(Paint.Style.FILL);
        linePaint.setColor(context.getResources().getColor(
                android.R.color.holo_red_dark));
        linePaint.setAntiAlias(true);
        linePaint.setStrokeWidth((float) 2.0);

        //文字画笔
        textPaint = new Paint();
        textPaint.setTextAlign(Paint.Align.CENTER);
        textPaint.setStyle(Paint.Style.FILL);
        textPaint.setColor(context.getResources().getColor(
                android.R.color.holo_red_dark));
        textPaint.setAntiAlias(true);
        textPaint.setTextSize(Tool.dip2px(context, 10));

        //文字画笔
        pointPaint = new Paint();
        pointPaint.setTextAlign(Paint.Align.CENTER);
        pointPaint.setStyle(Paint.Style.FILL);
        pointPaint.setColor(context.getResources().getColor(
                android.R.color.holo_blue_light));
        pointPaint.setAntiAlias(true);
        pointPaint.setTextSize(Tool.dip2px(context, 20));
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        width = MeasureSpec.getSize(widthMeasureSpec);
        height = MeasureSpec.getSize(heightMeasureSpec);
        int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
        int heightSpec = MeasureSpec.makeMeasureSpec(height,MeasureSpec.EXACTLY);
        setMeasuredDimension(widthSpec, heightSpec);
    }
    protected float[][] points = new float[][]{{1,10}, {2,47}, {3,11}, {4,38}, {5,9},{6,52}, {7,14}, {8,37}, {9,29}, {10,31}};

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //平移坐标原点
        canvas.translate(50,height-50);

        //有多少条数据,把X轴分成多少份,
        drawLineXAxis(canvas);

        drawLineYAxis(canvas);

        drawLinePoints(canvas);
    }
    float pointX = 0;
    float pointY = 0;
    private void drawLinePoints(Canvas canvas) {
        float pointXTemp = 0;
        float pointYTemp = 0;

        for(int i =0;i<points.length;i++){

            float temp =  points[i][0]%points.length;

            if(temp==0){
                pointX = 0+(points[i][0])*((width-100)/points.length);
            }else{
                pointX = 0+(points[i][0]%points.length)*((width-100)/points.length);
            }

//            pointY = pointY - points[i][1]*((height-100)/points.length);
            pointY = 0-(points[i][1]/60)*((height-100));
//            canvas.drawPoint(pointX,pointY,pointPaint);
            //pointY = 0-points[i][1]*6;
            canvas.drawCircle(pointX, pointY, 5, pointPaint);
            canvas.drawText(i+1+"",pointX-10,pointY-10,pointPaint);
            canvas.drawText("("+((int)points[i][0]+","+(int)points[i][1])+")",pointX-20,pointY-20,textPaint);
            if( i!=0) {
                canvas.drawLine(pointXTemp, pointYTemp, pointX, pointY, linePaint);
            }
            pointXTemp = pointX;
            pointYTemp = pointY;

        }
    }

    private void drawLineYAxis(Canvas canvas) {
        int startX = 0;
        int startY = 0;
        int spaceing = (height-100)/points.length;
        //每次画一小段
        for (int i = 0;(startY+spaceing*i)<height-50;i++){

            canvas.drawLine(startX,startY,startX ,startY-spaceing*i,linePaint);
            canvas.drawCircle(startX, startY-spaceing*i, 5, linePaint);

            canvas.drawText(6*i+"",startX-30,startY-spaceing*i,textPaint);
        }
    }

    private void drawLineXAxis(Canvas canvas) {
        int startX = 0;
        int startY = 0;
        int spaceing = (width-100)/points.length;
        //每次画一小段
        for (int i = 0;(startX+spaceing*i)<width-50;i++){

            canvas.drawLine(startX,startY,startX+spaceing*i ,startY,linePaint);
            canvas.drawCircle(startX+spaceing*i, startY, 5, linePaint);

            canvas.drawText(i+0+"",startX+spaceing*i,startY+30,textPaint);
        }
    }
}
<com.example.demochart.chart.SelfLineChartView
    android:layout_width="match_parent"
    android:layout_height="300dp"
    android:id="@+id/selfLineChartView"/>

接下来 不知道怎么写 了 中断了,也算第一篇文章不管好坏完成了吧。

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

推荐阅读更多精彩内容