实际效果图
核心#代码
public FlagsPieView(Context c, @Nullable AttributeSet attrs, int defStyleAttr) {
super(c, attrs, defStyleAttr);
this.context = c;
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.PieView);
mRadius = typedArray.getDimension(R.styleable.PieView_radius, 150);
mBorderLength = typedArray.getDimension(R.styleable.PieView_border_length, 15);
mTextColor = typedArray.getColor(R.styleable.PieView_textColor, Color.BLACK);
mTextSize = typedArray.getDimension(R.styleable.PieView_textSize, 30);
centerTitle = typedArray.getString(R.styleable.PieView_centerTitle);
centerHint = typedArray.getString(R.styleable.PieView_centerHint);
typedArray.recycle();
init();
}
/**
* 测量文字的高度
*/
public float measureTextHeight(Paint paint) {
float height = 0f;
if (null == paint) {
return height;
}
Paint.FontMetrics fontMetrics = paint.getFontMetrics();
height = fontMetrics.descent - fontMetrics.ascent;
return height;
}
private void init() {
//外圆
mPiePaint = new Paint();
mPiePaint.setAntiAlias(true);
mPiePaint.setStyle(Paint.Style.STROKE);
mPiePaint.setStrokeWidth(mBorderLength*0.6f);
//内圆
mInnerPiePaint = new Paint();
mInnerPiePaint.setAntiAlias(true);
mInnerPiePaint.setStyle(Paint.Style.FILL);
//绘制大圆的矩形
preRectF = new RectF();
//绘制内圆的矩形
preInnerRectF = new RectF();
//弧度小方格
mBlockAgePaint = new Paint();
mBlockAgePaint.setAntiAlias(true);
mBlockAgePaint.setStyle(Paint.Style.FILL);
mBlockAgePaint.setStrokeWidth(mTextSize);
//右侧小方格 说明
rectBlockage = new Rect();
//文字画笔
mTextPaint = new Paint();
mTextPaint.setAntiAlias(true);
mTextPaint.setColor(mTextColor);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mWidth = resolveSize(DensityUtils.getScreenWidth(context), widthMeasureSpec);
mHeight = resolveSize((int) (DensityUtils.getScreenWidth(context) / 2), heightMeasureSpec);
setMeasuredDimension(mWidth, mHeight);
//TODO 最大半径
radiusMax = (Math.min(mHeight/2, mWidth/2)-mBorderLengt);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawPie(canvas, preRectF);
drawCenter(canvas, preInnerRectF);
drawBlockAge(canvas);
}
//绘制饼图
private void drawPie(Canvas canvas, RectF preRectF) {
//兼容 短边的一半 作半径
//得到实际半径
float mRadiusValue = Math.min(mRadius, radiusMax);
//避免创建对象
preRectF.left = getPaddingLeft() + mLeft;
preRectF.top = (mHeight - mRadiusValue * 2-getPaddingTop()*2) / 2 + getPaddingTop();
preRectF.right = preRectF.left + mRadiusValue * 2;
preRectF.bottom = preRectF.top + mRadiusValue * 2;
float currentAngle = 0;//当前角度
for (int i = 0; i < mData.size(); i++) {
PieData pieData = mData.get(i);
mPiePaint.setColor(Color.parseColor(mData.get(i).getGetColor()));
canvas.drawArc(preRectF, currentAngle, pieData.getPercentage() * 360, false, mPiePaint);
currentAngle += pieData.getPercentage() * 360;
}
//圆内文本
preInnerRectF.left = preRectF.left + mBorderLength / 3;
preInnerRectF.top = preRectF.top + mBorderLength / 3;
preInnerRectF.right = preRectF.right - mBorderLength / 3;
preInnerRectF.bottom = preRectF.bottom - mBorderLength / 3;
}
//绘制小矩形和文字
private void drawBlockAge(Canvas canvas) {
int areaHeight = DensityUtils.dip2px(context, 8);
int currentX = (int) (preRectF.right + mBorderLength * 3);
float textHeight = measureTextHeight(mTextPaint);
//每一份
int count = mData.size();
//内容高度
float contenHeight = count * (textHeight + areaHeight) - areaHeight;
float centerYResult = (mHeight - contenHeight) / 2;
float currentY = centerYResult + 2;
//测量文本的高度 0,1,2
for (int i = 0; i < count; i++) {
PieData pieData = mData.get(i);
float diff = i > 0 ? (textHeight + areaHeight) : 0;
currentY += diff;
mBlockAgePaint.setColor(Color.parseColor(mData.get(i).getGetColor()));
rectBlockage.left = currentX;
rectBlockage.top = (int) (currentY + 2);
rectBlockage.right = (int) (currentX + textHeight / 1.5);
rectBlockage.bottom = (int) (currentY + areaHeight);
canvas.drawRect(rectBlockage, mBlockAgePaint);
DecimalFormat fNum = new DecimalFormat("#0.0");
String percentage = fNum.format(pieData.getPercentage() * 100);
String title = pieData.getTitle() + " (" + percentage + "%)";
//mTextPaint.setColor(Color.parseColor(mData.get(i).getGetColor()));
canvas.drawText(title, currentX + mBorderLength * 1.4f,
currentY + rectBlockage.bottom - rectBlockage.top, mTextPaint);
}
}
/**
* 绘制圆中心文字
*
* @param rectF 基于圆环处理内圆
*/
private void drawCenter(Canvas canvas, RectF rectF) {
mInnerPiePaint.setColor(Color.WHITE);
canvas.drawArc(rectF, 0, 360, true, mInnerPiePaint);
mTextPaint.setColor(Color.parseColor("#333333"));
mTextPaint.setFakeBoldText(true);
mTextPaint.setTextSize(DensityUtils.sp2px(context, 12));
//中心title
textWidth = mTextPaint.measureText(centerTitle);
textHeight = measureTextHeight(mTextPaint);
int textX = (int) (rectF.left + (rectF.right - rectF.left) / 2);
canvas.drawText(centerTitle, textX - textWidth / 2, mHeight / 2, mTextPaint);
//hint title
mTextPaint.setFakeBoldText(false);
mTextPaint.setTextSize(DensityUtils.sp2px(context, 9));
mTextPaint.setColor(Color.parseColor("#777777"));
//hint
textHintWidth = mTextPaint.measureText(centerHint);
canvas.drawText(centerHint, textX - textHintWidth / 2, mHeight / 2 + textHeight, mTextPaint);
}
public void setData(List<PieData> data) {
mData = data;
postInvalidate();
}
How Use
//xml
<com.lib_custom_view.view.FlagsPieView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/assets_pie_chart_now"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:background="@drawable/module_view_bg_color"
app:border_length="10dp"
app:centerHint="Android技术栈"
app:centerTitle="2021,牛气冲天"
app:radius="60dp"
app:textSize="13dp" />
//Activity
val pieDataList: ArrayList<PieData> = ArrayList()
pieDataList.add(PieData("HotFix热修复", 0.15.toFloat(), "#F83130"))
pieDataList.add(PieData("Kotlin", 0.15.toFloat(), "#FC6E1E"))
pieDataList.add(PieData("ExecutorService", 0.30.toFloat(), "#FEC01D"))
pieDataList.add(PieData("Okhttp", 0.20.toFloat(), "#62D54E"))
pieDataList.add(PieData("IntentService", 0.20.toFloat(), "#34DFC4"))
assets_pie_chart_now.setData(pieDataList)
小技巧
1.圆右边的内容(方块+文本)相对于圆进行偏移
2.多个方块统一居中公式:第一个方块的top值=(View总高度-多个方块总高度)/2,其他依次+自身+间隔
3.最大半径限制,不可超过View总高度的一半
4.初始化的时候创建好对象容器
5.不可变得文本信息放到自定义属性中去
………