这是一个自定义的圆环图像,支持动画展示,可以自定义圆环的颜色和占比,主要用以展示一些数据占比方面展示的 android 圆环。
android 的自定义圆环实现有很多种方法,这里只介绍我实现的思路。主要思路是先画一个大圆,然后再画一个与大圆同圆心的小圆,然后小圆的颜色可以设置为背景色,这样看上去就是一个圆环了。
1. 布局文件中直接使用自定义圆环(RingView),控件的宽和高需要固定的尺寸
- 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">
- android:id="@+id/rvRingView"
- android:layout_gravity="center"
- android:layout_width="300dp"
- android:layout_height="300dp" />
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");//直接设置String类型的数据
- // mRvRingView.setAnglesData(12.2,230,6799.01,1,111,200);//直接设置double类型的数据
- // mRvRingView.setAngles(20, 40, 100, 180, 20);//设置的是角度
- // mRvRingView.setRingStartAngle(-90);//设置圆环的开始角度,不设置默认是-90
- //设置画笔的颜色,支持字符串和资源文件可变参数。
- mRvRingView.initPaint("#123456", "#fea123", "#fefefe", "#78da10", "#1121de", "#aacc18");//支持字符串
- // mRvRingView.initPaint(R.color.color_first_part,R.color.color_second_part,
- // R.color.color_third_part,R.color.color_fourth_part,
- // R.color.color_fifth_part,R.color.color_sixth_part);
- // mRvRingView.setInnerCirclePaintColor("#ffffff");//内圆的画笔颜色,默认#ffffff
- mRvRingView.setRingStrokeWidth(40);//圆环的环宽,默认20
- // mRvRingView.showViewWithAnimation(1000);//自定义动画时长展示圆环
- // mRvRingView.showViewWithoutAnimation();//展示圆环不带动画
- mRvRingView.showViewWithAnimation();//动画展示圆环,默认2s
- }
- }
3. 自定义 view 的源码
- public class RingView extends View {
- private static final int CIRCLE_ANGLE = 360; //圆环的角度
- private static final int RING_STROKE_WIDTH = 20; //默认圆环的宽度为20dp
- 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"); //没有数据的paint的颜色
- private int mInnerCirclePaintColor = Color.parseColor("#ffffff"); //内圆的paint的颜色
- 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; _i < mAngles.size(); _i++) { //计算需要画几段圆弧
- 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; _i < _temp; _i++) {
- mPaints.add(mNoAssetsPaint);
- }
- }
- for (int _i = 0; _i < level; _i++) {
- 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; _i < colors.size(); _i++) {
- 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; _i < colors.length; _i++) {
- _colors.add(Color.parseColor(colors[_i]));
- }
- initPaint(_colors);
- }
- public void initPaint(int...colorIds) {
- ArrayList < Integer > _colors = new ArrayList < Integer > ();
- for (int _i = 0; _i < colorIds.length; _i++) {
- _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; _i < angles.length; _i++) {
- _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; _i < mAngles.size(); _i++) {
- _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; _i < data.length; _i++) {
- _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; _i < data.length; _i++) {
- _dbData[_i] = data[_i].divide(_total, 10, ROUND_HALF_UP).multiply(BigDecimal.valueOf(360));
- }
- int[] _intData = new int[data.length];
- for (int _i = 0; _i < data.length; _i++) {
- //数值小于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; _i < _intData.length; _i++) {
- _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; _i < data.length; _i++) {
- _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; _i < data.length; _i++) {
- _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"。
来源: https://juejin.im/post/5a2f80386fb9a044ff316e0f