public
MyView
(
Context
context
)
{
super
(
context
)
;
}
public
MyView
(
Context
context
,
Attribute
AttributeSer - 自定义属性
每个控件都会有属性,既然是自定义控件怎么能少了自定义属性呢! 1.为了方便展开话题,我们先照做在 res/values/ 目录下添加一个 attrs.xml
这个文件。
attrs.xml
<?
xml
version
=
"
1.0
"
encoding
=
"
utf-8
"
?><
resources
> <!-- 这里会写什么呢!,当然是我们的自定义属性 --> <
declare
-
styleable
name
=
"
MyViewStyle
"
> <
declare
-
styleable
name
=
"
MyViewStyle
"
> <
attr
name
=
"
content
"
format
=
"
string
"
/> <
attr
name
=
"
isShow
"
format
=
"
boolean
"
/> <
attr
name
=
"
background
"
format
=
"
color
"
/> <
attr
name
=
"
select
"
> <
flag
name
=
"
s1
"
value
=
"
1
"
/> <
flag
name
=
"
s2
"
value
=
"
2
"
/> <
flag
name
=
"
s3
"
value
=
"
3
"
/> </
attr
> </
declare
-
styleable
></
resources
>
Set
attrs
)
{
super
(
context
,
attrs
)
;
}
支持的自定义属性
- reference:引用资源
- string:字符串
- Color:颜色
- boolean:布尔值
- dimension:尺寸值
- float:浮点型
- integer:整型
- fraction:百分数
- enum:枚举类型
- flag:位或运算
2.有了这些自定义属性接下来当然就是依附在我们需要的自定义控件上了,先写在 xml 代码上把。
<
com
.
xlhgo
.
viewdemo
.
MyView
app:content
=
"
内容为王
"
app:is_show
=
"
false
"
app:view_background
=
"
@color/colorAccent
"
app:select
=
"
s1
"
android:layout_width
=
"
wrap_content
"
android:layout_height
=
"
wrap_content
"
/>
3. 既然有了自定义属性与控件,接下来就是要在我们的java代码中去获取这些自定义属性了。
private
String
mContent
;
private
Boolean
mIsShow
;
private
int
mBackground
;
private
int
mSelect
;
public
MyView
(
Context
context
,
AttributeSet
attrs
)
{
super
(
context
,
attrs
)
;
TypedArray
typedArray
=
context
.
obtainStyledAttributes
(
attrs
,
R
.
styleable
.
MyViewStyle
)
;
if
(
typedArray
!=
null
)
{
mContent
=
typedArray
.
getString
(
R
.
styleable
.
MyViewStyle_content
)
;
mIsShow
=
typedArray
.
getBoolean
(
R
.
styleable
.
MyViewStyle_is_show
,
true
)
;
mBackground
=
typedArray
.
getColor
(
R
.
styleable
.
MyViewStyle_view_background
,
Color
.
RED
)
;
mSelect
=
typedArray
.
getInt
(
R
.
styleable
.
MyViewStyle_select
,
0
)
;
}
Log
.
d
(
"
viewStyle
"
,
"
content:
"
+
mContent
)
;
Log
.
d
(
"
viewStyle
"
,
"
isShow:
"
+
mIsShow
)
;
Log
.
d
(
"
viewStyle
"
,
"
background:
"
+
mBackground
)
;
Log
.
d
(
"
viewStyle
"
,
"
select:
"
+
mSelect
)
;
}
自定义控件 - View - onMeasure
既然是一个控件,啥都别说,肯定要先有他的宽高,但是我们明明已经在xml中定义了控件的 layout_widght 与 layout_height
,为什么还要特地重写自定义控件的。至于为什么请看下面。
几个概念
Android 系统给我们提供了一个 MeasureSpec类,通过它来帮助我们测量 View,MeasureSpec 是一个 32 的 int 值,其中高 2 为为测量模式,低30位为测量的大小,这难道是二进制!是的,但是大可放心,google 还是挺为广大数学渣着想的,通过 MeasureSpec 的方法可以获取我们需要的数值。
再来说说测量模式把,测试模式可以为一下三种:
- EXACTLY:精确模式 当我们将控件的 layout_width 与 layout_height 属性制定为具体数值时(排除 wrap_content),系统就使用这个模式,(同时 View 类的 onMeasure 默认是 EXACTLY 模式,所以不重写 onMeasure 的话,就只能使用默认模式)
- AT_MOST:最大值模式 当控件的 layout_width 属性或 layout_height 属性为 wrap_content 时,控件大小一般随着子控件或内容的变化而变化,此时控件的此尺寸只要不超过父控件允许的最大尺寸。
- UNSPECIFIED: 它不能其大小测量模式,View想多大就多大,通常情况在回执自定义View时才使用。
懵懂了以上的几个概念以后就开始来重写这个方法把,或许你会波不急待,因为如果你对先前对自定义 View 没有认识,也会像我一开始看了还是一脸懵逼像。
@
Override
protected
void
onMeasure
(
int
widthMeasureSpec
,
int
heightMeasureSpec
)
{
super
.
onMeasure
(
widthMeasureSpec
,
heightMeasureSpec
)
;
setMeasuredDimension
(
getSize
(
widthMeasureSpec
)
,
getSize
(
heightMeasureSpec
)
)
;
}
private
int
getSize
(
int
measureSpec
)
{
int
result
=
0
;
int
specMode
=
MeasureSpec
.
getMode
(
measureSpec
)
;
int
specSize
=
MeasureSpec
.
getSize
(
measureSpec
)
;
switch
(
specMode
)
{
case
MeasureSpec
.
EXACTLY
:
result
=
200
;
break
;
case
MeasureSpec
.
AT_MOST
:
result
=
Math
.
min
(
1
00
,
specSize
)
;
break
;
case
MeasureSpec
.
UNSPECIFIED
:
result
=
400
;
break
;
}
return
result
;
}
就是这,大功搞成了。 相应大家应该对 View 的 onMeasure 不陌生了,但是故事还没结束,我们再来说说 layout 方法把。
自定义控件 - View - layout
layout,顾名思义,就是定义这个控件所处的布局,其实即便我们通过 onMeasure 决定了这个控件的宽高,但是并不是所有的宽高都是能显示出来的,我们还需要通过 layout 给这个控件分配可以使用的控件。
因为 layout 不难,理解即可,所以毫不客气的直接上代码了。
@
Override
public
void
layout
(
int
l
,
int
t
,
int
r
,
int
b
)
{
super
.
layout
(
l
,
t
,
r
,
b
)
;
Log
.
d
(
"
layout:
"
,
"
l:
"
+
l
)
;
Log
.
d
(
"
layout:
"
,
"
t:
"
+
t
)
;
Log
.
d
(
"
layout:
"
,
"
r:
"
+
r
)
;
Log
.
d
(
"
layout:
"
,
"
b:
"
+
b
)
;
}
当前控件的宽高为200px 输出结果为: l:48 t:48 r:248 b:248
聪明的同学想必已经看出来了这四个参数的意思,不懂的我就补个图吧。
大致就是这样,自己去体会我画图的意境把...(我用触摸板画图容易吗!)
理解了 layout 使用之后,个人认为 layout 在自定义 View 中使用的并不是非常多。但我还是要多提两句,如果我们修改 super.layout(l,t,r±100,b±100)
之后,控件也会更具所分配的位置进行改变,或者增加,但是不会再去影响其他控件的位置,这点与onMeasure是有很大的不同。 demo:我们在MyView这个控件下添加一个控件,看看他的位置是如何显示的:
c
大致就 layout 的使用说完了,接下来就说说自定以中最重要的一个方法把!
自定义控件 - View - onDraw
该准备的准备了,不该说的也说了,接下来就就是要重写的我们的onDraw方法,既然是自定义View,无非就是显得'花销'一点,定制性高点,那么onDraw方法非调用不可。
View的绘制离不开 Canvas,所以在重写 onDraw 方法的时候,系统传递给我们一个 Canvas 对象,剩下的就是配合一个自己创建的 Paint 去绘制我们想要的界面。话不说多,上代码 duang!
@
Override
protected
void
onDraw
(
Canvas
canvas
)
{
super
.
onDraw
(
canvas
)
;
Paint
paint
=
new
Paint
(
)
;
canvas
.
drawCircle
(
50
,
50
,
50
,
paint
)
;
}
就这样,我们简单的绘制了一个圆形,是不是你感觉自定义View这篇文章到此结束了,错!我们来做个小实验。我们在 super.onDraw(canvas); 下面添加一个 log Log.d("myView","onDraw");,接着给这个控件添加一个事件,来看看会是怎么样。
findViewById
(
R
.
id
.
myview
)
.
setOnClickListener
(
new
View
.
OnClickListener
(
)
{
@
Override
public
void
onClick
(
View
view
)
{
view
.
invalidate
(
)
;
}
}
)
;
invalidate 方法是刷新 View,当我们点击这个控件的时候,这个onDraw 会被反复的调用(除了点解,这个方法经常被调用),同时也意味着,其实我们 onDraw 代码中的 Paint 会被多次创建,嘿嘿,是不是明白什么了,接下来我们最后说说关于自定义View优化。
注:关于 Canvas,Paint 等绘制的 API 使用可以参考小猪前辈写的文章,毕竟这篇博客是延续小猪的博客来写的,所以就不重复造轮子了。
自定义控件 - View - 优化之路
不管是任何开发,优化之路任重道远,其实关于自定义控件的优化方式挺多,在这里博客可能说的不是非常详细。如果有更多的建议欢迎提出。
优化方式1 基于上述我们发现的问题,onDraw 方法会被方法调用,导致 Paint 被反复实例化,所以我们不能在这个方法去new对象,最好的解决方法,我们可以直接在构造方法中是实例化,定义为成员变量,就是辣么简单。
优化方式2 。。。。抱歉,臣妾无能为力,实现想不出来了,以后要是有想法马上补充。