今天周日,起床拉开窗帘阳光瞬间映射到屋里,于是打开窗户呼吸了几口新鲜空气,神清气爽。
今天给大家带来一个即简单又实用的例子,自定义饼图。
老规矩,先看效果图
我们可以看到,该View可以细分为两部分,左侧饼图和右侧对饼图的描述。
1 自定义属性
<declare-styleable name="PieChart">
<attr name="radius" format="dimension"/>
<attr name="border_length" format="dimension"/>
<attr name="textSize" format="dimension"/>
<attr name="textColor" format="color"/>
</declare-styleable>
四个属性分别是:饼图半径、矩形块边长、文字大小、文字颜色。
代码中对属性进行引用:
public MyPieChartView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.PieChart);
mRadius = typedArray.getDimension(R.styleable.PieChart_radius,150);
mBorderLength = typedArray.getDimension(R.styleable.PieChart_border_length,25);
mTextColor = typedArray.getColor(R.styleable.PieChart_textColor,R.color.black);
mTextSize = typedArray.getDimension(R.styleable.PieChart_textSize,30);
typedArray.recycle();
init();
}
注意事项:在XML中使用的时候一定要加上命名空间,否则自定义属性会引用不到,本例中直接用的app命名空间。
2 宽高的测量
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mWidth = resolveSize(600,widthMeasureSpec);
mHeight = resolveSize(400,heightMeasureSpec);
setMeasuredDimension(mWidth,mHeight);
}
测量宽高我直接用的Android提供的resolveSize(),我们来看一下resolveSize()的源码
public static int resolveSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
result = Math.min(size, specSize);
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
其实我们自己去进行宽高测量的话基本上也是写成这样,传入的size为最大值。如果对宽高没特殊要求,我建议直接使用resolveSize(),简单方便又使用。
3 饼图的绘制
//绘制饼图
private void drawPie(Canvas canvas){
float currentAngle = 0;//当前角度
for(int i=0;i<mData.size();i++){
PieData pieData = mData.get(i);
mPiePaint.setColor(Color.parseColor(mColors.get(i)));
int centerX = mHeight/2;
int centerY = mHeight/2;
//扇形所在的正方形
RectF rectF = new RectF(centerX-mRadius,centerY-mRadius,centerX+mRadius,centerY+mRadius);
//绘制扇形
canvas.drawArc(rectF,currentAngle,pieData.getPercentage()*360,true,mPiePaint);
currentAngle += pieData.getPercentage()*360;
Log.i(TAG,"mDataSize="+mData.size());
}
}
一个饼图应该由多个扇形进行组合而成,获取到起始角度和终止角度即可进行扇形的绘制。PieData即为调用者传入的扇形类,有两个属性:扇形对应的标题和百分比,通过百分比乘360即可获取到扇形的角度。需要注意的是当前角度为上一次绘制的终止角度。
4 矩形和文字的绘制
//绘制矩形和文字
private void drawSquare(Canvas canvas){
int padding = mHeight/30;
int currentX = mHeight;
int currentY = padding + 50;
for(int i=0;i<mData.size();i++){
if(i%10==0){//一列超过10个的时候进行换列
if(i!=0) {
currentX = currentX + (int) mBorderLength + padding;
}
currentY = padding + 50;
}else {
currentY += padding+mBorderLength;
}
PieData pieData = mData.get(i);
//对颜色进行设置,和扇形一一对应
mSquarePaint.setColor(Color.parseColor(mColors.get(i)));
Rect rect = new Rect(currentX,currentY,currentX+(int)mBorderLength,currentY+(int)mBorderLength);
//绘制正方形
canvas.drawRect(rect,mSquarePaint);
DecimalFormat fNum = new DecimalFormat("##0.0");
String percentage =fNum.format( pieData.getPercentage()*100);
String title = pieData.getTitle() +" ("+ percentage +"%)";
canvas.drawText(title,currentX+(int)mBorderLength+padding,
currentY+(int)mBorderLength,mTextPaint);
Log.i(TAG,"percentage"+pieData.getPercentage()+"");
}
}
矩形块和文字的绘制也非常简单,注释写的也非常清楚。这里需要注意一点,当每列绘制的矩形块和扇形描述超过10个的时候需要进行换列,所以这个时候也存在一些小问题,如果饼图中的扇形很多怎么办,比如说有100个,这个时候绘制100个矩形块是不现实的,所以本例适用于需要绘制的扇形小于10个的场景。
至此整个饼图的制作就完成了,制作过程可以细分为四部分:自定义属性的定义、宽高的测量、饼图的绘制,矩形块和描述信息的绘制,都非常的简单。Demo已托管至github:自定义饼图
5 总结
到现在我也写了算是有三篇文章了,三篇全是自定义View的实例,也就前两天一个朋友跟我说,我这样上来就直接写实例的制作显得很生硬,他建议我写一些基础的文章,然后将实例放在对应基础的后面进行讲解,我仔细想想也是,上来就直接讲制作的过程,很少的去描述用到的知识点,这样一篇文章下来,能帮助到读者的微乎甚微。所以我决定做一个自定义View系列,从基础讲起,实例起到巩固知识点的作用,但是......现在还不行,因为内容确实有点多,我得筹划一段时间。在此我要感谢我这个朋友,给我提出了这么宝贵的建议,几番思考也让我意识到自己很多的不不足,所以我决定以后的文章尽量的系统化,讲述偏原理性的知识。下一篇文章我准备为大家带来:多线程系列(一)多线程基础