10.1 问题
需要通过组合现有的元素来创建自定义的小部件。
10.2 解决方案
(API Level 1)
通过扩展通用的ViewGroup并添加所需的功能就能创建自定义的小部件。创建自定义小部件或可重用用户界面元素的最简单、最实用的方法就是利用Android SDK提供的现有小部件来创建组合控件。
10.3 实现机制
ViewGroup及其子类LinearLayout、RelativeLayout等能帮助你摆放控件的位置,这样你就可以专注于添加所需的功能。
TextImageButton
下面将创建一个Android SDK中没有原生提供的小部件:含有图片或文字的按钮。为了实现这个小部件,我们创建TextImageButton类,这是对FrameLayout的扩展。其中包含一个用于放置文本内容的TextView,以及一个用于放置图片内容的ImageView(参见以下代码)。
自定义TextImageButton小部件
public class TextImageButton extends FrameLayout {
private ImageView imageView;
private TextView textView;
/* 构造函数 */
public TextImageButton(Context context) {
this(context, null);
}
public TextImageButton(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TextImageButton(Context context, AttributeSet attrs, int defaultStyle) {
//通过系统的按钮样式初始化父布局
// 这样会设置clickable属性和按钮背景来匹配当前的主题
super(context, attrs, android.R.attr.buttonStyle);
imageView = new ImageView(context, attrs, defaultStyle);
//创建子视图
textView = new TextView(context, attrs, defaultStyle);
//创建子视图的LayoutParams为WRAP_CONTENT并居中显示
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT,
Gravity.CENTER);
//添加视图
this.addView(imageView, params);
this.addView(textView, params);
//如果有图片,切换到图片模式
if(imageView.getDrawable() != null) {
textView.setVisibility(View.GONE);
imageView.setVisibility(View.VISIBLE);
} else {
textView.setVisibility(View.VISIBLE);
imageView.setVisibility(View.GONE);
}
}
/* 存取器*/
public void setText(CharSequence text) {
//切换到文字模式
textView.setVisibility(View.VISIBLE);
imageView.setVisibility(View.GONE);
//设置文字
textView.setText(text);
}
public void setImageResource(int resId) {
//切换到图片模式
textView.setVisibility(View.GONE);
imageView.setVisibility(View.VISIBLE);
//设置图片
imageView.setImageResource(resId);
}
public void setImageDrawable(Drawable drawable) {
//切换到图片模式
textView.setVisibility(View.GONE);
imageView.setVisibility(View.VISIBLE);
//设置图片
imageView.setImageDrawable(drawable);
}
}
SDK中所有的小部件都有至少两个(通常为3个)构造函数。第一个构造函数的参数只有一个Context,通常用于在代码中新建视图。另外两个构造函数用于将XML转换成视图,XML文件中定义的属性会传递给AttributeSet参数。这里我们用Java的this()符号调用实际完成所有工作的函数,从而实现了这两个构造函数。以这种方式构造自定义控件就确保了我们能在XML布局中定义此视图。如果不实现这两个属性化的构造函数(attributed constructor),就不能在XML布局中使用自定义的控件。
为了让FrameLayout看起来更像一个标准的按钮,我们在构造函数中传入了android.R.attr.buttonStyle属性。这里定义了和当前主题匹配的样式并设置了视图上。这不仅设置了背景来匹配其他的按钮实例,还让视图变得可单击和可获得焦点(因为这些样式就是系统样式的一部分)。只要有可能,都应该从当前主题中加载自定义小部件的外观,从而可轻松定制并与应用程序的其他部分保持统一。
构造函数还创建一个TextView和一个ImageView,将它们放入布局中。向每个子构造函数传递相同的属性集,从而可以正确读取特定于每个构造函数设置的XML属性(例如文字和图片状态)。其他的代码根据属性中传递的数据设置默认的显示模式(文字或图片)。
加入存取器函数是为了方便以后改变按钮的内容。这些函数还负责在内容变化时在文字和图片模式间进行切换。
因为这个自定义的控件并没有在android.view或android.widget包中,所有在XML布局中使用该控件必须使用全名。以下两段代码演示了含有该自定义小部件的Activity。
res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<com.examples.customwidgets.TextImageButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Click Me!"
android:textColor="#000" />
<com.examples.customwidgets.TextImageButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher" />
</LinearLayout>
使用了新的自定义小部件的Activity
public class MyActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
注意,我们还是可以使用传统的属性来设置要显示的文字或图片。这是因为我们用属性化构,造函数来构造各个元素(FrameLayout、TextView和ImageView),所以每个视图都会根据自己的需求设置参数,忽略其他参数。
如果我们定义使用该布局的Activity,效果如图所示。