最近在做一个项目,用到了自定义组合控件。一直来对自定义一组合控件都是有点映像,但是亲手做的时候发现有个地方卡主了。百度了很多资料才将问题给解决,就将它记下。
先上个效果图:
自定义组合控件首先当然少不了一个布局,它就是将一个自定义的布局通过LayoutInflater变成一个View加载的。
自定义布局代码:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="15dp" >
<ImageView
android:id="@+id/firstpage_icon"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_centerVertical="true"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"/>
<TextView
android:id="@+id/firstpage_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/firstpage_icon"
android:textSize="20sp" />
<TextView
android:id="@+id/firstpage_desc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/firstpage_title"
android:layout_toRightOf="@+id/firstpage_icon"
android:textColor="#777777" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="15dp"
android:src="@drawable/clicktogo" /> //这里有设置值,因为该组合控件的这个值都是一个箭头
</RelativeLayout>
接着自定义一个类(FirstPageCustomView),一般继承自RelativeLayout或者LinearLayout。这里继承自RelativeLayout。该类中首先要做的就是实现父类构造函数,父类有三个构造函数,对于有自定义布局加载方式的应该实现两个参数或者三个参数的构造。这里拿两个参数的构造函数作为例子。
下面上FirstPageCustomView的代码:
public class FirstPageCustomView extends RelativeLayout {
private ImageView firstpage_icon;
private TextView firstpage_title;
private TextView firstpage_desc;
// 加载布局文件
private void initView(Context context) {
View.inflate(context, R.layout.customview_firstpage, this);
firstpage_icon = (ImageView) this.findViewById(R.id.firstpage_icon);
firstpage_title = (TextView) this.findViewById(R.id.firstpage_title);
firstpage_desc = (TextView) this.findViewById(R.id.firstpage_desc);
}
public FirstPageCustomView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView(context);
}
//这是两个参数的构造函数
public FirstPageCustomView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
}
上面的代码就已经将自定义的布局加载进自定义的类中。关键代码是:View.inflate(context, R.layout.customview_firstpage, this);
接下来就可以在Activity中使用该控件了。
如在界面的布局文件中如下:
<com.example.qzz.customview.FirstPageCustomView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:background="#ffffff">
</com.example.qzz.customview.FirstPageCustomView>
但是上面这样做了还不够,因为自定义组合控件的每个子控件没有设置值,要在FirstPageCustomView类中对每个子控件进行设置:
public class FirstPageCustomView extends RelativeLayout {
private ImageView firstpage_icon;
private TextView firstpage_title;
private TextView firstpage_desc;
// 加载布局文件
private void initView(Context context) {
View.inflate(context, R.layout.customview_firstpage, this);
firstpage_icon = (ImageView) this.findViewById(R.id.firstpage_icon);
firstpage_title = (TextView) this.findViewById(R.id.firstpage_title);
firstpage_desc = (TextView) this.findViewById(R.id.firstpage_desc);
}
public FirstPageCustomView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView(context);
}
public FirstPageCustomView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
firstpage_icon.setImageResource(R.drawable.news_icon);
firstpage_title.setText("最新活动新闻");
firstpage_desc.setText("最新赞助活动一键浏览...");
}
}
这样做完,就可以很好的显示我们想要的效果了。但是还没有结束,以上我们会发现一个问题,那就是自定义控件的每个子控件都要在类中赋值,这个根本不能满足我们的要求。想下,如果我们的布局中要用到两个这个控件,那就实现不了了,不可能每用一次这个控件就要重新定义一个控件。
这个时候就要用到自定义控件属性了。
自定义属性的步骤为:在res/values下定义一个attrs.xml文件,在文件中自定义属性。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="FirstPageCustomView">
<attr name="icon" format="reference" />
<attr name="title" format="string" />
<attr name="desc" format="string" />
</declare-styleable>
</resources>
declare-styleable name="FirstPageCustomView"指定是哪一个自定义控件的自定义属性,这里表示给FirstPageCustomView这个自定义控件自定义属性。
format="reference"表示该属性的值引用一个资源id值。对于format的可以取值,网上一堆资料,而且很详细,这里就不说了。
每个attr标签表示一个自定义属性,name属性指定自定义属性名
这样就可以在布局文件中使用自定义属性了,但是自定义属性的前缀要自己定义,也就是要自定义一个命名空间。如:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="50dp"
android:background="#40a1ff"
android:gravity="center_vertical" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:gravity="center"
android:text="自定义组合控件学习"
android:textColor="#ffffff"
android:textSize="22sp"
android:textStyle="bold" />
</RelativeLayout>
红色字体部分就是一个命名空间,它指定了属性前缀是android:,并且指定了属性的定义在android系统内部。要自己定义属性就要自己定义一个命名空间。说来其实很简单:
xmlns:firstpage(前缀)="http://schemas.android.com/apk/res/包名"。这样一写就可以直接在布局文件中使用自定义属性了。
<com.example.qzz.customview.FirstPageCustomView
firstpage:icon="@drawable/news_icon"
firstpage:title="最新新闻"
firstpage:desc="最新活动一键浏览..."
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:background="#ffffff">
</com.example.qzz.customview.FirstPageCustomView>
之后就直接在FirstPageCustomView类构造函数中拿到属性值进行设置就行了。
public FirstPageCustomView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
/**
* 获得我们所定义的自定义样式属性
*/
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.FirstPageCustomView);
int n = a.getIndexCount();
for (int i = 0; i < n; i++) {
int attr = a.getIndex(i);
switch (attr) {
case R.styleable.FirstPageCustomView_icon:
int resource = a.getResourceId(i, 0);//对于属性值是一个资源id的,这里拿到资源id值再复制给子控件
firstpage_icon.setImageResource(resource);
break;
case R.styleable.FirstPageCustomView_title:
// 默认颜色设置为黑色
String title = a.getString(i);
firstpage_title.setText("最新新闻");
break;
case R.styleable.FirstPageCustomView_desc:
// 默认设置为16sp,TypeValue也可以把sp转化为px
String desc = a.getString(i);
firstpage_desc.setText("最新活动一键浏览...");
break;
}
}
}
自定义控件就说到这里,有什么地方有问题的,欢迎大家提出来,一起进步。