2012年12月24日月曜日

[安藤]android Fragment+tabhost 實作各個tab都有自己的view切換history

Fragment是android 3.0新加入的一個view界面。
出現了這個界面之後,總算可以在一個activity上面做任意的view切換。
原本是用來針對較大畫面的tablet機種使用,不過早期的tabhost元件也因為一次只能卡一個view,造成要在每個tab上面做view切換的時候需要實做一些旁枝末節的東西。

有了fragment之後,這些問題終於得到解決。
在這裡要介紹如何使用fragment達到使用tabhost,並在每個單獨tab裡面擁有activity的切換效果。

首先第一步,你需要使用api level 13+的package。若是還在使用舊的api包的話請更新。
之後在ADT的Menu Bar上面的project-->properties裡面更換Target。

接著建立一個fragment_layout.xml放在res/layout下面。(因為下面會介紹的fragment切換必須用到。)
<framelayout android:id="@+id/fl_fc" android:layout_height="fill_parent" android:layout_width="fill_parent" xmlns:android="http://schemas.android.com/apk/res/android">


</framelayout>

再繼承一個FragmentActivity。要給Tabhost使用的。

public class MyFragmentActivity extends FragmentActivity {

public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  if (savedInstanceState == null)  //可以判斷是不是第一次產生
  {
    setContentView(R.layout.fragmentlayout);
    FrameLayout frameLayout  = (FrameLayout)findViewById(R.id.fl_fc);
    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        ft.replace(R.id.fl_fc, new MyFragment());
        ft.commit();
    }

  }
}


再來一個基本的Fragment:
(之前的設計後來會出現無法使用popBackStack跳轉到指定的Fragment的問題,
所以把frame tracsaction 獨立出來為一個function, 更新為可以泛用的設計。)
public class MyFragment extends Fragment implements View.OnClickListener {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {  //補充:據說savedInstanceState這東西永遠不會出現... 另外onCreateView會一直被call,就算是從上一層回來,你並沒有去destroy它...   
        super.onCreateView(inflater, container, savedInstanceState);
        View view = inflater.inflate( R.layout.myfragmentlayout, container, false);    //attachToRoot 必須為 false!
        RelativeLayout rl = (RelativeLayout)view.findViewById(R.id.rl);  //這只是demo該怎麼取layout上面的view object
        view.setFocusableInTouchMode(true);  //這行是為了要讓back鍵可以作用在fragment上面而加的。
        view.requestFocus();   //這行是為了要讓back鍵可以作用在fragment上面而加的。        
        return view;
    }

    //按鈕換頁的動作。
    public void onClick(View arg0) {
        Bundle args = new Bundle();
        args.putInt("data", 0);

        //下面的這個method, 使用在FragmentActivity class上面的時候,用「getSupportFragmentManager」,使用在Fragment class上面,用「getFragmentManager()」
        FragmentTran(MyFragment.class, getSupportFragmentManager(), null);

    }
}
以下是更換Fragment的動作。當作Static Method用。
public static void FragmentTran(Class fragment, FragmentManager fm, Bundle bundle)
{
        FragmentTransaction ft = fm.beginTransaction();
        try {
            Fragment newFragment = (Fragment) fragment.newInstance();
            if (bundle != null)
                newFragment.setArguments(bundle);
            ft.replace(R.id.fl_fragmentcontainer, newFragment, fragment.getName());
            ft.setTransition(FragmentTransaction.TRANSIT_ENTER_MASK);
            ft.addToBackStack(fragment.getName());  //此為讓popBackStack可以回到指定fragment的關鍵。
            ft.commit();
} catch (InstantiationException e1) {
    // TODO Auto-generated catch block
    e1.printStackTrace();
} catch (IllegalAccessException e1) {
    // TODO Auto-generated catch block
    e1.printStackTrace();
}

}
可以用這個架構如法炮製。然後tabhost裡面setcontent的動作:
tabHost.addTab(tabHost.newTabSpec("0")
.setIndicator(xxxView).setContent(new Intent(this, MyFragmentActivity.class)));

下面是一些註解:

fragment_layout.xml只是一個簡單的layout,當做容納fragment的container view。
可不可以只用程式產生不用xml指定? 解決ft.replace的第一個參數要帶resource的問題的話應該就可以,自己試看看吧?


FragmentTransaction是一個重要的動作。相當於intent的作用。能夠切換不同的fragment。使用的流程: new, 設定fragment的動作,commit()。 commit一定要在最後做。

newFragment.setArguments(args);  相當於intent的putExtra.
在你的下一個Fragment裡面使用getArguments();來取得傳來的Bundle。

ft.setTransition可以指定fragment切換的時候的換頁淡入淡出效果。

ft.addToBackStack(fragment.getName()); 可以將這次的整個切換流程記錄在stack裡面,方便按下back按鈕的時候恢復成之前的狀態。
手動pop的方法:

    getActivity().getSupportFragmentManager().popBackStack();


要操作context的時候用 getActivity() 來替代。

view.setFocusableInTouchMode(true);
view.requestFocus();
這兩個動作是為了保證這個fragment一定會在focus狀態,不會出現按了back鍵結果整個
FragmentActivity被關掉。(為了找這問題花了點時間...)

inflate的時候使用View.inflate(getActivity)的方法會出問題。有興趣的話也可以試看看。






簡述transaction的各個動作:






add(int containerid, Fragment, String tag)
新增 fragment 到container(通常是一個Layout)上面. 可重複加,顯示階層是後面疊前面。
remove(Fragment)
移除 fragment. 不須指定在哪個container. 此動作會消滅所指定的fragment.
replace(mContainerId, Fragment)
移除contriner上面舊的 fragment, 並更換為新的. 舊的fragment只會被pause不會被消滅.
hide()
隱藏fragment。 跟設定visibility為invisible的動作類似。
show()
隱藏fragment。 跟設定visibility為visible的動作類似。
detach() (API 13)
attach() (API 13)
        這兩個還沒研究到。不解釋。





參考連結:
    http://marakana.com/s/post/1250/android_fragments_tutorial
    針對fragment的系統面的解釋比官方文件還要簡單易懂。
    
    http://givemepass.blogspot.tw/2012/07/tabactivity.html
    中文的fragment+tab解說。一開始先採用了文中的方法,後來才發現backstack
    只能有一組,回頭來改用此文的設計方式,達到在每個activity裡面都可以有不同的
    fragment stack紀錄。

1 件のコメント:

  1. 註解寫得不錯,

    但是function 放錯地方, 名稱有的沒有改到

    若都修正了應該會很不錯

    返信削除