自定义View(二)之折线图
上次画了个比较常用的课程表,这次我们画的也是一个很常用的东西———折线图
还是先看效果图
很简单的一个折线图,也不怎么好看,但是比起上次的课程表却多了点要用的知识
这次我们需要用到自定义属性,毕竟画折线图你总要预先把长宽、分成几份之类的先知道才能画吧
1. 首先在在res/values下建立一个名叫attrs的文件,用来存放我们的属性文件
然后便开始自定义我们自己的view的属性
<?xml version="1.0" encoding="utf-8"?>
<resources >
<attr name ="setX" format ="integer" />
<attr name ="setY" format ="integer" />
<attr name ="intervalX" format ="integer" />
<attr name ="intervalY" format ="integer" />
<attr name ="unitX" format ="string" />
<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
{
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);
winWidth = measureWidth(widthMeasureSpec);
winHeight = measureHeight(heightMeasureSpec);
setMeasuredDimension(winWidth, winHeight);
widthSize = winWidth / (intervalX + 2 );
heightSize = winHeight / (intervalY + 2 );
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);
}
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);
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);
}
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,如果有需要的小伙伴可以自己优化一下