您的位置:首页 > 移动开发 > Android开发

Android学习之自定义view(三)

2015-09-12 22:36 423 查看
自定义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就算完成了。当然,这只是很初步的定义,后面还会进一步学习。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: