Android自定义View之画圆环(手把手教你如何一步步画圆环)

关于自定义View:

好了,吐槽时间到.自定义view是Android开发知识体系中的重点,也是难点.好多小伙伴(也包括我)之前对自定义view也是似懂非懂.那种感觉老难受了.因此作为社会主义好青年,怎么能够不加钻研呢?那可不是你我的风格哦.因此,我将通过几篇博文来展示自定义view的基本流程.另外,我说一下,关于自定义view的学习呢,我们还是得动手敲代码,实践出真知!因此,我强烈建议同志们先从最基本,最简单的自定义view画起,这样在自定义view的过程中,才能了解其原理,从而为以后画出各种各样炫酷的view,打下基础. OK,本片博文将通过自定义圆环,带你去探索自定义view的奥妙…

俗话说无规矩不成方圆,无图全是扯淡!

实现的效果图:

这里写图片描述

自定义圆环流程:

这里写图片描述

上图就是自定义圆环的指导图(非常重要).下面就根据这个神图,来向大家介绍下如何自定义圆环?
 
Step 1:画里面的白色小圆
事实上,里面的白色圆圈如果不画的话,我们也可以画圆环,但是,但是,但是,重要的事情说三遍.强烈建议不要遗漏.因为在画圆时候,我们能对半径等参数有更加清晰,便于后续圆环的绘制.再说了,画个圆又咋了,无非几行代码的事.

Step 2:画矩形(正方形)
这个正方形是画圆环的最核心的步骤.如果想要画出图中绿色的圆环,这个正方形是必须的.注意看正方形是红边圆圈的外接圆.而这个所谓的红色圆圈正好处在圆环中间位置.图中一目了然,不在瞎啰嗦了.

Step 3:画圆环
其实到了这一步,我们的圆环就已经出来了.此处我们可以做的是圆环的属性的调节,比如,你可以调节圆环的颜色啥的
###关于自定义view的基本知识储备
如果你对自定义view的基本流程已经有了一个基本的认知的话,可以跳过次步骤.如果你不是很熟悉的话,推荐你先阅读下Android自定义View的官方套路 ,里面介绍的还可以.
 

自定义圆环步骤:

Step 1: 继承View

对Android有一些了解的朋友都知道,android为我们提供的很多View都是继承与View的。所以我们自定义的View当然也是继承于View,当然如果你要自定义的View拥有某些android已经提供的控件的功能,你可以直接继承于已经提供的控件。

public class SuperCircleView extends View {
      public SuperCircleView(Context context) {
        this(context, null);
    }
    public SuperCircleView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
        public SuperCircleView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SuperCircleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
      .....
      .....
    }
}
Step 2:定义自定义属性

大部分情况我们的自定义View需要有更多的灵活性,比如我们在xml中指定了颜色大小等属性,在程序运行时候控件就能展示出相应的颜色和大小。所以我们需要自定义属性自定义属性通常写在资源文件res/values/attrs.xml文件中.

贴出attr_super_circle.xml属性文件

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="SuperCircleView">

        <!-- 圆的半径 -->
        <attr name="min_circle_radio" format="integer"/>

        <!-- 圆环的宽度 -->
        <attr name="ring_width" format="float"/>

        <!-- 内圆的颜色 -->
        <attr name="circle_color" format="color"/>

        <!-- 外圆的颜色 -->
        <attr name="max_circle_color" format="color"/>

        <!-- 圆环的默认颜色 -->
        <attr name="ring_normal_color" format="color"/>

        <!-- 圆环要显示的彩色的区域(随着数值的改变,显示不同大小的彩色区域)-->
        <attr name="ring_color_select" format="integer"/>

        <!-- 绘制内容的数值 -->
        <attr name="maxValue" format="integer" />
        <attr name="value" format="integer" />

    </declare-styleable>

</resources>
Step 3:在xml中引用自定义的圆环控件

其实这一步,正常情况下是应该放在自定义圆环完成之后.但是为了演示Step4中获取自定义属性的逻辑,暂时放在这一步了.由于通常我们需要拿到属性做一些事情,因此就需要在xml中设置了控件自定义属性。否则定义自定义属性就没有意义了。
直接上布局文件activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <FrameLayout
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:layout_gravity="center">

        <com.example.zq.drawcircledemo.SuperCircleView
            android:id="@+id/superview"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            app:maxValue="100"
            app:value="20"
            app:ring_width="60" />


        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginBottom="60dp"
            android:text="信息完成度"
            android:textColor="#CFD5DE"
            android:textSize="18sp" />


        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginTop="10dp"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/tv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="0"
                android:textColor="#506946"
                android:textSize="80sp" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="%"
                android:textSize="28sp" />
        </LinearLayout>
    </FrameLayout>


</LinearLayout>

布局文件中的

   app:maxValue="100"
   app:ring_width="60" 

设置的属性就是我们在attr.xml中定义的.至于如何获取这些自定义属性的数值,后面会展示.

Step 4:自定义圆环初始化操作

有三个构造方法(一个参数、两个参数、三个参数),其中两个参数的构造方法必须有。

在 public SuperCircleView(Context context, AttributeSet attrs, int defStyleAttr){…}中进行初始化的操作.基本是一些获取自定义属性的操作

public class SuperCircleView extends View {
    private final String TAG = "SuperCircleView";

    private ValueAnimator valueAnimator;
    private int mViewCenterX;   //view宽的中心点(可以暂时理解为圆心)
    private int mViewCenterY;   //view高的中心点(可以暂时理解为圆心)

    private int mMinRadio; //最里面白色圆的半径
    private float mRingWidth; //圆环的宽度
    private int mMinCircleColor;    //最里面圆的颜色
    private int mRingNormalColor;    //默认圆环的颜色
    private Paint mPaint;
    private int color[] = new int[3];   //渐变颜色

    private RectF mRectF; //圆环的矩形区域
    private int mSelectRing = 0; //要显示的彩色区域(岁数值变化)
    private int mMaxValue;

    public SuperCircleView(Context context) {
        this(context, null);
    }

    public SuperCircleView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SuperCircleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SuperCircleView);
        //最里面白色圆的半径
        mMinRadio = a.getInteger(R.styleable.SuperCircleView_min_circle_radio, 300);
        //圆环宽度
        mRingWidth = a.getFloat(R.styleable.SuperCircleView_ring_width, 40);

        //最里面的圆的颜色(绿色)
        mMinCircleColor = a.getColor(R.styleable.SuperCircleView_circle_color, context.getResources().getColor(R.color.green));
        //圆环的默认颜色(圆环占据的是里面的圆的空间)
        mRingNormalColor = a.getColor(R.styleable.SuperCircleView_ring_normal_color, context.getResources().getColor(R.color.gray));
        //圆环要显示的彩色的区域
        mSelectRing = a.getInt(R.styleable.SuperCircleView_ring_color_select, 0);
    
        mMaxValue = a.getInt(R.styleable.SuperCircleView_maxValue, 100);

        a.recycle();

        //抗锯齿画笔
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        //防止边缘锯齿
        mPaint.setAntiAlias(true);
        //需要重写onDraw就得调用此
        this.setWillNotDraw(false);

        //圆环渐变的颜色
        color[0] = Color.parseColor("#FFD300");
        color[1] = Color.parseColor("#FF0084");
        color[2] = Color.parseColor("#16FF00");
    }
}
Step 5:画圆环

首先说一些,自定义view的话一般会重写一下三个方法:
onDraw(): 是用来绘制View图像,这个方法必须有.
onMeasure(): 用于改变View 的大小。
onLayout(): 用于改变View在父控件中的位置

Ok,继续.
(1).确定待画里面圆形以及矩形的位置

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        //view的宽和高,相对于父布局(用于确定圆心)
        int viewWidth = getMeasuredWidth();
        int viewHeight = getMeasuredHeight();
        mViewCenterX = viewWidth / 2;
        mViewCenterY = viewHeight / 2;
        //画矩形
        mRectF = new RectF(mViewCenterX - mMinRadio - mRingWidth / 2, mViewCenterY - mMinRadio - mRingWidth / 2, mViewCenterX + mMinRadio + mRingWidth / 2, mViewCenterY + mMinRadio + mRingWidth / 2);
    }

(2).绘制圆环


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPaint.setColor(mMinCircleColor);
        canvas.drawCircle(mViewCenterX, mViewCenterY, mMinRadio, mPaint);
        //画默认圆环
        drawNormalRing(canvas);
        //画彩色圆环
        drawColorRing(canvas);
    }
    
 /**
     * 画默认圆环
     *
     * @param canvas
     */
    private void drawNormalRing(Canvas canvas) {
        Paint ringNormalPaint = new Paint(mPaint);
        ringNormalPaint.setStyle(Paint.Style.STROKE);
        ringNormalPaint.setStrokeWidth(mRingWidth);
        ringNormalPaint.setColor(mRingNormalColor);//圆环默认颜色为灰色
        canvas.drawArc(mRectF, 360, 360, false, ringNormalPaint);
    }

    /**
     * 画彩色圆环
     *
     * @param canvas
     */
    private void drawColorRing(Canvas canvas) {
        Paint ringColorPaint = new Paint(mPaint);
        ringColorPaint.setStyle(Paint.Style.STROKE);
        ringColorPaint.setStrokeWidth(mRingWidth);
        ringColorPaint.setShader(new SweepGradient(mViewCenterX, mViewCenterX, color, null));
        //逆时针旋转90度
        canvas.rotate(-90, mViewCenterX, mViewCenterY);
        canvas.drawArc(mRectF, 360, mSelectRing, false, ringColorPaint);
        ringColorPaint.setShader(null);
    }
  

ok,代码中的注释已经很详细了,我就不再细说了.

我只想说一下,代码中在绘制彩色圆环的时canvas.rotate(-90, mViewCenterX, mViewCenterY);注释中已经说了左边旋转90度.那么问题来了为什么要逆时针旋转90度呢?
这个是因为在后面画渐变色的圆弧时,drawArc和SweepGradient这两个类的起始点0度不是在我们习惯的圆环最上面那个点,而是从圆环最右边那个点开始,所以逆时针旋转90度就能让它从最上面的点开始.

盗图演示:
这里写图片描述

这下明白了吧.如果你还体会不深刻的话,就让你看一下,假如不逆时针旋转90度的话,是什么样的效果吧.
这里写图片描述

看到需要逆时针旋转90度的区别了吧.

Step 6:设置自定义view的部分监听事件

通过上述步骤我们所需的圆环基本完成了.但是,要知道,画圆环不是目的,目的是让圆环去传表达或者描述一些信息,常用的场景,比如用来显示计步器的步数,显示文件下载的完成度等等.因此我们可以适当的添加点监听事件.

 //***************************************用于更新圆环表示的数值*****************************************************
 /**
     * 设置当前值
     *
     * @param value
     */
    public void setValue(int value,TextView textView) {
        if (value > mMaxValue) {
            value = mMaxValue;
        }
        int start = 0;
        int end = value;
        startAnimator(start, end, 2000,textView);
    }

    private void startAnimator(int start, int end, long animTime, final TextView textView) {
        valueAnimator = ValueAnimator.ofInt(start, end);
        valueAnimator.setDuration(animTime);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                Log.i(TAG, "onAnimationUpdate: animation.getAnimatedValue()::"+animation.getAnimatedValue());
                int i = Integer.valueOf(String.valueOf(animation.getAnimatedValue()));
                textView.setText(i + "");
                //每个单位长度占多少度
                 mSelectRing=(int) (360 * (i / 100f));
                Log.i(TAG, "onAnimationUpdate: mSelectRing::"+mSelectRing);
                invalidate();
            }
        });
        valueAnimator.start();
    }

 

主程序 MainActivity.java:

  public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    SuperCircleView mSuperCircleView;
    TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = findViewById(R.id.tv);
        mSuperCircleView = findViewById(R.id.superview);
        mSuperCircleView.setValue(100, textView);
        mSuperCircleView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //随机设定圆环大小
                int i = new Random().nextInt(100) + 1;
                Log.i(TAG, "onClick: i::" + i);
                mSuperCircleView.setValue(i, textView);
            }
        });
    }
}

上述代码中设计属性动画ValueAnimator的相关知识,有兴趣的同学自己学习下.
好了,至此,自定义圆环结束.如果有疑问的话,请留言.诺诺的说一句,如果你感觉这篇文章写的还行的话,请给个赞.我感觉我需要被鼓励,哈哈,开玩笑的…

附上示例:
https://download.csdn.net/download/zhangqunshuai/10486568

 
参考文章:
一步步做Android自定义圆环百分比控件
Android自定义View的官方套路
Android 自定义view实现圆环

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值