Как изменить представление содержимого, когда выбрана другая вкладка? (пример связан с Android HoneyCombGallery)

Я пытаюсь изменить пример кода HoneyCombGallery, чтобы изменить отображаемый вид при изменении вкладок. В примере, предоставленном Google, отображаемые фрагменты всегда одинаковы при переключении вкладок. Меняются только отображаемые данные. Однако я хочу изменить фрагменты и классы, отображаемые при изменении вкладок.

Вот код, который я изменил в примере с HoneyCombGallery: (MainActivity.java)

public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
    int nTabSelected = tab.getPosition();
    switch (nTabSelected) {
    case 0:
        setContentView(R.layout.settings_fragment);
        break;
    case 1:
        setContentView(R.layout.main);
        break;
    }

все остальное примерно так же. (также обратите внимание, что, как и ранее, MainActivity.onCreate вызывает setContentView(R.layout.main) - я не слышал, что вызов setContentView несколько раз не разрешен. Я все равно собираюсь делать это при каждом щелчке вкладки)

А вот макет, который надувается в коде (settings_fragment.xml)

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/frags2">
    <fragment class="com.example.android.hcgallery.SettingsFragment"
            android:id="@+id/settings_fragment2"
            android:visibility="gone"
            android:layout_marginTop="?android:attr/actionBarSize"
            android:layout_width="@dimen/titles_size"
            android:layout_height="match_parent" />
</LinearLayout>

«Фрагмент настроек» - это мой класс, который расширяет фрагмент: (SettingsFragment.java)

public class SettingsFragment extends Fragment {
    private View v;
    @Override
    public View onCreateView(final LayoutInflater inflater,
            final ViewGroup container, final Bundle savedInstanceState) {

        if (v != null)
            return v;
        v = inflater.inflate(R.layout.settings_layout, container, false);
        return v;
    }
}

XML также очень прост (settings_layout.xml):

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/settings_layout" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingTop="10dip">

    <TextView android:text="1.0.0" android:id="@+id/tv_version2"
        android:layout_marginTop="?android:attr/actionBarSize"
        android:textSize="15dp" android:textStyle="bold" android:typeface="sans"
        android:layout_height="wrap_content" android:layout_width="200dp"
        android:paddingTop="10dip" android:gravity="right"  >
    </TextView>
</RelativeLayout>

Это кажется простым, и когда приложение запускается, settings_fragment виден, поскольку по умолчанию выбрана 0-я вкладка. Когда мы выбираем следующую вкладку, я ожидаю, что setContentView(R.layout.main) будет выполнен, и я увижу основной макет (это то же самое, что и в примере Google: никаких изменений). Однако я получаю это исключение:

12-15 16:49:28.501: ERROR/AndroidRuntime(31485): FATAL EXCEPTION: main
12-15 16:49:28.501: ERROR/AndroidRuntime(31485): android.view.InflateException: Binary XML file line #23: Error inflating class fragment
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:688)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at android.view.LayoutInflater.rInflate(LayoutInflater.java:724)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at android.view.LayoutInflater.inflate(LayoutInflater.java:479)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at android.view.LayoutInflater.inflate(LayoutInflater.java:391)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at android.view.LayoutInflater.inflate(LayoutInflater.java:347)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at com.android.internal.policy.impl.PhoneWindow.setContentView(PhoneWindow.java:224)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at android.app.Activity.setContentView(Activity.java:1777)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at com.example.android.hcgallery.MainActivity.onTabSelected(MainActivity.java:109)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at com.android.internal.app.ActionBarImpl.selectTab(ActionBarImpl.java:462)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at com.android.internal.app.ActionBarImpl$TabImpl.select(ActionBarImpl.java:787)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at com.android.internal.widget.ActionBarView$TabClickListener.onClick(ActionBarView.java:950)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at android.view.View.performClick(View.java:3100)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at android.view.View$PerformClick.run(View.java:11644)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at android.os.Handler.handleCallback(Handler.java:587)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at android.os.Handler.dispatchMessage(Handler.java:92)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at android.os.Looper.loop(Looper.java:126)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at android.app.ActivityThread.main(ActivityThread.java:3997)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at java.lang.reflect.Method.invokeNative(Native Method)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at java.lang.reflect.Method.invoke(Method.java:491)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:841)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:599)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at dalvik.system.NativeStart.main(Native Method)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485): Caused by: java.lang.IllegalArgumentException: Binary XML file line #23: Duplicate id 0x7f09000a, tag null, or parent id 0x7f090009 with another fragment for com.example.android.hcgallery.TitlesFragment
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at android.app.Activity.onCreateView(Activity.java:4095)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:664)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     ... 21 more

Ошибка выглядит так: «android.view.InflateException: строка двоичного XML-файла № 23: ошибка при раздувании фрагмента класса»

Что это значит? Могу ли я не использовать setContentView для изменения фрагментов и макетов по своему усмотрению, когда выбраны разные вкладки? Если это неправильный способ иметь разные макеты для разных вкладок, как правильно это сделать?

У меня есть весь исходный код в архиве, загруженный в HoneycombGallery_Modified.zip - https://docs.google.com/open?id=0B0FHtQOpOYx6YWFiMDcxYmEtOGFkMy00NWU2LTk2MzMtZWY0YzdmZWUzMDU0 (Нажмите «Файл->Загрузить оригинал», если хотите загрузить весь zip-файл)


person Community    schedule 15.12.2011    source источник


Ответы (3)


Вам следует прочитать Документацию для разработчиков по фрагментам и изучить Выполнение транзакций фрагментов.

person kaspermoerch    schedule 15.12.2011
comment
Я просмотрел руководства по фрагментам... однако, почему setContentView() не работает? Это сработало в первый раз, когда была выбрана 0-я вкладка. Почему происходит сбой во второй раз при выборе следующей вкладки? Можем ли мы не выполнить setContentView для одного и того же XML-файла во второй раз? - person ; 15.12.2011
comment
Документы Android, похоже, прямо не говорят, что вы не можете вызывать setContentView дважды в Activity. Однако я бы воздержался от этого, вероятно, это также сделало бы ваш Activity класс BLOB. Тогда Fragments это лучший путь. - person kaspermoerch; 15.12.2011
comment
Хорошо, спасибо. Если бы я использовал фрагменты, я столкнулся бы с этой проблемой: когда планшет поворачивается, происходят следующие вещи: создается MainActivity, снова выбирается вкладка, и Android также воссоздает мои фрагменты. Однако MainActivity и логика tabSelected также пытаются воссоздать те же фрагменты, что и при первом запуске AP. Я могу убедиться, что не создаю в MainActivity onCreate, посмотрев на saveInstanceState. Но в итоге мы создадим его в onTabSelected. Таким образом, при вращении мы создадим фрагменты дважды. Есть ли способ избежать этого? - person ; 15.12.2011
comment
Я, наконец, использовал фрагменты и исправил эту проблему, поэтому отметил первого человека, который попросил меня использовать фрагменты, как правильный ответ. - person ; 08.01.2012

Мне удалось добиться того, что вам нужно.

Моя ситуация: у меня есть панель действий с несколькими вкладками, и для правильного отображения этих вкладок нужны разные макеты. Также я использую ActionBarSherlock для обратной совместимости, но я думаю, что тот же принцип можно сделать и с оригинальными классами.

У меня есть 2 вида вкладок:

  • Вкладки для отображения списка с фрагментом для отображения содержимого выбранных элементов в списке (это типичный образец фрагментов)
  • Вкладки для отображения простого веб-представления

Вот как я это сделал: у меня есть класс ActionBarActivity, который инициализирует вкладки и содержит TabListener.

Вот мой ActionBarActivity:

public class ActionBarActivity
        extends SherlockFragmentActivity
{
    /**
    * The current layoutId on the screen
    */
    private int layoutId;
    protected final int LAYOUT_LIST = R.layout.list_container;
    protected final int LAYOUT_WEBVIEW = R.layout.webview_container;

    @Override
    public void onCreate( Bundle savedInstanceState )
    {
        super.onCreate( savedInstanceState );
        ActionBar actionBar = getSupportActionBar();
        actionBar.setNavigationMode( ActionBar.NAVIGATION_MODE_TABS );
        getSupportActionBar().removeAllTabs();
        // we pass the layout to use for each tab
        addTab("Tab 1", LAYOUT_LIST, ListFragment.class);
        addTab("Tab 2", LAYOUT_WEBVIEW, WebViewFragment.class);

        // in case of screen orientation
        if ( savedInstanceState != null ) {
            actionBar.setSelectedNavigationItem( savedInstanceState.getInt( "tab", 0 ) );
        }
        // initial setting of the content 
        updateContentView( actionBar.getSelectedTab() );

    }

    private void addTab( String title, int contentLayoutId, Class<? extends Fragment> tabClass )
    {
        Tab t = getSupportActionBar().newTab();
        t.setText( tabTitle );
        t.setTag( contentLayoutId );
        t.setTabListener( new TabListener( this, tabTitle, activity, null ) );
        getSupportActionBar().addTab( t );
    }

    // that's how I deal to replace the layout used to display the tab's content 
    protected void updateContentView( Tab t )
    {
        FragmentManager fgtManager = getSupportFragmentManager();
        int tabLayoutId = ( Integer ) t.getTag();

        if ( tabLayoutId == LAYOUT_WEBVIEW ) {

            Fragment details = fgtManager.findFragmentById( R.id.detail_fragment);

            if(details != null && details.isInLayout()){
                FragmentTransaction transact = fgtManager.beginTransaction();
                transact.remove( details );
                transact.commit();
                details = null;
            }
        }

        if ( this.layoutId != tabLayoutId ) {
            layoutId = tabLayoutId;
            setContentView( layoutId );
        }
    }

    @Override
    protected void onSaveInstanceState( Bundle outState )
    {
        super.onSaveInstanceState( outState );
        outState.putInt( "tab", getSupportActionBar().getSelectedNavigationIndex() );
    }

    // see APIDemos.app.FragmentTabs for more details 
    public static class TabListener
            implements ActionBar.TabListener
    {
        private SherlockFragmentActivity mActivity;

        public void onTabSelected( Tab tab, FragmentTransaction ft )
        {
            // When we select a tab we udpate the content of the ActionBarActivity 
            ((ActionBarActivity)mActivity).updateContentView( tab );

            mFragment = mActivity.getSupportFragmentManager().findFragmentByTag( mTag );
            if ( mFragment == null ) {
                mFragment = Fragment.instantiate( mActivity, mClass.getName(), mArgs );
                ft.add( R.id.content_fragment, mFragment, mTag );
            } else {
                ft.attach( mFragment );
            }
        }
    }
}

И два моих макета:

макет/list_container.xml:

<?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:orientation="horizontal">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="0.6"
        android:id="@+id/content_fragment"/>
    <fragment
        android:id="@+id/detail_fragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="0.4"
        class="my.webview.WebViewFragment">
    </fragment>
</LinearLayout>

макет/webview_container.xml:

<?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:orientation="vertical">
        <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_weight="1"
        android:id="@+id/content_fragment"/>
</LinearLayout>    

PS: это мой первый вклад, поэтому буду рад критике.

PPS: я француз, поэтому, возможно, я забыл некоторые английские ошибки, извините.

person PierreB    schedule 17.08.2012

я не думаю, что вам следует снова устанавливать ContentView, его можно установить только один раз. Установите соответствующий фрагмент в контейнер фрагмента, используя fragmentTransaction.replace()

person Rajdeep Dua    schedule 15.12.2011
comment
Как я упоминал в предыдущем комментарии, при повороте планшета будет вызываться fragmentTransaction.replace (как будет вызываться onTabSelected), а затем Android также воссоздает мои фрагменты. Таким образом, мои фрагменты будут созданы дважды. Могу ли я как-то этого избежать? - person ; 15.12.2011