當(dāng)制作一款復(fù)雜的應(yīng)用程序時(shí)隘梨,你會(huì)在不同的場(chǎng)合中經(jīng)常重復(fù)使用一些組合控件程癌。解決這個(gè)問題的辦法之一是創(chuàng)建一個(gè)囊括邏輯和布局的視圖,以便可以重復(fù)使用而不用在不同的場(chǎng)合中寫重復(fù)的代碼轴猎。這篇文章將會(huì)教你如何使用組合布局去創(chuàng)建簡(jiǎn)單易用的自定義的布局嵌莉。
1.介紹
在Android中,將多個(gè)控件打包在一起的視圖捻脖,被稱為組合視圖(或者組合控件)锐峭。本篇文章中,你將會(huì)創(chuàng)建一個(gè)控制滾動(dòng)列表選擇的布局可婶,會(huì)使用到Android SDK中的Spinner來獲取滾動(dòng)列表的值沿癞。效果如下:

2.創(chuàng)建項(xiàng)目
我們創(chuàng)建一個(gè)SDK最低等級(jí)為4.0的工程。這個(gè)工程只包含一個(gè)空白的MainActivity矛渴,這個(gè)Activity僅僅做了初始化布局的工作:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
MainActivity對(duì)應(yīng)的布局文件僅僅值包含了一個(gè)空的RelativeLayout椎扬。組合視圖將會(huì)在之后顯示出來。
3.創(chuàng)建一個(gè)組合控件
為了創(chuàng)建一個(gè)組合控件,你必須創(chuàng)建一個(gè)新的類來管理組合控件的視圖蚕涤。對(duì)于兩側(cè)的spinner筐赔,需要兩個(gè)Button按鈕作為箭頭,以及需要一個(gè)TextView來顯示選擇的值揖铜。
首先,為組合控件的類創(chuàng)建一個(gè)sidespinner_view.xml布局文件茴丰,記住要使用<merge>標(biāo)簽來包裹三個(gè)控件:
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<Button
android:id="@+id/sidespinner_view_previous"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="@+id/sidespinner_view_value"/>
<TextView
android:id="@+id/sidespinner_view_current_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="24sp" />
<Button
android:id="@+id/sidespinner_view_next"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</merge>
然后,我們需要?jiǎng)?chuàng)建SideSpinner類來初始化布局天吓,并且將箭頭圖標(biāo)作為按鈕的背景圖片较沪。到此為止,這個(gè)組合控件什么也做不了失仁,因?yàn)槲覀冞€沒有展示任何東西尸曼。
public class SideSpinner extends LinearLayout {
private Button mPreviousButton;
private Button mNextButton;
public SideSpinner(Context context) {
super(context);
initializeViews(context);
}
public SideSpinner(Context context, AttributeSet attrs) {
super(context, attrs);
initializeViews(context);
}
public SideSpinner(Context context,
AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
initializeViews(context);
}
/**
* 填充布局
*
* @param context
* 布局所需要的Context
*/
private void initializeViews(Context context) {
LayoutInflater.from(context).inflate(R.layout.sidespinner_view, this);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
//使用Android內(nèi)置的圖片來作為前進(jìn)和后退按鈕的背景圖片
mPreviousButton = (Button) this
.findViewById(R.id.sidespinner_view_previous);
mPreviousButton
.setBackgroundResource(android.R.drawable.ic_media_previous);
mNextButton = (Button)this
.findViewById(R.id.sidespinner_view_next);
mNextButton
.setBackgroundResource(android.R.drawable.ic_media_next);
}
}
你應(yīng)該注意到了,這個(gè)組合視圖繼承了Linearlayout類萄焦。這意味著任何使用這個(gè)組合控件的視圖都能使用Linearlayout的屬性究恤。這個(gè)組合控件看起來有點(diǎn)怪,因?yàn)樗母鶚?biāo)簽為<merge>钉寝,而不是常見的<LinearLayout>或者<RelativeLayout>蔬芥。
當(dāng)你在MainActivity中添加組合控件時(shí),這個(gè)組合控件的標(biāo)簽將會(huì)扮演<LinearLayout>的角色冒签。
4.將組合控件添加到布局文件中
現(xiàn)在編譯運(yùn)行這個(gè)項(xiàng)目的話在抛,你會(huì)發(fā)現(xiàn)能編譯通過,但是看不見任何東西萧恕。因?yàn)槲覀兊慕M合視圖并不在MainActivity中刚梭。雙側(cè)滾動(dòng)按鈕必須想其他控件一樣被添加到Activity,標(biāo)簽的名字就是SideSpinner類的名字票唆,當(dāng)然要包含包名朴读。
<com.cindypotvin.sidespinnerexample.SideSpinner
android:id="@+id/sidespinner_fruits"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"/>
由于我么好呢的SideSpinner類繼承自Linearlayout類,所以在<SideSpinner>標(biāo)簽中就能使用Linearlayout的屬性走趋。如果這時(shí)候運(yùn)行項(xiàng)目衅金,雙側(cè)滾動(dòng)按鈕會(huì)顯示出來,但是沒有任何值出現(xiàn)簿煌。
5.向組合控件添加方法
如果我們想要真正的使用這個(gè)組合控件的話氮唯,還有幾件事情需要我們完成:向其中添加值,選擇值以及獲取值姨伟。
private CharSequence[] mSpinnerValues = null;
private int mSelectedIndex = -1;
public void setValues(CharSequence[] values) {
mSpinnerValues = values;
// 選擇字符串?dāng)?shù)組中的第一個(gè)值為默認(rèn)值
setSelectedIndex(0);
}
public void setSelectedIndex(int index) {
if (mSpinnerValues == null || mSpinnerValues.length == 0)
return;
if (index < 0 || index >= mSpinnerValues.length)
return;
mSelectedIndex = index;
TextView currentValue;
currentValue = (TextView)this
.findViewById(R.id.sidespinner_view_current_value);
currentValue.setText(mSpinnerValues[index]);
// 如果顯示了第一個(gè)值惩琉,就隱藏向前按鈕
if (mSelectedIndex == 0)
mPreviousButton.setVisibility(INVISIBLE);
else
mPreviousButton.setVisibility(VISIBLE);
// 如果顯示了最后一個(gè)值,則隱藏最后一個(gè)按鈕
if (mSelectedIndex == mSpinnerValues.length - 1)
mNextButton.setVisibility(INVISIBLE);
else
mNextButton.setVisibility(VISIBLE);
}
public CharSequence getSelectedValue() {
if (mSpinnerValues == null || mSpinnerValues.length == 0)
return "";
if (mSelectedIndex < 0 || mSelectedIndex >= mSpinnerValues.length)
return "";
return mSpinnerValues[mSelectedIndex];
}
public int getSelectedIndex() {
return mSelectedIndex;
}
當(dāng)布局里面所有的控件都被填充好授滓,準(zhǔn)備使用的時(shí)候琳水,onFinishInflate方法會(huì)被調(diào)用肆糕。如果你需要修改組合控件的布局的話,就在這個(gè)方法里面修改在孝。
因此诚啃,我們就可以在onFinishInflate方法里面,使用剛才創(chuàng)建的幾個(gè)方法私沮,來控制Button按鈕的前進(jìn)始赎、后退的動(dòng)作。
@Override
protected void onFinishInflate() {
// When the controls in the layout are doing being inflated, set
// the callbacks for the side arrows.
super.onFinishInflate();
// 當(dāng)左箭頭按鈕被按下時(shí)仔燕,將選中的下標(biāo)設(shè)置為前一個(gè)值
mPreviousButton = (Button) this
.findViewById(R.id.sidespinner_view_previous);
mPreviousButton
.setBackgroundResource(android.R.drawable.ic_media_previous);
mPreviousButton.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
if (mSelectedIndex > 0) {
int newSelectedIndex = mSelectedIndex - 1;
setSelectedIndex(newSelectedIndex);
}
}
});
// 當(dāng)右箭頭按鈕被按下時(shí)造垛,將選中的下標(biāo)設(shè)置為后一個(gè)值
mNextButton = (Button)this
.findViewById(R.id.sidespinner_view_next);
mNextButton
.setBackgroundResource(android.R.drawable.ic_media_next);
mNextButton.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
if (mSpinnerValues != null
&& mSelectedIndex < mSpinnerValues.length - 1) {
int newSelectedIndex = mSelectedIndex + 1;
setSelectedIndex(newSelectedIndex);
}
}
});
// 設(shè)置第一個(gè)值為默認(rèn)值
setSelectedIndex(0);
}
在我們新創(chuàng)建的setValues方法和setSelectedIndex方法中,我們可以從代碼中初始化雙側(cè)滾動(dòng)按鈕和其他視圖一樣晰搀,你需要使用findViewById來找到組合視圖五辽。然后就可以在返回的對(duì)象中調(diào)用組合視圖中的任何公共方法。
下面的代碼片段展示了如何在MainActivity方法中更新onCreate方法外恕,這樣就能使用setValues方法杆逗。同時(shí)我們調(diào)用setSelectedIndex方法來將第二個(gè)值設(shè)置為默認(rèn)值。
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 初始化side spinner
SideSpinner fruitsSpinner;
fruitsSpinner = (SideSpinner)this
.findViewById(R.id.sidespinner_fruits);
CharSequence fruitList[] = { "Apple",
"Orange",
"Pear",
"Grapes" };
fruitsSpinner.setValues(fruitList);
fruitsSpinner.setSelectedIndex(1);
}
}
這時(shí)如果運(yùn)行項(xiàng)目鳞疲,你會(huì)發(fā)現(xiàn)雙側(cè)滾動(dòng)按鈕顯示出來了罪郊,并且將Orange作為默認(rèn)值顯示了出來。
6.向組合控件中添加布局屬性
在Android SDK里面可用的視圖都可以通過代碼來修改尚洽。但是一些屬性也可以直接在對(duì)應(yīng)的視圖中設(shè)置悔橄。下面我們將會(huì)向雙側(cè)滾動(dòng)按鈕這個(gè)組合控件添加一個(gè)屬性,來顯示按鈕的值腺毫。
首先我們要做的癣疟,就是在/res/values/attr.xml文件中定義屬性。每一個(gè)組合控件的的屬性都應(yīng)當(dāng)被<declare-styleable>標(biāo)簽所囊括拴曲,對(duì)于我們這個(gè)組合控件争舞,類名也應(yīng)該顯示出來
<resources>
<declare-styleable name="SideSpinner">
<attr name="values" format="reference" />
</declare-styleable>
</resources>
在<attr>標(biāo)簽中,name屬性值就是定義的新屬性的名字澈灼,而format屬性值就是新屬性值的類型。
對(duì)于顯示的值的集合店溢,reference的意思是“指向在資源文件中定義好的字符串集合的引用”叁熔。通常來說,自定義屬性的類型有一下幾種:boolean,color,dimension,enum,integer,float以及stirng.
下面將會(huì)顯示如何創(chuàng)建values所指向的字符串床牧。這些字符串必須添加在/res/values/strings.xml中就像下面的代碼:
<resources>
<string-array name="vegetable_array">
<item>Cucumber</item>
<item>Potato</item>
<item>Tomato</item>
<item>Onion</item>
<item>Squash</item>
</string-array>
</resources>
為了測(cè)試一下新的values屬性荣回,在MainActivity布局文件中,創(chuàng)建一個(gè)新的組合控件戈咳。在使用這個(gè)新屬性之前心软,這個(gè)屬性必須要有一個(gè)前綴壕吹,而這個(gè)前綴就是在RelativeLayout中添加的一個(gè)命名空間,比如xmlns:sidespinner="http://schemas.android.com/apk/res-auto".
下面就是activity_main.xml的最終摸樣:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:sidespinner="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.cindypotvin.sidespinnerexample.SideSpinner
android:id="@+id/sidespinner_fruits"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"/>
<com.cindypotvin.sidespinnerexample.SideSpinner
android:id="@+id/sidespinner_vegetables"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
android:layout_below="@id/sidespinner_fruits"
sidespinner:values="@array/vegetable_array" />
</RelativeLayout>
最后删铃,SideSpinner類需要做一些修改耳贬,來讀取values屬性的值。每一個(gè)控件的值都可以在AttributeSet對(duì)象中獲取到猎唁,而這個(gè)AttributeSet對(duì)象則是在視圖構(gòu)造方法中傳入的參數(shù)咒劲。
為了獲取自定義的values屬性的值。首先調(diào)用obtainStyledAttributes方法诫隅,這個(gè)方法需要傳入兩個(gè)參數(shù)腐魂,第一個(gè)是AttributeSet對(duì)象,第二個(gè)是Styleable的屬性引用逐纬。該方法返回的是一個(gè)TepedArray對(duì)象蛔屹。
然后調(diào)用TypedArray對(duì)象的getter方法,來獲取正確的屬性值豁生。傳入定義好的屬性名作為參數(shù)兔毒。下面的代碼段將向你展示如何修改組合控件的構(gòu)造方法來獲取控件值的集合,并且將他們?cè)O(shè)置到組合控件中:
public SideSpinner(Context context) {
super(context);
initializeViews(context);
}
public SideSpinner(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray;
typedArray = context
.obtainStyledAttributes(attrs, R.styleable.SideSpinner);
mSpinnerValues = typedArray
.getTextArray(R.styleable.SideSpinner_values);
typedArray.recycle();
initializeViews(context);
}
public SideSpinner(Context context,
AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
TypedArray typedArray;
typedArray = context
.obtainStyledAttributes(attrs, R.styleable.SideSpinner);
mSpinnerValues = typedArray
.getTextArray(R.styleable.SideSpinner_values);
typedArray.recycle();
initializeViews(context);
}
如果你啟動(dòng)應(yīng)用沛硅,你會(huì)看到兩側(cè)的按鈕將會(huì)如我們想象中的那樣工作眼刃。
7.狀態(tài)保存
我們需要完成的最后一步就是保存和恢復(fù)控件狀態(tài)。當(dāng)一個(gè)Activity被銷毀重建時(shí)(比如設(shè)備旋轉(zhuǎn)的時(shí)候)摇肌,控件顯示的值應(yīng)當(dāng)自動(dòng)保存和恢復(fù)擂红,下面的代碼將實(shí)現(xiàn)這一功能。
private static String STATE_SELECTED_INDEX = "SelectedIndex";
private static String STATE_SUPER_CLASS = "SuperClass";
@Override
protected Parcelable onSaveInstanceState() {
Bundle bundle = new Bundle();
bundle.putParcelable(STATE_SUPER_CLASS,
super.onSaveInstanceState());
bundle.putInt(STATE_SELECTED_INDEX, mSelectedIndex);
return bundle;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
Bundle bundle = (Bundle)state;
super.onRestoreInstanceState(bundle
.getParcelable(STATE_SUPER_CLASS));
setSelectedIndex(bundle.getInt(STATE_SELECTED_INDEX));
}
else
super.onRestoreInstanceState(state);
}
8.總結(jié)
雙側(cè)滾動(dòng)按鈕現(xiàn)在是完成了围小。兩側(cè)的按鈕都能正常工作昵骤。兩側(cè)的值也能自動(dòng)恢復(fù)和創(chuàng)建。現(xiàn)在你就可以在Android應(yīng)用中使用組合控件了肯适。