android自定义控件绘制位置,Android 自定义控件 (二) 动态绘制 饼状图

本文详细介绍了如何在Android中使用Canvas动态绘制饼状图,包括计算圆心位置、半径、圆弧角度等关键步骤,并实现动画效果以增加视觉趣味性。在处理不同设备尺寸时,确保饼图在指定区域内正确绘制。此外,还涉及到了辅助线和标记文字的绘制技巧,以及如何优化文字对齐和显示。
摘要由CSDN通过智能技术生成

? ? ? ?? 上面的一篇博客 , 已经介绍了安卓Canvas 绘制的柱状图 , 具体到项目中, 使用起来不要很简单 ; 当然了 , 项目中用到的统计图表远不止柱状图这么简单 , 比如饼图, 相比柱状图而言 ,饼状图样式显得尤为新颖 , 增添了几分趣味性 ,?接下来就动手实现一下动态绘制的饼状图 ,?顺表加了些辅助的功能.

? ? ? ? 废话不多说, 直接上图:

20181124151118142.gif

? ? ? ?? 饼状图相对于柱状图 , 稍微复杂一点 , 不过 , 只要掌握了原理, 分分钟搞定它 ; 绘制饼状图 , 首先要确定的是圆心位置 和 半径大小 , 刚开始写的时候 , 只考虑了在手机上绘制的情况 , 绘制的效果不要太好看了 ; 不巧 有一天 , 搞了个平板 , 发现坐标越界了 , 饼图半径太大 , 导致有一部分区域的圆弧没有显示出来 , 后来才想到 , 手机宽度小, 长度大 , ? 平板和TV电视 宽度大 , 长度小 , 所以 ,综合手机和TV电视 , 得出最终的解决方案? :? 半径R的取值 为? R =? width? /2? >? height? / 2? ?? height /2 : width/2? ;? 就是取小值 , 这样的话 , 就能保证饼图在既定的区域中绘制了 .

? ? ? ? 老样子 ,还是在onLayout ( ) 中获取控件的宽高等信息 : ? ? ? ??

@Override

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

super.onLayout(changed, left, top, right, bottom);

if (changed) {

startX = getPaddingLeft() + basePadding;

endX = getMeasuredWidth() - getPaddingRight() - basePadding;

startY = getMeasuredHeight() - getPaddingBottom() - basePadding;

endY = getPaddingTop() + basePadding;

radius = new float[2];

float R1 = startY - endY;

float R2 = endX - -startX;

radius[0] = startX + R2 / 2;

radius[1] = endY + R1 / 2;

whiteR = R1 > R2 ? R2 / 10 : R1 / 12;//宽度>高度 选择高度 , 宽度

pieR = R1 > R2 ? R2 / 4 : R1 / 4;

areaArc = new RectF(radius[0] - pieR, radius[1] - pieR, radius[0] + pieR, radius[1] + pieR);

}

}

? ? ? ?? 简单分析下需要用到的数据 , 各区域圆弧的画笔 , 颜色不同 , 所以需要计算各区域所占的比重 ,绘制圆弧开始的角度startAngle和扫过的角度sweepAngle ,这两个值需要单独计算 ,并且 标记当前区域的颜色

public ChartPie setData(List data) {

if (data != null) {

float total = getTotal(data);

Paint paint;

float rate;

float[] point;

double arcPI = Math.PI * 2 / 360;//π的值

float startAngle = 0;// 扇形开始的角度

for (int i = 0; i < data.size(); i++) {

ChartPieBean bean = data.get(i);

rate = bean.value / total;//当前对象值所占比例

bean.rate = rate;

bean.startAngle = startAngle;

bean.sweepAngle = rate * 360;//当前对象所占比例 对应的 角度

//计算当前圆弧上的中心点'

if (radius != null) {

point = new float[2];

point[0] = (float) (radius[0] + pieR * Math.cos(arcPI * bean.startAngle + bean.sweepAngle));

point[1] = (float) (radius[1] + pieR * Math.sin(arcPI * bean.startAngle + bean.sweepAngle));

arcPoints.put(i, point);

}

paint = new Paint(basePaint);

paint.setColor(ContextCompat.getColor(getContext(), bean.colorRes));

paintMap.put(i, paint);

pieBeanMap.put(i, bean);

startAngle += bean.sweepAngle;//不要忘记累加

}

}

return this;

}

? ? ? 先不着急画圆弧, 直接绘制饼图, 显得太生硬了 ,我都 还没准备好 ,你就给我显示完了 ?? 如果绘制的时候加上动画 , 让用户看到整个绘制过程 , 岂不是更加有趣味性呢 ! 因为是画的圆弧 , 所以为了让动画更流畅 , 这里的值动画的取值范围是 0 ~ 360 , 这样在动画更新的时候 ,更新UI的进度效果会更好

private void startAnimator() {

if (!isFirst) return;//只能绘制一次

if (starting) {

return;

}

starting = true;

valueAnimator = ValueAnimator.ofFloat(startAnimatorValue, endAnimatorValue).setDuration(duration);

valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

@Override

public void onAnimationUpdate(ValueAnimator valueAnimator) {

mAnimatorValue = (float) valueAnimator.getAnimatedValue();

if (starting) {

invalidate();

}

}

});

valueAnimator.addListener(new Animator.AnimatorListener() {

@Override

public void onAnimationStart(Animator animation) {

}

@Override

public void onAnimationEnd(Animator animation) {

starting = false;

isFirst = false;

}

@Override

public void onAnimationCancel(Animator animation) {

}

@Override

public void onAnimationRepeat(Animator animation) {

}

});

valueAnimator.start();

}

? ? ?终于到了onDraw了 , 看上面的效果图,中间的白色区域 , 其实我就是画了个圆 , 动画更新的时候不断地去绘制它, 覆盖外面的饼图 , 哈哈 ,暂时也想不到什么好的方案 ...

private void drawCenter(Canvas canvas) {

canvas.drawCircle(radius[0], radius[1], whiteR, centerPiePaint);

}

? ? ?接下来就是最外层饼图的绘制了 , 之前想了好多方案 , 最终确定了下面的方法 ,不要太简单了 ; mAnimatorValue >= startAngle+sweepAngle? 小于当前进度的区域 , 全部绘制出来 , 当mAnimatorValue < startAngle + sweepAngle 时 , 此时的进度在当前区域中, 还未超过当前区域最大值 ,? 所以mAnimatorValue - startAngle 的意思就是 仅仅绘制超出当前区域最小值的部分?

private void drawPie(Canvas canvas) {

for (int i = 0; i < pieBeanMap.size(); i++) {

ChartPieBean bean = pieBeanMap.get(i);

float angle = bean.startAngle + bean.sweepAngle;

if (angle <= mAnimatorValue) {

canvas.drawArc(areaArc, bean.startAngle, bean.sweepAngle, true, paintMap.get(i));

} else {

float sweepAngle = mAnimatorValue - bean.startAngle;

if (sweepAngle >= 0) {

canvas.drawArc(areaArc, bean.startAngle, sweepAngle, true, paintMap.get(i));

}

}

}

}

? ? 不过这里不得不 提一下 , 为什么这里要判断 sweepAngle >= 0? 呢 ? 因为动画的取值范围是 0 ~ 360? ; 所以刚开始的时候mAnimatorValue会小于startAngle ,? 如果继续绘制 ,就是负值了 , 会反方向绘制 , 所以一定要加条件过滤下.

? ? 好了,到这里,饼状图就绘制完成了 , 不过效果图里面还有些辅助线和标记文字 , 也不是很难啦 , 就不详细介绍了 , 值得注意的是 , 标记文字绘制的时候 , 注意canvas.drawText( " ", x ,y ,paint ) ; 这里的 x 跟paint的居中方式有关 , 设置LEFT居左的话 , x值是文字的左边对齐边界值 ,? 设置为CENTER居中显示 , x才是在中心位置 , 还有个点值得注意 , 字体的行高和绘制文字坐标 y 的关系 , 瞬间感觉 android 就是个坑 , 绘制个文字都这么麻烦 ! 没办法 , 我这里是把辅助线当作了绘制文字的baseLine? , 所以上面的文字需要减去行高的1/4 , 下面的文字需要加上行高的 3/4 , 基本就对称显示了 , 这样即使是在55`的小米TV上面 ,也能够保持对称 , 关于paint绘制文字的行高问题 , 这里可以参考下博客

canvas.drawText(String.valueOf(bean.value), this.endX, stopY - height / 4, textPaint);

canvas.drawText(bean.type, this.endX, stopY + height / 4 * 3, textPaint);

? ? 至此 , canvas动态绘制饼状图就完成了 , 总体效果还是蛮不错的 , 代码里面我有将控件添加到Parent的onScrollChangeListener中 , 仅仅绘制可视区域 , 这样更节省内存

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值