能夠說ActivityGroup是Google提供的一個很是優秀的API,但它須要作稍微複雜的重寫才能用起來比較方便,本文擬將實現這個稍微複雜的重寫。TabActivity做爲ActivityGroup惟一的子類卻讓人大失所望。 android
首先來講ActivityGroup的優秀之處以及它的必要性,它爲開發者提供了一種可能,這種可能不將Activity做爲屏幕的頂級元素(Context)呈現,而是嵌入到ActivityGroup當中。這是一種極大的飛躍,它將場景(Context)細分化了,ActivityGroup是一個主場景,而用戶能夠經過導航按鈕來切換想要的子場景。如使用微博功能,它是一個至關宏大的場景,具備看最新的廣播信息、本身發微博、修改資料等子場景,用戶能夠經過按鈕來切換到想要的子場景,而這個子場景仍活動於主場景之中。讓一個主場景能擁有多個邏輯處理模塊,主場景再也不負責子場景邏輯,主場景只負責切換場景的邏輯,即每個Activity(子場景)擁有一個邏輯處理模塊,一個ActivityGroup有多個Activity,卻不干預Activity的邏輯,這無疑細分化和模塊化了邏輯代碼。ActivityGroup和它將要內嵌的Activity所要實現的功能徹底能夠用只一個Activity來完成,你能夠試想,當你把一個ActivityGroup和它所擁有的Activity的邏輯代碼放在一個Activity中時,那這個Activity會擁有多少行代碼,爲維護帶來很是的不便。 app
再來講說TabActivity的不足之處,首先,TabActivity本身獨有的視圖幾乎沒人使用(也就是難看的標籤頁按鈕形式),國內開發者用到的特性幾乎都是從ActivityGroup繼承下來的。還有就是TabActivity的強制依賴關係,它的佈局文件必須將TabHost做根標籤,而且id必須爲"@android:id/tabhost",必須有TabWidget標籤,且它的id必須是"@android:id/tabs",還有加載Activity的View容器,id必須爲@android:id/tabcontent。光是強制依賴關係,我就以爲不是很舒服。不只僅是TabActivity,在一些特殊的Activity中,如ListActivity都存在這種強制依賴關係,ListActivity必須有id爲xxx(想不起來了)的ListView,我想這些弊端應該得到Google開發者的重視。 ide
那麼我下面我就將本身實現ActivityGroup,告別強制依賴關係,併爲所欲爲的創建視圖。下面這個類是一個抽象類,開發者只需對這個抽象類稍作修改,並加以實現本身的視圖就能告別TabActivity。 模塊化
package com.chenjun.demo.abstracttabactivity; import android.app.Activity; import android.app.ActivityGroup; import android.app.LocalActivityManager; import android.content.Intent; import android.os.Bundle; import android.view.ViewGroup; import android.widget.CompoundButton; import android.widget.LinearLayout; import android.widget.LinearLayout.LayoutParams; import android.widget.RadioButton; /** * 本身實現的一個通用ActivityGroup。 * 能夠經過簡單的重寫它來製做有導航按鈕和用導航按鈕控制動態加載Activity的ActivityGroup。 * 開發者須要在實現類中實現三個方法: * 1.指定動態加載Activity的容器的對象,getContainer()方法。 * 2.初始化全部的導航按鈕,initRadioBtns()方法,開發者要遍歷全部的導航按鈕並執行initRadioBtn(int id)方法。 * 3.實現導航按鈕動做監聽器的具體方法,onCheckedChanged(...)方法。這個方法將實現某個導航按鈕與要啓動對應的Activity的關聯關係,能夠調用setContainerView(...)方法。 * @author zet * */ public abstract class AbstractMyActivityGroup extends ActivityGroup implements CompoundButton.OnCheckedChangeListener{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); initRadioBtns(); } //加載Activity的View容器,容器應該是ViewGroup的子類 private ViewGroup container; private LocalActivityManager localActivityManager; /** * 加載Activity的View容器的id並非固定的,將命名規則交給開發者 * 開發者能夠在佈局文件中自定義其id,經過重寫這個方法得到這個View容器的對象 * @return */ abstract protected ViewGroup getContainer(); /** * 供實現類調用,根據導航按鈕id初始化按鈕 * @param id */ protected void initRadioBtn(int id){ RadioButton btn = (RadioButton) findViewById(id); btn.setOnCheckedChangeListener(this); } /** * 開發者必須重寫這個方法,來遍歷並初始化全部的導航按鈕 */ abstract protected void initRadioBtns(); /** * 爲啓動Activity初始化Intent信息 * @param cls * @return */ private Intent initIntent(Class<?> cls){ return new Intent(this, cls).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); } /** * 供開發者在實現類中調用,能將Activity容器內的Activity移除,再將指定的某個Activity加入 * @param activityName 加載的Activity在localActivityManager中的名字 * @param activityClassTye 要加載Activity的類型 */ protected void setContainerView(String activityName, Class<?> activityClassTye){ if(null == localActivityManager){ localActivityManager = getLocalActivityManager(); } if(null == container){ container = getContainer(); } //移除內容部分所有的View container.removeAllViews(); Activity contentActivity = localActivityManager.getActivity(activityName); if (null == contentActivity) { localActivityManager.startActivity(activityName, initIntent(activityClassTye)); } //加載Activity container.addView( localActivityManager.getActivity(activityName) .getWindow().getDecorView(), new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); } }
須要重寫的方法以及爲何須要重寫我都已在原代碼中標明。下面咱們來具體的實現這個類,來達到咱們想要的預期。 佈局
package com.chenjun.demo.abstracttabactivity; import android.os.Bundle; import android.view.ViewGroup; import android.widget.CompoundButton; import android.widget.RadioButton; public class TestMyActivityGroup extends AbstractMyActivityGroup{ //加載的Activity的名字,LocalActivityManager就是經過這些名字來查找對應的Activity的。 private static final String CONTENT_ACTIVITY_NAME_0 = "contentActivity0"; private static final String CONTENT_ACTIVITY_NAME_1 = "contentActivity1"; private static final String CONTENT_ACTIVITY_NAME_2 = "contentActivity2"; private static final String CONTENT_ACTIVITY_NAME_3 = "contentActivity3"; private static final String CONTENT_ACTIVITY_NAME_4 = "contentActivity4"; @Override protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.my_activity_group); super.onCreate(savedInstanceState); ((RadioButton)findViewById(R.id.radio_button0)).setChecked(true); } /** * 找到自定義id的加載Activity的View */ @Override protected ViewGroup getContainer() { return (ViewGroup) findViewById(R.id.container); } /** * 初始化按鈕 */ @Override protected void initRadioBtns() { initRadioBtn(R.id.radio_button0); initRadioBtn(R.id.radio_button1); initRadioBtn(R.id.radio_button2); initRadioBtn(R.id.radio_button3); initRadioBtn(R.id.radio_button4); } /** * 導航按鈕被點擊時,具體發生的變化 */ @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (isChecked) { switch (buttonView.getId()) { case R.id.radio_button0: setContainerView(CONTENT_ACTIVITY_NAME_0, ContentActivity0.class); break; case R.id.radio_button1: setContainerView(CONTENT_ACTIVITY_NAME_1, ContentActivity1.class); break; case R.id.radio_button2: setContainerView(CONTENT_ACTIVITY_NAME_2, ContentActivity2.class); break; case R.id.radio_button3: setContainerView(CONTENT_ACTIVITY_NAME_3, ContentActivity3.class); break; case R.id.radio_button4: setContainerView(CONTENT_ACTIVITY_NAME_4, ContentActivity4.class); break; default: break; } } } }
佈局文件: 學習
<?xml version="1.0" encoding="utf-8"?> <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_marginTop="0.0px" xmlns:android="http://schemas.android.com/apk/res/android"> <LinearLayout android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <LinearLayout android:id="@+id/container" android:layout_width="fill_parent" android:layout_height="0.0dip" android:layout_weight="1.0" /> <RadioGroup android:gravity="center_vertical" android:layout_gravity="bottom" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content"> <RadioButton android:id="@+id/radio_button0" android:layout_marginTop="2.0dip" android:text="按鈕1" style="@style/tab_radio" android:drawableTop="@android:drawable/ic_menu_call" /> <RadioButton android:id="@+id/radio_button1" android:layout_marginTop="2.0dip" android:text="按鈕2" style="@style/tab_radio" android:drawableTop="@android:drawable/ic_menu_camera" /> <RadioButton android:id="@+id/radio_button2" android:layout_marginTop="2.0dip" android:text="按鈕3" style="@style/tab_radio" android:drawableTop="@android:drawable/ic_menu_agenda" /> <RadioButton android:id="@+id/radio_button3" android:layout_marginTop="2.0dip" android:text="按鈕4" style="@style/tab_radio" android:drawableTop="@android:drawable/ic_menu_delete" /> <RadioButton android:id="@+id/radio_button4" android:layout_marginTop="2.0dip" android:text="按鈕5" style="@style/tab_radio" android:drawableTop="@android:drawable/ic_menu_help" /> </RadioGroup> </LinearLayout> </LinearLayout>
具體的實現效果(這裏Activity基本沒有內容): 字體
具體的代碼演示就差很少了,這裏要作一些說明的: this
1.開發者在本身的實現類中的onCreate方法中,必須先設置視圖,再調用super.oncreate(...)方法。具體爲何看了抽象類的源代碼我相信讀者應該會明白。 spa
2.關於導航按鈕使用RadioButton。Android沒有特地爲咱們定製適合咱們在這種場合下使用的按鈕,也就是上面能夠設置簡筆畫,下面有文字說明。解決方案:1)使用ImageButton,將簡筆畫和文字說明P在一張圖片裏面,但這樣有一個很是明顯的弊端,文字說明的文字字體是固定的,是P在圖片裏的,那麼不和系統的文字同樣。若是用戶使用一些比較花哨的系統文字,而導航按鈕倒是宋體,在上面的內容部分是他的系統文字,那麼我很難想象他下一次是否還會打開您所開發的應用。2)本身去實現一個View,去代替RadioButton,出於學習目的這是好的。最佳的解決方案我仍是認爲是用RadioButton,只需對它稍作修改便可,具體能夠參照新浪微博的資源文件。 .net
缺陷反思:這些代碼都是我從重構得來的,當時開發的時候並無設計好開發流程(我是先有那個實現類,纔有了那個抽象類的)。本身寫的ActivityGroup與TabActivity相比,優勢顯而易見,缺點就是可能不穩定,但暫時沒有發現Bug,動態加載的Activity的邏輯代碼都能正確執行。