Подтвердить что ты не робот

Android FragmentTabHost - Не полностью выпечен?

Мне хотелось узнать, удалось ли кому-то добиться успеха в настройке вкладок с помощью FragmentTabHost, который поставляется с новым уровнем API Android 17.

Я был в восторге от того, что смог вложить вкладку в мой ViewPager SherlockFragments, но у меня возникают проблемы с простыми вещами, такими как перемещение вкладок в нижней части или изменение макета вкладок.

Кто-нибудь видел хороший пример использования этой функции?

Это единственный пример, который я мог найти в документах Android, а theres практически ничего не описывает его использование. Он также, кажется, игнорирует все, что определено в макете для R.id.fragment1.

Мой вопрос, я полагаю, будет, если кто-нибудь встретит хороший учебник re: 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 в библиотеке поддержки. Пользователь здесь, в коде Google, предложил следующее:

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-макет для фрагмента:

<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>
4b9b3361

Ответ 1

Я, наконец, дошел до сути. Существует проблема с FragmentTabHost.java, которая всегда будет создавать для вас элемент TabHost, независимо от того, что вы определяете в XML и накачиваете заранее.

Как таковой, я прокомментировал эту часть кода при написании своей собственной версии 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 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;
}
}

Ответ 2

Я думаю, было ошибкой установить метод initFragmentTabHost() в конструктор. В то время TabHost не его дети - это происходит после. LinearLayout, например, работать со своими детьми по методу onMeasure() (grepcode). ViewGroup в конструкторе просто инициализирующие переменные и установите mChildrenCount = 0 (grepcode).

Все, что я могу сделать, это только costumize FragmentTabHost:

<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" />

И costumize Tabs (есть проблемы с высотами вкладок, я решаю их в коде):

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

Я думаю, что другая настройка с помощью TabWidget мы можем делать только с программным манипулированием, например:

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

ИМХО, это нехорошо.

Ответ 3

Насколько я проверял решение jamisOn, это хорошо. Важно не инициализировать MyFragmentTabHost своим конструктором. По крайней мере, если класс, содержащий MyFragmentTabHost, является фрагментом. Я не проверял с помощью FragmentActivity...

Ответ 4

Я хотел бы упомянуть еще несколько вопросов с FragmentTabHost. Я использую ViewPager, где каждая страница (Вид) содержит FragmenTabHost, и мне пришлось преодолеть несколько проблем:

1) FragmentTabHost предполагает, что это единственный FragmentTabHost в родительском FragmentManager (второй аргумент FragmentTabHost.setup()). Это приводит к остальным проблемам...

2) "теги", которые вы указываете при вызове addTab(), передаются непосредственно в FragmentManager, поэтому, если вы просто используете жестко маркированные теги для всех своих страниц (совершенно разумная вещь), ваша первая страница создаст фрагменты вкладки а каждая другая страница будет повторно использовать эти вкладки. Да, страница 2 управляет страницей 1...

Решение состоит в создании уникальных имен тегов. Я добавил номер страницы в строковые строки:

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) Все фрагменты вкладки помещаются в контейнер, идентифицированный только "view id" (третий аргумент FragmentTabHost.setup()). Это означает, что когда FragmentManager разрешает viewId для представления, он всегда находит первый экземпляр (с первой страницы). Все остальные страницы игнорируются.

Решение этой задачи состоит в том, чтобы назначать уникальные идентификаторы вашим представлениям "содержимое вкладки", например:

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 уничтожается. Вы хотите добавить этот код в onDetachedFromWindow() в свою локальную копию FragmentTabHost.java

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

Возможно, вы также можете обойти эти проблемы, используя FragmentPagerAdapter или FragmentStatePagerAdapter (делает фрагменты) вместо стандартного PagerAdapter (делает Views). Затем вы вызываете FragmentTabHost.setup( ... fragment.getChildFragmentManager() ... ).