一、效果圖
二、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(FragmentTabHostTwoActivity.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(FragmentTabHostTwoActivity.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 }