Лучшая практика для вложенных фрагментов в Android 4.0, 4.1 (‹4.2) без использования библиотеки поддержки

Я пишу приложение для планшетов 4.0 и 4.1, для которых я не хочу использовать вспомогательные библиотеки (если они не нужны), но только поэтому API 4.x.

Итак, моя целевая платформа очень хорошо определена как:> = 4.0 и ‹= 4.1.

Приложение имеет многопанельный макет (два фрагмента, один маленький слева, один фрагмент содержимого справа) и панель действий с вкладками.

Подобно этому:

введите описание изображения здесь

Щелчок по вкладке на панели действий изменяет «внешний» фрагмент, а внутренний фрагмент становится фрагментом с двумя вложенными фрагментами (1. маленький фрагмент левого списка, 2. фрагмент с широким содержимым).

Теперь мне интересно, как лучше всего заменить фрагменты и особенно вложенные фрагменты. ViewPager является частью библиотеки поддержки, для этого класса нет собственной альтернативы 4.x. В моем понимании это кажется «устаревшим». - http://developer.android.com/reference/android/support/v4/view/ViewPager.html

Затем я прочитал примечания к выпуску Android 4.2 относительно ChildFragmentManager, что было бы хорошо, но я ориентируюсь на 4.0 и 4.1, так что это тоже нельзя использовать.

ChildFragmentManager доступен только в версии 4.2

К сожалению, почти нет хороших примеров, демонстрирующих передовые методы использования фрагментов без библиотеки поддержки, даже во всех руководствах для разработчиков Android; и особенно ничего о вложенных фрагментах.

Итак, мне интересно: просто невозможно писать приложения 4.1 с вложенными фрагментами без использования библиотеки поддержки и всего, что с ней связано? (нужно использовать FragmentActivity вместо Fragment и т. д.?) Или что было бы лучше всего?


Проблема, с которой я сейчас сталкиваюсь в разработке, заключается в следующем:

Библиотека поддержки Android теперь также поддерживает вложенные фрагменты, поэтому вы можете реализовать дизайн вложенных фрагментов на Android 1.6 и выше.

Примечание. Вы не можете преобразовать макет во фрагмент, если этот макет содержит <fragment>. Вложенные фрагменты поддерживаются только при динамическом добавлении к фрагменту.

Поскольку я помещаю определение вложенных фрагментов в XML, что, по-видимому, вызывает ошибку, например:

Caused by: java.lang.IllegalArgumentException: Binary XML file line #15: Duplicate id 0x7f090009, tag frgCustomerList, or parent id 0x7f090008 with another fragment for de.xyz.is.android.fragment.CustomerListFragment_

На данный момент я делаю для себя вывод: даже на 4.1, когда я даже не хочу ориентироваться на платформу 2.x, вложенные фрагменты, как показано на скриншоте, невозможны без библиотеки поддержки.

(На самом деле это может быть больше вики-статья, чем вопрос, но, может быть, кому-то это уже удавалось раньше).

Обновление:

Полезный ответ находится по адресу: Фрагмент внутри фрагмента


person Mathias Conradt    schedule 25.03.2013    source источник
comment
У вас есть три варианта: 1. Таргетинг только на 4.2 с собственными вложенными фрагментами. 2. Целевой объект 4.x с вложенными фрагментами из библиотеки поддержки 3. Не используйте вложенные фрагменты для каких-либо других сценариев целевой платформы. Это должно ответить на ваш вопрос. Кроме того, вы не можете использовать вложенные фрагменты, встроенные в макет xml, все они должны быть добавлены в код. вряд ли есть хорошие примеры, демонстрирующие лучшие практики использования фрагментов без библиотеки поддержки - структура поддержки фрагментов копирует родную, поэтому любой пример должен работать в любом случае.   -  person user    schedule 25.03.2013
comment
@Luksprog Спасибо за ваши комментарии. Я предпочитаю ваше решение 2, и фрагменты хорошо работают в библиотеке поддержки, но вкладки в ActionBar нет - черт возьми, мне нужно было бы использовать ActionBarSherlock, но вкладки не будут интегрированы в ActionBar тогда, а только под ним (что не t необходимо для 4.x). И ActionBar.TabListener поддерживает только фрагменты из android.app.Fragment, а не из библиотеки поддержки.   -  person Mathias Conradt    schedule 25.03.2013
comment
К сожалению, нет никакого способа привязать фрагменты библиотеки поддержки к собственным API-интерфейсам, которые используют фрагменты, поэтому да, вам нужно будет использовать ActionBarSherlock. Мне нужно было бы использовать ActionBarSherlock, но тогда вкладки не будут интегрированы в ActionBar, а только под ним (что не обязательно для 4.x). - вы уверены в этом? Из того, что я видел, использование библиотеки полностью воспроизводит API SDK, включая эти вкладки.   -  person user    schedule 25.03.2013
comment
@Luksprog На самом деле, я не уверен, и мне интересно, как это делает родное приложение «Контакты» на моей вкладке Samsung Galaxy; Я просто нигде не видел вкладок, встроенных в ActionBar, ни в одном из примеров ABS; Я попробую или найду способ сделать это; но вы правы, мне обязательно нужно использовать ABS, поскольку developer.android. com / tools / extras / support-library.html говорится: ActionBar не поддерживается библиотекой. Однако при создании меню параметров вы можете объявить, какие элементы должны быть добавлены в панель действий, когда она доступна (на Android 3.0 или новее) - бесполезно.   -  person Mathias Conradt    schedule 25.03.2013
comment
Я не знаком с приложением «Контакты» на вкладке «Galaxy», но имейте в виду, что вы всегда можете столкнуться с пользовательской реализацией ActionBar (встроенной в Samsung). Присмотритесь к ActionBarSherlock, у него есть вкладки в ActionBar, если есть место.   -  person user    schedule 25.03.2013
comment
@Luksprog Я считаю, что вы уже предоставили единственный ответ, который можно дать, не могли бы вы дать его как правильный ответ.   -  person Warpzit    schedule 22.05.2013
comment
Я не думаю, что это ответ. Это был очень конкретный вопрос, и я уверен, что Матиас действительно знал альтернативы. Мне интересно, почему у него так много голосов? Я действительно имею в виду. Итак, вы читаете о новой функции, которая только 4.2, Google предоставляет способ использовать функциональность в предыдущей версии, и тем не менее у нас есть вопрос, спрашивающий, как использовать функциональность 4.2 в предыдущих версиях без необходимости использовать инструменты, которые делают это ?   -  person Pork 'n' Bunny    schedule 06.06.2013
comment
@Pork Моя основная причина вопроса: есть ли какие-либо обходные пути для вложенных фрагментов без необходимости использовать библиотеку поддержки и все остальные элементы представления. Это означает, что если я переключусь на библиотеку поддержки, я буду использовать FragmentActivity вместо Fragment. Но я действительно хочу использовать фрагмент, все, что мне нужно, - это замена вложенных фрагментов, но не всех компонентов v4. Т.е. через другие библиотеки с открытым исходным кодом и т. д. Например, приведенный выше снимок экрана работает на 4.0, и мне интересно, используют ли они ABS, SupportLib или что-то еще.   -  person Mathias Conradt    schedule 13.06.2013
comment
ABS построен на основе библиотеки поддержки ... Если вас беспокоит раздувание, то для этого и нужен proguard.   -  person Pork 'n' Bunny    schedule 15.06.2013


Ответы (5)


Ограничения

Таким образом, вложение фрагментов внутри другого фрагмента невозможно с xml независимо от того, какую версию FragmentManager вы используете.

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

Итак, вложение без использования getChildFragmentManger? Суть childFragmentManager в том, что он откладывает загрузку до завершения предыдущей транзакции фрагмента. И, конечно, это естественно поддерживалось только в версии 4.2 или в библиотеке поддержки.

Вложение без ChildManager - Решение

Решение, конечно! Я занимаюсь этим уже давно (с тех пор, как был анонсирован ViewPager).

См. ниже; Это Fragment, который откладывает загрузку, поэтому внутрь него можно загрузить Fragment.

Это довольно просто, Handler - действительно действительно удобный класс, по сути, обработчик ожидает, пока пространство для выполнения в основном потоке после того, как текущая транзакция фрагмента завершит фиксацию (поскольку фрагменты мешают пользовательскому интерфейсу, который они запускают в основном потоке).

// Remember this is an example, you will need to modify to work with your code
private final Handler handler = new Handler();
private Runnable runPager;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    return inflater.inflate(R.layout.frag_layout, container, false);
}

@Override
public void onActivityCreated(Bundle savedInstanceState)
{
    super.onActivityCreated(savedInstanceState);
    runPager = new Runnable() {

        @Override
        public void run()
        {
          getFragmentManager().beginTransaction().addFragment(R.id.frag_container, MyFragment.newInstance()).commit();
        }
    };
    handler.post(runPager);
}

/**
 * @see android.support.v4.app.Fragment#onPause()
 */
@Override
public void onPause()
{
    super.onPause();
    handler.removeCallbacks(runPager);
}

Я бы не стал считать это «лучшей практикой», но у меня есть живые приложения, использующие этот хак, и у меня пока нет проблем с ним.

Я также использую этот метод для встраивания пейджеров представлений - https://gist.github.com/chrisjenx/3405429

person Chris.Jenkins    schedule 16.06.2013
comment
Как вы обрабатываете вложенные фрагменты макета? - person pablisco; 27.07.2014
comment
Единственный способ увидеть, как это работает, - использовать CustomLayoutInflater. Когда вы встретите элемент fragment, вы переопределите супер-реализацию и попытаетесь самостоятельно разобрать / раздуть его. Но для этого потребуется ОЧЕНЬ много усилий, что выходит за рамки вопроса StackOverflow. - person Chris.Jenkins; 30.07.2014
comment
Привет, может ли кто-нибудь помочь мне в этом вопросе ?? Я действительно застрял ... stackoverflow .com / questions / 32240138 / - person Nicks; 02.09.2015

Лучший способ сделать это в версиях до API 17 - вообще этого не делать. Попытка реализовать такое поведение вызовет проблемы. Однако это не означает, что его нельзя убедительно подделать с помощью текущего API 14. Я сделал следующее:

1 - посмотрите на связь между фрагментами http://developer.android.com/training/basics/fragments/communicating.html

2 - переместите макет xml FrameLayout из существующего фрагмента в макет Activity и скройте его, задав высоту 0:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:tools="http://schemas.android.com/tools"
          android:layout_width="fill_parent"
          android:layout_height="fill_parent">
<FrameLayout android:id="@+id/content"
          android:layout_width="300dp"
          android:layout_height="match_parent" />


<FrameLayout android:id="@+id/lstResults"
             android:layout_width="300dp"
             android:layout_height="0dp"
             android:layout_below="@+id/content"
             tools:layout="@layout/treeview_list_content"/>


<FrameLayout android:id="@+id/anomalies_fragment"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
        android:layout_toRightOf="@+id/content" />

3 - Implement the interface in the parent Fragment

    OnListener mCallback;

// Container Activity must implement this interface
public interface OnListener 
{
    public void onDoSomethingToInitChildFrame(/*parameters*/);
    public void showResults();
    public void hideResults();
}

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);

    // This makes sure that the container activity has implemented
    // the callback interface. If not, it throws an exception
    try {
        mCallback = (OnFilterAppliedListener) activity;
    } catch (ClassCastException e) {
        throw new ClassCastException(activity.toString()
                + " must implement OnListener");
    }
}

@Override
public void onActivityCreated(Bundle savedInstanceState) 
{
    super.onActivityCreated(savedInstanceState);

    mCallback.showResults();
}

@Override
public void onPause()
{
    super.onPause();

    mCallback.hideResults();
}

public void onClickButton(View view)
{
    // do click action here

    mCallback.onDoSomethingToInitChildFrame(/*parameters*/);
}

4 - Реализуйте интерфейс в родительском Activity

открытый класс YourActivity extends Activity реализует yourParentFragment.OnListener {

public void onDoSomethingToInitChildFrame(/*parameters*/)
{
    FragmentTransaction ft = getFragmentManager().beginTransaction();
    Fragment childFragment = getFragmentManager().findFragmentByTag("Results");
    if(childFragment == null)
    {
        childFragment = new yourChildFragment(/*parameters*/);
        ft.add(R.id.lstResults, childFragment, "Results");
    }
    else
    {
        ft.detach(childFragment);

        ((yourChildFragment)childFragment).ResetContent(/*parameters*/);

        ft.attach(childFragment);
    }
    ft.commit();

    showResultsPane();
}

public void showResults()
{
    FragmentTransaction ft = getFragmentManager().beginTransaction();
    Fragment childFragment = getFragmentManager().findFragmentByTag("Results");
    if(childFragment != null)
        ft.attach(childFragment);
    ft.commit();

    showResultsPane();
}

public void showResultsPane()
{
    //resize the elements to show the results pane
    findViewById(R.id.content).getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
    findViewById(R.id.lstResults).getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
}

public void hideResults()
{
    //resize the elements to hide the results pane
    findViewById(R.id.content).getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
    findViewById(R.id.lstResults).getLayoutParams().height = 0;

    FragmentTransaction ft = getFragmentManager().beginTransaction();
    Fragment childFragment = getFragmentManager().findFragmentByTag("Results");
    if(childFragment != null)
        ft.detach(childFragment);
    ft.commit();
}

}

5. Наслаждайтесь, с помощью этого метода вы получаете ту же гибкую функциональность, что и функция getChildFragmentManager () в среде до API 17. Как вы, возможно, заметили, дочерний фрагмент больше не является дочерним по отношению к родительскому фрагменту, а теперь является дочерним по отношению к активности, этого действительно невозможно избежать.

person Alex    schedule 04.09.2014

Мне пришлось столкнуться с этой конкретной проблемой из-за комбинации NavigationDrawer, TabHost и ViewPager, у которой были сложности с использованием библиотеки поддержки из-за TabHost. Кроме того, мне пришлось поддерживать минимальный API JellyBean 4.1, поэтому использование вложенных фрагментов с getChildFragmentManager было недопустимым вариантом.

So my problem can be distilled to...

TabHost (for top level)
+ ViewPager (for just one of the top level tabbed fragments)
= need for Nested Fragments (which JellyBean 4.1 won't support)

Мое решение состояло в том, чтобы создать иллюзию вложенных фрагментов без фактического вложения фрагментов. Я сделал это, заставив основное действие использовать TabHost И ViewPager для управления двумя родственными представлениями, видимость которых регулируется переключением layout_weight между 0 и 1.

//Hide the fragment used by TabHost by setting height and weight to 0
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 0);
mTabHostedView.setLayoutParams(lp);
//Show the fragment used by ViewPager by setting height to 0 but weight to 1
lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1);
mPagedView.setLayoutParams(lp);

Это фактически позволило моему поддельному «Вложенному фрагменту» работать как независимое представление, пока я вручную управлял соответствующими весами макета.

Вот мой файл activity_main.xml:

<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.ringofblades.stackoverflow.app.MainActivity">

    <TabHost
        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:background="@drawable/background_image"
                android:layout_width="match_parent"
                android:layout_weight="0.5"
                android:layout_height="0dp"/>
            <android.support.v4.view.ViewPager
                xmlns:tools="http://schemas.android.com/tools"
                android:id="@+id/pager"
                android:background="@drawable/background_image"
                android:layout_width="match_parent"
                android:layout_weight="0.5"
                android:layout_height="0dp"
                tools:context="com.ringofblades.stackoverflow.app.MainActivity">
                <FrameLayout
                    android:id="@+id/container"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent" />
            </android.support.v4.view.ViewPager>
            <TabWidget android:id="@android:id/tabs"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />
        </LinearLayout>
    </TabHost>

    <fragment android:id="@+id/navigation_drawer"
        android:layout_width="@dimen/navigation_drawer_width"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:name="com.ringofblades.stackoverflow.app.NavigationDrawerFragment"
        tools:layout="@layout/fragment_navigation_drawer" />
</android.support.v4.widget.DrawerLayout>

Обратите внимание, что «@ + id / pager» и «@ + id / container» являются братьями и сестрами с 'android: layout_weight = "0.5"' и 'android: layout_height = "0dp"'. Это сделано для того, чтобы я мог видеть это в превью при любом размере экрана. В любом случае их вес будет изменяться в коде во время выполнения.

person Kain Shin    schedule 03.04.2014
comment
Привет, мне любопытно, почему вы решили использовать TabHost вместо ActionBar с вкладками? Я сам перешел с TabHost на ActionBar, и мой код стал чище и компактнее ... - person IgorGanapolsky; 08.05.2014
comment
Насколько я помню, одним из недостатков использования вкладок в ActionBar является то, что он решает автоматически отображать их как раскрывающееся меню счетчика (в случае маленького экрана), и это было не очень хорошо для меня. Но я не уверен на 100%. - person WindRider; 09.05.2014
comment
@ Игорь, я где-то читал здесь, на SO, что использование ActionBar вкладок с Navigation Drawer нехорошо, потому что оно автоматически помещает вкладки поверх вида вашего ящика. Извините, у меня нет ссылки, чтобы подтвердить это. - person Azurespot; 09.02.2015
comment
@NoniA. Тем не менее, TabHost больше нет. Используйте материальный дизайн прямо сейчас с панелью инструментов! - person IgorGanapolsky; 09.02.2015
comment
@ Игорь, что такое материальный дизайн с панелью инструментов? - person Azurespot; 10.02.2015
comment
@NoniA. blog.xamarin.com/android-tips-hello-toolbar -goodbye-action-bar Вы вообще следите за развитием Android? - person IgorGanapolsky; 10.02.2015
comment
Ух, спасибо за ссылку @ Игорь! Я обязательно это проверю. Я все еще новичок, так что мне нужно изучить еще миллион вещей с Android, но этот кажется жемчужиной! Спасибо еще раз. - person Azurespot; 11.02.2015

Основываясь на ответе @ Chris.Jenkins, это решение, которое хорошо работает для меня, для удаления фрагментов во время событий жизненного цикла (которые имеют тенденцию вызывать исключения IllegalStateExceptions). При этом используется комбинация подхода Handler и проверки Activity.isFinishing () (в противном случае будет выдана ошибка «Невозможно выполнить это действие после onSaveInstanceState»).

import android.app.Activity;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;

public abstract class BaseFragment extends Fragment {
    private final Handler handler = new Handler();

    /**
     * Removes the {@link Fragment} using {@link #getFragmentManager()}, wrapped in a {@link Handler} to
     * compensate for illegal states.
     *
     * @param fragment The {@link Fragment} to schedule for removal.
     */
    protected void removeFragment(@Nullable final Fragment fragment) {
        if (fragment == null) return;

        final Activity activity = getActivity();
        handler.post(new Runnable() {
            @Override
            public void run() {
                if (activity != null && !activity.isFinishing()) {
                    getFragmentManager().beginTransaction()
                            .remove(fragment)
                            .commitAllowingStateLoss();
                }
            }
        });
    }

    /**
     * Removes each {@link Fragment} using {@link #getFragmentManager()}, wrapped in a {@link Handler} to
     * compensate for illegal states.
     *
     * @param fragments The {@link Fragment}s to schedule for removal.
     */
    protected void removeFragments(final Fragment... fragments) {
        final FragmentManager fragmentManager = getFragmentManager();
        final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

        for (Fragment fragment : fragments) {
            if (fragment != null) {
                fragmentTransaction.remove(fragment);
            }
        }

        final Activity activity = getActivity();
        handler.post(new Runnable() {
            @Override
            public void run() {
                if (activity != null && !activity.isFinishing()) {
                    fragmentTransaction.commitAllowingStateLoss();
                }
            }
        });
    }
}

Использование:

class MyFragment extends Fragment {
    @Override
    public void onDestroyView() {
        removeFragments(mFragment1, mFragment2, mFragment3);
        super.onDestroyView();
    }
}
person HelloImKevo    schedule 07.08.2015

Хотя у OP могут быть особые обстоятельства, которые не позволяют ему использовать библиотеку поддержки, большинству людей следует ее использовать. Документация Android рекомендует это, и это сделает ваше приложение доступным для самой широкой аудитории.

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

введите здесь описание изображения

person Suragch    schedule 13.09.2016
comment
Я был ОП. Причина отказа от использования библиотеки поддержки заключалась в том, что это было внутреннее приложение компании, в котором используемое оборудование было четко определено как ›= 4.0 и‹ = 4.1. Не было необходимости охватывать широкую аудиторию, это был внутренний персонал и не было намерения использовать приложение за пределами компании. Единственная причина поддержки библиотеки - быть обратно совместимой, но можно было бы ожидать, что все, что вы можете делать с библиотекой поддержки, вы сможете реализовать без нее. Почему нативная более высокая версия имеет меньше функций, чем библиотека поддержки, единственная цель которой - быть совместимой с предыдущими версиями. - person Mathias Conradt; 14.09.2016
comment
Тем не менее, конечно, вы могли бы использовать библиотеку поддержки, и я мог бы тоже. Просто не понимал, почему Google предлагает функции ТОЛЬКО в библиотеке поддержки, но не за ее пределами, или почему они даже называют ее библиотекой поддержки, а не делают ее общим стандартом, если это все равно лучше всего. Вот хорошая статья о библиотеке поддержки: martiancraft.com/blog/2015 / 06 / android-support-library - person Mathias Conradt; 14.09.2016
comment
@ Матиас, хорошая статья. - person Suragch; 14.09.2016