Mejores prácticas para fragmentos anidados en Android 4.0, 4.1 (


Estoy escribiendo una aplicación para tabletas 4.0 y 4.1, para las cuales no quiero usar las bibliotecas de soporte (si no es necesario), sino la 4.x api solo por lo tanto.

Así que mi plataforma de destino está muy bien definida como: > = 4.0 y

La aplicación tiene un diseño multi-panel (dos fragmentos, uno pequeño a la izquierda, un fragmento de contenido a la derecha) y una barra de acciones con pestañas.

Similar a esto:

introduzca la descripción de la imagen aquí

Al hacer clic en una pestaña en la barra de acciones, se cambia el'exterior' fragmento, y el fragmento interno entonces es un fragmento con dos fragmentos anidados (1. pequeño fragmento de la lista izquierda, 2. fragmento de contenido amplio).

Ahora me pregunto cuál es la mejor práctica para reemplazar fragmentos y especialmente fragmentos anidados. El ViewPager es parte de la biblioteca de soporte, no hay nativo 4.x alternativa para esta clase. Parecen estar "en desuso" en mi sentido. - http://developer.android.com/reference/android/support/v4/view/ViewPager.html

Entonces leí las notas de la versión para Android 4.2, con respecto a ChildFragmentManager, que sería una buena opción, pero estoy apuntando 4.0 y 4.1, por lo que esto no se puede utilizar tampoco.

ChildFragmentManager solo está disponible en 4.2

Desafortunadamente, apenas hay buenos ejemplos por ahí que muestren mejor prácticas para fragmentos usos sin la biblioteca de soporte, incluso en toda la guía para desarrolladores de Android; y especialmente nada con respecto a los fragmentos anidados.

Así que me pregunto: ¿simplemente no es posible escribir aplicaciones 4.1 con fragmentos anidados sin usar la biblioteca de soporte y todo lo que viene con ella? (necesidad de usar FragmentActivity en lugar de Fragment, etc.?) O ¿cuál sería la mejor práctica?


El problema que estoy teniendo actualmente en el el desarrollo es exactamente esta declaración:

La Biblioteca de soporte de Android ahora también admite fragmentos anidados, por lo que puede implementar diseños de fragmentos anidados en Android 1.6 y versiones posteriores.

Nota: No se puede inflar un diseño en un fragmento cuando ese diseño incluye un <fragment>. Los fragmentos anidados solo se admiten cuando se agregan a un fragmento dinámica.

Porque pongo definir los fragmentos anidados en XML, lo que aparentemente causa un error como:

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_

En este momento, concluyo por mí mismo: incluso en 4.1, cuando ni siquiera quiero apuntar al 2.x plataforma, los fragmentos anidados como se muestra en la captura de pantalla no son posibles sin la biblioteca de soporte.

(Esto podría ser más una entrada wiki que una pregunta, pero tal vez alguien más lo haya manejado antes).

Actualización:

A la respuesta útil está en: Fragmento Dentro del fragmento

Author: Community, 2013-03-25

5 answers

Limitaciones

Por lo tanto, no es posible anidar fragmentos dentro de otro fragmento con xml, independientemente de la versión de FragmentManager que utilice.

Así que tienes que agregar fragmentos a través de código, esto puede parecer un problema, pero a la larga hace que tus diseños sean superflexibles.

Entonces anidando sin usar getChildFragmentManger? La esencia detrás de childFragmentManager es que aplaza la carga hasta que la transacción del fragmento anterior haya terminado. Y, por supuesto, solo fue soportado naturalmente en 4.2 o el biblioteca de soporte.

Anidamiento sin ChildManager - Solución

Solución, Claro! He estado haciendo esto desde hace mucho tiempo, (desde que el ViewPager fue anunciado).

Ver a continuación; Este es un Fragmentque aplaza la carga, por lo que Fragment s se pueden cargar dentro de él.

Es bastante simple, la Handler es una clase realmente muy útil, efectivamente el manejador espera un espacio para ejecutarse en el hilo principal después de que la transacción de fragmento actual haya terminado de confirmar (como fragmentos interferir con la interfaz de usuario que se ejecutan en el hilo principal).

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

No lo consideraría una "mejor práctica", pero tengo aplicaciones en vivo que usan este truco y aún no tengo ningún problema con él.

También uso este método para incrustar buscapersonas de vista - https://gist.github.com/chrisjenx/3405429

 58
Author: Chris.Jenkins,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-06-16 10:21:07

La mejor manera de hacer esto en pre-API 17 es no hacerlo en absoluto. Tratar de implementar este comportamiento va a causar problemas. Sin embargo, esto no quiere decir que no se pueda falsificar de manera convincente utilizando la API 14 actual. Lo que hice fue lo siguiente:

1-mira la comunicación entre fragmentos http://developer.android.com/training/basics/fragments/communicating.html

2-mueve tu layout xml FrameLayout desde tu Fragmento existente al layout de la actividad y escóndelo dando una altura de 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-Implementar la interfaz en el fragmento padre

    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-Implementar la interfaz en la actividad padre

La clase pública YourActivity extiende la actividad implementa yourParentFragment.Lista de usuarios {

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 - Disfrute, con este método se obtiene la misma funcionalidad fluida que con la función getChildFragmentManager() en un pre-API 17 envoronment. Como habrás notado, el fragmento hijo no es ya sea realmente un hijo del fragmento padre, pero ahora un hijo de la actividad, esto realmente no se puede evitar.

 2
Author: Alex,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-04 15:13:17

Tuve que lidiar con este problema exacto debido a una combinación de NavigationDrawer, TabHost y ViewPager que tuvo complicaciones con el uso de la biblioteca de soporte debido a TabHost. Y luego también tuve que soportar la API min de JellyBean 4.1, por lo que usar fragmentos anidados con getChildFragmentManager no era una opción.

Así que mi problema se puede destilar a...

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)

Mi solución fue crear la ilusión de fragmentos anidados sin que realmente se anidaran fragmentos. Hice esto teniendo la actividad principal utiliza TabHost Y ViewPager para administrar dos vistas hermanas cuya visibilidad se administra alternando layout_weight entre 0 y 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);

Esto efectivamente permitió que mi falso "Fragmento Anidado" operara como una vista independiente siempre y cuando administrara manualmente los pesos de diseño relevantes.

Aquí está mi 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>

Tenga en cuenta que "@+id/pager" y "@+id/container" son hermanos con 'android:layout_weight="0.5"' y 'android:layout_height="0dp"'. Esto es para que pueda verlo en la vista previa para cualquier tamaño de pantalla. Sus pesos serán manipulados en código durante el tiempo de ejecución, de todos modos.

 1
Author: Kain Shin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-03 18:20:30

Construyendo sobre @Chris.Jenkins respuesta, esta es la solución que ha estado funcionando bien para mí, para la eliminación de fragmento(s) durante los eventos del ciclo de vida (que tienen una tendencia a lanzar IllegalStateExceptions). Esto utiliza una combinación del enfoque de Controlador y una Actividad.isFinishing () check (de lo contrario lanzará un error para "No se puede realizar esta acción después deavaveinstancestate).

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

Uso:

class MyFragment extends Fragment {
    @Override
    public void onDestroyView() {
        removeFragments(mFragment1, mFragment2, mFragment3);
        super.onDestroyView();
    }
}
 1
Author: HelloImKevo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-07 21:39:26

Aunque el OP puede tener circunstancias especiales que le impidan usar la Biblioteca de Soporte, la mayoría de la gente debería usarla. La documentación de Android lo recomienda, y hará que su aplicación esté disponible para el público más amplio posible.

En mi respuesta más completa aquí hice un ejemplo que demuestra cómo usar fragmentos anidados con la biblioteca de soporte.

introduzca la descripción de la imagen aquí

 1
Author: Suragch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:26:27