FragmentTabHost+Fragment实现底部导航栏功能

一、效果图

 

二、FragmentTabHost+Fragment实现

1.activity布局文件

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     xmlns:tools="http://schemas.android.com/tools"
 4     android:layout_width="match_parent"
 5     android:layout_height="match_parent"
 6     android:orientation="vertical"
 7     tools:context=".FragmentTabHostActivity">
 8 
 9     <FrameLayout
10         android:id="@+id/fragment_content"
11         android:layout_width="match_parent"
12         android:layout_height="0dp"
13         android:layout_weight="1">
14 
15     </FrameLayout>
16 
17     <View
18         android:layout_width="match_parent"
19         android:layout_height="0.5dp"
20         android:background="@color/colorGray" />
21 
22     <android.support.v4.app.FragmentTabHost
23         android:id="@+id/tab"
24         android:layout_width="match_parent"
25         android:layout_height="wrap_content"
26         android:layout_marginTop="5dp"
27         android:layout_marginBottom="5dp">
28 
29     </android.support.v4.app.FragmentTabHost>
30 
31 </LinearLayout>

注意:其中FrameLayout标签为fragment展示的容器,FragmentTabHost标签才是用来展示底部tab的容器,这这种布局下,两者的id命名可以随意。

2.activity文件

 1 package com.matrix.navigation;
 2 
 3 import android.support.v4.app.FragmentTabHost;
 4 import android.support.v7.app.AppCompatActivity;
 5 import android.os.Bundle;
 6 import android.view.LayoutInflater;
 7 import android.view.View;
 8 import android.widget.ImageView;
 9 import android.widget.TextView;
10 
11 import com.matrix.navigation.fragment.CaseFragment;
12 import com.matrix.navigation.fragment.ClassificationFragment;
13 import com.matrix.navigation.fragment.HomeFragment;
14 import com.matrix.navigation.fragment.SettingFragment;
15 
16 public class FragmentTabHostActivity extends AppCompatActivity {
17 
18     private String[] tabs = new String[]{"首页", "分类", "案例", "设置"};
19     private Class[] mFragmentClasses = new Class[]{HomeFragment.class, ClassificationFragment.class,
20             CaseFragment.class, SettingFragment.class};
21     private int[] selectorImg = new int[]{R.drawable.tab_home_selector, R.drawable.tab_classification_selector,
22             R.drawable.tab_case_selector, R.drawable.tab_setting_selector};
23 
24     @Override
25     protected void onCreate(Bundle savedInstanceState) {
26         super.onCreate(savedInstanceState);
27         setContentView(R.layout.activity_fragment_tab_host_two);
28 
29         FragmentTabHost tabHost = findViewById(R.id.tab);
30         // 初始化tabHost
31         tabHost.setup(FragmentTabHostActivity.this, getSupportFragmentManager(), R.id.fragment_content);
32         for (int i = 0; i < 4; i++) {
33             tabHost.addTab(tabHost.newTabSpec(tabs[i]).setIndicator(getTabView(i)), mFragmentClasses[i], null);
34         }
35         // 设置默认tab
36         tabHost.setCurrentTab(2);
37     }
38 
39     /**
40      * tab的view对象
41      *
42      * @param index 索引
43      * @return view对象
44      */
45     private View getTabView(int index) {
46         View inflate = LayoutInflater.from(FragmentTabHostActivity.this).inflate(R.layout.item_tab, null);
47         ImageView tabImage = inflate.findViewById(R.id.tab_image);
48         TextView tabTitle = inflate.findViewById(R.id.tab_title);
49         tabImage.setImageResource(selectorImg[index]); // 通过selector来控制图片的改变
50         tabTitle.setText(tabs[index]);// 通过selector来控制文字颜色的改变
51         return inflate;
52     }
53 }

其中item_tab的布局代码如下:

<?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:gravity="center"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/tab_image"
        android:layout_width="20dp"
        android:layout_height="20dp" />

    <TextView
        android:id="@+id/tab_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@color/tab_text_color_selector"
        android:textSize="12sp" />

</LinearLayout>

 tab_text_color_selector用来控制tab文字选中和未选中时颜色变化,一定要用android:state_selected

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:color="@color/colorOrangeRed" android:state_selected="true" />
    <item android:color="@color/colorGray" android:state_selected="false"/>
</selector>

同样对于tab图片的控制如下(举一例说明),一定要用android:state_selected

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/ic_home_selected" android:state_selected="true" />
    <item android:drawable="@drawable/ic_home_common" android:state_selected="false" />
</selector>

正常情况下,按照上述步骤应该就可以实现所要的效果了。

三、其他一些说明

1.当布局文件使用下面这种的话

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     xmlns:tools="http://schemas.android.com/tools"
 4     android:layout_width="match_parent"
 5     android:layout_height="match_parent"
 6     android:orientation="vertical"
 7     tools:context=".FragmentTabHostActivity">
 8 
 9     <android.support.v4.app.FragmentTabHost
10         android:id="@+id/tab_host"
11         android:layout_width="match_parent"
12         android:layout_height="match_parent">
13 
14         <LinearLayout
15             android:layout_width="match_parent"
16             android:layout_height="match_parent"
17             android:orientation="vertical">
18 
19             <FrameLayout
20                 android:id="@android:id/tabcontent"
21                 android:layout_width="match_parent"
22                 android:layout_height="0dp"
23                 android:layout_weight="1">
24 
25             </FrameLayout>
26 
27             <View
28                 android:layout_width="match_parent"
29                 android:layout_height="0.5dp"
30                 android:background="@color/colorGray" />
31 
32             <TabWidget
33                 android:id="@android:id/tabs"
34                 android:layout_width="match_parent"
35                 android:layout_height="wrap_content"
36                 android:layout_marginTop="5dp"
37                 android:layout_marginBottom="5dp">
38 
39             </TabWidget>
40 
41         </LinearLayout>
42 
43     </android.support.v4.app.FragmentTabHost>
44 
45 </LinearLayout>

则FrameLayout和TabWidget标签的id必须使用系统默认的,不能随意命名,否则会报错

2.使用系统的android.support.v4.app.FragmentTabHost,每次点击tab进行切换时,对应的fragment都会被重新创建,因为源码中是通过attach和detach方法来管理fragment的。所以如果需要点击时不重新创建的话,可以自定义一个FragmentTabHost,使用show和hide方法来管理fragment。自定义代码如下:

  1 package com.matrix.navigation.widget;
  2 
  3 import android.content.Context;
  4 import android.content.res.TypedArray;
  5 import android.os.Bundle;
  6 import android.os.Parcel;
  7 import android.os.Parcelable;
  8 import android.support.annotation.NonNull;
  9 import android.support.annotation.Nullable;
 10 import android.support.v4.app.Fragment;
 11 import android.support.v4.app.FragmentManager;
 12 import android.support.v4.app.FragmentTransaction;
 13 import android.util.AttributeSet;
 14 import android.view.View;
 15 import android.view.ViewGroup;
 16 import android.widget.FrameLayout;
 17 import android.widget.LinearLayout;
 18 import android.widget.TabHost;
 19 import android.widget.TabWidget;
 20 
 21 import java.util.ArrayList;
 22 
 23 /**
 24  * <p> 描述:自定义FragmentTabHost解决切换时fragment重新创建的问题</p>
 25  * <p> 作者:xc</p>
 26  * <p> 时间:2019/03/19</p>
 27  */
 28 public class MyFragmentTabHost extends TabHost
 29         implements TabHost.OnTabChangeListener {
 30     private final ArrayList<TabInfo> mTabs = new ArrayList<>();
 31 
 32     private FrameLayout mRealTabContent;
 33     private Context mContext;
 34     private FragmentManager mFragmentManager;
 35     private int mContainerId;
 36     private TabHost.OnTabChangeListener mOnTabChangeListener;
 37     private TabInfo mLastTab;
 38     private boolean mAttached;
 39 
 40     static final class TabInfo {
 41         final @NonNull
 42         String tag;
 43         final @NonNull
 44         Class<?> clss;
 45         final @Nullable
 46         Bundle args;
 47         Fragment fragment;
 48 
 49         TabInfo(@NonNull String _tag, @NonNull Class<?> _class, @Nullable Bundle _args) {
 50             tag = _tag;
 51             clss = _class;
 52             args = _args;
 53         }
 54     }
 55 
 56     static class DummyTabFactory implements TabHost.TabContentFactory {
 57         private final Context mContext;
 58 
 59         public DummyTabFactory(Context context) {
 60             mContext = context;
 61         }
 62 
 63         @Override
 64         public View createTabContent(String tag) {
 65             View v = new View(mContext);
 66             v.setMinimumWidth(0);
 67             v.setMinimumHeight(0);
 68             return v;
 69         }
 70     }
 71 
 72     static class SavedState extends BaseSavedState {
 73         String curTab;
 74 
 75         SavedState(Parcelable superState) {
 76             super(superState);
 77         }
 78 
 79         SavedState(Parcel in) {
 80             super(in);
 81             curTab = in.readString();
 82         }
 83 
 84         @Override
 85         public void writeToParcel(Parcel out, int flags) {
 86             super.writeToParcel(out, flags);
 87             out.writeString(curTab);
 88         }
 89 
 90         @Override
 91         public String toString() {
 92             return "FragmentTabHost.SavedState{"
 93                     + Integer.toHexString(System.identityHashCode(this))
 94                     + " curTab=" + curTab + "}";
 95         }
 96 
 97         public static final Parcelable.Creator<SavedState> CREATOR
 98                 = new Parcelable.Creator<SavedState>() {
 99             @Override
100             public SavedState createFromParcel(Parcel in) {
101                 return new SavedState(in);
102             }
103 
104             @Override
105             public SavedState[] newArray(int size) {
106                 return new SavedState[size];
107             }
108         };
109     }
110 
111     public MyFragmentTabHost(Context context) {
112         // Note that we call through to the version that takes an AttributeSet,
113         // because the simple Context construct can result in a broken object!
114         super(context, null);
115         initFragmentTabHost(context, null);
116     }
117 
118     public MyFragmentTabHost(Context context, AttributeSet attrs) {
119         super(context, attrs);
120         initFragmentTabHost(context, attrs);
121     }
122 
123     private void initFragmentTabHost(Context context, AttributeSet attrs) {
124         final TypedArray a = context.obtainStyledAttributes(attrs,
125                 new int[]{android.R.attr.inflatedId}, 0, 0);
126         mContainerId = a.getResourceId(0, 0);
127         a.recycle();
128 
129         super.setOnTabChangedListener(this);
130     }
131 
132     private void ensureHierarchy(Context context) {
133         // If owner hasn't made its own view hierarchy, then as a convenience
134         // we will construct a standard one here.
135         if (findViewById(android.R.id.tabs) == null) {
136             LinearLayout ll = new LinearLayout(context);
137             ll.setOrientation(LinearLayout.VERTICAL);
138             addView(ll, new FrameLayout.LayoutParams(
139                     ViewGroup.LayoutParams.MATCH_PARENT,
140                     ViewGroup.LayoutParams.MATCH_PARENT));
141 
142             TabWidget tw = new TabWidget(context);
143             tw.setId(android.R.id.tabs);
144             tw.setOrientation(TabWidget.HORIZONTAL);
145             ll.addView(tw, new LinearLayout.LayoutParams(
146                     ViewGroup.LayoutParams.MATCH_PARENT,
147                     ViewGroup.LayoutParams.WRAP_CONTENT, 0));
148 
149             FrameLayout fl = new FrameLayout(context);
150             fl.setId(android.R.id.tabcontent);
151             ll.addView(fl, new LinearLayout.LayoutParams(0, 0, 0));
152 
153             mRealTabContent = fl = new FrameLayout(context);
154             mRealTabContent.setId(mContainerId);
155             ll.addView(fl, new LinearLayout.LayoutParams(
156                     LinearLayout.LayoutParams.MATCH_PARENT, 0, 1));
157         }
158     }
159 
160     /**
161      * @deprecated Don't call the original TabHost setup, you must instead
162      * call {@link #setup(Context, FragmentManager)} or
163      * {@link #setup(Context, FragmentManager, int)}.
164      */
165     @Override
166     @Deprecated
167     public void setup() {
168         throw new IllegalStateException(
169                 "Must call setup() that takes a Context and FragmentManager");
170     }
171 
172     public void setup(Context context, FragmentManager manager) {
173         ensureHierarchy(context);  // Ensure views required by super.setup()
174         super.setup();
175         mContext = context;
176         mFragmentManager = manager;
177         ensureContent();
178     }
179 
180     public void setup(Context context, FragmentManager manager, int containerId) {
181         ensureHierarchy(context);  // Ensure views required by super.setup()
182         super.setup();
183         mContext = context;
184         mFragmentManager = manager;
185         mContainerId = containerId;
186         ensureContent();
187         mRealTabContent.setId(containerId);
188 
189         // We must have an ID to be able to save/restore our state.  If
190         // the owner hasn't set one at this point, we will set it ourselves.
191         if (getId() == View.NO_ID) {
192             setId(android.R.id.tabhost);
193         }
194     }
195 
196     private void ensureContent() {
197         if (mRealTabContent == null) {
198             mRealTabContent = (FrameLayout) findViewById(mContainerId);
199             if (mRealTabContent == null) {
200                 throw new IllegalStateException(
201                         "No tab content FrameLayout found for id " + mContainerId);
202             }
203         }
204     }
205 
206     @Override
207     public void setOnTabChangedListener(OnTabChangeListener l) {
208         mOnTabChangeListener = l;
209     }
210 
211     public void addTab(@NonNull TabHost.TabSpec tabSpec, @NonNull Class<?> clss,
212                        @Nullable Bundle args) {
213         tabSpec.setContent(new DummyTabFactory(mContext));
214 
215         final String tag = tabSpec.getTag();
216         final TabInfo info = new TabInfo(tag, clss, args);
217 
218         if (mAttached) {
219             // If we are already attached to the window, then check to make
220             // sure this tab's fragment is inactive if it exists.  This shouldn't
221             // normally happen.
222             info.fragment = mFragmentManager.findFragmentByTag(tag);
223 //            if (info.fragment != null && !info.fragment.isDetached()) {
224             if (info.fragment != null) {
225                 final FragmentTransaction ft = mFragmentManager.beginTransaction();
226 //                ft.detach(info.fragment);
227                 ft.hide(info.fragment);
228                 ft.commit();
229 
230             }
231         }
232 
233         mTabs.add(info);
234         addTab(tabSpec);
235     }
236 
237     @Override
238     protected void onAttachedToWindow() {
239         super.onAttachedToWindow();
240 
241         final String currentTag = getCurrentTabTag();
242 
243         // Go through all tabs and make sure their fragments match
244         // the correct state.
245         FragmentTransaction ft = null;
246         for (int i = 0, count = mTabs.size(); i < count; i++) {
247             final TabInfo tab = mTabs.get(i);
248             tab.fragment = mFragmentManager.findFragmentByTag(tab.tag);
249 //            if (tab.fragment != null && !tab.fragment.isDetached()) {
250             if (tab.fragment != null) {
251                 if (tab.tag.equals(currentTag)) {
252                     // The fragment for this tab is already there and
253                     // active, and it is what we really want to have
254                     // as the current tab.  Nothing to do.
255                     mLastTab = tab;
256                 } else {
257                     // This fragment was restored in the active state,
258                     // but is not the current tab.  Deactivate it.
259                     if (ft == null) {
260                         ft = mFragmentManager.beginTransaction();
261                     }
262 //                    ft.detach(tab.fragment);
263                     ft.hide(tab.fragment);
264                 }
265             }
266         }
267 
268         // We are now ready to go.  Make sure we are switched to the
269         // correct tab.
270         mAttached = true;
271         ft = doTabChanged(currentTag, ft);
272         if (ft != null) {
273             ft.commit();
274             mFragmentManager.executePendingTransactions();
275         }
276     }
277 
278     @Override
279     protected void onDetachedFromWindow() {
280         super.onDetachedFromWindow();
281         mAttached = false;
282     }
283 
284     @Override
285     protected Parcelable onSaveInstanceState() {
286         Parcelable superState = super.onSaveInstanceState();
287         SavedState ss = new SavedState(superState);
288         ss.curTab = getCurrentTabTag();
289         return ss;
290     }
291 
292     @Override
293     protected void onRestoreInstanceState(Parcelable state) {
294         if (!(state instanceof SavedState)) {
295             super.onRestoreInstanceState(state);
296             return;
297         }
298         SavedState ss = (SavedState) state;
299         super.onRestoreInstanceState(ss.getSuperState());
300         setCurrentTabByTag(ss.curTab);
301     }
302 
303     @Override
304     public void onTabChanged(String tabId) {
305         if (mAttached) {
306             final FragmentTransaction ft = doTabChanged(tabId, null);
307             if (ft != null) {
308                 ft.commit();
309             }
310         }
311         if (mOnTabChangeListener != null) {
312             mOnTabChangeListener.onTabChanged(tabId);
313         }
314     }
315 
316     @Nullable
317     private FragmentTransaction doTabChanged(@Nullable String tag,
318                                              @Nullable FragmentTransaction ft) {
319         final TabInfo newTab = getTabInfoForTag(tag);
320         if (mLastTab != newTab) {
321             if (ft == null) {
322                 ft = mFragmentManager.beginTransaction();
323             }
324 
325             if (mLastTab != null) {
326                 if (mLastTab.fragment != null) {
327 //                    ft.detach(mLastTab.fragment);
328                     ft.hide(mLastTab.fragment);
329                 }
330             }
331 
332             if (newTab != null) {
333                 if (newTab.fragment == null) {
334                     newTab.fragment = Fragment.instantiate(mContext,
335                             newTab.clss.getName(), newTab.args);
336                     ft.add(mContainerId, newTab.fragment, newTab.tag);
337                 } else {
338 //                    ft.attach(newTab.fragment);
339                     ft.show(newTab.fragment);
340                 }
341             }
342 
343             mLastTab = newTab;
344         }
345 
346         return ft;
347     }
348 
349     @Nullable
350     private TabInfo getTabInfoForTag(String tabId) {
351         for (int i = 0, count = mTabs.size(); i < count; i++) {
352             final TabInfo tab = mTabs.get(i);
353             if (tab.tag.equals(tabId)) {
354                 return tab;
355             }
356         }
357         return null;
358     }
359 
360 }

 

posted @ 2019-03-20 15:13  XC_main  阅读(4187)  评论(3编辑  收藏  举报