引言
在Android的應用中,經常會見到底部菜單,例如微信的底部菜單如下所示:
而在企業級的Android應用中,也存在同樣的需求,但與微信這些大眾軟件的區別在於企業級的Android應用由於UI頁面很多,每個頁面都需要有底部菜單,而且每個頁面的底部菜單按鈕還可能完全不一樣,所以,為了使每個頁面保持一致性並為UI頁面制作時提供便利,針對底部菜單進行專門的設計封裝,就顯得特別重要。
設計選型
在設計底部菜單時,有下面兩種思路:
一、單獨定制底部菜單塊,並將菜單塊代碼引入到每一個需要使用到底部菜單的layout.xml文件中,然后再在代碼中定義需要使用的按鈕。
此方法符合正常思維邏輯,缺點在於每個頁面都要重復去引入,其實這個是可以省掉的。
二、Android中,可以將某塊自定義的UI從layout.xml中實例化出來使用,使用的是LayoutInflater,基於該技術,我們可以換一種思路來解決這個問題:寫頁面layout文件的時候不用關心底部菜單。然后在展示的代碼中,定義一個大的視圖頁面將頁面的layout文件以及底部菜單的layout包含進來,過程如下圖所示:
詳細設計
1. 設計大的視圖頁面layout:bottom_menu_layout.xml,該頁面用來包含以后自定義的業務頁面,以及底部菜單。

<?xml version="1.0"encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical"> <LinearLayout android:orientation="horizontal"android:layout_width="fill_parent" android:gravity="bottom" android:layout_height="wrap_content" android:id="@+id/bottom_menu_button_group_id"> </LinearLayout> </LinearLayout>
2. 設計底部菜單按鈕layout:bottom_menu_button_frame.xml,該頁面用來定義每一個按鈕的樣式,一般需要包含一個位於上方的圖片以及一個位於下方的按鈕文字。

<?xml version="1.0"encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="64px" android:layout_height="wrap_content" android:id="@+id/bottom_menu_template_button_id" android:background="@drawable/tab_one_normal"> <ImageView android:id="@+id/bottom_menu_template_img_id" android:paddingTop="5px"android:layout_gravity="center" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/image" /> <TextView android:layout_width="wrap_content"android:id="@+id/bottom_menu_template_text_id" android:layout_height="wrap_content" android:layout_gravity="center" android:gravity="center" android:textSize="11sp"android:paddingBottom="5px" /> </LinearLayout>
3. 自定義封裝按鈕的JAVA類:BottomButton,封裝底部菜單按鈕的基本信息:圖片、文字、按鈕事件、是否當前被選中的按鈕等。

public class BottomButton { // 按鈕菜單文字 private String text; // 按鈕菜單圖片 private int backgroundResource; // 點擊事件。 private View.OnClickListener clickListener; // 是否當前已經選中的按鈕,如果是則高亮,並且忽略點擊事件。 private boolean isCurrent = false; }
4. 自定義底部菜單Layout 類:BottomMenuLayout,該類繼承自LinearLayout,用來處理按鈕的展示,該類負責以下三件事情:
a) 將底部菜單layout加入到整個大視圖頁面中。
b) 綁定每一個菜單按鈕。
c) 重新計算整個布局的大小,將菜單固定在底部。

public class BottomMenuLayout extends LinearLayout { //實例化layout使用的類 private LayoutInflater mInflater; //保存菜單按鈕的集合,每一個集合元素代表一個按鈕,包含了按鈕所需要的信息:圖片,文字,按鍵處理事件。 private List<BottomButton> bottomButtons; //封裝菜單按鈕的布局。 private View bottomMenuLayout; public void processInitButton() { //初始化布局,將底部菜單layout加入到視圖中。 initLayout(this.getContext()); //綁定每一個菜單按鈕 bindingButton(); //重新計算整個布局的大小,使用整個屏幕的高度減去底部菜單的高度, //得出並設置中間頁面部分的高度,就能夠將菜單固定在底部。 resizeLayout(); }
注:這里屏蔽了實現細節,具體參考附件代碼。
5.當以上每一個零散的部分都完成以后,就需要一個組裝者,來負責將各個部分組裝在一起,使之能夠正常運作起來,這里定義一個繼承自Activity的類:BaseBottomMenuActivity ,用來充當組裝者的角色,開發業務時需要更改方式,原來直接繼承自Activity的類改為繼承該類。該類主要完成以下工作:
a) 創建出整個大視圖頁面。
b) 創建出中間內容部分頁面,也就是由開發者定義的實際layout內容。並加到整個大視圖的頁面中來。
c) 創建出底部菜單,並將底部菜單加入到整個大視圖的頁面中來。
d) 需要得知子類繼承該類后,具體使用的layout頁面的ID,定義抽象方法由子類實現以提供該ID。
e) 需要得知子類繼承該類后,具體需要哪些菜單按鈕,定義抽象方法由子類實現以提供按鈕的列表。
f) 需要得知子類繼承該類后,還需要做哪些頁面初始化的工作,定義抽象方法由子類實現以便在頁面初始過程中調用。

public abstract class BaseBottomMenuActivity extends Activity { private LayoutInflater mInflater; //實例化layout使用的類 protected BottomMenuLayout bottomMenuLayout; //底部菜單UI部分 protected View contentView; //頁面中間UI部分 final protected void onCreate(Bundle savedInstanceState) { //a) 創建出整個大視圖頁面。 //b) 創建出中間內容部分頁面,也就是由開發者定義的實際layout內容。並加到整個大視圖的頁面中來。 //c) 創建出底部菜單,並將底部菜單加入到整個大視圖的頁面中來。 } /** * 子類實現后,在原來的onCreate方法中內容移到這里來操作。 * @paramsavedInstanceState */ protected abstract void onCreatOverride(Bundle savedInstanceState); /** * 返回layout xml的ID * 原本在Activity的onCreate方法中調用的setContentView(R.layout.xxxxLayoutId); 現在從該方法返回。 * @return */ public abstract int getContentViewLayoutResId(); /** * 創建底部菜單,需要子類實現,在此方法中, * 創建多個BottomButton對象並放置到List中返回即可。 * 如果需要哪一個按鈕當前被選中,則設置BottomButton的isCurrent屬性為ture. * @param bottomButtons * @param bottomMenuLayout * @return */ public abstractList<BottomButton> getButtonList();
注:這里屏蔽了實現細節,具體參考附件代碼。
實現及使用
具體實現代碼見附件。
在上述設計的各部分都實現后,就可以進行使用了,使用時,與平常開發正常頁面的步驟一樣,首先畫layout.xml,然后定義activity,進行綁定並配置AndroidManifest.xml文件,不同的地方在於定義的Activity需要繼承BaseBottomMenuActivity。並且根據以下步驟開發該Activity:
1. 在子類中繼承實現getButtonList方法,在方法中封裝BottomButton對象返回,每一個BottomButton代表一個菜單項,具體屬性見BottomButton定義。
2. 在子類中繼承實現getContentViewLayoutResId方法,返回layout xml的ID。
3. 在子類中繼承實現onCreatOverride方法,原先在onCreat方法中完成的事情挪到這里,super.onCreate(savedInstanceState);和setContentView就不需要調用了。
測試
讓我們來寫一個簡單的Demo來進行測試,該Demo中,除了底部菜單,只有一個TextView文本,該類繼承了BaseBottomMenuActivity:

public class Demo extends BaseBottomMenuActivity { public List<BottomButton> getButtonList() { Map<String,String> buttonMaps = new HashMap<String,String>(); buttonMaps.put("Menu1", String.valueOf(R.drawable.home)); buttonMaps.put("Menu2", String.valueOf(R.drawable.home)); buttonMaps.put("Menu3", String.valueOf(R.drawable.home)); buttonMaps.put("Menu4", String.valueOf(R.drawable.home)); buttonMaps.put("主菜單", String.valueOf(R.drawable.home)); List<BottomButton>buttons = new ArrayList<BottomButton>(); Iterator<String> itKey =buttonMaps.keySet().iterator(); while(itKey.hasNext()) { Stringkey = itKey.next(); StringvalueRes = buttonMaps.get(key); BottomButtononeBottomButton = new BottomButton(); oneBottomButton.setText(key); oneBottomButton.setBackgroundResource(Integer.parseInt(valueRes)); buttons.add(oneBottomButton); } return buttons; } public int getContentViewLayoutResId() { return R.layout.main; } protected void onCreatOverride(Bundle savedInstanceState) { } }
在返回按鈕的getButtonList方法中,返回了5個按鈕。
其布局文件如下:

<?xml version="1.0"encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" /> </LinearLayout>
結果程序效果如下圖所示: