自定义View(二)之折线图

自定义View(二)之折线图

上次画了个比较常用的课程表,这次我们画的也是一个很常用的东西———折线图
还是先看效果图

这里写图片描述

很简单的一个折线图,也不怎么好看,但是比起上次的课程表却多了点要用的知识
这次我们需要用到自定义属性,毕竟画折线图你总要预先把长宽、分成几份之类的先知道才能画吧

1. 首先在在res/values下建立一个名叫attrs的文件,用来存放我们的属性文件

这里写图片描述

然后便开始自定义我们自己的view的属性

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--X轴长度-->
    <attr name="setX" format="integer"/>
    <!--Y轴长度-->
    <attr name="setY" format="integer"/>
    <!--X轴间隔数-->
    <attr name="intervalX" format="integer"/>
    <!--Y轴间隔数-->
    <attr name="intervalY" format="integer"/>
    <!--X轴单位-->
    <attr name="unitX" format="string"/>
    <!--Y轴单位-->
    <attr name="unitY" format="string"/>

    <declare-styleable name="LineChartView">
        <attr name="setX"/>
        <attr name="setY"/>
        <attr name="intervalX"/>
        <attr name="intervalY"/>
        <attr name="unitX"/>
        <attr name="unitY"/>
    </declare-styleable>

</resources>

2. 然后就可以在我们的布局中使用它们了,别忘了加上声明

xmlns:custom="http://schemas.android.com/apk/res-auto"
custom这个名字可以随便你改,后面的是统一的,这也是android studio的方便之处之一了
如果你还在用ADT的话,那就要改下了,把res-auto改成你自定义的view的具体路径了,比如我就要改成
xmlns:custom="http://schemas.android.com/apk/res/com.example.administrator.customview.custom/LineChartView
Σ( ° △ °|||)︴ 这么长呢,但也没办法啊。。。
具体布局如下
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:custom="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <com.example.administrator.customview.custom.LineChartView
        android:id="@+id/lineChartView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        custom:setX="80"
        custom:setY="120"
        custom:intervalX="8"
        custom:intervalY="12"
        custom:unitX="X"
        custom:unitY="Y"
        />
</LinearLayout>

3. 接下来我们就要到View中把我们设置进去的属性读出来了

在两个参数的构造方法中读取,三个的那个要涉及到style,我们暂时用不到
 public LineChartView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        //获取自定义属性
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LineChartView);
        int n = a.getIndexCount();
        for (int i = 0; i < n; i++)
        {
            int attr = a.getIndex(i);
            switch (attr)
            {
                case R.styleable.LineChartView_setX:
                    x = a.getInt(attr, 100);
                    break;
                case R.styleable.LineChartView_setY:
                    y = a.getInt(attr, 80);
                    break;
                case R.styleable.LineChartView_intervalX:
                    intervalX = a.getInt(attr, 10);
                    break;
                case R.styleable.LineChartView_intervalY:
                    intervalY = a.getInt(attr, 8);
                    break;
                case R.styleable.LineChartView_unitX:
                    unitX = a.getString(attr);
                    break;
                case R.styleable.LineChartView_unitY:
                    unitY = a.getString(attr);
                    break;
            }

        }
        a.recycle();
    }

4. 剩下的就都是我们的老朋友了

onMeasure

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //计算出view的宽高并保存
        winWidth = measureWidth(widthMeasureSpec);
        winHeight = measureHeight(heightMeasureSpec);
        setMeasuredDimension(winWidth, winHeight);

        //把宽分成intervalX份,高分成intervalY份,上下左右各留一份空白,其余是表格
        widthSize = winWidth / (intervalX + 2);
        heightSize = winHeight / (intervalY + 2);

        //字体为宽度的36分之一,自我感觉正好
        TEXT_SIZE = winWidth / 36;

        //一格的数值大小
        xData = x / intervalX;
        yData = y / intervalY;

        //初始化笔
        textPaint = new Paint();
        textPaint.setColor(Color.argb(200, 95, 87, 84));
        textPaint.setTextAlign(Paint.Align.CENTER);
        textPaint.setTextSize(TEXT_SIZE);
    }
 //计算view的宽度
    private int measureWidth(int measureSpec)
    {
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        if (specMode == MeasureSpec.EXACTLY)
        {
            result = specSize;
        }
        else
        {
            result = 300;
            if (specMode == MeasureSpec.AT_MOST)
            {
                result = Math.min(result, specSize);
            }
        }
        return result;
    }

5. onDraw

@Override
    protected void onDraw(Canvas canvas)
    {
        //画两条基准线
        canvas.drawLine(widthSize, heightSize * (intervalY + 1), widthSize * (intervalX + 1) +
                widthSize / 2, heightSize * (intervalY + 1), baseLinePaint);
        canvas.drawLine(widthSize, heightSize * (intervalY + 1), widthSize, heightSize / 2,
                baseLinePaint);
        //画箭头
        Path pathX = new Path();
        pathX.moveTo(widthSize * (intervalX + 1) + widthSize / 2, heightSize * (intervalY + 1));
        // 此点为多边形的起点
        pathX.lineTo(widthSize * (intervalX + 1) + widthSize / 2 - TEXT_SIZE, heightSize *
                (intervalY + 1) - TEXT_SIZE / 3);
        pathX.lineTo(widthSize * (intervalX + 1) + widthSize / 2 - TEXT_SIZE, heightSize *
                (intervalY + 1) + TEXT_SIZE / 3);
        pathX.close(); // 使这些点构成封闭的多边形
        canvas.drawPath(pathX, baseLinePaint);

        Path pathY = new Path();
        pathY.moveTo(widthSize, heightSize / 2);// 此点为多边形的起点
        pathY.lineTo(widthSize - TEXT_SIZE / 3, heightSize / 2 + TEXT_SIZE);
        pathY.lineTo(widthSize + TEXT_SIZE / 3, heightSize / 2 + TEXT_SIZE);
        pathY.close(); // 使这些点构成封闭的多边形
        canvas.drawPath(pathY, baseLinePaint);
        //标注分段值
        //x轴
        for (int i = 2; i < intervalX + 2; i++)
        {
            canvas.drawText(xData * (i - 1) + "", widthSize * i, heightSize * (intervalY + 1) +
                    heightSize / 2, textPaint);
            //间隔线
            canvas.drawLine(widthSize * i, heightSize * (intervalY + 1), widthSize * i,
                    heightSize * (intervalY + 1) - TEXT_SIZE / 3, baseLinePaint);
        }
        //y轴
        for (int i = 2; i < intervalY + 2; i++)
        {
            canvas.drawText(yData * (i - 1) + "", widthSize / 2, heightSize * (intervalY + 2 - i)
                    , textPaint);
            //间隔线
            canvas.drawLine(widthSize, heightSize * (intervalY + 2 - i), widthSize + TEXT_SIZE /
                    3, heightSize * (intervalY + 2 - i), baseLinePaint);
        }
        //原点
        canvas.drawText("0", widthSize / 2, heightSize * (intervalY + 1) + heightSize / 2,
                textPaint);
        //单位
        canvas.drawText(unitX, widthSize * (intervalX + 1) + widthSize / 2, heightSize *
                (intervalY + 1) + heightSize / 2, textPaint);
        canvas.drawText(unitY, widthSize / 2, heightSize / 2, textPaint);

        //画点与折线
        if (data != null)
        {
            pointFList = new ArrayList<>();
            for (int i = 0; i < data.size(); i++)
            {
                PointF pointF = data.get(i);
                float xPer = pointF.x / x;
                float yPer = pointF.y / y;
                int xPos = (int) (xPer * intervalX * widthSize + widthSize);
                int yPos = (int) ((1 - yPer) * intervalY * heightSize + heightSize);
                pointFList.add(new PointF(xPos, yPos));
            }
            Gson gson = new Gson();
            for (int i = 0; i < pointFList.size(); i++)
            {
                PointF pointF = pointFList.get(i);
                if (i + 1 < pointFList.size())
                {
                    PointF pointF2 = pointFList.get(i + 1);
                    canvas.drawLine(pointF.x, pointF.y, pointF2.x, pointF2.y, linePaint);
                }
                canvas.drawCircle(pointF.x, pointF.y, TEXT_SIZE / 4, pointPaint);
                canvas.drawText("(" + data.get(i).x + ", " + data.get(i).y + ")", pointF.x,
                        pointF.y - 20, textPaint);
            }
        }
    }
这里我用了gson解析传递进来的json数据,不清楚的小伙伴可以去问问度娘或者谷哥

6. 别忘了留下方法给动态添加数据和限制

    public void setData(List<PointF> data)
    {
        this.data = data;
        invalidate();
    }

    public void setXY(int x, int y)
    {
        this.x = x;
        this.y = y;
        invalidate();
    }

    public void setIntervalXY(int intervalX, int intervalY)
    {
        this.intervalX = intervalX;
        this.intervalY = intervalY;
        invalidate();
    }

    public void setUnitX(String unitX, String unitY)
    {
        this.unitX = unitX;
        this.unitY = unitY;
        invalidate();
    }

到此为止,一个简单的折线图就新鲜出炉了,我这里为了方便数据用的是int,数据结构用的也是自带的PointF,如果有需要的小伙伴可以自己优化一下

源码在这里呢

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值