[关闭]
@act262 2016-11-21T16:23:51.000000Z 字数 4524 阅读 1064

Android FragmentTabHost分析

Android


WHAT

FragmentTabHost extends TabHost

基本功能继承了TabHost,Content使用Fragment展示。实现底部、顶部Tab,其他区域展示内容的效果。

  1. // ContentView
  2. private FrameLayout mRealTabContent;
  3. private Context mContext;
  4. private FragmentManager mFragmentManager;
  5. // ContentView的ID
  6. private int mContainerId;
  7. private TabHost.OnTabChangeListener mOnTabChangeListener;
  8. // 上次切换的Tab信息
  9. private TabInfo mLastTab;
  10. private boolean mAttached;

WHY

实现了TabHost.TabContentFactoryFactory

  1. static class DummyTabFactory implements TabHost.TabContentFactory {
  2. private final Context mContext;
  3. public DummyTabFactory(Context context) {
  4. mContext = context;
  5. }
  6. @Override
  7. public View createTabContent(String tag) {
  8. // 这个貌似没有用到,真实的ContentView是mRealTabContent,其ID就是mContainerId
  9. View v = new View(mContext);
  10. v.setMinimumWidth(0);
  11. v.setMinimumHeight(0);
  12. return v;
  13. }
  14. }

初始化

  1. private void initFragmentTabHost(Context context, AttributeSet attrs) {
  2. final TypedArray a = context.obtainStyledAttributes(attrs,
  3. new int[] { android.R.attr.inflatedId }, 0, 0);
  4. // 指定inflatedId
  5. mContainerId = a.getResourceId(0, 0);
  6. a.recycle();
  7. super.setOnTabChangedListener(this);
  8. }

确保有TabWidget和ConentView

  1. // 自动生成ViewTree,上面是TabWidget,下面是Content
  2. private void ensureHierarchy(Context context) {
  3. // If owner hasn't made its own view hierarchy, then as a convenience
  4. // we will construct a standard one here.
  5. if (findViewById(android.R.id.tabs) == null) {
  6. LinearLayout ll = new LinearLayout(context);
  7. ll.setOrientation(LinearLayout.VERTICAL);
  8. addView(ll, new FrameLayout.LayoutParams(
  9. ViewGroup.LayoutParams.MATCH_PARENT,
  10. ViewGroup.LayoutParams.MATCH_PARENT));
  11. TabWidget tw = new TabWidget(context);
  12. tw.setId(android.R.id.tabs);
  13. tw.setOrientation(TabWidget.HORIZONTAL);
  14. ll.addView(tw, new LinearLayout.LayoutParams(
  15. ViewGroup.LayoutParams.MATCH_PARENT,
  16. ViewGroup.LayoutParams.WRAP_CONTENT, 0));
  17. FrameLayout fl = new FrameLayout(context);
  18. fl.setId(android.R.id.tabcontent);
  19. ll.addView(fl, new LinearLayout.LayoutParams(0, 0, 0));
  20. mRealTabContent = fl = new FrameLayout(context);
  21. mRealTabContent.setId(mContainerId);
  22. ll.addView(fl, new LinearLayout.LayoutParams(
  23. LinearLayout.LayoutParams.MATCH_PARENT, 0, 1));
  24. }
  25. }
  26. // 确保必须有指定的mContainerId
  27. private void ensureContent() {
  28. if (mRealTabContent == null) {
  29. mRealTabContent = (FrameLayout)findViewById(mContainerId);
  30. if (mRealTabContent == null) {
  31. throw new IllegalStateException(
  32. "No tab content FrameLayout found for id " + mContainerId);
  33. }
  34. }
  35. }

如果布局中的FragmentTabHost有指定android:inflatedId则使用这个方法初始化,一般是使用布局设置的情况。

setup(Context context, FragmentManager manager)

或者指定一个containerId,一般用在java代码中写布局的情况。

setup(Context context, FragmentManager manager, int containerId)


切换Tab,通过FragmentManager的事物来管理的

  1. public void onTabChanged(String tabId) {
  2. if (mAttached) {
  3. final FragmentTransaction ft = doTabChanged(tabId, null);
  4. if (ft != null) {
  5. ft.commit();
  6. }
  7. }
  8. if (mOnTabChangeListener != null) {
  9. mOnTabChangeListener.onTabChanged(tabId);
  10. }
  11. }
  12. @Nullable
  13. private FragmentTransaction doTabChanged(@Nullable String tag,
  14. @Nullable FragmentTransaction ft) {
  15. final TabInfo newTab = getTabInfoForTag(tag);
  16. if (mLastTab != newTab) {
  17. if (ft == null) {
  18. ft = mFragmentManager.beginTransaction();
  19. }
  20. if (mLastTab != null) {
  21. if (mLastTab.fragment != null) {
  22. ft.detach(mLastTab.fragment);
  23. }
  24. }
  25. if (newTab != null) {
  26. if (newTab.fragment == null) {
  27. newTab.fragment = Fragment.instantiate(mContext,
  28. newTab.clss.getName(), newTab.args);
  29. ft.add(mContainerId, newTab.fragment, newTab.tag);
  30. } else {
  31. ft.attach(newTab.fragment);
  32. }
  33. }
  34. mLastTab = newTab;
  35. }
  36. return ft;
  37. }

每次新旧的切换是使用detach和attach,所以会造成Fragment的生命周期(onCreatedView)重复执行,导致每次都会重新加载布局等问题。
解决方案有2个:
1. 重写doTabChanged方法,使用FragmentTransaction的show/hide来切换显示、隐藏。
2. 在Fragment的onCreateView中判断这个rootView是否已经存在,不存在才加载View。


HOW TO USE

布局中使用,基本上和TabHost一样,只是把TabHost控件换成了FragmentTabHost

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <android.support.v4.app.FragmentTabHost xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent">
  5. <LinearLayout
  6. android:layout_width="match_parent"
  7. android:layout_height="match_parent"
  8. android:orientation="vertical">
  9. <FrameLayout
  10. android:id="@android:id/tabcontent"
  11. android:layout_width="match_parent"
  12. android:layout_height="0dp"
  13. android:layout_weight="1" />
  14. <TabWidget
  15. android:id="@android:id/tabs"
  16. android:layout_width="match_parent"
  17. android:layout_height="48dp" />
  18. </LinearLayout>
  19. </android.support.v4.app.FragmentTabHost>

在Java代码中设置

  1. FragmentTabHost tabHost = (FragmentTabHost) findViewById(android.R.id.tabhost);
  2. // 指定content id
  3. // tabHost.setup(this, getSupportFragmentManager(), android.R.id.tabcontent);
  4. // content id = xml 中设置的inflatedId
  5. tabHost.setup(this, getSupportFragmentManager());
  6. TabHost.TabSpec tabSpec1 = tabHost.newTabSpec("TabA").setIndicator("TabA");
  7. TabHost.TabSpec tabSpec2 = tabHost.newTabSpec("TabB").setIndicator("TabB");
  8. tabHost.addTab(tabSpec1, Tab1Fragment.class, null);
  9. tabHost.addTab(tabSpec2, Tab2Fragment.class, null);
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注