目录
4.MyView(Context context, AttributeSet attrs)方法
6.dispatchDraw(Canvas canvas)方法
1.自定义控件的XML布局文件( res/layout/xxx.xml )
2.设置组合控件的属性的XML文件 ( res/value/attrs.xml )
3.创建自定义组合控件类( app / java / 包名 / xxx.java )
通常开发Android应用程序的界面时,都不直接使用View控件,而是使用View控件的子类。例如,如果要显示一段文字,可以使用View控件的子类TextView控件;如果要显示一个按钮,可以使用View控件的子类Button控件。虽然Android提供了很多继承自View类的控件,但是在实际开发中,还会出现不满足需求的情况,此时我们可以通过自定义控件的方式实现。最简单的自定义控件就是创建一个继承View或其子类的类 ( app / java / 包名 / xxx.java ) ,并重写该类的构造方法。
实例代码如下:
public class MyView extends RelativeLayout {
public MyView(Context context) {
super(context);
//在Java代码中使用的方法
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
//在XML布局文件中使用的方法
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//该方法用于测量尺寸,在该方法中可以设置控件本身或其子控件的宽高
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//该方法用于绘制图像
}
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
//用于指定布局中子控件的位置
}
//其他自定义控件的方法
}
public XXX( Context context )方法是在Java文件中使用的构造方法,public XXX( Context context , AttributeSet attrs )方法是在XML布局文件中使用的方法。
一、实现方法
由于系统自带的控件不能满足需求中的某种样式或功能,所以我们需要在自定义控件中通过重写指定的方法来添加额外的样式和功能。自定义控件常用的3个方法的具体介绍如下:
1.onMeasure()方法
该方法用于测量尺寸,在该方法中可以设置控件本身或其子控件的宽高,onMeasure()方法的具体介绍如下:
onMeasure( int widthMeasureSpec , int heightMeasureSpec )
onMeasure()方法中第一个参数widthMeasureSpec表示获取父容器指定的该控件宽度,第二个参数heightMeasureSpec表示获取父容器指定的该控件高度。
widthMeasureSpec和heightMeasureSpec参数不仅包含父容器指定的属性值,还包含父容器的测量模式,测量模式分为三种,具体介绍如下:
EXACTLY:当自定义控件的宽高值设置为具体值时使用,如100dp、match_parent等,此时控件的宽高值是精确的尺寸。
AT_MOST:当自定义控件的宽高值为wrap_content时使用,此时控件的宽高值是控件中的数据内容可获得的最大空间值。
UNSPECIFIED:当父容器没有指定自定义控件的宽高值时使用。
需要注意的是,虽然参数widthMeasureSpec和heightMeasureSpec是父容器为该控件指定的宽高,但是该控件还需要通过 setMeasureDimension( int i , int i ) 方法设置具体的宽高。
2.onDraw()方法
该方法用于绘制图像,onDraw()方法的具体介绍如下:
onDraw( Canvas canvas )
onDraw()方法中的参数canvas表示画布。Canvas类(画布)经常与Paint类(画笔)配合使用,使用Paint类可以在Canvas类中绘制图像。
3.onLayout()方法
onLayout()方法用于指定布局中子控件的位置,该方法通常在自定义的ViewGroup容器中重写。
onLayout()方法的具体介绍如下:
onLayout( boolean changed , int left , int top , int right , int bottom )
onLayout()方法中有5个参数,其中第1个参数changed表示自定义控件的大小和位置是否发生变化,剩余的4个参数left、top、right、bottom分别表示子控件与父容器左边、顶部的距离;即左与父左,上与父上,右与父左,下与父上。
4.MyView(Context context, AttributeSet attrs)方法
用于XML布局文件中的构造方法
使用context.obtainStyledAttribute(Attribute attrs,R.styleable.~)可以获取TypeArray型的属性合集。
5.MyView(Context context)方法
用于Java文件中的构造方法
6.dispatchDraw(Canvas canvas)方法
该方法与onDraw()方法类似,均为用于绘制的方法;区别在于onDraw()方法的调用在绘制子控件前,而dispatchDraw()方法的调用在绘制子控件后。即onDraw()->绘制子控件->dispatchDraw()。如果想子控件遮住绘制部分则使用onDraw(),如果想子控件不遮住绘制部分则使用dispatchDraw()。
二、尺寸测量,修改尺寸( onMeasure()方法 )
1.测量模式
Android的视图提供了三种测量模式:
(1)MeasureSpec.AT_MOST 达到最大值,即match_parent。
(2)MeasureSpec.UNSPECIFIED 未指定(实际就是自适应),即wrap_content。
(3)MeasureSpec.EXACTLY 精确尺寸,即具体dp值。
2.获取测量模式
MeasureSpec.getMode( int widthMeasureSpec )
3.获取实际大小
MeasureSpec.getSize( int widthMeasureSpec )
//例
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//获取测量模式
int widthMode=MeasureSpec.getMode(widthMeasureSpec);
//判断模式
if(widthMode==MeasureSpec.EXACTLY){
//模式为具体dp值
//获取实际宽度
int widthSize=MeasureSpec.getSize(widthMeasureSpec);
Log.d("OK","设定值,实际宽度:"+widthSize);
}
else if(widthMode==MeasureSpec.UNSPECIFIED){
//模式为未指定(自适应),wrap_content
//获取实际宽度
int widthSize=MeasureSpec.getSize(widthMeasureSpec);
Log.d("OK","自适应,实际宽度:"+widthSize);
}
else if (widthMode==MeasureSpec.AT_MOST) {
//模式为达到最大值,match_parent
//获取实际宽度
int widthSize=MeasureSpec.getSize(widthMeasureSpec);
Log.d("OK","最大值,实际宽度:"+widthSize);
}
//根据获取到的实际宽度、实际高度重新设置子控件宽高
... ...
//根据获取到的大小设置当前自定义控件大小
setMeasuredDimension(width, height);
}
4.测量文本大小
(1)测量文本宽度
//创建一个Paint(画笔)对象
Paint paint=new Paint();
//设置画笔的文本大小
paint.setTextSize(textSize);
//测量文本宽度
paint.measureText(text);
(2)测量文本高度
使用FontMetrics类
FontMetrics类的距离属性:
top 行的顶部与基线的距离。
ascent 字符的顶部与基线的距离。
descent 字符的底部与基线的距离。
bottom 行的底部与基线的距离。
leading 行间距。
//创建一个Paint(画笔)对象
Paint paint=new Paint();
//设置画笔的文本大小
paint.setTextSize(textSize);
//获取FontMetrics对象
Paint.FontMetrics fm=paint.getFontMetrics();
//文本高度
float h=fm.descent-fm.ascent;
三、子控件布局( onLayout()方法 )
重写onLayout()方法中的常用方法:
getChildCount() 获取子控件数量
getChildAt( int index ) 获取指定index的子控件,返回View
view.getVisibility() 获取控件存在状态,GONE,TRUE,FALSE
view.getMeasuredHeight() 获取控件高度px
view.getMeasuredWidth() 获取控件宽度px
view.layout( int l , int t , int r , int b ) 设置控件四周边界与父容器左和上距离,左与父左,上与父上,右与父左,下与父上。
//例
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for (int i=0;i< getChildCount() ;i++){
View view= getChildAt(i) ;
if( view.getVisibility() !=GONE){
int width= view.getMeasuredWidth() ;
int height= view.getMeasuredHeight();
//重设子控件布局位置
view.layout(~,~,~,~);
}
}
}
在其他方法中使用requestLayout()会再次调用onLayout()重定义布局。
四、属性及XML布局
1.自定义控件的XML布局文件( res/layout/xxx.xml )
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</RelativeLayout>
2.设置组合控件的属性的XML文件 ( res/value/attrs.xml )
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- 声明-风格 自定义控件名-->
<declare-styleable name="MyView">
<attr name="attrN1" format="string"/>
<!-- 属性名 属性类型 -->
<attr name="attrN2">
<flag name="FLAG_N1" value="1"/>
<!-- 属性名 属性值(此值只能为int) -->
<flag name="FLAG_N2" value="1"/>
</attr>
</attr name="attrN3" format="reference"/>
<!-- 属性名 属性值(资源R.~.~) -->
</declare-styleable>
</resources>
declare-声明 styleable-风格 attr-属性 flag-标识、标志
format="reference"标识类型为资源 ( R.~.~ )
3.创建自定义组合控件类( app / java / 包名 / xxx.java )
public class MyView extends RelativeLayout { //继承已有控件(RelativeLayout、LinearLayout等)
private int i;
private TextView textView;
public MyView(Context context) {
super(context);
//用于Java文件中的构造函数
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
//用于XML布局文件中的构造函数
getAttrs(context,attrs);
setView(context);
}
private void getAttrs(Context context,AttributeSet attrs){
//获取属性值
TypedArray typedArray=context.obtainStyledAttributes(attrs,R.styleable.MyView);
i=typedArray.getInt(R.styleable.MyView_attrN2,1);
int id=typedArray.getResourceId(R.styleable.MyView_attrN3,R.~.~);
//typedArray.getString typedArray.getBoolean
//回收
typedArray.recycle();
}
private void setView(Context context){
LayoutInflater.from(context).inflate(R.layout.myview,this);
textView=(TextView) findViewById( ~ );
textView.setText(i);
}
}
获取自定义控件属性值以后一定要回收TypedArray
使用getResourceId()获取类型为reference的资源ID
自定义组合控件需要继承已有控件(RelativeLayout、LinearLayout等)
LayoutInflater.from( context ).inflate( R.layout.myview , this ); 根为this
4.使用自定义组合控件
<MyView
app:attrN1="testString"
app:attrN2="FLAGN1"
... ...
/>
自定义属性使用: app: 属性名 = " 属性值 "
5.注:自定义控件类需实现方法
public class MyView extends RelativeLayout {
public MyView(Context context) {
super(context);
//用于Java文件中的构造函数
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
//用于XML布局文件中的构造函数
TypedArray ta=context.obtainStyledAttributes(attrs,R.styleable.MyView);
LayoutInflater.from(context).inflate(R.layout.myview,this);
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//该方法用于测量尺寸,在该方法中可以设置控件本身或其子控件的宽高
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//该方法用于绘制图像
}
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
//用于指定布局中子控件的位置
}
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
//该方法用于绘制图像,与onDraw()方法类似
}
}
五、立即重绘当前控件
在控件已完成绘制后,可以使用invalidate()方法重绘当前控件。常使用在自定义方法修改当前控件。
public class MyView extends RelativeLayout {
//其他自定义控件的方法... ...
protected void setXXX(){
//设置控件...
//立即重绘控件
invalidate();
}
}