Android TabHost中切換Activity
(2013-04-15 更新)上一篇文章 Android TabHost Without TabActivity 提到如何以 Activity 取代過期的 TabActivity 來操作 TabHost,官方文件中建議以 Fragment 取代 TabActivity,但小蛙還沒試出來使用 Fragment 的方式(同事盛哥那邊有試出來,之後徵詢盛哥同意後再把 Fragment 的做法分享出來),小蛙在這邊記錄使用 Activity + TabHost + 頁籤中切換 Activity,並且保存各個頁籤的Back Stack。注意:官方建議使用 Fragment,除非是想跟小蛙一樣懶惰到不想動到整體架構,想要進行「最少修改」,還是聽從官方建議使用 Fragment 比較好喔!
上個星期跟盛哥試了一段時間使用 Fragment 後還是不得其門而入(兩個人對 Fragment 都還不太熟悉),卡住的原因是現在有兩個 Tab,當 Tab1 進入到第二個畫面,Tab2 進到第三個畫面,但使用者切回 Tab1 的時候,Tab1 的狀態無法被保留(第二畫面),這個影響就是如果使用者透過 Tab1 已經搜尋到想要的資料後,因為某些原因切到 Tab2,這時候想點回 Tab1 時,資料卻已經消失,這對使用者體驗來說是非常差的。
前天看到盛哥留言已經解決了上述問題,而在看到盛哥解決之前,小蛙也在
Android: TabActivity Nested Activities @ Henrik Larsen Toft、
Android : How to have multiple activities under a single tab of TabActivity @ GammaPoint
這兩篇文章中找到曙光,要讓Activity彼此切換,在這邊使用到了 ActivityGroup 去管理各個 Activity 切換的動作,並且額外設置了一個 ArrayList<View> history 當作 Back Stack 來使用。但小蛙照著做卻還是沒辦法使用,所以修改了一些東西。
做法從上圖可以看得出來(也有可能畫得太爛造成大家看不太懂),首先小蛙在 MainActivity 中建立了 3 個 Tab,分別是 Tab1、Tab2、Tab3,而 Tab1 中會有兩次切換 Activity 的動作(既然是兩次為什麼會有 3 個 Activity?等等後面小蛙會說明),以此類推 Tab2 也相同,Tab3 則是很單純的直接顯示一個 Activity7。
會使用到同一頁籤中切換 Activity 的所有 Activity 都必須透過 ActivityGroup 來做管理,也就是圖中的 Activity1、Activity2、Activity3 都是可以透過 ActivityGroup1 來管理,同理可說明 ActivityGroup2,每個 ActivityGroup 又另外設置了個別的 Back Stack 來管理當使用者按下 Back 鍵時的行為 (例如:該退出程式還是回到上一個 Activity、切換 Tab 時該 Tab 原本停留的狀態),至於 Activity7 因為只有單一頁面所以不需要這麼麻煩。介紹完架構家族之後,進到程式碼的部份。
承上篇文章 Android TabHost Without TabActivity,在 MainActivity 中的 TabHost 加入頁籤。
mHost.addTab(mHost.newTabSpec(getString(R.string.tab_two_name)) .setIndicator(getString(R.string.tab_two_name)) .setContent(new Intent(this, ActivityGroup1.class)));
小蛙在測試了上面兩篇文章的方法後發現,必須要在 ActivityGroup1 中直接先載入 Activity1 (也就是 ActivityGroup 僅用來”操作”這些 Activity,並沒有實質的內容呈現,如果讓 ActivityGroup 在這邊有自己的內容呈現,會造成 Back Stack 運作錯誤,也有可能是小蛙實作上出了問題,如果照著上面兩篇文章試不出來的網友,不妨參照小蛙的做法),以下是 ActivityGroup1 的程式碼:
public class ActivityGroup1 extends ActivityGroup{ /** 設定成 static 讓其他的子 Activity 可以存取 / public static ActivityGroup1 group; /* Back Stack / private ArrayList history; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.history = new ArrayList(); group = this; // ActivityGroup1 只是一個外框,在這個外框中載入其他要用的 Activity // 如果沒有這個外框會發生錯誤 View view = getLocalActivityManager() .startActivity("Activity1", new Intent(ActivityGroup1.this, Activity1.class).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)) .getDecorView(); // 馬上載入真正要執行的 Activity replaceView(view); } /* * 在 ActivityGroup 中切換 Activity * @param v / public void replaceView(View v) { // 可在這插入換頁動畫 history.add(v); setContentView(v); } /* * 當使用者按下 back 的時候,把之前存起來的 stack 撈回來顯示 / public void back() { // 原本的範例是寫 > 0,但會發生錯誤 if(history.size() > 1) { history.remove(history.size()-1); View v = history.get(history.size()-1); // 可在這插入換頁動畫 setContentView(v); }else { // back stack 沒有其他頁面可顯示,直接結束 finish(); } } /* * 複寫聆聽按下 back 事件,呼叫 back() */ // 由於我的股票精算師是使用1.6+,因此採用onKeyDown來監聽 @Override public boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_BACK: back(); break; } return true; } }
至於在Activity1中的設定如下:
public class Activity1 extends Activity{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity1); Button button = (Button) findViewById(R.id.button1); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 如果沒有這個外框會發生錯誤 View view = ActivityGroup1.group.getLocalActivityManager().startActivity("Activity2", new Intent(Activity1.this, Activity2.class).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)).getDecorView(); // 載入真正要執行的 Activity ActivityGroup1.group.replaceView(view); } }); }
基本上到這邊就已經可以正常運作囉!有什麼問題可以留言在這邊,小蛙會盡可能的協助解決。
2013-01-30 有網友反應 MediaFire 的檔案無法下載,更新下載路徑。範例檔下載。
2012-10-22 範例檔下載
您好:
我想請問我今天有tab1 , tab2,
tab1接 activitygroup -> 子activity1 -> 子activity2,
tab2接 一個activity,
我今天該如何在tab1裡的子activity2畫面中,點選tab2後,再點回tab1就可以出現子activity1的畫面呢?
目前是還是會停留在子activity2,謝謝~
您好:
最近正好用到這方面技巧,但看了你的ActivityGroup1,發現MonitorGroup group那裡會出錯,
載了您的範例檔內沒有ActivityGroup1這隻class檔,是否可以分享一下這隻原始檔與MonitorGroup。
謝謝您~
Dear james:
感謝您發現這個錯誤 …
MonitorGroup group 設定成 static 目的讓其他 activity 存取,
所以就是範例檔裡的 Activity1,
範例檔應該可以跑才對,可以直接使用範例檔下去改,
Sorry … ><
感謝您的回覆。
後來我有試出來了,謝謝。不過我沒有使用您上面寫的MonitorGroup class,因為我找不到此class。
所以後來我把 MonitorGroup 改成
public static ActivityGroup group;
這樣就可以囉~ 提供其他網友參考。
p.s 這兩天有發現有一些關於這個子activity的問題,我再研究看看,這兩天再跟版主請教討論~謝謝~
這篇真棒
最近用fragment用到超煩碰到一堆問題跑來找這篇就解決了
順便在這邊po一下最近碰到的一個問題的解決方法
當你的activity裡面有listview的時候發現back會失效(會直接結束程式)
因為keydown事件被listview吃掉了
解決方法
http://phenix.blogbus.com/logs/158999678.html
Dear huli:
感謝您的分享 ^___^
大大!! 我在 child activity 裡面放入 facebook login (官方activity方法) 但我按登入他就把我全部的Destroy 我要怎麼讓他轉跳的時候不把我原本的Activity onDestroy掉???
Dear ken:
小蛙測試之前附上的 Tab 範例檔跟照照明星臉的 Facebook login 使用上應該是沒問題唷!
小蛙測試的方法是在 Activity3 的 button 點選之後跳出 facebook 登入視窗,
成功登入後跳出發表文章到塗鴉牆的範例!
facebook.authorize( getParent(), new String[]{“publish_stream”}, new DialogListener(){
});
比較特別的地方是第一個要取得父親的 Activity 不然會出錯。
Dear 小蛙,
我這邊遇到一個狀況,不知道你是否有遇到相同的狀況。
在你的示意圖中在Activity2按下back鍵,返回到Activity1這時Activity1畫面(View)是正確的,
但其中事件都監聽器都失效了,setContentView(v) 似乎只有將View切換過去。
目前我的作法是在每一頁都加上Button以Intent的方式切換。
不知道蛙大有沒有方法可以讓back()也能正常使用的方法?
謝謝!!
我解決這個問題了,在setContentView(v)後執行一次onCreate(null)監聽器就會有效果了。
但是這樣子好像是暴力法,不知道會不會有負面影響,哈哈
還是謝謝蛙大的教學 ^^
Dear Andyang:
不好意思沒幫上什麼忙 ^_^”
可以麻煩你寄這個的範例檔給我嗎??謝謝~因為ActivityGroup1那裏就出錯了>”<!!
Dear Hsuan:
範例檔已經附在文章裡面囉!!!
小娃, 我剛學ANDROID, 現在有個問題, 我用JAVA 可以抓到網頁上我要的內, 但不知道怎麼改成ANDROID可以運作的, 我用JAVA里的REGEX把不要的內容都除掉了, 只剩下我要的轉成了ARRAY. 在JAVA中已經可以用了. 可是不懂ANDROID 怎麼套用上去.
Dear bebeyim:
首先您要先注意您所使用的抓網頁的套件是否跟android的版本相容,
(有可能JAVA中用到比較特殊的東西)
這裡不太清楚是 1. 抓的部分出問題,2. 濾出要的內容出問題,3. 轉成array 出問題,
1.的話可能是您使用的抓網頁套件在android中沒有(要記得放在libs下面)
或是改用android下內含的HttpGet來抓取,
2.跟3.應該Java跟Android實作上差不多!
(沒有錯誤訊息,只能從您的敘述中去猜測囉!)
祝您好運囉 ^____________^
Dear 小蛙,
能不能給我你的電郵, 我把資料e-mail給你看一下?
謝謝!!
Dear bebeyim:
寄到 [email protected],小蛙有空的話再幫你看看!!
小蛙,
謝謝您, 我剛E-MAIL給你了, 附件是GetPrice.java
有空請幫忙看看, 謝謝!!
原來大家都有問題啊? 請問為什麼一定要改呢? TabActivity 還是可以用啊?
要不想我想, TAB 就用ICON 代替了, 然後連去FRAGMENT, 這樣在掛版跟功能上好像沒分別?
我剛學了3天, 很多問題, 請大家幫忙多多解釋一下!
Dear bebeyim:
功能上看起來是沒什麼差異,都可以達到相同的功能,
Google會這樣做應該是有某些因素(ex. 較輕量化、安全性、手機平版一致性 …)等,
小蛙並沒有去找實際原因,以上原因是小蛙胡亂猜測的(最可能的狀態是Fragment在平板跟手機都可以有比較好的使用者體驗),
可以確定的是原本的做法Google已經宣告過期了 …
小蛙 ,你提到的那个fragment方式实现tab内部跳转解决了吗?
现在我也遇到了这个问题,还请帮忙啊!
使用 fragment 的文章在這邊 Android Tabhost with FragmentActivity,
範例檔在這邊 http://www.mediafire.com/?5z0azaqi46kng06,
先試試看囉!只有基本的實作,先跑跑範例檔看看功能是不是自己需要的!
Good Luck!^___^
我的邮箱是[email protected] 谢谢了博主
Dear wangkun:
已經寄囉!
lz 我执行这个的ActivityGroup1.group.replaceView(view); 的时候报错,group引用变量有点问题,能不能给个demo程序代码啊
看了您不錯的範例
我試了一下
目前有ActivityGroup
裡面有Activity1和Activity2
當點了Tab後
ActivityGroup會去intent Activity1
Activity1是個listActivity
當點了其中一個list的item時會intent Activity2
問題來了
1. 當ActivityGroup -> Activity1 -> Activity2
再來按back鍵會回到Activity1這邊操作沒問題
若在Activity1再按back鍵會造成Force Close
會出現以下錯誤訊息
FragmentManagerImpl.checkStateLoss() line: 1265
FragmentManagerImpl.popBackStackImmediate() line: 451
ContractedStoreListViewActivity(Activity).onBackPressed() line: 2121
2. 若ActivityGroup -> Activity1
再來按back鍵會離開App是正常的
想請問有關於問題1有何見解??
ps. 目前裝置是4.0.4, eclipse上的build target是4.0 google apis
哈上stackoverflow找到解答了
原來是API>11以上才會遇到這個問題
感謝小蛙的範例^^
^___^
太感謝你的demo檔了
看到你切換Activity還使用了animation
使在太酷了
謝謝你
Dear 寫程式要淡定:
希望對您有幫助囉!^___^
首先感謝大大的demo。。
有個問題想問一下, 用tabhost 好像要很難解決在子tab裡執行onActivityResult沒有回應的問題。。不知大大能否解釋一下 如果用你的demo來示範一下 更易明白 謝謝~~
Dear Luils:
不好意思,最近可能比較沒時間試您說的問題,
剛大概看了一下 StackOverflow 裡面一篇文章似乎有解決的方法,
等小蛙有空一些再來測試看看,
如果這篇文章中的方法可行的話,也請Luils留言讓小蛙知道唷!
感恩!^____^
http://stackoverflow.com/questions/4268178/startactivityforresult-from-activitygroup
最後花了好幾小時才解決呢。。
這篇: http://www.cnblogs.com/relinson/archive/2012/03/25/startActivityForResult.html
不過 “3.自定義接口 ” <<這個不用加上去。。
Dear Luils:
真是太感謝您了,小蛙有空再試試!
謝謝您提供方法 ^____^
路過學習 受惠於大大的demo檔 留言感謝!
Dear 小賴:
很高興能幫上您的忙
^___^
大大您好
我想請問一個問題,我依照您的範例在MainActivity中建立了3個Tab,分別是Tab1、Tab2、Tab3,在Tab1上是顯示會員資料的部分(Android-php-nysql),可是我寫好了後,程式執行時並不會馬上顯現會員資料,而是切到Tab2或Tab3的時候,然後再切回Tab1就會顯示會員資料了,這是哪部分出問題呢,研究好多天都不知道在哪邊..如果您知道的話幫忙回覆,謝謝哦。
更正錯字..Android-php-mysql
再補充一下,我發現程式剛執行時第一個畫面是tab1,但tab1裡面的按鈕原本點擊後會偵測是否有輸入字,因為按鈕上面有三個editText,若有任一個沒輸入會顯示Toast,結果也是要切換到tab2或tab3,才能使tab1裡面的功能有作用,這個問題好怪啊…
Dear 小吳:
小蛙測試的時候並沒有遇到您說的那種狀況 ~
建立一個button跟edittext,
按下button後,若edittext沒有輸入資料,
則用Toast印出訊息,
還是您要不要把程式碼mail給小蛙呢?
[email protected]
感謝大大提供的Demo檔,實在是救了小弟一命。
非常感謝哦,謝謝。
^__^
略懂,求个demo看看,感谢之
Dear 菜鳥1号:
建議您改使用Fragment + FragmentActivity,因為ActivityGroup也被Google標示為過期了!
可參考Android Tabhost with FragmentActivity
^_____^
小蛙把之前測試的範例檔案附上
(檔案有點亂,沒時間重新整理過 >< ) 範例檔下載