Android使用Fragment来实现TabHost的功能(解决切换Fragment状态不保存)以及各个Fragment之间的通信


如新浪微博下面的标签切换功能,我以前也写过一篇博文(http://www.cnblogs.com/tiantianbyconan/archive/2012/02/24/2366237.html),可以实现,用的是TabHost。但是android发展比较迅速,TabHost这玩意现在已经被弃用了,虽说用现在也能用,但是被弃用的东西还是少用为妙。

官方有个FragmentTabHost这么一个替代品,于是试了一下,发现每次切换tab,都会调用onCreateView()方法,控件被重新加载,也就是说你从tab1切换到别的tab后,再切换回来,tab1的状态并没有保存,重新加载了控件。

搞了半天,暂时没有好的解决办法(有朋友知道解决办法的话,希望联系我,赐教下哈)

于是,怒了,自己实现一个吧- -

 

先来看看整个demo的结构:

TabAFm到TabEFm都是Fragment,并且每个Fragment对应一个布局文件。

TabAFm.java:



 1 package com.wangjie.fragmenttabhost;
2
3 import android.app.Activity;
4 import android.os.Bundle;
5 import android.support.v4.app.Fragment;
6 import android.view.LayoutInflater;
7 import android.view.View;
8 import android.view.ViewGroup;
9
10 /*
11 Created with IntelliJ IDEA.
12 Author: wangjie email:tiantian.china.2@gmail.com
13 Date: 13-6-14
14 Time: 下午2:39
15 /
16 public class TabAFm extends Fragment{
17 @Override
18 public void onAttach(Activity activity) {
19 super.onAttach(activity);
20 System.out.println(“AAAAAAAAAAonAttach”);
21 }
22
23 @Override
24 public void onCreate(Bundle savedInstanceState) {
25 super.onCreate(savedInstanceState);
26 System.out.println(“AAAAAAAAAA
onCreate”);
27 }
28
29 @Override
30 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
31 System.out.println(“AAAAAAAAAAonCreateView”);
32 return inflater.inflate(R.layout.tab_a, container, false);
33 }
34
35 @Override
36 public void onActivityCreated(Bundle savedInstanceState) {
37 super.onActivityCreated(savedInstanceState);
38 System.out.println(“AAAAAAAAAA
onActivityCreated”);
39 }
40
41 @Override
42 public void onStart() {
43 super.onStart();
44 System.out.println(“AAAAAAAAAAonStart”);
45 }
46
47 @Override
48 public void onResume() {
49 super.onResume();
50 System.out.println(“AAAAAAAAAA
onResume”);
51 }
52
53 @Override
54 public void onPause() {
55 super.onPause();
56 System.out.println(“AAAAAAAAAAonPause”);
57 }
58
59 @Override
60 public void onStop() {
61 super.onStop();
62 System.out.println(“AAAAAAAAAA
onStop”);
63 }
64
65 @Override
66 public void onDestroyView() {
67 super.onDestroyView();
68 System.out.println(“AAAAAAAAAAonDestroyView”);
69 }
70
71 @Override
72 public void onDestroy() {
73 super.onDestroy();
74 System.out.println(“AAAAAAAAAA
onDestroy”);
75 }
76
77 @Override
78 public void onDetach() {
79 super.onDetach();
80 System.out.println(“AAAAAAAAAA____onDetach”);
81 }
82 }


View Code

如上述代码所示,TabAFm是一个Fragment,对应的布局文件是tab_a.xml,并实现了他的所有的生命周期回调函数并打印,便于调试

tab_a.xml布局中有个EditText

其他的Fragment大同小异,这里就不贴出代码了

 

现在来看MainActivity:



 1 package com.wangjie.fragmenttabhost;
2
3 import android.os.Bundle;
4 import android.support.v4.app.Fragment;
5 import android.support.v4.app.FragmentActivity;
6 import android.widget.RadioGroup;
7
8 import java.util.ArrayList;
9 import java.util.List;
10
11 public class MainActivity extends FragmentActivity {
12 /*
13 Called when the activity is first created.
14 */
15 private RadioGroup rgs;
16 public List<Fragment> fragments = new ArrayList<Fragment>();
17
18 public String hello = “hello “;
19
20 @Override
21 public void onCreate(Bundle savedInstanceState) {
22 super.onCreate(savedInstanceState);
23 setContentView(R.layout.main);
24
25 fragments.add(new TabAFm());
26 fragments.add(new TabBFm());
27 fragments.add(new TabCFm());
28 fragments.add(new TabDFm());
29 fragments.add(new TabEFm());
30
31
32 rgs = (RadioGroup) findViewById(R.id.tabs_rg);
33
34 FragmentTabAdapter tabAdapter = new FragmentTabAdapter(this, fragments, R.id.tab_content, rgs);
35 tabAdapter.setOnRgsExtraCheckedChangedListener(new FragmentTabAdapter.OnRgsExtraCheckedChangedListener(){
36 @Override
37 public void OnRgsExtraCheckedChanged(RadioGroup radioGroup, int checkedId, int index) {
38 System.out.println(“Extra—- “ + index + “ checked!!! “);
39 }
40 });
41
42 }
43
44 }


View Code

MainActivity上述代码所示

MainActivity是包含Fragment的Activity(也就是这里的5个Fragment)

他继承了FragmentActivity(因为我这里用的是android-support-v4.jar)

用一个List<Fragment>去维护5个Fragment,也就是5个tab

main布局中有一个id为tab_content的FrameLayout,用来存放要显示的Fragment。底部有一个RadioGroup,用于tab的切换,如下:



  1 <?xml version=”1.0” encoding=”utf-8”?>
2 <LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android
3 android:orientation=”vertical”
4 android:layout_width=”fill_parent”
5 android:layout_height=”fill_parent”
6 android:background=”@android:color/white”
7 >
8 <LinearLayout
9 android:layout_width=”fill_parent”
10 android:layout_height=”fill_parent”
11 android:orientation=”vertical”
12 >
13
14 <FrameLayout
15 android:id=”@+id/tab_content”
16 android:layout_width=”fill_parent”
17 android:layout_height=”0dp”
18 android:layout_weight=”1.0”
19 android:background=”#77557799”
20 />
21
22 <RadioGroup
23 android:id=”@+id/tabs_rg”
24 android:layout_width=”fill_parent”
25 android:layout_height=”wrap_content”
26 android:orientation=”horizontal”
27 android:gravity=”center”
28 android:paddingTop=”7dp”
29 android:paddingBottom=”7dp”
30 >
31 <RadioButton
32 android:id=”@+id/tab_rb_a”
33 android:layout_width=”0dp”
34 android:layout_height=”wrap_content”
35 android:drawableTop=”@drawable/tablatestalert”
36 android:button=”@null”
37 android:text=”Tab1”
38 android:textColor=”#000000”
39 android:textSize=”13sp”
40 android:layout_weight=”1.0”
41 android:gravity=”center”
42 android:singleLine=”true”
43 android:checked=”true”
44 android:background=”@drawable/selector_tab”
45 />
46 <RadioButton
47 android:id=”@+id/tab_rb_b”
48 android:layout_width=”0dp”
49 android:layout_height=”wrap_content”
50 android:drawableTop=”@drawable/tabsearch”
51 android:button=”@null”
52 android:text=”Tab2”
53 android:textColor=”#000000”
54 android:textSize=”13sp”
55 android:layout_weight=”1.0”
56 android:gravity=”center”
57 android:singleLine=”true”
58 android:background=”@drawable/selector_tab”
59 />
60 <RadioButton
61 android:id=”@+id/tab_rb_c”
62 android:layout_width=”0dp”
63 android:layout_height=”wrap_content”
64 android:drawableTop=”@drawable/tabrecommd”
65 android:button=”@null”
66 android:text=”Tab3”
67 android:textColor=”#000000”
68 android:textSize=”13sp”
69 android:layout_weight=”1.0”
70 android:gravity=”center”
71 android:singleLine=”true”
72 android:background=”@drawable/selector_tab”
73 />
74 <RadioButton
75 android:id=”@+id/tab_rb_d”
76 android:layout_width=”0dp”
77 android:layout_height=”wrap_content”
78 android:drawableTop=”@drawable/tabconfigicon”
79 android:button=”@null”
80 android:text=”Tab4”
81 android:textColor=”#000000”
82 android:textSize=”13sp”
83 android:layout_weight=”1.0”
84 android:gravity=”center”
85 android:singleLine=”true”
86 android:background=”@drawable/selector_tab”
87 />
88 <RadioButton
89 android:id=”@+id/tab_rb_e”
90 android:layout_width=”0dp”
91 android:layout_height=”wrap_content”
92 android:drawableTop=”@drawable/tababoutus”
93 android:button=”@null”
94 android:text=”Tab5”
95 android:textColor=”#000000”
96 android:textSize=”13sp”
97 android:layout_weight=”1.0”
98 android:gravity=”center”
99 android:singleLine=”true”
100 android:background=”@drawable/selector_tab”
101 />
102
103 </RadioGroup>
104 </LinearLayout>
105 </LinearLayout>


View Code

现在回到MainActivity中,下面这个FragmentTabAdapter类是关键,是我自己编写的用于绑定和处理fragments和RadioGroup之间的逻辑关系


FragmentTabAdapter tabAdapter = new FragmentTabAdapter(this, fragments, R.id.tab_content, rgs);

 

现在看下FragmentTabAdapter:



  1 package com.wangjie.fragmenttabhost;
2
3 import android.support.v4.app.Fragment;
4 import android.support.v4.app.FragmentActivity;
5 import android.support.v4.app.FragmentTransaction;
6 import android.widget.RadioGroup;
7
8 import java.util.List;
9
10 /
11 Created with IntelliJ IDEA.
12 Author: wangjie email:tiantian.china.2@gmail.com
13 Date: 13-10-10
14 Time: 上午9:25
15 */
16 public class FragmentTabAdapter implements RadioGroup.OnCheckedChangeListener{
17 private List<Fragment> fragments; // 一个tab页面对应一个Fragment
18 private RadioGroup rgs; // 用于切换tab
19 private FragmentActivity fragmentActivity; // Fragment所属的Activity
20 private int fragmentContentId; // Activity中所要被替换的区域的id
21
22 private int currentTab; // 当前Tab页面索引
23
24 private OnRgsExtraCheckedChangedListener onRgsExtraCheckedChangedListener; // 用于让调用者在切换tab时候增加新的功能
25
26 public FragmentTabAdapter(FragmentActivity fragmentActivity, List<Fragment> fragments, int fragmentContentId, RadioGroup rgs) {
27 this.fragments = fragments;
28 this.rgs = rgs;
29 this.fragmentActivity = fragmentActivity;
30 this.fragmentContentId = fragmentContentId;
31
32 // 默认显示第一页
33 FragmentTransaction ft = fragmentActivity.getSupportFragmentManager().beginTransaction();
34 ft.add(fragmentContentId, fragments.get(0));
35 ft.commit();
36
37 rgs.setOnCheckedChangeListener(this);
38
39
40 }
41
42 @Override
43 public void onCheckedChanged(RadioGroup radioGroup, int checkedId) {
44 for(int i = 0; i < rgs.getChildCount(); i++){
45 if(rgs.getChildAt(i).getId() == checkedId){
46 Fragment fragment = fragments.get(i);
47 FragmentTransaction ft = obtainFragmentTransaction(i);
48
49 getCurrentFragment().onPause(); // 暂停当前tab
50 // getCurrentFragment().onStop(); // 暂停当前tab
51
52 if(fragment.isAdded()){
53 // fragment.onStart(); // 启动目标tab的onStart()
54 fragment.onResume(); // 启动目标tab的onResume()
55 }else{
56 ft.add(fragmentContentId, fragment);
57 }
58 showTab(i); // 显示目标tab
59 ft.commit();
60
61 // 如果设置了切换tab额外功能功能接口
62 if(null != onRgsExtraCheckedChangedListener){
63 onRgsExtraCheckedChangedListener.OnRgsExtraCheckedChanged(radioGroup, checkedId, i);
64 }
65
66 }
67 }
68
69 }
70
71 /
72 切换tab
73 @param idx
74 /
75 private void showTab(int idx){
76 for(int i = 0; i < fragments.size(); i++){
77 Fragment fragment = fragments.get(i);
78 FragmentTransaction ft = obtainFragmentTransaction(idx);
79
80 if(idx == i){
81 ft.show(fragment);
82 }else{
83 ft.hide(fragment);
84 }
85 ft.commit();
86 }
87 currentTab = idx; // 更新目标tab为当前tab
88 }
89
90 /**
91 获取一个带动画的FragmentTransaction
92 @param index
93 @return
94 /
95 private FragmentTransaction obtainFragmentTransaction(int index){
96 FragmentTransaction ft = fragmentActivity.getSupportFragmentManager().beginTransaction();
97 // 设置切换动画
98 if(index > currentTab){
99 ft.setCustomAnimations(R.anim.slide_left_in, R.anim.slide_left_out);
100 }else{
101 ft.setCustomAnimations(R.anim.slide_right_in, R.anim.slide_right_out);
102 }
103 return ft;
104 }
105
106 public int getCurrentTab() {
107 return currentTab;
108 }
109
110 public Fragment getCurrentFragment(){
111 return fragments.get(currentTab);
112 }
113
114 public OnRgsExtraCheckedChangedListener getOnRgsExtraCheckedChangedListener() {
115 return onRgsExtraCheckedChangedListener;
116 }
117
118 public void setOnRgsExtraCheckedChangedListener(OnRgsExtraCheckedChangedListener onRgsExtraCheckedChangedListener) {
119 this.onRgsExtraCheckedChangedListener = onRgsExtraCheckedChangedListener;
120 }
121
122 /**
123 切换tab额外功能功能接口
124 */
125 static class OnRgsExtraCheckedChangedListener{
126 public void OnRgsExtraCheckedChanged(RadioGroup radioGroup, int checkedId, int index){
127
128 }
129 }
130
131 }


View Code

这里解决Fragment切换重新加载布局的办法,用的是把几个Fragment全部Add,然后根据要显示的哪个Fragment设置show或者hide

效果输出:

10-10 11:55:41.168: INFO/System.out(18368): AAAAAAAAAAonAttach      // 第一次进入,显示TabA
10-10 11:55:41.168: INFO/System.out(18368): AAAAAAAAAA
onCreate
10-10 11:55:41.168: INFO/System.out(18368): AAAAAAAAAAonCreateView
10-10 11:55:41.175: INFO/System.out(18368): AAAAAAAAAA
onActivityCreated
10-10 11:55:41.179: INFO/System.out(18368): AAAAAAAAAAonStart
10-10 11:55:41.179: INFO/System.out(18368): AAAAAAAAAA
onResume
10-10 11:55:44.980: INFO/System.out(18368): AAAAAAAAAAonPause      // 从TabA切换到TabB(TabA调用onPause)
10-10 11:55:44.980: INFO/System.out(18368): Extra—- 1 checked!!!
10-10 11:55:44.996: INFO/System.out(18368): BBBBBBBBBBB
onAttach
10-10 11:55:44.996: INFO/System.out(18368): BBBBBBBBBBBonCreate
10-10 11:55:44.996: INFO/System.out(18368): BBBBBBBBBBB
onCreateView
10-10 11:55:45.004: INFO/System.out(18368): BBBBBBBBBBBonActivityCreated
10-10 11:55:45.004: INFO/System.out(18368): BBBBBBBBBBB
onStart
10-10 11:55:45.004: INFO/System.out(18368): BBBBBBBBBBBonResume
10-10 11:55:52.062: INFO/System.out(18368): BBBBBBBBBBB
onPause      // 从TabB切换到TabC(TabB调用onPause)
10-10 11:55:52.062: INFO/System.out(18368): Extra—- 2 checked!!!
10-10 11:55:52.082: INFO/System.out(18368): CCCCCCCCCConAttach
10-10 11:55:52.082: INFO/System.out(18368): CCCCCCCCCC
onCreate
10-10 11:55:52.086: INFO/System.out(18368): CCCCCCCCCConCreateView
10-10 11:55:52.090: INFO/System.out(18368): CCCCCCCCCC
onActivityCreated
10-10 11:55:52.090: INFO/System.out(18368): CCCCCCCCCConStart
10-10 11:55:52.090: INFO/System.out(18368): CCCCCCCCCC
onResume
10-10 11:56:06.535: INFO/System.out(18368): CCCCCCCCCConPause      // 从TabC切换到TabB(TabC调用onPause)
10-10 11:56:06.535: INFO/System.out(18368): BBBBBBBBBBB
onResume    // 从TabC切换到TabB(TabB调用onResume)
10-10 11:56:06.535: INFO/System.out(18368): Extra—- 1 checked!!!

 

好了,到此为止,我们已经用Fragment实现了类似TabHost的功能了,下面来看下各个Fragment之间的通信

现在的情况是TabAFm中有个EditText,TabBFm中有个Button,MainActivity中有个变量“hello”

要做的是,切换到TabA,输入“I’m TabA”,切换到B,点击Button后,Toast显示“hello I’m TabA”

MainActivity中没什么好说的,就一个hello变量:


public String hello = “hello “;

TabAFm在布局文件tab_a.xml中加个EditText,设置个id就可以了

TabBFm中:



 1 @Override
2 public void onActivityCreated(Bundle savedInstanceState) {
3 super.onActivityCreated(savedInstanceState);
4 System.out.println(“BBBBBBBBBBB____onActivityCreated”);
5 this.getView().findViewById(R.id.clickme).setOnClickListener(new View.OnClickListener() {
6 @Override
7 public void onClick(View view) {
8 // 获得绑定的FragmentActivity
9 MainActivity activity = ((MainActivity)getActivity());
10 // 获得TabAFm的控件
11 EditText editText = (EditText) activity.fragments.get(0).getView().findViewById(R.id.edit);
12
13 Toast.makeText(activity, activity.hello + editText.getText(), Toast.LENGTH_SHORT).show();
14 }
15 });
16 }


View Code


// 获得绑定的FragmentActivity
MainActivity activity = ((MainActivity)getActivity());

通过getActivity()即可得到Fragment所在的FragmentActivity

 

最终效果图:

demo下载地址:http://pan.baidu.com/s/1wxsIX



来源博客:Wang Jie's Blog's Blog
本文链接:https://blog.wangjiegulu.com/2013/10/10/Android使用Fragment来实现TabHost的功能(解决切换Fragment状态不保存)以及各个Fragment之间的通信/
版权声明:本博客所有文章除特别声明外,均采用 CC BY 4.0 CN协议 许可协议。转载请注明出处。