(点击
上方公众号
,可快速关注)
来源:伯乐在线专栏作者 - Code4Android
链接:http://android.jobbole.com/84776/
点击 → 了解如何加入专栏作者
每次听到某大牛谈论自定义View,顿时敬佩之心,如滔滔江水连绵不绝,心想我什么时候能有如此境界,好了,心动不如行动,于是我开始了自定义View之路,虽然过程有坎坷,但是结果我还是挺满意的。我知道大牛还遥不可及,但是我已使出洪荒之力。此篇博客记录本人初入自定义View之路。
既然是初出茅庐,自然是按部就班的进行,先来一张效果图
本文章所写项目代码的GitHub链接
https://github.com/xiehui999/CustomBall
自定义属性
自定义属性,就是在资源文件夹下values目录中创建一个attrs.xml文件,
文件结构如下所示,atrr标签就是我们要自定义的一些属性,name就是自定义属性的名字,那么format是做什么的呢?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
declare
-
styleable
name
=
""
>
attr
name
=
"centerText"
format
=
""
>
attr
>
attr
name
=
" "
>
enum
name
=
""
value
=
" "
>
enum
>
enum
name
=
""
value
=
" "
>
enum
>
attr
>
declare
-
styleable
>
resources
>
format是属性对应的值的类型,有十个值
-
enm 枚举类型,例 android:orientation=”vertical” 此值有horizontal,和 vertical
-
dimension 尺寸值
-
color 颜色值,例 android:textColor = “#00FF00”
-
boolean 布尔值,true or false
-
flag 位或运算
-
float 浮点型
-
fraction 百分数,
-
reference 参考某一资源ID,例 android:background = “@drawable/ic_launcher”
-
string 字符串类型
-
integer 整型值
知道了这些值得含义,就可以自定义我们自己的属性了,对于这个进度条,我们可以自定义圆的半径,颜色,和圆中心文本的大小,颜色,文本,最后attrs.xml文件为
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
declare
-
styleable
name
=
"CustomBallView"
>
attr
name
=
"centerText"
format
=
"string"
>
attr
>
attr
name
=
"centerTextSize"
format
=
"dimension"
>
attr
>
attr
name
=
"centerTextColor"
format
=
"color"
>
attr
>
attr
name
=
"ballColor"
format
=
"color"
>
attr
>
attr
name
=
"ballRadius"
format
=
"dimension"
>
attr
>
declare
-
styleable
>
resources
>
布局文件配置相关内容
在布局文件要配置我们自定义的属性,首先要自定义命名空间,
如上图,如果在as中命名空间写成http://schemas.android.com/apk/res/包名 此时as会报错,这是gradle造成的,在eclipse中如果自定义的属性 是不能用res-auto的 必须得替换成你自定义view所属的包名,如果你在恰好使用的自定义属性被做成了lib 那就只能使用res-auto了,而在android-studio里,无论你是自己写自定义view 还是引用的lib里的自定义的view 都只能使用res-auto这个写法。以前那个包名的写法 在android-studio里是被废弃无法使用的
所以配置后的布局文件如下
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
LinearLayout
xmlns
:
android
=
"http://schemas.android.com/apk/res/android"
xmlns
:
customBallView
=
"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
=
"com.example.xh.customball.MainActivity"
tools
:
showIn
=
"@layout/activity_main"
>
TextView
android
:
id
=
"@+id/textView"
android
:
layout_width
=
"wrap_content"
android
:
layout_height
=
"wrap_content"
android
:
visibility
=
"gone"
android
:
text
=
"Hello World!"
/>
com
.
example
.
xh
.
customball
.
CustomBall
android
:
background
=
"@color/colorPrimary"
android
:
layout_centerInParent
=
"true"
android
:
layout_margin
=
"10dp"
customBallView
:
centerText
=
"30%"
customBallView
:
centerTextSize
=
"28dp"
customBallView
:
centerTextColor
=
"#000000"
customBallView
:
ballColor
=
"@color/colorAccent"
customBallView
:
ballRadius
=
"30dp"
android
:
layout_width
=
"260dp"
android
:
layout_height
=
"260dp"
>
com
.
example
.
xh
.
customball
.
CustomBall
>
LinearLayout
>
自定义控件
有了上边的操作,接下来就开始到了真正自定义控件的时候了,创建一个CustomBall类继承View类,先看构造方法,我们写成构造方法最终调用三个参数的构造方法,获取自定义属性的值及初始化工作就在三个参数构造方法中进行。下面我先来绘制一个圆,文字画在圆心试试手,效果如图
当然绘制这个图形,首先获取我们自定义属性值,可通过下面获取属性值
注意通过TypedArray 获取属性值后要执行typedArray.recycle();回收内存,防止内存泄漏。
/**
* 获取自定义属性
*/
TypedArray
typedArray
=
context
.
obtainStyledAttributes
(
attrs
,
R
.
styleable
.
customBallView
);
centerText
=
typedArray
.
getString
(
R
.
styleable
.
customBallView_centerText
);
Log
.
e
(
"TAG"
,
"centerText"
+
centerText
);
centerTextSize
=
typedArray
.
getDimension
(
R
.
styleable
.
customBallView_centerTextSize
,
24f
);
centerTextColor
=
typedArray
.
getColor
(
R
.
styleable
.
customBallView_centerTextColor
,
0xFFFFFF
);
ballColor
=
typedArray
.
getColor
(
R
.
styleable
.
customBallView_ballColor
,
0xFF4081
);
radius
=
typedArray
.
getDimension
(
R
.
styleable
.
customBallView_ballRadius
,
260f
);
typedArray
.
recycle
();
初始化画笔
/**
* 初始化画笔
*/
private
void
initPaint
()
{
roundPaint
=
new
Paint
();
roundPaint
.
setColor
(
ballColor
);
roundPaint
.
setAntiAlias
(
true
);
//抗锯齿
fontPaint
=
new
Paint
();
fontPaint
.
setTextSize
(
centerTextSize
);
fontPaint
.
setColor
(
centerTextColor
);
fontPaint
.
setAntiAlias
(
true
);
fontPaint
.
setFakeBoldText
(
true
);
//粗体
}
接下来我们先画一个圆,先通过下面方法获取空间本身的宽和高,然后调用canvas.drawCircle(width/2, height/2, radius, roundPaint);画圆,在原点设置为控件中心位置,即点(width/2, height/2),半径为radius,画笔roundPaint,接下来绘制文字,将位子绘制在圆的中心。
width
=
getWidth
()
;
height
=
getHeight
();
如果我们通过canvas.drawText(centerText, width/2, height/2, fontPaint);绘制文字的话,发现文字并不是在中心位置,那么我们可以做一下调整,canvas.drawText(centerText, width/2, height/2, fontPaint);先通过float textWidth = fontPaint.measureText(centerText);获取文字的宽度,canvas.drawText(centerText, width/2-textWidth /2, height/2, fontPaint);此时文字依然不在中心,那么此时我们研究一下文字到底是怎么绘制的,为什么坐标试试中心了,绘制出来的效果依然有偏差呢。
要关注文字绘制的话,FontMetrics这个类是必须要知道的因为它的作用是测量文字,它里面呢就定义了top,ascent,descent,bottom,leading五个成员变量其他什么也没有。先看源码
public
static
class
FontMetrics
{
/**
* The maximum distance above the baseline for the tallest glyph in
* the font at a given text size.
*/
public
float
top
;
/**
* The recommended distance above the baseline for singled spaced text.
*/
public
float
ascent
;
/**
* The recommended distance below the baseline for singled spaced text.
*/
public
float
descent
;
/**
* The maximum distance below the baseline for the lowest glyph in
* the font at a given text size.
*/
public
float
bottom
;
/**
* The recommended additional space to add between lines of text.
*/
public
float
leading
;
}
这个类是Paint的静态内部类,通过注释我们就知道了每个变量的含义,为了更生动的理解这几个变量含义,我们通过下面的一张图来分别解释每个变量的含义
-
Baseline(基线) 在Android中,文字的绘制都是从Baseline处开始的
-
ascent(上坡度)Baseline往上至文字“最高处”的距离我们称之为ascent,
-
descent(下坡度)Baseline往下至文字“最低处”的距离我们称之为descent(下坡度)
-
leading(行间距)表示上一行文字的descent到该行文字的ascent之间的距离
-
top 对于ascent上面还有一部分内边距,内边距加上ascent即为top值
-
bottom descent和内边距的加上descent距离值得注意的一点,Baseline上方的值为负,下方的值为正如下图文字30%的ascent,descent,top,bottom。
通过上面的分析,我们就得出了将文本绘制中心的代码如下
//测量文字的宽度
float
textWidth
=
fontPaint
.
measureText
(
centerText
);
float
x
=
width
/
2
-
textWidth
/
2
;
Paint
.
FontMetrics
fontMetrics
=
fontPaint
.
getFontMetrics
();
float
dy
= -
(
fontMetrics
.
descent
+
fontMetrics
.
ascent
)
/
2
;
float
y
=
height
/
2
+
dy
;
canvas
.
drawText
(
centerText
,
x
,
y
,
fontPaint
);
至此这个简单自定义的View基本实现,此时我改了布局配置文件为宽高
android
:
layout_width
=
"wrap_content"
android
:
layout_height
=
"wrap_content"
或者
android
:
layout_width
=