2012-11-20 219 views
3

我在應用程序中使用新的API 17實現了嵌套的片段,它爲此提供了FragmentTabHost。但是,我有一個父片段在我的簡單的2個片段有一些基本的東西麻煩:自定義FragmentTabHost的實現

  1. 我想嵌套的標籤是在底部(視圖1和視圖2選項卡)
  2. 我想自定義標籤實際看起來比標準不同

Screenshot of issue

有沒有人使用這些工作,知道如何實現這一目標?下面是示例代碼,我有和運行:

import android.os.Bundle; 
import android.support.v4.app.Fragment; 
import android.support.v4.app.FragmentTabHost; 
import android.view.LayoutInflater; 
import android.view.View; 
import android.view.ViewGroup; 

public class FragmentTabsFragmentSupport extends Fragment { 
private FragmentTabHost mTabHost; 

@Override 
public View onCreateView(LayoutInflater inflater, ViewGroup container, 
     Bundle savedInstanceState) { 
    mTabHost = new FragmentTabHost(getActivity()); 
    mTabHost.setup(getActivity(), getChildFragmentManager(), R.id.fragment1); 

    mTabHost.addTab(mTabHost.newTabSpec("Tab1").setIndicator("Simple"), 
      NestedFragment1.class, null); 
    mTabHost.addTab(mTabHost.newTabSpec("Tab2").setIndicator("Contacts"), 
      NestedFragment2.class, null); 

    return mTabHost; 
} 

@Override 
public void onDestroyView() { 
    super.onDestroyView(); 
    mTabHost = null; 
} 
} 

我曾嘗試以下,以底部對齊的標籤,但沒有運氣:

TabWidget mTabWidget = mTabHost.getTabWidget(); 
mTabWidget.setGravity(Gravity.BOTTOM); 
mTabWidget.setVerticalGravity(Gravity.BOTTOM); 

回答

4

我終於明白了這一點。 FragmentTabHost.java存在一個問題,無論您在XML中定義了什麼並事先膨脹,它總是會爲您創建一個TabHost元素。

因此,在編寫我自己的FragmentTabHost.java版本時,我注意到了部分代碼。

確保在你的XML佈局使用這個新版本,<com.example.app.MyFragmentTabHost

,當然還有它充氣:

Fragment1.java:

mTabHost = (MyFragmentTabHost) view.findViewById(android.R.id.tabhost); 
mTabHost.setup(getActivity(), getChildFragmentManager(), android.R.id.tabcontent); 

MyFragmentTabHost.java:

package com.example.app; 

import java.util.ArrayList; 

import android.content.Context; 
import android.content.res.TypedArray; 
import android.os.Bundle; 
import android.os.Parcel; 
import android.os.Parcelable; 
import android.support.v4.app.Fragment; 
import android.support.v4.app.FragmentManager; 
import android.support.v4.app.FragmentTransaction; 
import android.util.AttributeSet; 
import android.view.View; 
import android.widget.FrameLayout; 
import android.widget.TabHost; 

/** 
* Special TabHost that allows the use of {@link Fragment} objects for 
* its tab content. When placing this in a view hierarchy, after inflating 
* the hierarchy you must call {@link #setup(Context, FragmentManager, int)} 
* to complete the initialization of the tab host. 
* 
*/ 
public class MyFragmentTabHost extends TabHost 
    implements TabHost.OnTabChangeListener { 
private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>(); 
private FrameLayout mRealTabContent; 
private Context mContext; 
private FragmentManager mFragmentManager; 
private int mContainerId; 
private TabHost.OnTabChangeListener mOnTabChangeListener; 
private TabInfo mLastTab; 
private boolean mAttached; 

static final class TabInfo { 
    private final String tag; 
    private final Class<?> clss; 
    private final Bundle args; 
    private Fragment fragment; 

    TabInfo(String _tag, Class<?> _class, Bundle _args) { 
     tag = _tag; 
     clss = _class; 
     args = _args; 
    } 
} 

static class DummyTabFactory implements TabHost.TabContentFactory { 
    private final Context mContext; 

    public DummyTabFactory(Context context) { 
     mContext = context; 
    } 

    @Override 
    public View createTabContent(String tag) { 
     View v = new View(mContext); 
     v.setMinimumWidth(0); 
     v.setMinimumHeight(0); 
     return v; 
    } 
} 

static class SavedState extends BaseSavedState { 
    String curTab; 

    SavedState(Parcelable superState) { 
     super(superState); 
    } 

    private SavedState(Parcel in) { 
     super(in); 
     curTab = in.readString(); 
    } 

    @Override 
    public void writeToParcel(Parcel out, int flags) { 
     super.writeToParcel(out, flags); 
     out.writeString(curTab); 
    } 

    @Override 
    public String toString() { 
     return "FragmentTabHost.SavedState{" 
       + Integer.toHexString(System.identityHashCode(this)) 
       + " curTab=" + curTab + "}"; 
    } 

    public static final Parcelable.Creator<SavedState> CREATOR 
      = new Parcelable.Creator<SavedState>() { 
     public SavedState createFromParcel(Parcel in) { 
      return new SavedState(in); 
     } 

     public SavedState[] newArray(int size) { 
      return new SavedState[size]; 
     } 
    }; 
} 

public MyFragmentTabHost(Context context) { 
    // Note that we call through to the version that takes an AttributeSet, 
    // because the simple Context construct can result in a broken object! 
    super(context, null); 
    initFragmentTabHost(context, null); 
} 

public MyFragmentTabHost(Context context, AttributeSet attrs) { 
    super(context, attrs); 
    initFragmentTabHost(context, attrs); 
} 

private void initFragmentTabHost(Context context, AttributeSet attrs) { 
    TypedArray a = context.obtainStyledAttributes(attrs, 
      new int[] { android.R.attr.inflatedId }, 0, 0); 
    mContainerId = a.getResourceId(0, 0); 
    a.recycle(); 

    super.setOnTabChangedListener(this); 


    /*** REMOVE THE REST OF THIS FUNCTION ***/ 
    /*** findViewById(android.R.id.tabs) IS NULL EVERY TIME ***/ 
} 

/** 
* @deprecated Don't call the original TabHost setup, you must instead 
* call {@link #setup(Context, FragmentManager)} or 
* {@link #setup(Context, FragmentManager, int)}. 
*/ 
@Override @Deprecated 
public void setup() { 
    throw new IllegalStateException(
      "Must call setup() that takes a Context and FragmentManager"); 
} 

public void setup(Context context, FragmentManager manager) { 
    super.setup(); 
    mContext = context; 
    mFragmentManager = manager; 
    ensureContent(); 
} 

public void setup(Context context, FragmentManager manager, int containerId) { 
    super.setup(); 
    mContext = context; 
    mFragmentManager = manager; 
    mContainerId = containerId; 
    ensureContent(); 
    mRealTabContent.setId(containerId); 

    // We must have an ID to be able to save/restore our state. If 
    // the owner hasn't set one at this point, we will set it ourself. 
    if (getId() == View.NO_ID) { 
     setId(android.R.id.tabhost); 
    } 
} 

private void ensureContent() { 
    if (mRealTabContent == null) { 
     mRealTabContent = (FrameLayout)findViewById(mContainerId); 
     if (mRealTabContent == null) { 
      throw new IllegalStateException(
        "No tab content FrameLayout found for id " + mContainerId); 
     } 
    } 
} 

@Override 
public void setOnTabChangedListener(OnTabChangeListener l) { 
    mOnTabChangeListener = l; 
} 

public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args) { 
    tabSpec.setContent(new DummyTabFactory(mContext)); 
    String tag = tabSpec.getTag(); 

    TabInfo info = new TabInfo(tag, clss, args); 

    if (mAttached) { 
     // If we are already attached to the window, then check to make 
     // sure this tab's fragment is inactive if it exists. This shouldn't 
     // normally happen. 
     info.fragment = mFragmentManager.findFragmentByTag(tag); 
     if (info.fragment != null && !info.fragment.isDetached()) { 
      FragmentTransaction ft = mFragmentManager.beginTransaction(); 
      ft.detach(info.fragment); 
      ft.commit(); 
     } 
    } 

    mTabs.add(info); 
    addTab(tabSpec); 
} 

@Override 
protected void onAttachedToWindow() { 
    super.onAttachedToWindow(); 

    String currentTab = getCurrentTabTag(); 

    // Go through all tabs and make sure their fragments match 
    // the correct state. 
    FragmentTransaction ft = null; 
    for (int i=0; i<mTabs.size(); i++) { 
     TabInfo tab = mTabs.get(i); 
     tab.fragment = mFragmentManager.findFragmentByTag(tab.tag); 
     if (tab.fragment != null && !tab.fragment.isDetached()) { 
      if (tab.tag.equals(currentTab)) { 
       // The fragment for this tab is already there and 
       // active, and it is what we really want to have 
       // as the current tab. Nothing to do. 
       mLastTab = tab; 
      } else { 
       // This fragment was restored in the active state, 
       // but is not the current tab. Deactivate it. 
       if (ft == null) { 
        ft = mFragmentManager.beginTransaction(); 
       } 
       ft.detach(tab.fragment); 
      } 
     } 
    } 

    // We are now ready to go. Make sure we are switched to the 
    // correct tab. 
    mAttached = true; 
    ft = doTabChanged(currentTab, ft); 
    if (ft != null) { 
     ft.commit(); 
     mFragmentManager.executePendingTransactions(); 
    } 
} 

@Override 
protected void onDetachedFromWindow() { 
    super.onDetachedFromWindow(); 
    mAttached = false; 
} 

@Override 
protected Parcelable onSaveInstanceState() { 
    Parcelable superState = super.onSaveInstanceState(); 
    SavedState ss = new SavedState(superState); 
    ss.curTab = getCurrentTabTag(); 
    return ss; 
} 

@Override 
protected void onRestoreInstanceState(Parcelable state) { 
    SavedState ss = (SavedState)state; 
    super.onRestoreInstanceState(ss.getSuperState()); 
    setCurrentTabByTag(ss.curTab); 
} 

@Override 
public void onTabChanged(String tabId) { 
    if (mAttached) { 
     FragmentTransaction ft = doTabChanged(tabId, null); 
     if (ft != null) { 
      ft.commit(); 
     } 
    } 
    if (mOnTabChangeListener != null) { 
     mOnTabChangeListener.onTabChanged(tabId); 
    } 
} 

private FragmentTransaction doTabChanged(String tabId, FragmentTransaction ft) { 
    TabInfo newTab = null; 
    for (int i=0; i<mTabs.size(); i++) { 
     TabInfo tab = mTabs.get(i); 
     if (tab.tag.equals(tabId)) { 
      newTab = tab; 
     } 
    } 
    if (newTab == null) { 
     throw new IllegalStateException("No tab known for tag " + tabId); 
    } 
    if (mLastTab != newTab) { 
     if (ft == null) { 
      ft = mFragmentManager.beginTransaction(); 
     } 
     if (mLastTab != null) { 
      if (mLastTab.fragment != null) { 
       ft.detach(mLastTab.fragment); 
      } 
     } 
     if (newTab != null) { 
      if (newTab.fragment == null) { 
       newTab.fragment = Fragment.instantiate(mContext, 
         newTab.clss.getName(), newTab.args); 
       ft.add(mContainerId, newTab.fragment, newTab.tag); 
      } else { 
       ft.attach(newTab.fragment); 
      } 
     } 

     mLastTab = newTab; 
    } 
    return ft; 
} 
} 
+0

此修復[7月8日合併](https://android-review.googlesource.com/#/c/59957/),應該位於支持庫的下一修訂版(> 18)中。 – cwc

0

退房the answer here的信息上的定製標籤和this answer有關將標籤放在底部的信息。

+0

不幸的是,在上面的示例中更改'R.id.fragment1'的XML佈局完全沒有。我不相信FragmentTabHost利用XML佈局文件中的部分,除非我做的不正確。 – jamis0n

+0

我可以通過將TabHost中的佈局更改爲相對佈局並在TabWidget上設置'ParentAlignBottom = true'來使它與下面的答案一起工作。查看http://stackoverflow.com/a/3533787/288072以獲取更多信息 –

+0

您是否在使用sherlock碎片的查看傳呼機中使用此功能?多數民衆贊成我如何配置和佈局不工作。奇怪的是,更改父級片段的XML佈局並不會改變佈局。 – jamis0n

1

在您的代碼

mTabHost.setup(getActivity(), getChildFragmentManager(), R.id.fragment1); 

我想知道R.id.fragment1的值,即xml文件。如果你爲我提供完整的項目,對我來說將會非常有幫助。我想在導航抽屜裏使用這個嵌套選項卡。