2012-12-03 1 views
18

は、私が見てみたかった17Android FragmentTabHost - まだ完全にベークされていませんか?誰もが新しいAndroidのAPIレベルが付属していますFragmentTabHostを使用して、タブのカスタマイズで成功を収めている場合

私はViewPagerのSherlockFragments内巣tabHostことができるように興奮していたが、私はよタブを下に移動する、またはタブのレイアウトを変更するなどの簡単なことをするのに問題があります。

この機能を使用する良い例は誰も見ていますか?

これは私がAndroidのドキュメントで見つけることができる唯一の例であり、その使い方を説明するものはまったくありません。 R.id.fragment1のレイアウトで定義されているものは無視されているようです。

私の疑問誰かが良いチュートリアルを読んだら、FragmentTabHost か、a)下の方に入れ子のタブを置くか、b)そのタブのレイアウトを変更する方法についてのアイデアがあるとします。

私はすべての通常の方法を試しましたが、XMLレイアウトファイルが上書きされているように見えるので、私はあまり運がありませんでした。

private FragmentTabHost mTabHost; 

@Override 
public View onCreateView(LayoutInflater inflater, ViewGroup container, 
     Bundle savedInstanceState) { 

    setContentView(R.layout.fragment_tabs); 
    mTabHost = (FragmentTabHost)findViewById(android.R.id.tabhost); 
    mTabHost.setup(this, getSupportFragmentManager(), R.id.realtabcontent); 

    mTabHost.addTab(mTabHost.newTabSpec("simple").setIndicator("Simple"), 
      FragmentStackSupport.CountingFragment.class, null); 
    mTabHost.addTab(mTabHost.newTabSpec("contacts").setIndicator("Contacts"), 
      LoaderCursorSupport.CursorLoaderListFragment.class, null); 
    mTabHost.addTab(mTabHost.newTabSpec("custom").setIndicator("Custom"), 
      LoaderCustomSupport.AppListFragment.class, null); 
    mTabHost.addTab(mTabHost.newTabSpec("throttle").setIndicator("Throttle"), 
      LoaderThrottleSupport.ThrottledLoaderListFragment.class, null); 

    return mTabHost; 
} 

いくつかの調査を行った後、サポートライブラリにFragmentTabHostを初期化している不具合があるようです。フラグメントのため

FragmentTabHost.java

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); 

    // If owner hasn't made its own view hierarchy, then as a convenience 
    // we will construct a standard one here. 


/***** HERE COMMENT CODE BECAUSE findViewById(android.R.id.tabs) EVERY TIME IS NULL WE HAVE OWN   LAYOUT ******// 


//  if (findViewById(android.R.id.tabs) == null) { 
//   LinearLayout ll = new LinearLayout(context); 
//   ll.setOrientation(LinearLayout.VERTICAL); 
//   addView(ll, new FrameLayout.LayoutParams(
//     ViewGroup.LayoutParams.FILL_PARENT, 
//     ViewGroup.LayoutParams.FILL_PARENT)); 
// 
//   TabWidget tw = new TabWidget(context); 
//   tw.setId(android.R.id.tabs); 
//   tw.setOrientation(TabWidget.HORIZONTAL); 
//   ll.addView(tw, new LinearLayout.LayoutParams(
//     ViewGroup.LayoutParams.FILL_PARENT, 
//     ViewGroup.LayoutParams.WRAP_CONTENT, 0)); 
// 
//   FrameLayout fl = new FrameLayout(context); 
//   fl.setId(android.R.id.tabcontent); 
//   ll.addView(fl, new LinearLayout.LayoutParams(0, 0, 0)); 
// 
//   mRealTabContent = fl = new FrameLayout(context); 
//   mRealTabContent.setId(mContainerId); 
//   ll.addView(fl, new LinearLayout.LayoutParams(
//     LinearLayout.LayoutParams.FILL_PARENT, 0, 1)); 
//  } 
} 

XMLレイアウト:ユーザーhere on Google codeはこれに提案を提供してきたと思い

<android.support.v4.app.FragmentTabHost 
xmlns:android="http://schemas.android.com/apk/res/android" 
android:id="@android:id/tabhost" 
android:layout_width="match_parent" 
android:layout_height="match_parent"> 
<LinearLayout 
    android:orientation="vertical" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent"> 
    <FrameLayout 
     android:id="@android:id/tabcontent" 
     android:layout_width="0dp" 
     android:layout_height="0dp" 
     android:layout_weight="0"/> 
    <FrameLayout 
     android:id="@+id/realtabcontent" 
     android:layout_width="match_parent" 
     android:layout_height="0dp" 
     android:layout_weight="1"/> 
    <TabWidget 
     android:id="@android:id/tabs" 
     android:orientation="horizontal" 
     android:layout_width="match_parent" 
     android:layout_height="wrap_content" 
     android:layout_weight="0"/> 

</LinearLayout> 
</android.support.v4.app.FragmentTabHost> 

答えて

14

私はついにこれの底に達しました。 FragmentTabHost.javaに問題があります.FragmentTabHost.javaは、あなたがXMLで定義したものであっても事前に膨らまされていても、常にTabHost要素を作成します。

このように、FragmentTabHost.javaの独自のバージョンを作成するときに、そのコードの一部をコメントアウトしました。

<com.example.app.MyFragmentTabHost、あなたのXMLレイアウトでこの新しいバージョンを使用するようにしてくださいそして、もちろん、それを膨らま:

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

私の答えをdownvoteする場合は、議論のためのコメントを残してください! – jamis0n

+1

なぜ、findViewById(android.R.id.tabs)がnullを返すのかを追跡しましたか?非常に一般的な意味では、そのIDが存在し、見つかるはずです... – Kiirani

+2

フラグメント化されていないFragmentActivityで使用すると@ jamis0nが機能しません同じスタックトレースが受信されました:java.lang.IllegalStateException: : –

2

、コンストラクタにメソッドinitFragmentTabHost()を設定することが間違いでした。その時TabHostは彼の子供をしない - それは後に起こる。 LinearLayoutは、例えば、onMeasure()の方法(grepcode)で彼の子供と一緒に働く。コンストラクタ内のViewGroupをinit変数に置き換え、mChildrenCount = 0grepcode)に設定します。

私がやったことができ、それが唯一のFragmentTabHostをcostumize何すべて:

<android.support.v4.app.FragmentTabHost xmlns:a="http://schemas.android.com/apk/res/android" 
    a:id="@android:id/tabhost" 
    style="@style/Widget.TabHost" 
    a:inflatedId="@+id/content" /> 

をそしてTabsをcostumize(私はコードでそれらを解決するため、タブの高さに問題がある):コードで

<LinearLayout xmlns:a="http://schemas.android.com/apk/res/android" 
    style="@style/Widget.Tab" > 
    <TextView 
     a:id="@android:id/title" 
     style="@style/Widget.TabTitle" /> 
</LinearLayout> 

tabSpec = mTabHost.newTabSpec(tag).setIndicator(createTab(caption)); 

... 

    private View createTab(CharSequence title) { 
     final View v = View.inflate(getActivity(), LAYOUT_TAB, null); 
     ((TextView) v.findViewById(android.R.id.title)).setText(title); 
     return v; 
    } 

他のカスタマイズはと思う私たちはプログラムで操作するだけで、次のようにすることができます:

final View tabs = (TabWidget) mTabHost.findViewById(android.R.id.tabs); 
    final ViewGroup parent = (ViewGroup) mTabHost.getChildAt(0); 
    parent.removeView(tabs); 
    parent.addView(tabs); 

IMHO、これは良くありません。

0

私が試した限り、jamisOn溶液は 良い。 MyFragmentTabHostをそのコンストラクタで初期化しないことが重要です。少なくとも、MyFragmentTabHostを保持するクラスがフラグメントである場合。私はFragmentActivityでテストしていません。

0

FragmentTabHostでさらに問題があることをお伝えしたいと思います。

1)FragmentTabHostは、それがその親FragmentManager(FragmentTabHost.setup()への第2引数)でのみFragmentTabHostだことを想定しています。私は、各ページ(ビュー)FragmenTabHostと私はいくつかの問題を克服しなければならなかった含まれていViewPagerを使用しています。これにより残りの問題が発生します...

2)addTab()を呼び出すときに指定する「タグ」は、FragmentManagerに直接渡されるため、すべてのページにハードコードされたタグを使用するだけでdo)あなたの最初のページはタブの断片を作成し、他のすべてのページはを再利用しますこれらのタブ。はい、ページ2のコントロールページ1 ...

ソリューションは、固有のタグ名を生成することです。すべてのタブの断片は唯一の「ビューid」(FragmentTabHost.setup()に第3引数)で識別される容器に入れます

public Object instantiateItem(ViewGroup container, int position) 
{ 
    ... 
    tabHost.addTab(tabHost.newTabSpec("tab1_" + position) ...); 
    tabHost.addTab(tabHost.newTabSpec("tab2_" + position) ...); 
    tabHost.addTab(tabHost.newTabSpec("tab3_" + position) ...); 
    ... 
} 

3):私は、ハードコード文字列にページ番号を追加しました。つまり、FragmentManagerがviewIdをViewに解決すると、最初のインスタンス(最初のページから)が常に検出されます。他のすべてのページは無視されます。これに

ソリューションは、たとえば、あなたの「タブの内容」ビューに固有のIDを割り当てることです:

public Object instantiateItem(ViewGroup container, int position) 
{ 
    View view = m_inflater.inflate(R.layout.page, null); 

    View tabContent = view.findViewById(R.id.realtabcontent); 
    tabContent.setId(m_nextViewId); 
    m_nextViewId++; 

    MyFragmentTabHost tabHost = (MyFragmentTabHost) view.findViewById(android.R.id.tabhost); 
    tabHost.setup(m_activity, m_activity.getSupportFragmentManager(), tabContent.getId()); 
    ... 
} 

4)破壊されたときには、タブの断片を除去しません。あなたがスワイプするとViewPagerは未使用のビューを破棄しますが、それらのビューに含まれるFragmentTabHostsはタブのフラグメントを「リーク」します。 ViewPagerが以前に使用したタグを使用して以前に見たページを再インスタンス化すると、FragmentTabHostはそれらのタブのフラグメントがすでに存在していることに気付き、単純にそれらを再接続します。フラグメントはViewPagerによって破棄されたビューを指しているため、これは爆発します。

解決策は、FragmentTabHostが破棄されたときにフラグメントを削除することです。あなたはおそらくも代わりに、標準のFragmentPagerAdapterまたはFragmentStatePagerAdapter(フラグメントを作る)を使用することによって、これらの問題を回避することができ


class MyFragmentTabHost 
{ 
    ... 

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

     boolean removeFragments = false; 
     if(mContext instanceof Activity) 
     { 
      Activity activity = (Activity)mContext; 
      removeFragments = !activity.isDestroyed(); 
     } 

     if(removeFragments) 
     { 
      FragmentTransaction ft = null; 
      for (int i = 0; i < mTabs.size(); i++) 
      { 
       TabInfo tab = mTabs.get(i); 
       if (tab.fragment != null) 
       { 
        if (ft == null) 
        { 
         ft = mFragmentManager.beginTransaction(); 
        } 
        ft.remove(tab.fragment); 
       } 
      } 

      if (ft != null) 
      { 
       ft.commit(); 
       mFragmentManager.executePendingTransactions(); 
      } 
     } 
    } 
FragmentTabHost.java

のローカルコピーに onDetachedFromWindow()にこのコードを追加したいと思いますPagerAdapter(ビューを作成する)。その後、 FragmentTabHost.setup(... fragment.getChildFragmentManager() ...)に電話します。

関連する問題