Android創(chuàng)建組合控件


當(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)用中使用組合控件了肯适。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末变秦,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子框舔,更是在濱河造成了極大的恐慌蹦玫,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,576評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件刘绣,死亡現(xiàn)場(chǎng)離奇詭異樱溉,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)纬凤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門福贞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人停士,你說我怎么就攤上這事挖帘⊥昀觯” “怎么了?”我有些...
    開封第一講書人閱讀 168,017評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵拇舀,是天一觀的道長(zhǎng)逻族。 經(jīng)常有香客問我,道長(zhǎng)你稚,這世上最難降的妖魔是什么瓷耙? 我笑而不...
    開封第一講書人閱讀 59,626評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮刁赖,結(jié)果婚禮上搁痛,老公的妹妹穿的比我還像新娘。我一直安慰自己宇弛,他們只是感情好鸡典,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著枪芒,像睡著了一般彻况。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上舅踪,一...
    開封第一講書人閱讀 52,255評(píng)論 1 308
  • 那天纽甘,我揣著相機(jī)與錄音,去河邊找鬼抽碌。 笑死悍赢,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的货徙。 我是一名探鬼主播左权,決...
    沈念sama閱讀 40,825評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼痴颊!你這毒婦竟也來了赏迟?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,729評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤蠢棱,失蹤者是張志新(化名)和其女友劉穎锌杀,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體泻仙,經(jīng)...
    沈念sama閱讀 46,271評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡抛丽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了饰豺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,498評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡允蜈,死狀恐怖冤吨,靈堂內(nèi)的尸體忽然破棺而出蒿柳,到底是詐尸還是另有隱情,我是刑警寧澤漩蟆,帶...
    沈念sama閱讀 36,183評(píng)論 5 350
  • 正文 年R本政府宣布垒探,位于F島的核電站,受9級(jí)特大地震影響怠李,放射性物質(zhì)發(fā)生泄漏圾叼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評(píng)論 3 333
  • 文/蒙蒙 一捺癞、第九天 我趴在偏房一處隱蔽的房頂上張望夷蚊。 院中可真熱鬧,春花似錦髓介、人聲如沸惕鼓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽箱歧。三九已至,卻和暖如春一膨,著一層夾襖步出監(jiān)牢的瞬間呀邢,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工豹绪, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留价淌,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,906評(píng)論 3 376
  • 正文 我出身青樓森篷,卻偏偏與公主長(zhǎng)得像输钩,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子仲智,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容