自定义view第三篇,废话少说,直接进入正题。
第一步:在attrs.xml文件中定义属性名和值,并在构造函数中获取该值
<resources>
<declare-styleablename="MyCustomView3">
<attrname="horizonal_spacing"format="dimension"/>
<attrname="vertical_spacing"format="dimension"/>
<attrname="layout_vertical_spacing"format="dimension"/>
</declare-styleable>
</resources>
public MyCustomView3(Context context, AttributeSet attrs) {
super(context, attrs);
//获取属性值
TypedArrayta = context.obtainStyledAttributes(attrs, R.styleable.MyCustomView3);
for(inti=0;i<ta.getIndexCount();i++){
int attr = ta.getIndex(i);
switch(attr){
case R.styleable.MyCustomView3_horizonal_spacing :
horizonal_spacing= ta.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP,16, getResources().getDisplayMetrics()));
break;
case R.styleable.MyCustomView3_vertical_spacing :
vertical_spacing =ta.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP,16, getResources().getDisplayMetrics()));
break;
}
}
ta.recycle();
}
注意:在构造函数中,horizonal_spacing和vertical_spacing的默认值通过(int)TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP,16, getResources().getDisplayMetrics())这个获取,默认为16dp;
也可以在dimension.xml文件中定义一个大小,然后通过getResources().getDimensionPixelSize(R.dimen.horizonal_spacing)获取。
第二步:重写onMeasure方法
在这里,当容器的宽和高被定义为wrap_content的时候:
宽度=(每张卡片的宽度+定义的水平间距) * 3;
高度=(每张卡片的高度+容器定义的垂直间距+视图定义的垂直间距) * 3;
//记录当为warp_content的时候容器的宽和高
int width = 0;
int height = 0;
for(inti=0;i<getChildCount();i++){
ViewchildView = getChildAt(i);
mParams =(MyLayoutParams)childView.getLayoutParams();
width+= mParams.width +horizonal_spacing;
//用布局定义的组件高度+定义的垂直间距+容器定义的垂直间距来决定子视图组件的高度
height+= mParams.height +mParams.layout_vertical_spacing+vertical_spacing;
}
第三步:重写onLayout方法
对子视图进行布局;
@Override
protectedvoid onLayout(boolean changed, int l,int t,int r,int b) {
//分别代表卡片的左x坐标,上y坐标,右x坐标,下y坐标
int cl = 0, ct = 0, cr = 0,cb = 0;
for(inti=0;i<getChildCount();i++){
ViewchildView = getChildAt(i);
mParams =(MyLayoutParams)childView.getLayoutParams();
switch(i){
case 0 :
//第一张卡片的左坐标为0,右坐标为视图定义的宽度;上y坐标为子视图自身定义的间距,下y坐标=上y坐标+视图定义的宽度
cr= mParams.width;
ct+= mParams.layout_vertical_spacing;
cb= mParams.height +mParams.layout_vertical_spacing+vertical_spacing;
break;
case 1 :
cl+= horizonal_spacing;
cr+= horizonal_spacing;
ct+= mParams.layout_vertical_spacing+vertical_spacing;
cb+= mParams.layout_vertical_spacing+vertical_spacing;
break;
case 2 :
cl+= horizonal_spacing;
cr+= horizonal_spacing;
ct+= mParams.layout_vertical_spacing+vertical_spacing;
cb+= mParams.layout_vertical_spacing+vertical_spacing;
break;
}
childView.layout(cl,ct, cr, cb);
}
}
第四步:比较关键。定义一个内部类,用于保存子视图的布局参数
/**
* 用于获取子视图的布局参数
* @authorwyb
*
*/
privateclass MyLayoutParamsextends ViewGroup.LayoutParams{
//和布局中子视图定义的垂直间距想对应
publicintlayout_vertical_spacing;
public MyLayoutParams(Contextcontext, AttributeSet attrs) {
super(context, attrs);
//获取垂直间距
TypedArraya = context.obtainStyledAttributes(attrs,
R.styleable.MyCustomView3);
try {
layout_vertical_spacing= a
.getDimensionPixelSize(
R.styleable.MyCustomView3_layout_vertical_spacing,
-1);
} finally {
a.recycle();
}
}
public MyLayoutParams(int arg0,int arg1) {
super(arg0, arg1);
//TODO Auto-generated constructor stub
}
publicMyLayoutParams(LayoutParams arg0) {
super(arg0);
//TODO Auto-generated constructor stub
}
}
在这个案例中MyLayoutParams类的layout_vertical_spacing就是保存子视图的垂直间距。虽然在容器的构造函数中可以获取这个属性值,用于容器的测量和子视图的布局,但这样显得容器类不是很高类聚,因为这个属性是属于子视图的,所以应该定义在这个内部类中
好,此时如果你就开始使用这个容器的话,在onMeasure方法的
mParams =(MyLayoutParams)childView.getLayoutParams();这一行就会报错
出现这个错误的原因是还没有重写下面这三个方法
@Override
public LayoutParams generateLayoutParams(AttributeSetattrs) {
//TODO Auto-generated method stub
returnnewMyLayoutParams(getContext(),attrs);
}
@Override
protected LayoutParamsgenerateLayoutParams(LayoutParams p) {
//TODO Auto-generated method stub
return new MyLayoutParams(p);
}
@Override
protected LayoutParamsgenerateDefaultLayoutParams() {
//TODO Auto-generated method stub
returnnewMyLayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
}
这三个方法的作用是为了告诉子视图有什么样的布局参数。比如现在MyLayoutParams是继承ViewGroup.LayoutParams,那么子视图想获取leftMargin这些属性的时候是无法获取,可以通过继承MarginLayoutParams来获取;其他属性依次类推,如想获取线性布局的orientation属性,就必须继承LinearLayout.LayoutParams这个类。
当然,也可以不用写这个内部类,然后在重写上面三个方法的时候直接返回某个类的对象即可;在这儿写这个内部类主要是让子视图拥有layout_vertical_spacing这个属性而已。
用法就很简单,直接在子视图中定义,如下:
<View
android:layout_width="70dp"
android:layout_height="100dp"
custom:layout_vertical_spacing="10dp"
android:background="#00FF00"/>
第五步:使用这个自定义的ViewGroup
<com.example.mycustomview3.MyCustomView3
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#000000"
custom:horizonal_spacing="20dp"
custom:vertical_spacing="30dp">
<View
android:layout_width="70dp"
android:layout_height="100dp"
android:background="#FF0000"/>
<View
android:layout_width="70dp"
android:layout_height="100dp"
custom:layout_vertical_spacing="10dp"
android:background="#00FF00"/>
<View
android:layout_width="70dp"
android:layout_height="100dp"
android:background="#0000FF"/>
</com.example.mycustomview3.MyCustomView3>
当为:wrap_content的时候:
当为300dp的时候:
到此,自定义ViewGroup就算完成了。当然,这只是很初步的定义,后面还会进一步学习。