关于自定义View,我们前面已经有三篇文章在介绍了,如果筒子们还没阅读,建议先看一下,分别是、、。这三篇文章中所述的自定义View都还是比较简单的,我们只使用了其中的绘图API,那么今天我们来看看自定义ProgressBar,在这个过程中,我们顺便来看看自定义View中两个非常关键的方法,一个是View的测量,还有一个是自定义属性。OK,废话不多说,先来看一张效果图:
OK,动手吧。
1.准备工作
写一个类继承自View,先来声明变量,看看我们需要哪些变量:
- /**
- * View默认的宽
- */
- private static final int DEFAULTWIDTH = 100;
- /**
- * View默认的高度
- */
- private static final int DEFAULTHEIGHT = 100;
- /**
- * 外层圆圈的线条宽度
- */
- private int stoke = 7;
- /**
- * 外层圆圈的线条颜色
- */
- private int circleColor = Color.BLACK;
- /**
- * 内外圆圈之间的间距
- */
- private int padding = 20;
- /**
- * 内层实体圆的颜色
- */
- private int sweepColor = Color.RED;
- /**
- * 开始绘制的角度
- */
- private int startAngle = -90;
- /**
- * 已经绘制的角度
- */
- private int sweepAngle = 0;
- /**
- * 每次增长的度数
- */
- private int sweepStep = 1;
- /**
- * 画笔
- */
- private Paint paint;
- /**
- * 绘制扇形需要的矩形
- */
- private RectF rectF;
- public MyProgressBar(Context context) {
- this(context, null);
- }
- public MyProgressBar(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
- public MyProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- paint = new Paint();
- paint.setAntiAlias(true);
- }
2.View绘制
接下来我们来看看View的绘制,大家看上面的效果图就知道,我们这里的绘制一共有两部分,一部分是外部圆环的绘制,还有一部分是内部扇形的绘制,那我们一步一步来:
1.绘制圆环
圆环的绘制很简单,直接看代码:
- //设置圆环的颜色
- paint.setColor(circleColor);
- //设置圆环的宽度
- paint.setStrokeWidth(stoke);
- //设置绘制模式为描边
- paint.setStyle(Paint.Style.STROKE);
- canvas.drawCircle(getWidth() / 2, getHeight() / 2, (float) (getWidth() / 2 - Math.ceil(stoke / 2.0)), paint);
2.绘制扇形
扇形的绘制需要我们先构造一个RectF类(当然这个并不是必须的操作),然后就可以开始绘制了,如下:
- //设置扇形的颜色
- paint.setColor(sweepColor);
- //设置扇形的绘制风格
- paint.setStyle(Paint.Style.FILL);
- //构造一个RectF 出来,扇形绘制在该RectF中
- rectF = new RectF(padding, padding, getWidth() - padding, getHeight() - padding);
- //绘制扇形
- //四个参数分别是扇形所在的矩形,开始绘制的角度,需要绘制的角度,扇形是否和矩形共用一个中心点,画笔
- canvas.drawArc(rectF, startAngle, sweepAngle, true, paint);
- //增加要绘制的角度
- sweepAngle += sweepStep;
- //如果要绘制的角度大于360度,就从0重新开始绘制
- sweepAngle = sweepAngle > 360 ? 0 : sweepAngle;
- invalidate();
OK,做好上面这几步之后,我的一个自定义ProgressBar基本上就显示出来了。这个时候我只需要在布局文件中添加上这个自定义控件即可,如下:
- <lenve.myprogressbar.MyProgressBar
- android:layout_width="128dp"
- android:layout_height="128dp"/>
3.View测量
当我们自定义一个View的时候,除了重写onDraw方法之外,还有一个方法有时候也需要我们重写,那就是onMeasure,onMeasure方法接收两个参数,分别是widthMeasureSpec和heightMeasureSpec,这两个参数我们称作测量规格,它们是一个32位的整型数据,这个数据中高2位表示View的测量模式,低30位表示View的测量值,测量模式分为3种,分别是:
1.EXACTLY:精确模式,对应我们在布局文件中设置宽高时给一个具体值或者match_parent
2.AT_MOST:最大值模式:对应设置宽高时给一个wrap_content
3.UNSPECIFIED:这种测量模式多用在ScrollView中
OK,了解了这些之后,接下来我们就来看看怎么样从widthMeasureSpec和heightMeasureSpec中提取出来宽高对应的测量模式与测量值。在MeasureSpec类中提供了两个静态方法,分别是getMode和getSize,只要我们将宽高的测量规格传递进去就可以获取它的测量模式和测量值。如下:
- //获取宽的测量模式
- int widthMode = MeasureSpec.getMode(widthMeasureSpec);
- //获取宽的测量值
- int widthSize = MeasureSpec.getSize(widthMeasureSpec);
- //获取高的测量模式
- int heightMode = MeasureSpec.getMode(heightMeasureSpec);
- //获取高的测量值
- int heightSize = MeasureSpec.getSize(heightMeasureSpec);
- switch (widthMode) {
- case MeasureSpec.EXACTLY:
- break;
- case MeasureSpec.AT_MOST:
- case MeasureSpec.UNSPECIFIED:
- //如果宽为wrap_content,则给定一个默认值
- widthSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULTWIDTH, getResources().getDisplayMetrics());
- break;
- }
- switch (heightMode) {
- case MeasureSpec.EXACTLY:
- break;
- case MeasureSpec.AT_MOST:
- case MeasureSpec.UNSPECIFIED:
- heightSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULTHEIGHT, getResources().getDisplayMetrics());
- break;
- }
- widthSize = heightSize = Math.min(widthSize, heightSize);
- setMeasuredDimension(widthSize, heightSize);
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- //获取宽的测量模式
- int widthMode = MeasureSpec.getMode(widthMeasureSpec);
- //获取宽的测量值
- int widthSize = MeasureSpec.getSize(widthMeasureSpec);
- //获取高的测量模式
- int heightMode = MeasureSpec.getMode(heightMeasureSpec);
- //获取高的测量值
- int heightSize = MeasureSpec.getSize(heightMeasureSpec);
- switch (widthMode) {
- case MeasureSpec.EXACTLY:
- break;
- case MeasureSpec.AT_MOST:
- case MeasureSpec.UNSPECIFIED:
- //如果宽为wrap_content,则给定一个默认值
- widthSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULTWIDTH, getResources().getDisplayMetrics());
- break;
- }
- switch (heightMode) {
- case MeasureSpec.EXACTLY:
- break;
- case MeasureSpec.AT_MOST:
- case MeasureSpec.UNSPECIFIED:
- heightSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULTHEIGHT, getResources().getDisplayMetrics());
- break;
- }
- widthSize = heightSize = Math.min(widthSize, heightSize);
- //设置测量结果
- setMeasuredDimension(widthSize, heightSize);
- }
4.自定义属性
做完上面这几步,我的自定义ProgressBar已经完成的差不多了,现在我如果想要修改圆环的颜色,圆环的线条的宽度,扇形的颜色等等这些属性的话只能在代码中修改,可是如果我想要在布局文件中来配置这些颜色,然后在代码中读取这些颜色再设置给paint又该怎么办呢?这里就涉及到我们的自定义属性了。OK,那么接下来我们就来看看自定义属性。
自定义属性需要我们首先在res/values文件夹中添加attrs文件(该文件名可以任意取,约定俗成取attrs),在attrs文件中来生命你要设置的属性,如下:
- <?xml version="1.0" encoding="utf-8"?>
- <resources>
- <declare-styleable name="MyProgressBar">
- <attr name="circleColor" format="color|reference"/>
- <attr name="sweepColor" format="color|reference"/>
- <attr name="stroke" format="dimension|reference"/>
- <attr name="sweepStep" format="integer|reference"/>
- <attr name="startAngle" format="integer|reference"/>
- <attr name="mypb_padding" format="dimension|reference"/>
- </declare-styleable>
- </resources>
1. boolean 属性取值为boolean类型 2. string 属性取值为文本类型 3. color 属性取值为颜色类型 4. dimension 属性值为尺寸 5. enum 属性取值是枚举类型,例如:LinearLayout中的:orientation="horizontal"属性 6. flag 属性取值进行或运算,比如android:layout_gravity="left|bottom" 7. fraction 属性取值为小数 8. float 属性取值为浮点数 9. integer 属性取值为整数 10. reference 属性取值可以引用一个值
OK,这一部分的工作完成之后,接下来我们就可以在布局文件中设置属性了,如下:
- <lenve.myprogressbar.MyProgressBar
- app:circleColor="#10ff03"
- app:mypb_padding="30dp"
- app:startAngle="0"
- app:stroke="10dp"
- app:sweepColor="#0184ff"
- app:sweepStep="2"
- android:layout_width="128dp"
- android:layout_height="128dp"/>
OK,但是光这样肯定不行,我只是在布局文件中设置了,代码里又该怎么样来获取布局文件中设置的值呢?修改构造方法如下:
- public MyProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- paint = new Paint();
- paint.setAntiAlias(true);
- //读取布局文件中设置的属性
- TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyProgressBar);
- //读取布局文件中定义的颜色值,第二个参数为默认值(如果布局文件中未设置该属性时使用)
- circleColor = ta.getColor(R.styleable.MyProgressBar_circleColor, this.circleColor);
- sweepColor = ta.getColor(R.styleable.MyProgressBar_sweepColor, this.sweepColor);
- startAngle = ta.getInt(R.styleable.MyProgressBar_startAngle, this.startAngle);
- sweepStep = ta.getInt(R.styleable.MyProgressBar_sweepStep, this.sweepStep);
- stroke = (int) ta.getDimension(R.styleable.MyProgressBar_stroke, stroke);
- padding = (int) ta.getDimension(R.styleable.MyProgressBar_mypb_padding, padding);
- //回收ta
- ta.recycle();
- }
OK,就是这么简单。
以上。