Найкраща практика для вкладених фрагментів в Android 4.0, 4.1 (<4.2) без використання бібліотеки підтримки


115

Я пишу програму для планшетів 4.0 та 4.1, для яких я не хочу використовувати бібліотеки підтримки (якщо вона не потрібна), але лише 4.x api.

Тому моя цільова платформа дуже чітко визначена як:> = 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, вкладені фрагменти, як показано на скріншоті, неможливі без бібліотеки підтримки.

(Це насправді може бути скоріше записом у вікі, ніж питанням, але, можливо, хтось інший це встиг раніше)

Оновлення:

Корисна відповідь є на: Фрагмент всередині фрагмента


22
У вас є три варіанти: 1. Націліть лише на 4.2 з вбудованими фрагментами. 2. Завдання 4.x із вкладеними фрагментами з бібліотеки підтримки 3. Не використовуйте вкладені фрагменти для будь-яких інших цільових сценаріїв платформи. Це має відповісти на ваше запитання. Крім того, ви не можете використовувати вкладений фрагмент, вбудований у макет xml, усі вони повинні бути додані в код. навряд чи є хороші приклади, які показують кращі практики використання фрагментів без бібліотеки підтримки - структура фрагмента підтримки копіює нативну, тому будь-який приклад повинен працювати в будь-якому випадку.
Luksprog

@Luksprog Дякуємо за ваші коментарі. Я віддаю перевагу вашому рішенню 2, і фрагменти добре працюють у бібліотеці підтримки, але вкладки в ActionBar не роблять - afaik, мені потрібно використовувати ActionBarSherlock, але вкладки не будуть інтегровані в ActionBar тоді, а лише внизу (що не є т необхідні для 4.х). І ActionBar.TabListener підтримують лише фрагменти з android.app.Fragment, а не з бібліотеки підтримки.
Матіас Конрадт

2
Я не знайомий із додатком "Контакти" на вкладці "Галактика", але майте на увазі, що ви завжди можете стикатися з власною реалізацією ActionBar(побудованої в будинку Samsung). Детальніше ознайомтеся з ActionBarSherlock, в ньому є вкладки в ActionBar, якщо є місце.
Luksprog

4
@Luksprog Я вважаю, що ви вже надали єдину відповідь, яку ви маєте дати, чи будете ви так люб'язною, щоб відповісти на це як належну відповідь.
Warpzit

1
@Pork Моя головна причина запитання: чи існують шляхи вирішення вкладених фрагментів без використання бібліотеки підтримки та всіх інших елементів перегляду. Тобто, якщо я перейду до бібліотеки підтримки, я б використовував FragmentActivity замість Fragment. Але я хочу використовувати Fragment, все, що я хочу, це заміна вкладених фрагментів , але не всіх компонентів v4. Тобто через інші бібліотеки з відкритим кодом тощо. Наприклад, знімок екрана працює на 4.0, і мені цікаво, чи використовуються вони ABS, SupportLib чи щось інше.
Mathias Conradt

Відповіді:


60

Обмеження

Тому вкладати фрагменти всередині іншого фрагмента неможливо з xml незалежно від того, яку версію FragmentManagerви використовуєте.

Тому вам доведеться додавати фрагменти за допомогою коду, це може здатися проблемою, але в перспективі ваш макет стає гнучким.

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

Гніздування без ChildManager - рішення

Рішення, звичайно! Я цим займаюся вже давно, (з моменту ViewPagerоголошення про це).

Дивись нижче; Це Fragmentте, що відкладає завантаження, тому Fragments можна завантажувати всередину нього.

Її досить простий 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


Як ви обробляєте компонування вкладених фрагментів?
pablisco

Єдиний спосіб, коли я міг бачити цю роботу, використовуючи CustomLayoutInflater, коли ви стикаєтесь з fragmentелементом, ви перекрили б суперреалізацію і спробуєте розібрати / роздути її самостійно. Але це буде ВЕЛИКОГО зусиль, а поза сферою питання StackOverflow.
Chris.Jenkins

Привіт, хтось може мені допомогти в цьому питанні ?? Я дійсно застряг .. stackoverflow.com/questions/32240138/…
Nicks

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 розширює Діяльність реалізує 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. Як ви, можливо, помітили, що дочірній фрагмент насправді вже не є дітьми батьківського фрагмента, але тепер це дитина, цього дійсно не уникнути.


1

Мені довелося вирішити цю точну проблему через поєднання NavigationDrawer, TabHost та ViewPager, які мали ускладнення при використанні бібліотеки підтримки через TabHost. І тоді мені також довелося підтримувати min API API JellyBean 4.1, тому використання вкладених фрагментів з getChildFragmentManager не було можливим.

Тож мою проблему можна перегнати на ...

TabHost (для верхнього рівня)
+ ViewPager (лише для одного з фрагментів з вкладками верхнього рівня)
= потреба в вкладених фрагментах (які JellyBean 4.1 не підтримує)

Моїм рішенням було створити ілюзію вкладених фрагментів без фактично вкладених фрагментів. Я зробив це, використовуючи основний напрямок використання TabHost AND 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);

Це фактично дозволило моєму підробленому "Вкладеному фрагменту" функціонувати як незалежний вигляд до тих пір, поки я вручну керував відповідними вагами макета.

Ось моя діяльність_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 / пейджер" та "@ + id / контейнер" є побратимами з "android: layout_weight =" 0.5 "'та' android: layout_height =" 0dp "'. Це так, що я бачу його в попередньому перегляді для будь-якого розміру екрана. Їх ваги будуть маніпулювати кодом під час виконання.


Привіт, мені цікаво, чому ви вирішили використовувати TabHost замість ActionBar з Tabs? Я сам перейшов з TabHost на просто ActionBar, і мій код став чистішим і компактнішим ...
ІгорГанапольський

Наскільки я пам’ятаю, один недолік використання вкладок у ActionBar полягає в тому, що він вирішує автоматично відображати їх як спанне меню (у випадку невеликого екрану), і це було недобре для мене. Але я не на 100% впевнений.
WindRider

@ Igor, я десь тут прочитав, SOщо використання ActionBarвкладок із символом a Navigation Drawerне є корисним, оскільки воно автоматично розмістить вкладки над видом вашого ящика. Вибачте, у мене немає посилання, щоб підтвердити це.
Azurespot


1
Нічого, дякую за посилання @Igor! Я перевірю це напевно. Я все ще початківець, тому є мільйон інших речей, щоб дізнатися з Android, але цей здається дорогоцінним каменем! Знову дякую.
Azurespot

1

Спираючись на @ 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();
    }
}

1

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

У своїй більш повній відповіді тут я зробив приклад, що демонструє, як використовувати вкладені фрагменти з бібліотекою підтримки.

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


Я був ОП. Причиною не користуватися службовою підтримкою було те, що це внутрішній додаток компанії, де використовуване обладнання було чітко визначено як> = 4.0 та <= 4.1. Не потрібно було охоплювати широку аудиторію, це внутрішній персонал і не має наміру використовувати додаток за межами компанії. Єдина причина підтримки LB - це бути сумісним назад - але можна було б сподіватися, що все, що ви можете зробити з бібліотекою підтримки, ви зможете досягти "споконвічно" без цього. Чому б "рідна" вища версія мала менше можливостей, ніж бібліотека підтримки, яка має на меті лише бути сумісною вниз.
Матіас Конрадт

1
Тим не менш, звичайно, ти можеш використовувати бібліотеку підтримки, і я могла б також. Просто не зрозумів, чому Google пропонує функції ТІЛЬКИ в бібліотеці підтримки, але не за її межами, або чому вони навіть називають її бібліотекою підтримки, а не роблять її загальним стандартом, якщо це все-таки найкраща практика. Ось хороша стаття про бібліотеку підтримки: martiancraft.com/blog/2015/06/android-support-library
Mathias Conradt

@Mathias, приємна стаття.
Сурагч
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.