自定义天气显示温度变化的LinearChart控件

这次发表的是前几个月搞定的一个自定义控件,那时自己在写一个小的查看天气的软件,在这过程中就涉及了显示天气变化的折线图,一开始想用一些画图框架来解决问题,不过考虑到就只用到LineChart折线图这一个控件就要导一个库有点太浪费了,所以就自己自定义简易版LineChart算了。好了不说闲话老规矩,先发张效果图先:

img.PNG

这就是这个自定义控件的最终效果,当然颜色你可以自己设置。
首先初始化自定义控件的各个变量,以便看得更清楚:

    //圆点旁边字体的大小
    private int CircleTextSize;
    //字体颜色
    private int CircleTextColor;
    //高的温度的线的颜色
    private int MinLineColor;
    //低的温度的线的颜色
    private int MaxLineColor;
    //圆点的颜色
     private int CircleColor;
    //画线的画笔
    private Paint LinePaint;
    //画圆点的画笔
    private Paint CirclePaint;
    //画字的画笔
    private Paint TextPaint;
    //存储Max轴的数据
    private List<Float> YValueMax=new ArrayList<>();
    //存储Min轴的数据
    private List<Float> YValueMin=new ArrayList<>();
    //控件的高度
    private int ChartHeight=0;
    //控件的长度
    private int ChartWidth=0;
    //缓存X轴的数据
    private List<Float> XValueWidth=new ArrayList<>();
    //画出Y轴最大值的数据
    private List<Float> mYAxisMax=new ArrayList<>();
    //画出Y轴最小值的数据
    private List<Float> mYAxisMin=new ArrayList<>();
    //设置透明度
    private int ChartAlpha=0;
    //圆点的半径
    private float mRadius=0;
    //折线的粗细
    private float StrokeWidth=0;
    //文字和上下的边的间隔
    private float marginHeigh=0;    

接着就是初始化各个自定义的变量:

public WeatherLineChart(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //初始化各参数
        TypedArray typedArray=context.getTheme().obtainStyledAttributes(
                attrs,R.styleable.WeatherLineChart,defStyleAttr,0);
        int numCount=typedArray.getIndexCount();
        for(int i=0;i<numCount;i++){
            int attr= typedArray.getIndex(i);
            switch(attr){
                case R.styleable.WeatherLineChart_MaxLineColor:
                      MaxLineColor=typedArray.getColor(attr, Color.RED);
                    break;
                case R.styleable.WeatherLineChart_MinLineColor:
                      MinLineColor=typedArray.getColor(attr,Color.BLUE);
                    break;
                case R.styleable.WeatherLineChart_CircleTextColor:
                      CircleTextColor=typedArray.getColor(attr,Color.GRAY);
                    break;
                case R.styleable.WeatherLineChart_CircleTextSize:
                      CircleTextSize=typedArray.getDimensionPixelSize(attr,(int)TypedValue.applyDimension(
                            TypedValue.COMPLEX_UNIT_SP,15,getResources().getDisplayMetrics()));
                    break;
                case R.styleable.WeatherLineChart_CircleColor:
                      CircleColor=typedArray.getColor(attr,Color.BLACK);
                    break;
                case R.styleable.WeatherLineChart_ChartAlpha:
                      ChartAlpha=typedArray.getInt(attr,220);
                    break;
            }
        }
        typedArray.recycle();

        float density=getResources().getDisplayMetrics().density;
        mRadius = 3 * density;
        StrokeWidth=density*3;
    marginHeigh=density*10;

        display=((WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
        WrapcontentWidth=display.getWidth();
        WrapcontentHight=display.getHeight();

        //初始化画线的画笔
        LinePaint=new Paint();
        LinePaint.setAntiAlias(true);
        LinePaint.setStyle(Paint.Style.STROKE);
        LinePaint.setStrokeWidth(StrokeWidth);
        LinePaint.setAlpha(ChartAlpha);

        //初始化画圆点的画笔
        CirclePaint=new Paint();
        CirclePaint.setAntiAlias(true);
        CirclePaint.setColor(CircleColor);
        CirclePaint.setAlpha(ChartAlpha);

        //初始化画字的画笔
        TextPaint=new Paint();
        TextPaint.setAntiAlias(true);
        TextPaint.setTextSize(CircleTextSize);
        TextPaint.setColor(CircleTextColor);
        TextPaint.setTextAlign(Paint.Align.CENTER);
        TextPaint.setAlpha(ChartAlpha);

    }

这的代码虽然有点多,不过都只是一些初始化的操作而已,所以看起来也不会很复杂。而最重要的代码段当然是绘制View的onDraw()方法。代码如下:

protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        ChartHeight=getHeight();
        ChartWidth=getWidth();
        if(XValueWidth!=null&&mYAxisMax!=null&&mYAxisMin!=null){
            XValueWidth.clear();
            mYAxisMax.clear();
            mYAxisMin.clear();
        }
        //初始化X轴的值
        initXValueData();
        //初始化Y轴的值
        initYValueData();
        //画出最大值的线
        DrawLine(canvas,XValueWidth,mYAxisMax,YValueMax,true);
        //画出最小值得线
        DrawLine(canvas,XValueWidth,mYAxisMin,YValueMin,false);
    }

这个onDraw()方法最重要的就是底下的四个方法。其中initXValueData()是算出各个点在这个控件的X轴的位置数据,initYValueData()是画出两条线的Y轴的位置数据。剩下的DrawLine()方法就是具体的画出每条折线。接下来,看看initXValueData()方法:

    //初始化X轴的值
    public void initXValueData(){
         //得到数据的个数
        int XNum=YValueMax.size();
        //得到距离最左边的距离
        float BaseWidth=ChartWidth/(XNum*2);
        //得到各点之间的间隔
        float tempWdith=BaseWidth*2;
        for(int i=0;i<XNum;i++){
        //得到各点的具体X轴坐标
            XValueWidth.add(BaseWidth);
            BaseWidth+=tempWdith;
        }
    }

这个方法我注释已经很清楚了,就是得到第一个点到最左边的距离(BaseWidth)。而各个点之间的距离是BaseWidth的两倍,进而就可以得到每个点的X轴的坐标数据。然后就是initYValueData(),代码如下:

    //初始化Y轴的值
    public void initYValueData(){
        //获取最大值
        float tempMax=YValueMax.get(0);
        //获取最小值
        float tempMin=YValueMax.get(0);

        //算出最高温度的最大值的最小值
        for(int i=1;i<YValueMax.size();i++){
            if(tempMax<YValueMax.get(i)){
                tempMax=YValueMax.get(i);
            }
            if(tempMin>YValueMax.get(i)){
                tempMin=YValueMax.get(i);
            }
        }

        //和最高温度的最大值和最小值比较进而得到所有数据的最大值和最小值
        for(int i=1;i<YValueMin.size();i++){
            if(tempMax<YValueMin.get(i)){
                tempMax=YValueMin.get(i);
            }
            if(tempMin>YValueMin.get(i)){
                tempMin=YValueMin.get(i);
            }
        }

      //温差
      float parts=tempMax-tempMin;
      //y轴一端到控件一端的距离
      float length = CircleTextSize+mRadius+marginHeigh;
      //y轴高度
      float yAxisHeight = ChartHeight-length*2;

        if(parts==0){
            //都为零没有温差
            for(int i=0;i<YValueMax.size();i++){
                mYAxisMax.add((float) (ChartHeight/2));
                mYAxisMin.add((float) (ChartHeight/2));
            }
        }else{
            //有温差
            float partVlaue=yAxisHeight/parts;
            //最小高度值
            for(int i=0;i<YValueMax.size();i++){
                //具体的Y轴坐标数据
                mYAxisMax.add(ChartHeight-partVlaue*(YValueMax.get(i)-tempMin)-length);
                mYAxisMin.add(ChartHeight-partVlaue*(YValueMin.get(i)-tempMin)-length);
            }
        }
    }

初始化Y轴的坐标数据时略显复杂。总的思路就是首先的得到上下两个折线总的数据的最大值和最小值。即tempMax和tampMin分别是总数据的最大值和最小值。最大值和最小值的相减即可得到温差。因为两条折线的上下是有文字显示每个点的,所以实际的Y轴的高度是整个View的高度减去文字大小和原点半径和设置的间隔。即//y轴一端到控件一端的距离 float length = CircleTextSize+mRadius+marginHeigh; //y轴高度 float yAxisHeight = ChartHeight-length*2;这段代码的意思。当温差(parts)等于0时,即各点温度都是一样的时候,两条折线是显示在整个View的中间的。否则是有温差情况,高度除于温差得到最小的高度值float partVlaue=yAxisHeight/parts;,然后整个View的高度减去每个实际的温度数据减去最小值再乘以最小的高度值的值在减去底下的文字高度等(length),就是这一点具体的Y轴的高度。上下两条的折线的原理都是一样的,为此就可以得到具体的Y轴的位置数值。
其实大部分代码都是在初始化数据,等数据初始化完之后就是画图的阶段了,代码如下:

//画图
  public void DrawLine(Canvas canvas,List<Float> XValue,List<Float> mYAxis,List<Float> YValue,boolean top){
        for(int i=0;i<XValue.size();i++){
            if(top){
                //画具体温度数据
                LinePaint.setColor(MaxLineColor);
                canvas.drawText(YValue.get(i)+"",XValue.get(i),mYAxis.get(i)-mRadius,TextPaint);
            }else{
                LinePaint.setColor(MinLineColor);
                //画具体温度数据
                canvas.drawText(YValue.get(i)+"",XValue.get(i),mYAxis.get(i)+CircleTextSize+mRadius,TextPaint);
            }
            if(i!=XValue.size()-1){
                
                //画每两点之间的连线
                canvas.drawLine(XValue.get(i),mYAxis.get(i),XValue.get(i+1),mYAxis.get(i+1),LinePaint);
            }
            //画每一点的原点
            canvas.drawCircle(XValue.get(i),mYAxis.get(i),mRadius,CirclePaint);
        }
    }

其中top参数假如是true的话代表的是上面一条折线,false的画代表的是下面的一条折线图。其实只要得到上面的各个点的X,Y轴坐标的数据之后剩下的只是用Canvas进行画线,画点和画文字,具体的看代码注释,注释已经写得很清楚了。

最后奉上源码。如果对你有帮助就请给我给星星或喜欢吧

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

推荐阅读更多精彩内容