10.建立組合控制元件
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,效果如圖所示。

同時顯示文字和圖片的TextImageButton