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

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

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

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

Аналогично этому:

enter image description here

Нажатие на вкладке на панели действий изменяет внешний фрагмент, а внутренний фрагмент - это фрагмент с двумя вложенными фрагментами (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; и особенно ничего относительно вложенных фрагментов.

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


Проблема, которую я сейчас имею в разработке, - это именно это утверждение:

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

Примечание. Вы не можете раздувать макет во фрагмент, если этот макет включает a <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, вложенные фрагменты, как показано на скриншоте, невозможны без поддержки библиотеки.

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

Update:

Полезный ответ: Фрагмент внутри фрагмента

Ответ 1

Ограничения

Так что вложенные фрагменты внутри другого фрагмента невозможно с помощью 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

Ответ 2

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

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

2 - переместите макет xml FrameLayout из существующего фрагмента в макет действия и спрячьте его, указав высоту 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 - Внедрить интерфейс в родительском фрагменте

    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 - реализовать интерфейс в родительской активности

открытый класс. 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-интерфейсе. Как вы, возможно, заметили, что дочерний фрагмент больше не является дочерним элементом родительского фрагмента, но теперь является дочерним элементом активности, этого действительно не избежать.

Ответ 3

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

Итак, моя проблема может быть переделана...

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 для управления двумя sibling Views, управление видимостью которых осуществляется путем переключения 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);

Это фактически позволило моему поддельному "Nested Fragment" работать как независимое представление, если я вручную управлял соответствующими весами макета.

Здесь my 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". Это так, что я вижу его в предварительном просмотре для любого размера экрана. Во всяком случае, их веса будут обрабатываться в коде во время выполнения.

Ответ 4

Основываясь на @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();
    }
}

Ответ 5

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

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

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