关于自定义View,我们前面已经有三篇文章在介绍了,如果筒子们还没阅读,建议先看一下,分别是android自定义View之钟表诞生记android自定义View之仿通讯录侧边栏滑动,实现A-Z字母检索android自定义View之NotePad出鞘记。这三篇文章中所述的自定义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;

OK,就这么几个变量,都很简单。接下来我们再看看构造方法,在构造方法中我只需要对画笔进行简单的初始化即可,如下(请大家注意构造方法的调用方式,如有疑问请查看之前自定义View的博客):

    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);

这里有一个值得说一下的地方,drawCircle方法有四个参数,前两个是圆环的中心点的坐标,第三个是半径,本来半径是View宽度的一半即可,但是由于系统在绘制的过程中圆环线条宽度的一半算在半径中,另一半不算在半径中,所以这里的半径我们要适当缩小。

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"/>

但是我在布局文件中添加自定义控件的时候只能给它一个固定的宽和高,如果给一个wrap_content或者match_parent那么我的自定义ProgressBar就会显示不正常,这个问题该怎么解决呢?这里就涉及到了一个新的知识,那就是View的测量。

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);

拿到宽高的测量模式和测量值之后,我们就可以做一个简单的处理了,大家已经知道测量模式一共分为三种,如果用户明确指定了View的宽和高那我就不去管它,如果用户给了一个wrap_content或者使用了第三种测量模式的话,那我就给View一个默认的宽和高,OK,就这么一个简单的逻辑,我们来看看代码:

        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;
}

OK,最后,由于我的ProgressBar是绘制一个圆,因此View的宽高必须是相同的,所以再添加一行代码:

widthSize = heightSize = Math.min(widthSize, heightSize);

OK,至此,我的View的宽高都确定下来了,最后我只需要调用setMeasuredDimension方法,告诉系统我的测量结果即可,如下:

setMeasuredDimension(widthSize, heightSize);

所以,一个完整的onMeasure方法应该是下面这个样子:

    @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);
}

OK,从此以后,你就可以在布局文件中给ProgressBar设置任意的宽和高了。

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>

首先我们要声明declare-styleable,给它取一个名字,这个name可以任意取,但是强烈建议取自定义View的类名,因为只有取类名,一会你在布局文件中添加这些属性时系统才会有提示。OK,里面的attr节点就是我们定义的一个个的属性了,name表示属性的颜色,format表示属性的取值,format取值主要有如下几种:

    1.   boolean 属性取值为boolean类型
    2.   string 属性取值为文本类型
    3.   color 属性取值为颜色类型
    4.   dimension 属性值为尺寸
    5.   enum 属性取值是枚举类型,例如:LinearLayout中的android: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,我们再来看看显示效果:

OK,就是这么简单。

源码下载:http://download.csdn.net/detail/u012702547/9507728

以上。

Android自定义View之ProgressBar出场记的更多相关文章

  1. android自定义view实现progressbar的效果

    一键清理是很多Launcher都会带有的功能,其效果也比较美观.实现方式也许有很多中,其中常见的是使用图片drawable来完成的,具体可以参考这篇文章:模仿实现360桌面水晶球式的一键清理特效.本文 ...

  2. 我的Android进阶之旅------>Android自定义View实现带数字的进度条(NumberProgressBar)

    今天在Github上面看到一个来自于 daimajia所写的关于Android自定义View实现带数字的进度条(NumberProgressBar)的精彩案例,在这里分享给大家一起来学习学习!同时感谢 ...

  3. Android 自定义 view(三)—— onDraw 方法理解

    前言: 上一篇已经介绍了用自己定义的属性怎么简单定义一个view<Android 自定义view(二) -- attr 使用>,那么接下来我们继续深究自定义view,下一步将要去简单理解自 ...

  4. Android 自定义View及其在布局文件中的使用示例(三):结合Android 4.4.2_r1源码分析onMeasure过程

    转载请注明出处 http://www.cnblogs.com/crashmaker/p/3549365.html From crash_coder linguowu linguowu0622@gami ...

  5. Android 自定义View及其在布局文件中的使用示例(二)

    转载请注明出处 http://www.cnblogs.com/crashmaker/p/3530213.html From crash_coder linguowu linguowu0622@gami ...

  6. Android自定义View

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/24252901 很多的Android入门程序猿来说对于Android自定义View ...

  7. android自定义View之仿通讯录侧边栏滑动,实现A-Z字母检索

    我们的手机通讯录一般都有这样的效果,如下图: OK,这种效果大家都见得多了,基本上所有的android手机通讯录都有这样的效果.那我们今天就来看看这个效果该怎么实现. 一.概述 1.页面功能分析 整体 ...

  8. Android 自定义View (一)

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/24252901 很多的Android入门程序猿来说对于Android自定义View ...

  9. Android 自定义View(button)

    很多的Android入门程序猿来说对于Android自定义View,可能都是比较恐惧的,但是这又是高手进阶的必经之路,所有准备在自定义View上面花一些功夫,多写一些文章.先总结下自定义View的步骤 ...

随机推荐

  1. js中replace用法

    js中replace的用法 replace方法的语法是:stringObj.replace(rgExp, replaceText) 其中stringObj是字符串(string),reExp可以是正则 ...

  2. 【HDOJ】1114 Piggy-Bank

    DP,先将coins按照重量排序可以优化. #include <stdio.h> #include <stdlib.h> #define MAXNUM 10005 #defin ...

  3. hdu4323Magic Number(dp)

    http://acm.hdu.edu.cn/showproblem.php?pid=4323 去年的多校 编辑距离的变形 暴力居然过了 还想了好久别的方法,想得很头疼 #include <ios ...

  4. addChildViewController 与 addSubview

    在viewcontrollerA中, 如果想把controllerB.view添加进来, 可以用 addSubview, 但如果controllerB中有个事件, 使用到 self.navigatio ...

  5. JS中document.createElement()用法及注意事项

    今天处理了一个日期选择器的ie和ff的兼容问题,本来这种情况就很难找错误,找了好久才把错误定位到js中创建元素的方法document.createElement(),这个方法在ie下支持这样创建元素 ...

  6. [原]H264帧内预测

    帧内预测模块大小 说明 4x4(亮度) 预测方式9种 8x8(亮度) 预测方式9种.只有high profile才有 16x16(亮度) 预测方式4种,只依赖左,上数据. 8x8(色度) 预测方式4种 ...

  7. [MarsZ]程序猿谈大学之大学应该学好哪些课程

    大家好,我是MarsZ,上次给大家带来了程序猿Mars谈大学之大学应该怎么过 ,反响还不错(……),这次继续给大家分析一下大学应该学好哪些课程. 首先必须说明两点:一,以下我说的仅代表个人观点.二,仅 ...

  8. poj2192(搜索)

    这个题目对于两个字符串A,B是否可以通过规则生成C. import java.util.Scanner; public class Main { public static void main(Str ...

  9. HW2.22

    import java.util.Scanner; public class Solution { public static void main(String[] args) { Scanner i ...

  10. Java 常见异常及趣味解释

    java.lang ArithmeticException 你正在试图使用电脑解决一个自己解决不了的数学问题,请重新阅读你的算术表达式并再次尝试. ArrayIndexOutOfBoundsExcep ...