说到自定义 View ,就一定得说说 android 系统的UI绘制流程。再说这个流程之前,我们先看一下在每一个 activity 页面中我们的布局 ui 所处的位置。
从上图就可以知道,我们平时使用的 setContentView()
这个方法就是用来设置 contentview
的。了解了,这个之后,我们还应该了解一下 android 中 view 的继承关系。
从上面的一张图中,我们可以看出 android 的 UI 控件主要有两种:view 和 viewgroup。那么像我们经常使用的 Button
,TextView
,ImageView
都属于 view 的范畴!FrameLayout
,LinearLayout
等都属于 viewgroup 的范畴!了解了这些基本知识之后,我们再来说说如果自定义控件!
我们要自定义控件,无非就是继承 view 或者 viewgroup 亦或者继承已有的控件在上面在进行扩展!在这里继承 view 和 继承 viewgroup 有点区别!我们先来说说相同点吧!
这个其实就是构造函数啦,在这里你可以为这个 view 设置特定的属性啊!那么如何自定义属性呢?首先你得在 res
-->
values
这个目录下新建 attrs
的资源文件!在这个文件中配置你要的自定义属性!先看一下代码
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="WeekSelectView">
<attr name="selectColor" format="color"></attr>
<attr name="unSelectColor" format="color"></attr>
<attr name="lineColor" format="color"></attr>
<attr name="textColor" format="color"></attr>
<attr name="textSize" format="dimension"></attr>
<attr name="selectSize" format="dimension"></attr>
<attr name="lineWidth" format="dimension"></attr>
<attr name="lineHeight" format="dimension"></attr>
<attr name="space" format="dimension"></attr>
</declare-styleable>
</resources>
其中的 declare-styleable
标签就是添加自定义属性用的,里面包含的每一个 attr
就代表每一个自定义属性!后天面的 format
属性代表每一个属性的类型!接着就是我该如何使用它们呢!我们只要在布局文件中使用代码就行了:
<com.kidbot.library.widget.weekselect.WeekSelectView
xmlns:weekselect="http://schemas.android.com/apk/res-auto"
android:id="@+id/weekselect"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
weekselect:lineHeight="10dp"
weekselect:lineWidth="50dp"
weekselect:selectSize="10dp"
weekselect:space="30dp"
weekselect:textSize="15sp" />
要注意的是如果要在布局文件中使用这些自定义属性得加这句话: xmlns:weekselect="http://schemas.android.com/apk/res-auto" 其中 weekselect 这个字段属于用户自定义!
好了知道如何使用了,那么在写自定义 view 的时候,我们该怎么获取这些这些值呢?
TypedArray typedArray=context.obtainStyledAttributes(attrs, R.styleable.WeekSelectView);
selectColor=typedArray.getColor(R.styleable.WeekSelectView_selectColor, Defalut_Select_Color);
unSelectColor=typedArray.getColor(R.styleable.WeekSelectView_unSelectColor, Defalut_UnSelect_Color);
lineColor=typedArray.getColor(R.styleable.WeekSelectView_lineColor, Defalut_Line_Color);
textSize = typedArray.getDimension(R.styleable.WeekSelectView_textSize, Defalut_Text_Size);
textColor = typedArray.getColor(R.styleable.WeekSelectView_textColor, Defalut_Text_Color);
selectSize = typedArray.getDimension(R.styleable.WeekSelectView_selectSize, Defalut_Select_Size);
lineHeight = typedArray.getDimension(R.styleable.WeekSelectView_lineHeight, Defalut_Line_Height);
lineWidth=typedArray.getDimension(R.styleable.WeekSelectView_lineWidth, Defalut_Line_Width);
space=typedArray.getDimension(R.styleable.WeekSelectView_space,Defalut_Space);
typedArray.recycle();
代码比较简单,我就不详细说了,唯一要注意的就是不要忘了释放这个资源。
好了,当我们获取到了 view 的一些初始化值之后呢,我们得计算我们的 view 的大小了!那怎么计算 view 的大小呢?只要覆写 onMeasure(int widthMeasureSpec, int heightMeasureSpec)
这个方法就行了。但在覆写这个方法之前,我们先得了解几个方法
MeasureSpec.getSize(int value)
这个方法是用来获取对应的宽高的!
MeasureSpec.getMode(int value)
这个方法是用来获取对应的宽高的模式的!这了模式有这个三个:
精确值模式,当layout_width或layout_height指定为具体数值,或者为match_parent时,系统使用EXACTLY
MeasureSpec.AT_MOST
最大值模式,指定为wrap_content时,控件的尺寸不能超过父控件允许的最大尺寸
MeasureSpec.UNSPECIFIED
不指定测量模式,View想多大就多大,一般不太使用
这个给出常用的覆写这个方法的代码:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSize=MeasureSpec.getSize(widthMeasureSpec);
int widthMode=MeasureSpec.getMode(widthMeasureSpec);
int heightSize=MeasureSpec.getSize(heightMeasureSpec);
int heightMode=MeasureSpec.getMode(heightMeasureSpec);
if (widthMode==MeasureSpec.EXACTLY){
width=widthSize;
lineWidth=(width-itemlength*selectSize-getPaddingRight()-getPaddingLeft()- textRect.width())/(itemlength-1);
}else{
width= (int) (selectSize*(itemlength)+lineWidth*(itemlength-1)+textRect.width());
}
if (heightMode==MeasureSpec.EXACTLY){
height=heightSize;
}else{
height= (int) (selectSize+space+textRect.height()+paddingTop);
}
setMeasuredDimension(width, height);
}
一般而言,当用户设置了具体的宽高的时候,我们使用设置的,如果没有,我们自己计算对应的宽高,最后设置!当完成这一步之后,我么就可以使用 getMeasuredWidth()
,getMeasuredHeight()
这两个方法来获取 view 对应的高度了!
好了,到了这一步,我们只要覆写 onDraw(Canvas canvas)
这个方法就行了,至于要绘制什么,这个是由开发者自己决定的。这里大家住一个问题,就是不要在方法中创建类,要不然会非常的耗资源!!如果大家想绘制出非常炫酷的图形或者效果来,那就得去学学 Canvas 这个类了!!这里给个链接 学习 Canvas
这里我要说的不同点就是如果继承了 viewgroup 之后,要多覆写一个方法 onLayout()
这个方法就是用来设置子view的位置的!
现在只有你覆写了这个方法之后 ,getHeight()
和getWidth()
才能获取到对应的值!现在可能有读者要问了,这两个方法和上面的 getMeasuredWidth()
,getMeasuredHeight()
这两个方法有什么区别?却别就是
自定义view 还有一个比较重要的问题,就是触摸事件,就是 touch 事件 在这个要涉及的问题就是 android 中触摸事件的传递机制了,这个也给个链接 android 触摸事件传递机制学习
如果触摸相关的已经学习好了,就可以在学习一下 android 动画的相关知识了,毕竟动态的事物比静态的要吸引人啊!这个在给出 android 动画学习
希望大家都能学会 android 自定义 view 这个看似高大上的技能,因为这个在 android 开发中太常用了!!!!