专栏首页Jack的Android之旅自定义天气显示温度变化的LinearChart控件

自定义天气显示温度变化的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进行画线,画点和画文字,具体的看代码注释,注释已经写得很清楚了。

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 模仿企鹅FM播放主页面滑动动态改变各视图的大小

    国庆的一个任务就是把自己之前写的代码搬到博客。这次给各位带来的是通过滑动来动态改变各个View的大小进而达到企鹅FM播放页面的滑动效果(仅仅是滑动效果),老规矩...

    HelloJack
  • 自定义View之雷达图

    当MeasureSpec.getMode不等于 MeasureSpec.EXACTLY时即父控件没有指定大小时,该View的宽时这个屏幕的宽再加它的左右间隔,而...

    HelloJack
  • 高仿京东金融的数值滚动尺

    以前博客讲的大部分都是静态的自定义View的编写,其实无非就是“画画”画出一个好看的效果,而这篇博客写的是写一个动态的自定义控价,这里不仅需要"画",还要各种事...

    HelloJack
  • Vagrant搭建Linux环境

    1、下载软件 https://www.virtualbox.org/wiki/Downloads https://www.vagrantup.com/dow...

    苦咖啡
  • LeetCode 970. 强整数

    给定两个正整数 x 和 y,如果某一整数等于 xi + yj,其中整数 i >= 0 且 j >= 0,那么我们认为该整数是一个强整数。

    Michael阿明
  • 我怀疑我哥们对我有意思? 不要怕,算法可根据面相测出性取向

    一项声称人工智能可以从面部图像中推断性取向的研究在2017年秋天引起了轩然大波。两个主要的LGBTQ(女同性恋者(Lesbians)、男同性恋者(Gays)、双...

    AiTechYun
  • 一个小白的 TensorFlow 学习笔记(一)

    本系列文章以《深度学习原理与TensorFlow实践》一书的内容为基础,结合网络上其他材料,提取并梳理了一些感觉比较有意义的点,也记录了一个菜鸟的心路历程。好东...

    雷博生
  • Windows平台下Python使用swig调用C++

    步骤0:swig简介 swig是一种可以将C++代码转换为多种脚本语言封装的工具,可以在swig官网www.swig.org下载,解压后将swig.exe的路径...

    月见樽
  • 【一起学系列】之单例模式:只推荐三种~

    **评价:**这样写有延迟加载的功能,但是加了一个synchronized大锁,因此多线程环境下效率较低

    Kerwin
  • Linux 系统下实践 VLAN

    使用 virt-manager 创建 KVM 虚拟机,方法比较简单,由于篇幅有限,大家可以查阅相关资料自行了解。

    CloudDeveloper

扫码关注云+社区

领取腾讯云代金券