前几天简单熟悉了下canvas的API,今天再来做个小demo巩固下
回顾完全自定义控件-Canvas之绘制基本形状
自定义饼图
效果展示
不用看懂。大致示意图
思路
- 通过控件所占的高度来确定圆的半径
- 通过圆、小方块、文字的大小,及他们的间距确认控件内部的布局
- 根据得到的数据算出所占角度,画出相应的扇形、小方块、文字
实现步骤
- 新建PieView类继承View。
- 重写View的三个构造函数,在构造函数中初始化数据。
- 在onSizeChanged()方法中,定义控件中各个部分的尺寸
- 在onDraw()方法中取得数据,并进行绘制。
- 在布局中使用控件
- 新建Pie对象,封装所需的颜色、占值、文字数据。
- 在MainActivity中调用控件的SetPie()方法完成数据的绑定
2. 重写View的三个构造函数,在构造函数中初始化数据
public PieView(Context context) {
this(context, null);
}
public PieView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PieView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPieColorList = new ArrayList<>(); //存放颜色
mPieValue = new ArrayList<>(); //存放所占值
mSrtingList = new ArrayList<>(); //存放文字
mPaint = new Paint();
mMaxString = ""; //最长的字符串,用于测量控件内容最大宽度
mPaint.setColor(Color.BLACK);
mPaint.setStrokeWidth(20);//画笔宽度
mPaint.setAntiAlias(true);//抗锯齿
}
3. 控件中各个物件尺寸的确定
通过控件的高度来决定饼图的大小
饼图直径=控件高度-任意值(比如10)
控件宽度=控件内容宽度+两边的间隔
控件内容宽度=饼图直径+饼图和矩形的距离+矩形的宽度+矩形和文字的距离+文字的宽度(只有文字的宽度由最长字符串测量决定,其他都固定)
控件内容的左边距=(控件宽度-内容宽度)/2
矩形的左边距=内容左边距+饼图直径+饼图和矩形的距离
文字的左边距=矩形的左边距+矩形宽度+矩形和文字的距离
饼图的圆心=(内容的左边距+圆半径,半高)
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//文字宽度
mTextWidth = (int) mPaint.measureText(mMaxString);
mControlHalfHeight = h / 2;
//饼图半径
mPieRadios = mControlHalfHeight - 5;
//控件内容宽度
int contentWidth = mPieRadios * 2 + PIE_RECT_PADDING + RECT_WIDTH + RECT_TEXT_PADDING + mTextWidth;
//内容的左边距
int contentMarginLeft = (w - contentWidth) / 2;
//矩形的左边距
mRectMarginLeft = contentMarginLeft + mPieRadios * 2 + PIE_RECT_PADDING;
//文字的左边距
mTextMarginLeft = mRectMarginLeft + RECT_WIDTH + RECT_TEXT_PADDING;
//第一个文字和控件顶部的距离
mPadding = h / mPieArrayList.size() * 0.8f;
//控制画圆的范围,圆心为(左边距+圆半径,半高)
oval = new RectF(contentMarginLeft, mControlHalfHeight - mPieRadios,
contentMarginLeft + mPieRadios * 2, mControlHalfHeight + mPieRadios);
}
为了不让控件在不同的屏幕尺寸下发生太大的改变,我们需要做一点适配工作,使用dp进行布局。
- 尺寸(dimens)适配
- 获取设备密度:
float density = getResources().getDisplayMetrics().density; - dp = px / 设备密度
- 常规设备密度: 320x240(0.75), 480x320(1), 800x480(1.5), 1280x720(2)
- 通过设置dp值, 让控件在不同的屏幕上显示的比例是一样的
- 在dimens.xml中制定尺寸, 适配屏幕
- 获取设备密度:
dimens.xml文件
16dp
16dp
16dp
30dp
6dp
15dp
18sp
25dp
在PieView中使用
public class PieView extends View {
//饼图和矩形的距离
private final int PIE_RECT_PADDING = getResources().getDimensionPixelSize(R.dimen.pie_rect_padding);
//矩形的宽度
private final int RECT_WIDTH = getResources().getDimensionPixelSize(R.dimen.rect_width);
//矩形和文字的距离
private final int RECT_TEXT_PADDING = getResources().getDimensionPixelSize(R.dimen.rect_text_padding);
......
4. 开始绘制
绘制单个扇形
private void drawPie(Canvas canvas, int amount) {
mPaint.setColor(mCurrentColor);
mPaint.setStyle(Paint.Style.FILL);
//角度通过值所占的百分比*360度确定
int angle = (int) (360f * amount / mMaxValue);
canvas.drawArc(oval, mStartAngle, angle, true, mPaint);
mStartAngle += angle;
}
绘制矩形
private void drawRect(Canvas canvas) {
if (mCurrentIndex == 0) {
RectF rect = new RectF(mRectMarginLeft, mPadding,
mRectMarginLeft + RECT_WIDTH, mPadding + RECT_WIDTH);
canvas.drawRect(rect, mPaint);
} else {
//如果矩形不是第一个,还要加上两个矩形的间隔距离
RectF rect = new RectF(mRectMarginLeft, (mCurrentIndex) * TEXT_VERTICAL_PADDING + mPadding,
mRectMarginLeft + RECT_WIDTH, (mCurrentIndex) * TEXT_VERTICAL_PADDING + mPadding + RECT_WIDTH);
canvas.drawRect(rect, mPaint);
}
}
绘制文字
private void drawText(Canvas canvas, String text) {
mPaint.setColor(TEXT_COLOR);
if (mCurrentIndex == 0) {
canvas.drawText(text, mTextMarginLeft, mPadding + TEXT_SIZE * 0.8f, mPaint);
} else {
canvas.drawText(text, mTextMarginLeft, (mCurrentIndex) * TEXT_VERTICAL_PADDING + mPadding + TEXT_SIZE * 0.8f, mPaint);
}
}
进行循环绘制
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mStartAngle = -90;
mCurrentIndex = 0;
mMaxValue = 100;
mPaint.setStyle(Paint.Style.FILL);
for (int i = 0; i < mPieValue.size(); i++) {
mCurrentColor = mPieColorList.get(mCurrentIndex);
drawPie(canvas, mPieValue.get(mCurrentIndex));
drawRect(canvas);
drawText(canvas, mSrtingList.get(mCurrentIndex));
mCurrentIndex++;
}
}
5. 在布局中使用控件
6. 新建Pie对象,封装所需数据
public class Pie {
//颜色
public int PieColor;
//所占的值
public int PieValue;
//文字
public String PieString;
public Pie(int pieValue, String pieString, int pieColor) {
this.PieValue = pieValue;
this.PieString = pieString;
this.PieColor = pieColor;
}
}
7. 在MainActivity中调用控件的SetPie()方法完成数据的绑定
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
PieView pieView = (PieView) findViewById(R.id.PieView);
ArrayList pieArrayList=new ArrayList<>();
Pie pie=new Pie(50,"JAVA需求",getResources().getColor(R.color.chart_color_1));
pieArrayList.add(pie);
Pie pie1=new Pie(30,"H5需求",getResources().getColor(R.color.chart_color_2));
pieArrayList.add(pie1);
Pie pie2=new Pie(10,"iOS需求",getResources().getColor(R.color.chart_color_3));
pieArrayList.add(pie2);
Pie pie3=new Pie(10,"Android需求",getResources().getColor(R.color.chart_color_4));
pieArrayList.add(pie3);
pieView.SetPie(pieArrayList);
//第二个饼图
PieView pieView2 = (PieView) findViewById(R.id.PieView2);
ArrayList pieArrayList2=new ArrayList<>();
pieArrayList2.add(new Pie(20,"Android高级工程师",getResources().getColor(R.color.red)));
pieArrayList2.add(new Pie(80,"Android小白",getResources().getColor(R.color.blue)));
pieView2.SetPie(pieArrayList2);
}
}
getColor方法在6.0中已经过时
可以参考以下方法:
ContextCompat.getColor(context, R.color.my_color)
http://blog.csdn.net/blue_bamboo/article/details/51131584
这里是项目地址。
OK终于完成了,感谢原作者谪仙!
参考文章
http://www.cnblogs.com/kimmy/p/4918659.html