Android 使用 Fragment 建立 Tab 取代 TabActivity

Fragment Tab Demo

註了又註:要建立 Tab 請改用 SlidingTab,容易實作,操作更靈活。(修改於 2015/6/11)
註:TabActivity 已經在 Android 3.2(API Level 13) 被棄用了,請改用 Actionbar。(修改於 2015/3/16)

原由

在很久很久以前(也才不過一、兩年前吧~)使用 TabActivity 來製作 Tab ,當初還不熟 Android ,花了點時間才做出 Tab。某天程式寫著寫著,突然發現 名稱上出現了一條刪除線,還加上黃色的毛毛蟲底線,然後想這是什麼鬼,查了一下才知道,原來是被 Deprecated ,就是被拋棄了 Q.Q,從 API Level 13(Android 3.2) 開始被棄用,官方文件是說,請改用 Fragment 來取代這個功能。

沒時間加上懶,所以一直拖著沒解決這個問題,眼看著 API Level 都已經來到 17 了,還是花點時間來研究看看,如何用 Fragment 來取代原本的 TabActivity 。Fragment 我還沒實際使用過,所以這篇只著重在使用 Fragment 製作 Tab 的方法上。

說明

首先會有一個主要的母體,用來包含其他的 Fragment ,這個母體要去繼承 FragmentActivity,其他被包含在其中的 tab 則要繼承 Fragment,所以現在會有一個主要的 MainActivity 及其子頁籤 AFragment, BFragment, CFragment, ...看你要有幾個子頁籤。

接下來是 Layout 檔,給 MainActivity 用的 layout 是使用 android.support.v4.app.FragmentTabHost 來製作母體,這和之前用 TabActivity 的方式差不多,只是把原本的 TabHost 換成這個 FragmentTabHost 而以。

其他子頁籤的 layout 則和一般 layout 一樣,就看你的頁籤內容是什麼。

動手

1. Layout (main.xml)

<android.support.v4.app.FragmentTabHost
 xmlns:android="http://schemas.android.com/apk/res/android"
 android:id="@android:id/tabhost"
 android:layout_width="match_parent"
 android:layout_height="match_parent">

   <LinearLayout
   android:orientation="vertical"
   android:layout_width="match_parent"
   android:layout_height="match_parent">

     <TabWidget
     android:id="@android:id/tabs" 
     android:orientation="horizontal"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:layout_weight="0"/>

     <FrameLayout
     android:id="@android:id/tabcontent"
     android:layout_width="0dp"
     android:layout_height="0dp"
     android:layout_weight="0"/>

     <FrameLayout
     android:id="@+id/realtabcontent"
     android:layout_width="match_parent"
     android:layout_height="0dp"
     android:layout_weight="1"/>

   </LinearLayout>
</android.support.v4.app.FragmentTabHost>
其他的子 layout 我只是單純放一個文字框用來顯示假內容,就自行製作吧~

2.程式碼 (MainActivity.java)

package com.tonycube.fragmenttabdemo;
import com.tonycube.demo.R;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTabHost;
public class MainActivity extends FragmentActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.main);

    FragmentTabHost tabHost = (FragmentTabHost)findViewById(android.R.id.tabhost);

    tabHost.setup(this, getSupportFragmentManager(), R.id.realtabcontent);

    //1
    tabHost.addTab(tabHost.newTabSpec("Apple")
                          .setIndicator("Apple"), 
                   AppleFragment.class, 
                   null);
    //2
    tabHost.addTab(tabHost.newTabSpec("Google")
                          .setIndicator("Google"), 
                   GoogleFragment.class, 
                   null);
    //3
    tabHost.addTab(tabHost.newTabSpec("Facebook")
                          .setIndicator("Facebook"), 
                   FacebookFragment.class, 
                   null);
    //4
    tabHost.addTab(tabHost.newTabSpec("Twitter")
                          .setIndicator("Twitter"), 
                   TwitterFragment.class, 
                   null);
  }

  /**************************
  * 
  * 給子頁籤呼叫用
  * 
  **************************/
  public String getAppleData(){
    return "Apple 123";
  }
  public String getGoogleData(){
    return "Google 456";
  }
  public String getFacebookData(){
    return "Facebook 789";
  }
  public String getTwitterData(){
    return "Twitter abc";
  }
}
這裡要注意的地方是,取得 FragmentTabHost 的方式是 findViewById(android.R.id.tabhost)。接著看你有幾個頁籤就用 addTab 把它加進來。下面"給子頁籤呼叫用"的 4 個方法是用來傳值給子頁籤用的,如果沒有要傳遞資料給子頁籤就可以省略。

以下我建了 4 個頁籤,分別為 AppleFragment、GoogleFragment、FacebookFragment 及 TwitterFragment ,呈現的內容都差不多,所以這邊只列出一個。

AppleFragment.java

package com.tonycube.fragmenttabdemo;
import com.tonycube.demo.R;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class AppleFragment extends Fragment {

  private String value = "";

  @Override
  public void onAttach(Activity activity) {
    super.onAttach(activity);

    MainActivity mainActivity = (MainActivity)activity;
    value = mainActivity.getAppleData();
  }

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

    return inflater.inflate(R.layout.frg_apple, container, false);
  }
  @Override
  public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    TextView txtResult = (TextView) this.getView().findViewById(R.id.textView1);
    txtResult.setText(value);
  }

}
onAttach 只有在頁籤第一次顯示時被呼叫,它會傳入母體 Activity ,所以這時候可以呼叫它的公開方法去取得值。

onCreateView 用來建立 layout 。onActivityCreated 是在母體 Activity onCreate之後呼叫,可以用來取得 layout 中的元件。這兩個方法在每次使用者點選該頁籤時都會被呼叫。

當你把剩下 3 個 Fragment 都做完後,整個就完成了,如下圖。 範例下載:FragmentTabDemo.zip
-- (補充 2014/5/29) --

讓 Tab 頁籤顯示在下方

如果要讓 Tab 頁籤顯示在下方,必須稍為調整 layout 中元件的順序,新的 main_bottom.xml 如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <FrameLayout
        android:id="@+id/realtabcontent"
        android:layout_width="match_parent"
        android:layout_height="0dip"
        android:layout_weight="1" />

    <android.support.v4.app.FragmentTabHost
        android:id="@android:id/tabhost"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <FrameLayout
            android:id="@android:id/tabcontent"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_weight="0" />
    </android.support.v4.app.FragmentTabHost>

</LinearLayout>
接著把 MainActivity.java 中的 setContentView(R.layout.main); 換成 R.layout.main_bottom 就可以了。
結果如下:

小建議:

把 Tab 設計在下方的方式對 Android 來說不太適合,為什麼?你可以看上面的結果圖,Tab 和系統的虛擬按鈕的位置非常接近,使用者有一定程度的機會,不小心誤觸虛擬按鈕而做出原本不想做的動作,這對使用者來不是個好用的設計。
-- (補充 2014/8/1) --

關於 Fragment 生命週期的補充

先看這篇的圖,

onCreate()

是用來讓你初始化資料,這時候和 View 無關,因為還沒建立,所以還不能存取任何 View 。

onCreateView()

這時候會需要讀取 layout 檔來建立 view ,並且回傳。但是這時候的 View 是指 Fragment 本身,而它的父容器也就是 Activity 這時並還未被建立,當然也就無法存取。

接著看這段

onActivityCreated()

當這個方法被呼叫時,表示整個 Activity 已經被建立了,因此這時候你對任何 View 或父容器 Activity 做動作都是可以的,如同 Activity 的 onCreate(),這兩個方法是相同的作用。

所以在 Activity 的 onCreate() 做的事,現在要在 Fragment 的 onActivityCreated() 做。
-- (補充 2015/5/12) --

關於 xml 的錯誤訊息

在 xml 的設計模式下,會出現「exception raised during rendering:no tab know for tag null」的警告訊息,並且畫面中不會出現任何物件,這是 Eclipse 無法描繪 tab 這個元件,並不是 xml 本身有錯誤,純綷是 Eclipse 不會畫 tab 而已,請忽略這個錯誤警告,只在文字模式下編輯 xml。

參考資料

本文網址:https://blog.tonycube.com/2013/07/android-fragment-tab-tabactivity.html
Tony Blog 撰寫,請勿全文複製,轉載時請註明出處及連結,謝謝 😀

88 則留言

  1. Tony大大你好

    我也用類似的方法建立了一組APP,不過我有在Fregment裡面做了FB帳號認證,造著官方的做法,也做到了認證(不是在MainActivity裡面),也取到了官方的Token使用者資料,這些資料也能顯示在登入的Fregment中,但是,我想要把這些資訊抓回MainActivity並傳給其他Fregment使用,這過程一直辦不到,後來找到TONY大大這篇教學,找到了相關傳值的方法,不過,卻還是卡在先後順序的問題。
    在MainActivity我使用下列函數呼叫Fragment

    public void onAttachFragment(Fragment fragment) {
    super.onAttachFragment(fragment);


    }

    但是卻只能叫一次,他不會隨著我另一邊的Fragment更新狀態而更新,參考了生命週期,卻也不知道能放在哪,大大有什麼更好的方法或更正或建議嗎?

    謝謝Tony大!

    回覆刪除
    回覆
    1. 今天也試了Fragment互相傳值,不過好像行不通(無法用pubic String XX())傳給另一個Fragment,本來的概念是

      1.FB登入Fragment得到值->傳給FragmentActivity->再傳給另一個Fragment顯示

      2.FB登入Fragment得到值->傳給FragmentActivity再由FragmentActivity去更改另一個Fragment的原件

      今天一直嘗試做2,但是都行不通(因為我找不到FragmentActivity類似onActivityCreated(bundle)的方法可以一直更新狀態,生命週期好像不一樣?!所以試了Fragment傳Fragment,不過好像行不通(好像不能互傳的樣子)

      再請教Tony大!!

      刪除
    2. 在 MainActivity 建立一個類別層級的變數,
      例如 private String globleData;
      接著建立它的存取子(getter, setter 方法)
      public void setGloblData(String data){
      globleData = data;
      }

      public String getGlobleData(){
      return globleData;
      }
      或是懶一點,直接把變數設為 static 也行,就不用寫存取子了。

      然後把每個子 Fragment 中,原本的
      MainActivity mainActivity = (MainActivity)activity;
      移到類別層級去建立,
      所以會有一個變數 private MainActivity mainActivity;
      在方法內指定它 mainActivity = (MainActivity)activity;
      這個是寫在 onAttach, 因為只有第一次建立的時候才需要去取得母Activity

      取值則寫在 onCreateView 或 onActivityCreated 這兩個方法在每次 Tab 被選中時都會再次呼叫。所以你就可以 mainActivity.set("xxx");
      再另一個 Fragment 中 String data = mainActivity.get();

      總之就是,你的共用值是寫在 MainActivity ,其他 Fragment 都可以去改變這個值,並在每次 oncCreateView 或 onActivityCreated 時,取得這個值,值就永遠會是最新的。你試試看吧~~

      刪除
    3. 如果你把 globleData 設為 static,
      例如 public static String globleData = "";
      那子 Fragment 中就不用把MainActivit提到外面,
      直接用 MainActivity.globleData 給值/取值就可以了。

      刪除
    4. Tony大!!我終於解決了!!好感動(哭
      這問題困擾我很久,在stackoverflow以及GOOGLE搜尋很久的答案始終沒有結果,由於是初學從頭摸索,很多問題都一直困擾我,在此感謝Tony大大!
      有問題會再請教,特此感謝Tony大 ^^

      刪除
    5. 感謝這篇教學
      我也有這個需求,但不知道仿照這個作法是否可行
      分頁一:連線資訊,設定連線的參數後開啟一個com port
      分頁二:設定ID,例如id_name 1
      分頁三:我真正想要做事情的分頁,必須先檢查分頁一的com port有沒有設定/開啟,分頁二的id 有沒有正確勾選
      做完某些事情之後,這個id的狀態可能需要改變,因此也需要修改分頁二的id狀態

      請問用您的範例來做可行嗎

      刪除
    6. 應該可以,分頁三可能要在分頁一或二沒設定的情況下顯示提示,不然使用者可能不知道要先設定。

      刪除
  2. 想請問大大!
    我想要在建立的其中一個頁籤中加上googlemap的顯示
    以及透過GPS建立自己的位置標籤在地圖上
    但是頁籤是繼承Fragment的,所以無法執行import的googlemap方法?
    我將定位的方法寫在MainActivity或是fragment中都無法執行
    請問該怎麼樣的寫法才能達到我要的效果,或是是我有地方寫錯了呢?
    新手撰寫androidapp的問題,感謝大大!

    回覆刪除
    回覆
    1. 在 Fragment 中使用地圖,要用 MapFragment
      看起來 MapFragment 會是 Fragment 的子類別,
      但其實有點複雜,要搞對關係才能用,
      請看 API 中的繼承關係(Package 名稱),

      這是 v4 版的 Fragment
      http://developer.android.com/reference/android/support/v4/app/Fragment.html
      要使用這個 SupportMapFragment
      http://developer.android.com/reference/com/google/android/gms/maps/SupportMapFragment.html

      com.google.android.gms.maps.SupportMapFragment 是 android.support.v4.app.Fragment 的子類別,所以如果你是用 v4 版的 Fragment 請用 SupportMapFragment。

      如果是用最新版本的 Fragment
      http://developer.android.com/reference/android/app/Fragment.html

      就可以用 MapFragment http://developer.android.com/reference/com/google/android/gms/maps/MapFragment.html

      如果是用 v4,layout 那邊也要寫對,API中都有寫示範。

      我沒試過在 Tab 中使用 Map,所以你要自己試看看。

      刪除
    2. 感謝大大的解說!!!
      大概有點頭緒了,看來還是要去努力研究API才行阿
      英文真的很重要阿...

      刪除
  3. Tony大,那這個傳值的方法可以應用到activity嗎?

    例如我開一個新的activity傳直給另一個activity也可以這樣做嗎?

    因為之後會用fragment去開一個可以用HTTPS傳值到SERVER,若行不通可能會以activity去開啟(之前要把GOOGLEMAP放到fragment行不通,於是我在fragment放了一個button連接到新的activity去啟動MAP),所以傳值也可以互傳嗎?還是要用Bnudle傳呢?

    在此請教Tony大大!

    回覆刪除
    回覆
    1. Activity 互相傳值,請用 startActivityForResult() 呼叫另一個 Activity把值傳過去,再用 onActivityResult() 方法等那個 Activity 傳回值。
      請參考
      http://developer.android.com/reference/android/app/Activity.html#StartingActivities

      刪除
    2. 感謝Tony大!馬上來試看看!

      感謝Tony大指導^^

      刪除
  4. mTabHost.newTabSpec("tab1").setIndicator("Tab 1", this.getResources().getDrawable( android.R.drawable.star_on))

    這個不能setIcon 有什麼方法可以解決呢? 謝謝

    回覆刪除
    回覆
    1. 加圖示的功能很麻煩,之前找過很多方法,大部份都是要去改 style,試了很久最後也沒改成功。

      刪除
  5. 想問大大~如何設定 執行時先顯示特定的Fragment

    回覆刪除
    回覆
    1. 好像沒這個功能(我不確定),
      建議你把它放在第一個 tab ,因為對使用者的認知來說,
      進來的第一個被顯示的 tab,通常會是第一個,
      如果你不照這個"常理"去顯示,可能會造成使用者的迷惑,
      除非你有特殊理由。

      刪除
  6. 作者已經移除這則留言。

    回覆刪除
  7. 謝謝Tony大大的程式碼分享,對新手的我幫助很大
    但是我不太了解main.xml檔案裡面,為什麼都沒有物件和畫面在裡頭?
    還有我想請問 : 怎麼把執行畫面上的Tab那行改成顯示在下方?
    新手問題 , 煩請指教 謝謝

    回覆刪除
    回覆
    1. main.xml 只是設計了一個容器,在程式碼中取得這個 FragmentTabHost 後,做 addTab 的動作來新增頁籤。

      把 Tab 顯示在下方的教學,我寫在文章的最後補充裡面,
      只是調整 layout 的排列順序。

      刪除
    2. 我了解了
      謝謝大大的補充

      刪除
  8. 請教一下Tony
    用Fragment當換頁的話
    java裡面 , 有些東西無法跟Activity一樣使用
    如:findViewById
    那有什麼方法可以改善?

    回覆刪除
    回覆
    1. 繼承 Fragment 的子類別,
      可以覆寫 "onAttach(Activity activity)" 方法,由方法的參數取得父容器的 Activity,你就可以去 findViewById。

      也可以覆寫 "onActivityCreated(Bundle savedInstanceState)" 方法,
      使用 this.getActivity() 去取得父容器的 Activity,和 "onAttach()"中取的 Activity 是同一個父容器。

      如果你是要取得本身 Fragment 的 view ,
      可以在 "onActivityCreated()" 方法中,使用 this.getView().findViewById(R.id.xxx) 來取得。

      說明書:
      http://developer.android.com/reference/android/support/v4/app/Fragment.html

      如果覺得太多東西,請先看
      getActivity()
      getView()

      傳值
      getArguments()
      setArguments(Bundle args)

      生命週期
      onActivityCreated(Bundle savedInstanceState)
      onCreate(Bundle savedInstanceState)
      onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
      onAttach(Activity activity)
      onDetach()
      onDestroy()
      onDestroyView()

      刪除
    2. 作者已經移除這則留言。

      刪除
    3. 請問版主你有寫過用
      Fragment
      使用取代Activity
      可以說明實例嗎?
      (我想在Fragment 增加 AutoCompleteTextView)

      刪除
    4. 我不確定你要實例是什麼,參考這篇
      http://blog.tonycube.com/2014/02/android-actionbarcompat-1.html
      系列文章,裡面有說明 Fragment 的使用。

      在 Fragment 增加 AutoCompleteTextView ,不就是把它拉進 layout ,只是這個 layout 是 fragment 的,不是 activity 的。

      刪除
    5. 版主我想請問的是 以前我用activity時 :
      都是在 onCreate() 的裡面寫方法
      如:button的 找 . 建立監聽 . 點擊事件、 TextView的 找 . 設定文字

      那現在我使用側選單 使用fragment時 :
      那要怎麼使用上述的方法?
      (我看過fragment的生命週期 , 還是不懂意思)
      煩請版主解答 , 謝謝 。

      刪除
    6. 看這篇
      http://developer.android.com/guide/components/fragments.html#Creating
      的圖,

      onCreate()
      是用來讓你初始化資料,這時候和 View 無關,因為還沒建立,所以還不能存取任何 View 。

      onCreateView()
      這時候會需要讀取 layout 檔來建立 view ,並且回傳。但是這時候的 View 是指 Fragment 本身,而它的父容器也就是 Activity 這時並還未被建立,當然也就無法存取。

      接著看這裡
      http://developer.android.com/guide/components/fragments.html#Lifecycle

      onActivityCreated()
      當這個方法被呼叫時,表示整個 Activity 已經被建立了,因此這時候你對任何 View 或父容器 Activity 做動作都是可以的,如同你說的單純的 Activity 的 onCreate(),這兩個方法是相同的作用。

      所以你以前在 Activity 的 onCreate() 做的事,現在要在 Fragment 的 onActivityCreated() 做。

      刪除
    7. 謝謝版主
      我有更進一步的了解了!
      對我有很大的幫助。
      謝囉

      刪除
  9. Tony大大 想請問一下
    如果想在fragment的Java中去建立一個方法
    是不是需要加一些額外的程式碼
    (例如我想在一個public void 去new一個DatePickerDialog)
    在忙碌之中還抽空來解惑 感激不盡

    回覆刪除
    回覆
    1. 每一個 Fragment 會有自己建立 View 的方法,
      在 onActivityCreated 方法中,
      你可以去取用 layout 中的 view。
      你的 DatePickerDialog 應該會寫在這裡,
      但因為它是對話框,所以你可能會有個按鈕什麼的去觸發,
      總之,你要處理 View 的顯示及加入事件等等,
      都是 onActivityCreated 方法中去做。

      刪除
    2. 謝謝Tony大大 我會試試的ˋˇˊ

      刪除
  10. 我照著上面的main.xml程式碼打出來
    在設計版面顯示怪的錯誤,開模擬器也開不起來
    是不是我的eclipse不支援Fragment
    如果要安裝更新需要怎麼使用
    目前用API19

    回覆刪除
    回覆
    1. 檢查看看 libs 資料夾裡面有沒有 android-support-v4.jar

      刪除
    2. 看錯誤訊息告訴你什麼吧~~

      刪除
  11. 能不能留個mail讓我傳一些檔案給你看看

    回覆刪除
  12. Tony老師您好
    請問Fragment有辦法連結新的Fragment 嗎?
    因為測試過如果再Fragment 開新的Intent的話,上面的Tab會不見,不知道有沒有方法,常駐上面那條Tab?

    回覆刪除
    回覆
    1. 另一篇
      http://blog.tonycube.com/2014/02/android-actionbarcompat-3.html
      有用到 FragmentTransaction 去做 Fragment 的切換,
      資料可以用 Bundle 去傳。

      刪除
  13. 請問一下,我成功把地圖座標放在某一頁分頁中了
    但是當我從其他分頁跳回來的時候(第一次點進去地圖分頁是有地圖的)
    整支程式就停止了
    是因為地圖的關係嗎?
    我試了很就一直沒成功

    回覆刪除
    回覆
    1. 要看DDMS有沒有顯示什麼訊息,
      你可以把地圖部份的程式碼先註解掉,
      看會不會停止就知道是不是有關了

      刪除
  14. 請問一下,我拿了您的範例來練習
    我在APPLE的LAYOUT裡新增了edittext,但是執行時無法輸入
    會跳到上面的tabhost 請問該怎麼解決Q_Q

    回覆刪除
    回覆
    1. 不太清楚,看一下有按鈕的範例試試:

      frg_apple.xml
      <?xml version="1.0" encoding="utf-8"?>
      <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="match_parent"
      android:layout_height="match_parent" >

      <TextView
      android:id="@+id/textView1"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_alignParentLeft="true"
      android:layout_alignParentTop="true"
      android:layout_marginLeft="105dp"
      android:layout_marginTop="94dp"
      android:text="Apple"
      android:textAppearance="?android:attr/textAppearanceLarge" />

      <EditText
      android:id="@+id/editText1"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_below="@+id/textView1"
      android:layout_centerHorizontal="true"
      android:layout_marginTop="30dp"
      android:ems="10"
      android:text="Magazine" />

      <Button
      android:id="@+id/button1"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_centerHorizontal="true"
      android:layout_centerVertical="true"
      android:text="Button" />

      </RelativeLayout>

      刪除
  15. Tony老師您好:
    我們是打算把APPLE改成登入介面(PS.有帳號密碼那種)
    但是還是無法輸入QAQ

    回覆刪除
  16. 請教Tony~
    我在練習你的範例程式碼中的main.xml檔
    會有No tab known for tag null的錯誤
    是可以執行的
    但是我將












    放進我的xml檔中
    然後把
    final FragmentTabHost tabHost = (FragmentTabHost)view.findViewById(android.R.id.tabhost);
    tabHost.setup(getActivity(), getChildFragmentManager(), R.id.realtabcontent);

    tabHost.addTab(tabHost.newTabSpec("Apple")
    .setIndicator("Apple"),
    Fragment_News.class,
    null);

    tabHost.addTab(tabHost.newTabSpec("Google")
    .setIndicator("Google"),
    Fragment_Notice.class,
    null);
    放在onCreateView中也是可以執行
    --------------------------------------------
    但是我的程式必須先去網路抓取資料後
    setAllnews() 讓Tabhost其中的一個Fragment去 getAllnews()後再顯示出來
    所以我把這段
    final FragmentTabHost tabHost = (FragmentTabHost)view.findViewById(android.R.id.tabhost);
    tabHost.setup(getActivity(), getChildFragmentManager(), R.id.realtabcontent);

    tabHost.addTab(tabHost.newTabSpec("Apple")
    .setIndicator("Apple"),
    Fragment_News.class,
    null);

    tabHost.addTab(tabHost.newTabSpec("Google")
    .setIndicator("Google"),
    Fragment_Notice.class,
    null);
    放置抓完資料後的handler裡
    但執行完後Logcat缺出現
    java.lang.IllegalStateException:No tab known for tag null
    .....
    等FragmentTabHost錯誤
    我也查過stack overflow也查不出個所以然
    想請教Tony大大知道問題或是邏輯哪裡有錯誤嗎?

    回覆刪除
    回覆
    1. 那段空白的程式碼是

      android.support.v4.app.FragmentTabHost
      android:id="@android:id/tabhost"
      android:layout_width="match_parent"
      android:layout_height="match_parent"

      LinearLayout
      android:orientation="vertical"
      android:layout_width="match_parent"
      android:layout_height="match_parent"

      TabWidget
      android:id="@android:id/tabs"
      android:orientation="horizontal"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_weight="0"/

      FrameLayout
      android:id="@android:id/tabcontent"
      android:layout_width="0dp"
      android:layout_height="0dp"
      android:layout_weight="0"/

      FrameLayout
      android:id="@+id/realtabcontent"
      android:layout_width="match_parent"
      android:layout_height="0dp"
      android:layout_weight="1"/

      /LinearLayout
      /android.support.v4.app.FragmentTabHost

      刪除
    2. 在這個(https://code.google.com/p/android/issues/detail?id=78772)討論串裡,它說在 xml 中 android.support.v4.app.FragmentTabHost 這個標籤裡面,如果沒有任何內容,就會立即丟出 No tab known for tab null 的錯誤,所以可能是你 Tabhost 在建立的時候,因為你要下載的資料還沒完成,因此尚未建立任何 tab ,就出錯了。瞭解一下 lifecycle 會有幫助。

      比較正確的做法是Tab先建好,像我的範例一樣,而下載資料的動作在各自的 Fragment 中去做非同步下載,而不是下載資料後才顯示整個 Tab。


      刪除
  17. 作者已經移除這則留言。

    回覆刪除
  18. 請教tony
    1.我可以每個fragment 擁有獨立的action bar,而不是共用MainActivity的? 即是我每次點擊tab 都會轉換action bar

    2.真的沒方法放icon?

    回覆刪除
    回覆
    1. 1.沒試過。「每次點擊tab都會轉換action bar」這樣的設計不會對使用者造成困惑嗎?

      2.還沒研究出來。

      刪除
    2. tabhost icon 你可以試試看
      LayoutInflater inflate = LayoutInflater.from(this);
      final View tab1 = inflate.inflate(R.layout.tab1, null);
      mTabHost.addTab(mTabHost.newTabSpec("tab1").setIndicator(tab1),
      Tab1Fragment.class, null);

      刪除
  19. 請教一下Tony大
    我嘗試在tab上加上卷軸(因為tab有點多),讓它們能夠滑動 但是一直失敗
    請問有沒有解決的辦法? > <

    回覆刪除
    回覆
    1. TabActivity 已經在 API Level 13(Android 3.2) 被棄用了,請改用 Actionbar,
      滑動可以參考 http://developer.android.com/design/building-blocks/tabs.html#scrollable

      刪除
  20. 想再問一下,使用了fragment是否不能使用thread呢 @_@ ?

    回覆刪除
    回覆
    1. ...又變成可以了 ... 原來字串要先初始化 才能使用

      刪除
  21. Tony大大您好,我複製你的code main.xml 這個檔案,但是貼上去的時候他都出現 "exception raised during rendering:no tab know for tag null" 這個錯誤訊息,我該如何解決??

    回覆刪除
    回覆
    1. 前面有人遇到同樣的問題,但是不知道有沒有解決。

      你看看程式碼中的 onCreateView 裡面,有沒有 tabHost.addTab() 這樣的程式碼,
      它會在建立 view 時把 tab 建好,如果沒有,就會出現 "no tab known for tag null" 的錯誤

      刪除
    2. 作者已經移除這則留言。

      刪除
  22. 請教TONY大,
    我在MainActivity的action bar 建立一個dialog,
    在關閉dialog時,要如何通知fragment去修改裡面的內容

    回覆刪除
    回覆
    1. 在 fragment 建一個 refresh() 的方法,
      這個方法裡面去做更新的動作,
      之後在 dialog 裡面去呼叫這個 refresh()

      刪除
  23. 您好,我也遇到了"exception raised during rendering:no tab know for tag null,
    可是我看的你的程式裡也沒有加tabHost.addTab() 這樣的程式碼,可是好像就過了,請問這是為什麼?
    還想再問一下請問是在子fragment中去建在主畫面的剩下三個tab嗎,這樣我不是那麼理解,
    我以為都是在mainactivity中去tabHost.add所有的Tab

    回覆刪除
    回覆
    1. 我剛剛實際看了一下xml,在文字模式時其實是沒有任何錯誤訊息,
      那段錯誤訊息是在設計模式時,Eclipse無法描繪你所指定的tab這個元件時出現的錯誤,
      所以不是xml有錯誤,而只是編輯器不能顯示的錯誤,不要理它就好,實際上你也不能做什麼,只是畫面無法顯示而已。

      你使用FragmentTabHost 當作母體,它的功能只是用來加入子元件(fragment),
      所以在MainActivity中的只是單純把子fragment加進來。
      至於子fragment,它是單獨的個體,不屬於任何母體,直到有母體把它加入。
      這樣你的MainActivity會很單純,而fragment也有各自的功能,要維護時就很容易。

      刪除
  24. 您好,請問如何在一個fragment中按一個按鈕切換到一個新的activity頁面?

    回覆刪除
    回覆
    1. activity 要開啟新的 activity 要用 intent 然後 startActivity,
      fragment 是被 activity 包含的,fragment 可以用 getActivity 來取得包含它的 activity
      然後你就可以去用它去開啟新的 activity

      刪除
    2. 請問是
      extends fragment
      改成
      extends activity嗎

      刪除
    3. 你問的東西太基礎了,我回答的內容如果看不懂,
      請去看一下 Activity 及 Fragment 的資料,
      或是官網的說明 http://developer.android.com/guide/components/fragments.html

      刪除
  25. 您好,我在fragment中放了listview,
    那simplecursoradapter在fragment裡的語法要如何撰寫?

    回覆刪除
    回覆
    1. 看看吧 https://thinkandroid.wordpress.com/2010/01/09/simplecursoradapters-and-listviews/

      刪除
  26. 您好
    想請問一個問題
    我參考您的code寫出一個apk 但是只能在4.4的版本使用
    在5.0以上的手機會有tab字元顯示不出來的問題
    想請問一下這個部分是否無法在android 5.0以上的手機使用
    謝謝

    回覆刪除
    回覆
    1. Android 版本太多,什麼都有可能,而且是從 4 跳到 5 ,更可能出問題,
      可以檢查一下 AndroidMainfest.xml 中的 SDK是否把 Target 設為 5 或最新的版本號,
      然後看看 logcat 有沒有顯示問題,
      但建議最好是改用 SlidingTab(http://blog.tonycube.com/2015/06/android-slidingtab.html),
      或查看看官方有沒有提供什麼新的解決方案。

      刪除
  27. 請問如果在第一個頁面的editText輸入文字,但是想要在點選Tab選單切到第二頁時讓該文字消除的話要如何寫呢?
    就是切換頁面再切回來時該editText是空的這樣,謝謝!

    回覆刪除
    回覆
    1. 看看 fragment 的 lifecycle(http://developer.android.com/reference/android/app/Fragment.html#Lifecycle),你可以在 onStart() 的時候把 editText.setText(""),或 onStop() 也可以。

      刪除
  28. 不好意思,再請問一下,假設我有tab1,tab2,tab3,在tab1裡面還有子頁tab1-1
    如果現在呈現的頁面是1-1,我想要設置我按下tab1的按鈕時可以直接回到tab1的頁面,請問這應該要在哪裏設置呢?
    謝謝!

    回覆刪除
    回覆
    1. 好複雜。你應該讓原本的tab1的內容當成1-0,也就是假設有3個子頁面那就要在最前面多一個頁面放tab1 的內容,這樣只要點tab1就預設顯示tab1-0

      刪除
    2. 那麼是會變成這樣嘍,假設有三個子頁,就要變成4個子頁,排列像是1-0,1-0,1-1,1-2這樣嗎?那是一樣在原本要顯示1-0的地方做設定還是在那個位置呢?不好意思,問題很多,謝謝!

      刪除
    3. 其實我覺得你的架構有點怪怪的,
      如果tab1下有子頁1-1,那所謂的tab1就只是子頁的容器,它的內容就只子頁而己,怎麼會選擇tab1還要顯示它自己的內容,那這時子頁會在哪呢?


      刪除
    4. 作者已經移除這則留言。

      刪除
    5. 不好意思~應該是我沒講清楚.
      應該說現在Tab1呈現了一個頁面1-1.然後我點擊了這一頁的按鈕進到了下一頁1-2.這時我想要去點擊Tab1讓她回到1-1的頁面

      就像大大作的Apple123那一頁的按鈕按下去之後會呈現另一個頁面.但是上面的Tab列還是在"Apple"的地方.這時我要按下"Apple"時會跳回到前一頁

      有點像重新載入的感覺.但是爬了好多地方的文都無法實現這個功能......還請大大幫幫忙

      刪除
    6. 1-1切到1-2和1-2切到1-1,做法不是一樣,
      你怎麼在1-1把畫面切到1-2的,把它寫在 tab1上不能實現嗎

      刪除
    7. 我也是這大大說得這樣寫的.可是當我正在1-2畫面的時候按Tab1怎麼樣都不會刷新頁面
      我也覺得很疑惑

      刪除
  29. 請問一下,如果要透過mainActivity接收到每個新訊息後通知fragment去更新訊息畫面,
    該怎麼從mainActivity呼叫fragment裡的更新函式呢?

    還想請問一下,會推薦寫成ActionBar 或是 ViewPager嗎? 還是如果不在呼外觀的差別就不太必要呢?
    謝謝!!

    回覆刪除
    回覆
    1. mainActivity可以持有子fragment 的實體,先在 fragment 中建立要更新的內容,mainActivity 在去呼叫它就可以了。

      看你的需求選擇,沒有好不好。

      刪除
  30. 作者已經移除這則留言。

    回覆刪除
  31. Tony你好~
    我將Google maps 放進 Fragment 後第一次點選Google Tab可以顯示,
    但點第二次就crash了!
    請問有方法可以解決嗎?
    謝謝你~

    回覆刪除
  32. Tony您好~
    因為需要跟其他設備溝通,所以我在fragment裡面做了handler來處理需要的資料,但是發現在handler使用setText會有問題,就連單純秀字串也有問題,我無法將收到的訊息setText在頁面上,確定收到的值都沒問題了(再log裡可以看到)

    請問有方法解決嗎,謝謝您~

    -----------------------------------------------------------------------------------------------------------------
    public class AppleFragment extends Fragment {

    private String value = "";
    private Button button1,button2;
    TextView text2;
    String threeinone0,threeinone1,threeinone2,threeinone3,threeinone4,threeinone5;
    TCPSend TCPSendThread;
    String WorkType;
    byte [] A ;


    @Override
    public void onAttach(Activity activity) {
    super.onAttach(activity);

    Log.d("=====>", "AppleFragment onAttach");
    MainActivity mainActivity = (MainActivity)activity;
    value = mainActivity.getAppleData();


    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
    Bundle savedInstanceState) {

    Log.d("=====>", "AppleFragment onCreateView");
    return inflater.inflate(R.layout.frg_apple, container, false);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    Log.d("=====>", "AppleFragment onActivityCreated");
    TextView txtResult = (TextView) this.getView().findViewById(R.id.textView1);
    TextView text2 = (TextView) this.getView().findViewById(R.id.textView2);


    txtResult.setText(value);


    button1=(Button)getView().findViewById(R.id.button1);
    button2=(Button)getView().findViewById(R.id.button2);

    Log.e("","error~~~~~~~~~~~~~~~~~~~~~~~");

    button1.setOnClickListener(new Button.OnClickListener(){

    public void onClick(View v) {
    Log.e("~~~~~","apple button touch");
    WorkType = "INFORMATION";
    byte[] test1 = {(byte) 0x06,(byte) 0x02,(byte) 0x83, (byte) 0xD1};
    TCPSendThread = new TCPSend("192.168.10.44", "8080", test1 , mHandler , WorkType);
    // TCPSendThread = new TCPSend("192.168.56.1", "8080", test1 , mHandler , WorkType);


    Log.e("~~~~~","TCP"+test1);
    TCPSendThread.start();


    }
    });



    button2.setOnClickListener(new Button.OnClickListener(){



    }

    //TODO Handler-------handle message---------------------------------------------------------------------------------------------------------
    private Handler mHandler = new Handler(){
    public void handleMessage(Message msg) {


    //TODO Handler------11

    if(msg.what ==11){

    byte[] result = (byte[])msg.obj;

    for(int i= 0; i < result.length;i++)
    {
    System.out.println("result:" + Integer.toString(result[i]& 0xFF));

    // System.out.println("result package:" + Integer.toString(result[i]));

    }



    double AA=(result[0]& 0xFF)*16777216;
    double BB=(result[1]& 0xFF)*65536;
    double CC=(result[2]& 0xFF)*256;
    double DD=(result[3]& 0xFF);
    double ALL=(result[0]& 0xFF)*16777216+(result[1]& 0xFF)*65536+(result[2]& 0xFF)*256+(result[3]& 0xFF);


    int ii=0;
    double AAA[] = new double[27];
    for(int i= 0; i < result.length;i=i+4){
    Log.e("i = ",""+i);
    Log.e("result.length = ",""+result.length);
    AAA[ii] = (result[i]& 0xFF)*16777216+(result[i+1]& 0xFF)*65536+(result[i+2]& 0xFF)*256+(result[i+3]& 0xFF);;
    ii=ii+1;

    }





    String A = Double.toString(AAA[0]);
    Log.e("","A="+A);
    text2.setText("123"); //********就連當純秀字串也會當掉******


    }



    }
    }
    };



    }

    回覆刪除
  33. 你可以用
    if (text2 == null){
    //..
    }
    來檢查,它應該是null,
    因為你的類別成員變數 TextView text2; 宣告後並沒有給值,
    反而在 onActivityCreated()中,又宣告了一個區域變數
    TextView text2 = (TextView) this.getView().findViewById(R.id.textView2);
    所以你的 mHandler 使用的 text2 自始至終都是 null,當然就不能對它做任何事。

    回覆刪除

留言小提醒:
1.回覆時間通常在晚上,如果太忙可能要等幾天。
2.請先瀏覽一下其他人的留言,也許有人問過同樣的問題。
3.程式碼請先將它編碼後再貼上。(線上編碼:http://bit.ly/1DL6yog)
4.文字請加上標點符號及斷行,難以閱讀者恕難回覆。
5.感謝您的留言,您的問題也可能幫助到其他有相同問題的人。