自定义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,如果有需要的小伙伴可以自己优化一下
源码在这里呢
版权声明:本文为UmGsoil原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。