这是一个自定义的圆环图像,支持动画展示,可以自定义圆环的颜色和占比,主要用以展示一些数据占比方面展示的android圆环。
圆环实现思路:
android的自定义圆环实现有很多种方法,这里只介绍我实现的思路。主要思路是先画一个大圆,然后再画一个与大圆同圆心的小圆,然后小圆的颜色可以设置为背景色,这样看上去就是一个圆环了。
实现效果:
使用方法:
1.布局文件中直接使用自定义圆环(RingView),控件的宽和高需要固定的尺寸
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:orientation="vertical">
<com.wuden.zxingproject.widgets.RingView
android:id="@+id/rvRingView"
android:layout_gravity="center"
android:layout_width="300dp"
android:layout_height="300dp" />
</LinearLayout>
复制代码
2.在对应的activty中调用一些方法来实现你的需求即可
public class TestActivity extends AppCompatActivity {
@Bind(R.id.rvRingView)
RingView mRvRingView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_test1);
ButterKnife.bind(this);
mRvRingView.setAnglesData("12.2","230","6799.01","1","111","200");
mRvRingView.initPaint("#123456", "#fea123", "#fefefe", "#78da10", "#1121de", "#aacc18");
mRvRingView.setRingStrokeWidth(40);
mRvRingView.showViewWithAnimation();
复制代码
3.自定义view的源码
public class RingView extends View {
private static final int CIRCLE_ANGLE = 360
private static final int RING_STROKE_WIDTH = 20
private Paint mNoAssetsPaint, mInnerCirclePaint
private ArrayList<Paint> mPaints
private int mRingStrokeWidth
private int mCanvasWidth, mCanvasHeight
private RectF mRingRect, mInnerRect
private int mDensity
private int mNoDataPaintColor = Color.parseColor("#cccccc")
private int mInnerCirclePaintColor = Color.parseColor("#ffffff")
private ArrayList<Integer> mAngles
private boolean mHasData = false
private ArrayList<Integer> mLevelStartAngles
private int mMoveAngle
private int mRingStartAngle = -90
private RingAnimation mRingAnim
public RingView(Context context) {
super(context)
init(context)
public RingView(Context context, AttributeSet attrs) {
super(context, attrs)
init(context)
public RingView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr)
init(context)
private void init(Context ctx) {
mDensity = (int) ctx.getResources().getDisplayMetrics().density
mRingStrokeWidth = RING_STROKE_WIDTH * mDensity
mPaints = new ArrayList<Paint>()
mAngles = new ArrayList<Integer>()
mLevelStartAngles = new ArrayList<Integer>()
mNoAssetsPaint = new Paint()
mNoAssetsPaint.setAntiAlias(true)
mNoAssetsPaint.setStyle(Paint.Style.FILL)
mNoAssetsPaint.setColor(mNoDataPaintColor)
mInnerCirclePaint = new Paint()
mInnerCirclePaint.setAntiAlias(true)
mInnerCirclePaint.setStyle(Paint.Style.FILL)
mInnerCirclePaint.setColor(mInnerCirclePaintColor)
mRingAnim = new RingAnimation()
@Override
protected void onDraw(Canvas canvas) {
if (mCanvasWidth == 0) {
initRect()
if (!mHasData) {//没有数据
mMoveAngle = CIRCLE_ANGLE
drawRingView(canvas, mRingStartAngle, mMoveAngle, mNoAssetsPaint)
} else {
int _level = 0
for (int _i = 0
if (mMoveAngle < mLevelStartAngles.get(1)) {
_level = 1
} else if (mMoveAngle > mLevelStartAngles.get(_i) && mMoveAngle <= mLevelStartAngles.get(_i + 1)) {
_level = _i + 1
drawRing(_level, canvas)
canvas.drawArc(mInnerRect, mRingStartAngle, CIRCLE_ANGLE, true, mInnerCirclePaint)
* @param level 圆环的段数
* @param canvas
private void drawRing(int level, Canvas canvas) {
if (level <= 0) {
drawRingView(canvas, mRingStartAngle, CIRCLE_ANGLE, mNoAssetsPaint)
return
if (mAngles.size() > mPaints.size()) {
int _temp = mAngles.size() - mPaints.size()
for (int _i = 0
mPaints.add(mNoAssetsPaint)
for (int _i = 0
if (_i == level - 1) {
drawRingView(canvas, mRingStartAngle + mLevelStartAngles.get(_i),
mMoveAngle - mLevelStartAngles.get(_i), mPaints.get(_i))
} else {
drawRingView(canvas, mRingStartAngle + mLevelStartAngles.get(_i), mAngles.get(_i), mPaints.get(_i))
* @param canvas
* @param startAngle 开始的角度
* @param sweepAngle 旋转的角度
* @param paint 画笔
private void drawRingView(Canvas canvas, int startAngle, int sweepAngle, Paint paint) {
if (sweepAngle != 0) {
canvas.drawArc(mRingRect, startAngle, sweepAngle, true, paint)
public void setNoDataPaintColor(int color) {
mNoAssetsPaint.setColor(getResources().getColor(color))
public void setNoDataPaintColor(String color) {
mNoAssetsPaint.setColor(Color.parseColor(color))
public void setInnerCirclePaintColor(int colorId) {
mInnerCirclePaint.setColor(getResources().getColor(colorId))
public void setInnerCirclePaintColor(String color){
mInnerCirclePaint.setColor(Color.parseColor(color))
public void initPaint(ArrayList<Integer> colors) {
mPaints.clear()
for (int _i = 0
Paint _paint = new Paint()
_paint.setAntiAlias(true)
_paint.setStyle(Paint.Style.FILL)
_paint.setColor(colors.get(_i))
mPaints.add(_paint)
public void initPaint(String... colors) {
ArrayList<Integer> _colors = new ArrayList<Integer>()
for (int _i = 0
_colors.add(Color.parseColor(colors[_i]))
initPaint(_colors)
public void initPaint(int... colorIds) {
ArrayList<Integer> _colors = new ArrayList<Integer>()
for (int _i = 0
_colors.add(getResources().getColor(colorIds[_i]))
initPaint(_colors)
private void initRect() {
mCanvasWidth = getWidth()
mCanvasHeight = getHeight()
mInnerRect = new RectF(mRingStrokeWidth, mRingStrokeWidth, mCanvasWidth - mRingStrokeWidth, mCanvasHeight - mRingStrokeWidth)
mRingRect = new RectF(0, 0, mCanvasWidth, mCanvasHeight)
* 设置圆环起始的角度
* @param angle
public void setRingStartAngle(int angle){
mRingStartAngle = angle
* 设置圆环的环宽
* @param width
public void setRingStrokeWidth(int width) {
mRingStrokeWidth = width * mDensity
invalidate()
* 所需要显示的数据的角度
* @param angles
public void setAngles(int... angles) {
ArrayList<Integer> _angles = new ArrayList<Integer>()
for (int _i = 0
_angles.add(angles[_i])
setAngles(_angles)
* 所需要显示的数据的角度
* @param angles
public void setAngles(ArrayList<Integer> angles) {
mAngles.clear()
mAngles.addAll(angles)
mLevelStartAngles.clear()
mLevelStartAngles.add(0)
int _angle = 0
for (int _i = 0
_angle += mAngles.get(_i)
mLevelStartAngles.add(_angle)
if (mAngles.get(_i) > 0) {
mHasData = true
* 设置数据来计算角度并绘制圆环
* @param data
public void setAnglesData(BigDecimal... data) {
BigDecimal _total = new BigDecimal("0.00")
for (int _i = 0
_total = _total.add(data[_i])
if (_total.compareTo(BigDecimal.valueOf(0)) == 0) {
mHasData = false
return
BigDecimal[] _dbData = new BigDecimal[data.length]
for (int _i = 0
_dbData[_i] = data[_i].divide(_total, 10, ROUND_HALF_UP).multiply(BigDecimal.valueOf(360))
int[] _intData = new int[data.length]
for (int _i = 0
//数值小于1且大于0的,就直接定1,否则转int类型,确保小数据也能出现在圆环上
_intData[_i] = _dbData[_i].compareTo(BigDecimal.valueOf(1.0)) < 0 && _dbData[_i].compareTo(BigDecimal.valueOf(0)) > 0 ?
1 : _dbData[_i].intValue()
//所有数据加起来可能会不满360也可能会超出360,由于精度的问题
//处理方案是把缺少的度数(有正也有负)加在最大的值上,这样图形出现的误差会不明显
int _remind = 360
int _maxPosition = -1, _max = _intData[0]
for (int _i = 0
_remind = _remind - _intData[_i]
if (_max <= _intData[_i]) {
_maxPosition = _i
_intData[_maxPosition] += _remind
//将最终的数据设置到圆环上
setAngles(_intData)
public void setAnglesData(String... data) {
BigDecimal[] _bdData = new BigDecimal[data.length]
for (int _i = 0
_bdData[_i] = new BigDecimal(TextUtils.isEmpty(data[_i]) ? "0" : data[_i])
setAnglesData(_bdData)
public void setAnglesData(double... data) {
BigDecimal[] _bdData = new BigDecimal[data.length]
for (int _i = 0
_bdData[_i] = BigDecimal.valueOf(data[_i])
setAnglesData(_bdData)
* 自定义动画时间的圆环
* @param animTime
public void showViewWithAnimation(int animTime) {
startAnimation(animTime)
* 默认时间(2000)的圆环
public void showViewWithAnimation() {
startAnimation(-1)
* 不带动画的圆环
public void showViewWithoutAnimation() {
mMoveAngle = CIRCLE_ANGLE
invalidate()
private void startAnimation(int animTime) {
mRingAnim.setDuration(animTime <= 0 ? 2000 : animTime)
startAnimation(mRingAnim)
private class RingAnimation extends Animation {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
mMoveAngle = (int) (interpolatedTime * CIRCLE_ANGLE)
invalidate()
复制代码
4.代码实现的一些注意点
1)控件的宽和高必须是固定的,不然无法显示。
2)画笔颜色的数组长度必须大于或等于数据数组的长度,不然超出的数据将由默认的没有数据的颜色显示。
3)在设置画笔颜色时,使用的字符串形式的颜色必须严格遵循颜色的书写方式,不然会出现无法正确显示view。例如:不支持“#fff”,支持“#ffffff”。
该控件中还存在很多需要优化的地方和更多的功能支持,后期会补充,希望各位大神可以给出指导性的意见和建议,十分感谢🙏。