2019-03-05 自定义View绘制线性表

      自定义View绘图,学的就是用的,怎么方便怎么来,就像随手涂鸦一样,以后都不会再

拾起,所以没怎么封装,也不标准。
第一次写个稍微完整点的例子,写的不好还请多多指教..

1、先来效果图:

图1
图2



素材1
素材2
素材3

2、图1的源码:

package com.lipy.linechart;

import android.content.Context;

import android.graphics.Canvas;

import android.graphics.Color;

import android.graphics.Paint;

import android.support.annotation.Nullable;

import android.util.AttributeSet;

import android.util.TypedValue;

import android.view.View;

import android.widget.Switch;

import java.util.ArrayList;

import java.util.List;

public class TrendLineChartView extends View {

    private List<Integer> trendData;    //走势数据

    private Paint redPaint,blackPaint,greenPaint; //圆点画笔

    private Paint realLinePaint,dashLinePaint;  //实线和灰线画笔

    private int defaultPadding = this.dp2px(getContext(),5); //默认内边距

    private int dotRadius = this.dp2px(getContext(),4);    //圆点的半径

    /**

    * 设置走势数据

    * 列表首项是最远天数,画在图表的最左边

    * 数据值对应  1:红点,0:绿点,-1:黑点

    */

    public void setTrendData(List<Integer> trendData){

        this.trendData = trendData;

        invalidate();

    }

    public TrendLineChartView(Context context) {

        this(context,null);

    }

    public TrendLineChartView(Context context, @Nullable AttributeSet attrs) {

        super(context, attrs);

        init();

    }

    private void init() {

        dashLinePaint = new Paint();

        dashLinePaint.setColor(Color.GRAY);

        dashLinePaint.setAlpha(100);

        dashLinePaint.setStrokeWidth(1.5f);

        realLinePaint = new Paint(dashLinePaint);

        realLinePaint.setColor(Color.parseColor("#555555"));

        realLinePaint.setAntiAlias(true);

        realLinePaint.setStrokeWidth(1.8f);

        redPaint = new Paint(realLinePaint);

        redPaint.setColor(Color.RED);

        redPaint.setStyle(Paint.Style.FILL);

        greenPaint = new Paint(redPaint);

        greenPaint.setColor(Color.GREEN);

        blackPaint = new Paint(greenPaint);

        blackPaint.setColor(Color.BLACK);

    }

    @Override

    protected void onDraw(Canvas canvas) {

        super.onDraw(canvas);

        drawBackground(canvas);

        drawData(canvas);

    }

    //绘制数据

    private void drawData(Canvas canvas) {

        int width = canvas.getWidth();

        int height = canvas.getHeight();

        if (trendData == null){

            return;

        }

        if (trendData.size() <= 1){

            //数据只有一条时,默认画在中间

            canvas.drawCircle(width/2,getDrawHeight(height,trendData.get(0)),dotRadius,getPaint(trendData.get(0)));

        }

        else{

            float space = (width-defaultPadding*2)/(trendData.size()-1);

            //画折线

            for (int i = 0; i < trendData.size()-1; i++) {

                canvas.drawLine(defaultPadding+i*space,getDrawHeight(height,trendData.get(i)),defaultPadding+(i+1)*space,getDrawHeight(height,trendData.get(i+1)),realLinePaint);

            }

            //画圆点

            for (int i = 0; i < trendData.size(); i++) {

                canvas.drawCircle(defaultPadding+i*space,getDrawHeight(height,trendData.get(i)),dotRadius,getPaint(trendData.get(i)));

            }

        }

    }

    //画三条虚线组成的背景

    private void drawBackground(Canvas canvas) {

        int width = canvas.getWidth();

        int height = canvas.getHeight();

        canvas.drawLine(defaultPadding,defaultPadding,width-defaultPadding,defaultPadding,dashLinePaint);

        canvas.drawLine(defaultPadding,height/2,width-defaultPadding,height/2,dashLinePaint);

        canvas.drawLine(defaultPadding,height-defaultPadding,width-defaultPadding,height-defaultPadding,dashLinePaint);

    }

    /**

    * 更具trendData数据获得对应颜色的画笔

    */

    private Paint getPaint(int result){

        if (result == -1){

            return blackPaint;

        }

        else if (result == 0){

            return  greenPaint;

        }

        else if (result == 1){

            return redPaint;

        }

        return greenPaint;

    }

    /**

    * @param height    控件的总高度

    * @param result    数据的走势

    * @return  计算出该圆点的Y坐标

    */

    private int getDrawHeight(int height,int result){

        if (result == -1){

            return height - defaultPadding;

        }

        else if (result == 0){

            return  height/2;

        }

        else if (result == 1){

            return defaultPadding;

        }

        return height/2;

    }

    private int dp2px(Context context, int dip) {

        final float scale = context.getResources().getDisplayMetrics().density;

        return (int) (dip * scale + 0.5f);

    }

    private int sp2px(Context context, float spValue) {

        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spValue, context.getResources().getDisplayMetrics());

    }

}

3、图2的源码:

package com.lipy.linechart;

import android.content.Context;

import android.graphics.Bitmap;

import android.graphics.BitmapFactory;

import android.graphics.BitmapShader;

import android.graphics.Canvas;

import android.graphics.Color;

import android.graphics.LinearGradient;

import android.graphics.Paint;

import android.graphics.Path;

import android.graphics.Rect;

import android.graphics.Shader;

import android.graphics.drawable.ShapeDrawable;

import android.graphics.drawable.shapes.PathShape;

import android.support.annotation.Nullable;

import android.util.AttributeSet;

import android.util.Log;

import android.util.TypedValue;

import android.view.View;

import java.util.Arrays;

import java.util.List;

public class HitRateLineGridView extends View {

    private List<Integer> hitRateData;    //命中率数据,  固定的4条数据。

    private String[] xLabels;  //x轴的标签

    private int maxIndex = 0;  //命中率最高的一项的索引,设置数据时就会计算

    private Bitmap bmLeftTip, bmCenterTip,bmRightTip;      //位于三个不同方位的提示图片

    private Paint hollowCirclePaint,solidCirclePaint; //空心元和实心圆画笔

    private Paint labelFontPaint,tipFontPaint;  //标签文字画笔和提示文字画笔

    private Paint realLinePaint,dashLinePaint;  //折线画笔和背景灰线画笔

    private Paint shaderBgPaint;    //阴影背景画笔

    private int dataColor = Color.parseColor("#d92e48"); //数据颜色,红色

    private int defaultPadding = this.dp2px(getContext(),20); //默认边距

    private int fontSize = this.sp2px(getContext(),8); //默认字体颜色

    private Path shaderPath;    //渐变阴影的路径,在绘制折线的时候记录路径

    /**

    * 设置命中率数据,取最近的7天

    * 不需要这个方法,已标记为过时

    */

    @Deprecated

    public void setHitRateData(List<Integer> hitRateData){

        this.hitRateData = hitRateData;

        if (hitRateData.size()<=7){

            //this.hitRateData = hitRateData;

            //++计算统计后再赋值给 hitRateData变量

        }

        else{

            //this.hitRateData = hitRateData.subList(hitRateData.size() - 7,hitRateData.size());

            //++计算统计后再赋值给 hitRateData变量

        }

        invalidate();

    }

    /**

    * 设置命中率数据,已经统计过的数据,注意!只接收固定的4条数据

    *分别是近7天的命中率,近5天的命中率,近3天的命中率,和近2天的命中率

    */

    public void setHitRateDataCounted(List<Integer> hitRateData){

        this.hitRateData = hitRateData;

        for (int i = 0; i < hitRateData.size(); i++) {

            if (hitRateData.get(maxIndex) < hitRateData.get(i)){

                maxIndex = i;

            }

        }

        invalidate();

    }

    /**

    * @param xLabels x轴底部的标签,最多取前4个

    */

    private void setXLabels(String[] xLabels){

        if (xLabels.length<=4){

            this.xLabels = xLabels;

        }

        else{

            this.xLabels = Arrays.copyOfRange(xLabels,0,4);

        }

        invalidate();

    }

    public HitRateLineGridView(Context context) {

        this(context,null);

    }

    public HitRateLineGridView(Context context, @Nullable AttributeSet attrs) {

        super(context, attrs);

        init();

    }

    private void init() {

        dashLinePaint = new Paint();

        dashLinePaint.setColor(Color.GRAY);

        dashLinePaint.setAlpha(100);

        dashLinePaint.setStrokeWidth(1.5f);

        labelFontPaint = new Paint();

        labelFontPaint.setColor(Color.GRAY);

        labelFontPaint.setTextSize(fontSize);

        setXLabels(new String[]{"近7","近5","近3","近2"} );

        realLinePaint = new Paint();

        realLinePaint.setColor(dataColor);

        realLinePaint.setStrokeWidth(4f);

        realLinePaint.setAntiAlias(true);

        solidCirclePaint = new Paint(realLinePaint);

        solidCirclePaint.setStrokeWidth(1.5f);

        //solidCirclePaint.setStyle(Paint.Style.FILL);    //设置实心

        hollowCirclePaint = new Paint(realLinePaint);

        hollowCirclePaint.setColor(Color.WHITE);    //采取在一个大实心圆里画一个小白圆来实现空心圆效果的策略

        hollowCirclePaint.setStrokeWidth(1.8f);

        //hollowCirclePaint.setStyle(Paint.Style.STROKE); //设置空心

        tipFontPaint = new Paint();

        tipFontPaint.setTextSize(sp2px(getContext(),10));

        tipFontPaint.setColor(Color.WHITE);

        bmLeftTip = getTipBitmap(R.drawable.bubble_red_left,defaultPadding * 3,defaultPadding);

        bmCenterTip = getTipBitmap(R.drawable.bubble_red_mid,defaultPadding * 3,defaultPadding);

        bmRightTip = getTipBitmap(R.drawable.bubble_red_right,defaultPadding * 3,defaultPadding);

        shaderPath = new Path();

        shaderBgPaint = new Paint();

        shaderBgPaint.setAntiAlias(true);

        shaderBgPaint.setStyle(Paint.Style.FILL);

    }

    @Override

    protected void onDraw(Canvas canvas) {

        super.onDraw(canvas);

        drawBackground(canvas);

        drawXLabels(canvas);

        drawData(canvas);

    }

    //绘制渐变阴影

    private void drawShader(Canvas canvas) {

        int width = canvas.getWidth();

        int height = canvas.getHeight();

        LinearGradient lg = new LinearGradient(width/2,height,width/2,0,

            Color.parseColor("#00d92e48"),

            Color.parseColor("#99d92e48"),

            Shader.TileMode.CLAMP);// CLAMP重复最后一个颜色至最后

        shaderBgPaint.setShader(lg);

        canvas.drawPath(shaderPath,shaderBgPaint);

    }

    //绘制数据

    private void drawData(Canvas canvas) {

        int width = canvas.getWidth();

        int height = canvas.getHeight();

        int gridHeight = height - defaultPadding*2;

        if (hitRateData == null){

            return;

        }

        float w_space = (width-defaultPadding*2)/3;

        //画折线

        for (int i = 0; i < 3; i++) {

            float startX = defaultPadding+i*w_space;

            float startY = (100-hitRateData.get(i)) *gridHeight/100 + defaultPadding;

            float stopX = defaultPadding+(i+1)*w_space;

            float stopY = (100-hitRateData.get(i+1))*gridHeight/100 + defaultPadding;

            canvas.drawLine(startX,startY,stopX,stopY,realLinePaint);

            //阴影

            if (i == 0){

                shaderPath.moveTo(startX,startY);

            }

            else if(i == 1){

                shaderPath.lineTo(startX,startY);

            }

            else if (i == 2){

                shaderPath.lineTo(startX,startY);

                shaderPath.lineTo(stopX,stopY);

                shaderPath.lineTo(stopX,height-defaultPadding);

                shaderPath.lineTo(defaultPadding,height-defaultPadding);

                shaderPath.close();

            }

        }

        //画阴影

        drawShader(canvas);

        //画圆点

        for (int i = 0; i < 4; i++) {

            if (i == maxIndex){    //最大项单独处理

                //画空心圆

                canvas.drawCircle(defaultPadding+i*w_space,(100-hitRateData.get(i))*gridHeight/100 + defaultPadding,dp2px(getContext(),5),solidCirclePaint);

                canvas.drawCircle(defaultPadding+i*w_space,(100-hitRateData.get(i))*gridHeight/100 + defaultPadding,dp2px(getContext(),3),hollowCirclePaint);

                //画提示图

                int tipTopValue = (100-hitRateData.get(i))*gridHeight/100 - dp2px(getContext(),6);  //提示图y轴方向的位置

                if (maxIndex == 0){

                    float tipLeftValue = i*w_space + dp2px(getContext(),1);

                    canvas.drawBitmap(bmLeftTip,tipLeftValue , tipTopValue, tipFontPaint);

                    canvas.drawText("命中率"+hitRateData.get(i)+"%", tipLeftValue + dp2px(getContext(),3), tipTopValue + dp2px(getContext(),12), tipFontPaint);

                }

                else if (maxIndex == 1){

                    float tipLeftValue = i*w_space - defaultPadding/2;

                    canvas.drawBitmap(bmCenterTip, tipLeftValue, tipTopValue, tipFontPaint);

                    canvas.drawText("命中率"+hitRateData.get(i)+"%", tipLeftValue + dp2px(getContext(),3), tipTopValue  + dp2px(getContext(),12), tipFontPaint);

                }

                else if(maxIndex == 2){

                    float tipLeftValue = i*w_space - defaultPadding/2;

                    canvas.drawBitmap(bmCenterTip, tipLeftValue, tipTopValue, tipFontPaint);

                    canvas.drawText("命中率"+hitRateData.get(i)+"%", tipLeftValue + dp2px(getContext(),3), tipTopValue  + dp2px(getContext(),12), tipFontPaint);

                }

                else if (maxIndex == 3){

                    float tipLeftValue = i*w_space - defaultPadding - dp2px(getContext(),1);

                    canvas.drawBitmap(bmRightTip, tipLeftValue, tipTopValue, tipFontPaint);

                    canvas.drawText("命中率"+hitRateData.get(i)+"%", tipLeftValue + dp2px(getContext(),3), tipTopValue  + dp2px(getContext(),12), tipFontPaint);

                }

            } else{

                canvas.drawCircle(defaultPadding+i*w_space,(100-hitRateData.get(i))*gridHeight/100 + defaultPadding,dp2px(getContext(),3),solidCirclePaint);

            }

        }

    }

    //绘制x轴下方的标签

    private void drawXLabels(Canvas canvas) {

        int width = canvas.getWidth();

        int height = canvas.getHeight();

        if (xLabels == null){

            return;

        }

        int space = (width-defaultPadding - defaultPadding) / 3;

        Path path = new Path();

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

            // path.reset();    //用路径也没法实现垂直绘制文字,得拆分了画

            // path.moveTo(i*space + defaultPadding,height - defaultPadding + 3);

            //path.lineTo(i*space + defaultPadding,height - 3);

            // canvas.drawTextOnPath(xLabels[i],path,0f,0f,labelFontPaint);

            canvas.drawText(xLabels[i],i*space + defaultPadding/2 + 3,height- defaultPadding/2 + 3,labelFontPaint);

        }

    }

    //绘制背景4*3表格,

    private void drawBackground(Canvas canvas) {

        int width = canvas.getWidth();

        int height = canvas.getHeight();

        //画5横

        canvas.drawLine(defaultPadding,defaultPadding,width-defaultPadding,defaultPadding,dashLinePaint);

        canvas.drawLine(defaultPadding,(height - defaultPadding - defaultPadding)/4 + defaultPadding ,width-defaultPadding,(height - defaultPadding - defaultPadding)/4 + defaultPadding,dashLinePaint);

        canvas.drawLine(defaultPadding,height/2 ,width-defaultPadding, height/2,dashLinePaint);

        canvas.drawLine(defaultPadding,(height - defaultPadding - defaultPadding)*3/4 + defaultPadding ,width-defaultPadding,(height - defaultPadding - defaultPadding)*3/4 + defaultPadding,dashLinePaint);

        canvas.drawLine(defaultPadding,height-defaultPadding,width-defaultPadding,height-defaultPadding,dashLinePaint);

        //画4竖

        canvas.drawLine(defaultPadding,defaultPadding,defaultPadding,height - defaultPadding,dashLinePaint);

        canvas.drawLine((width - defaultPadding - defaultPadding) / 3 + defaultPadding,defaultPadding,(width - defaultPadding - defaultPadding) / 3 + defaultPadding,height - defaultPadding,dashLinePaint);

        canvas.drawLine((width - defaultPadding - defaultPadding) *2/ 3 + defaultPadding,defaultPadding,(width - defaultPadding - defaultPadding)*2 / 3 + defaultPadding,height - defaultPadding,dashLinePaint);

        canvas.drawLine(width-defaultPadding,defaultPadding,width-defaultPadding,height - defaultPadding,dashLinePaint);

    }

    /**

    * @param sourceId  图片的资源id

    * @param width  指定宽度

    * @param height  指定高度

    * @return  适应控件大小的图片

    */

    private Bitmap getTipBitmap(int sourceId,int width,int height){

        Bitmap bitmap = BitmapFactory.decodeResource(getContext().getResources(),sourceId);

        if (bitmap.getWidth() == width && bitmap.getHeight() == height){    //写这个判断,避createScaledBitmap()的坑

            return bitmap;

        }

        return Bitmap.createScaledBitmap(bitmap,width,height,true);

    }

    private int dp2px(Context context, int dip) {

        final float scale = context.getResources().getDisplayMetrics().density;

        return (int) (dip * scale + 0.5f);

    }

    private int sp2px(Context context, float spValue) {

        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spValue, context.getResources().getDisplayMetrics());

    }

}

4、后话:

        这也没啥技术含量,就是官方的api用用,基本的计算公式摆摆,也不多废话了。

反正用代码画图挺好玩的,文章里可能会有些你一时没想到的小技巧吧。

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

推荐阅读更多精彩内容