Práctica recomendada para fragmentos anidados en Android 4.0, 4.1 (<4.2) sin usar la biblioteca de soporte

115

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 solo la API 4.x.

Entonces mi plataforma de destino está muy bien definida como:> = 4.0 y <= 4.1

La aplicación tiene un diseño de múltiples paneles (dos fragmentos, uno pequeño a la izquierda, un fragmento de contenido a la derecha) y una barra de acción con pestañas.

Similar a ésto:

ingrese la descripción de la imagen aquí

Al hacer clic en una pestaña de la barra de acción, se cambia el fragmento "externo" y el fragmento interno es un fragmento con dos fragmentos anidados (1. fragmento pequeño de la lista izquierda, 2. fragmento de contenido ancho).

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

Luego leí las notas de la versión para Android 4.2, ChildFragmentManagerque serían una buena opción, pero estoy apuntando a 4.0 y 4.1, por lo que tampoco se puede usar.

ChildFragmentManager solo está disponible en 4.2

Desafortunadamente, casi no hay buenos ejemplos que muestren las mejores prácticas para el uso de fragmentos sin la biblioteca de soporte, incluso en todas las guías para desarrolladores de Android; y especialmente nada con respecto a los fragmentos anidados.

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


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

La biblioteca de compatibilidad 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 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 de forma dinámica.

Porque puse 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_

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

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

Actualizar:

Una respuesta útil está en: Fragment Inside Fragment

Mathias Conradt
fuente
22
Tiene tres opciones: 1. Apuntar solo a 4.2 con fragmentos nativos anidados. 2. Destino 4.x con fragmentos anidados de la biblioteca de soporte 3. No utilice fragmentos anidados para ningún otro escenario de destino de plataforma. Esto debería responder a su pregunta. Además, no puede usar un fragmento anidado incrustado en el diseño xml, todos deben agregarse en el código. Casi no hay buenos ejemplos que muestren las mejores prácticas para el uso de fragmentos sin la biblioteca de soporte ; el marco de trabajo de fragmentos de soporte replica el nativo, por lo que cualquier ejemplo debería funcionar de cualquier manera.
Luksprog
@Luksprog Gracias por sus comentarios. Prefiero su solución 2, y los fragmentos funcionan bien en la biblioteca de soporte, pero las pestañas en ActionBar no lo hacen; afaik, necesitaría usar ActionBarSherlock, pero las pestañas no se integrarían en ActionBar entonces, sino solo debajo (que no es ' t necesario para 4.x). Y ActionBar.TabListener solo admite Fragmentos de android.app.Fragment, no de la biblioteca de soporte.
Mathias Conradt
2
No estoy familiarizado con la aplicación Contactos en la pestaña Galaxy, pero tenga en cuenta que siempre puede enfrentarse a una implementación personalizada del ActionBar(construido internamente por Samsung). Eche un vistazo más de cerca a ActionBarSherlock, tiene las pestañas en ActionBar si hay espacio.
Luksprog
4
@Luksprog Creo que ya ha proporcionado la única respuesta que hay para dar, ¿sería tan amable de ponerla como una respuesta adecuada?
Warpzit
1
@Pork Mi principal razón para la pregunta es: ¿hay alguna solución para los fragmentos anidados sin tener que usar la biblioteca de soporte y todos sus otros elementos de vista? Es decir, si cambio a la biblioteca de soporte, estaría usando FragmentActivity en lugar de Fragment. Pero quiero usar Fragmento, todo lo que quiero es un reemplazo para los Fragmentos anidados , pero no todos los componentes de la v4. Es decir, a través de otras bibliotecas de código abierto, etc. Por ejemplo, la captura de pantalla anterior se ejecuta en 4.0, y me pregunto si están usando ABS, SupportLib o cualquier otra cosa.
Mathias Conradt

Respuestas:

60

Limitaciones

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

Por lo tanto, debe agregar fragmentos a través del código, esto puede parecer un problema, pero a la larga hace que sus diseños sean superflexibles.

Entonces, ¿anidar sin usar getChildFragmentManger? La esencia detrás childFragmentManageres que pospone la carga hasta que finaliza la transacción del fragmento anterior. Y, por supuesto, solo se admitía naturalmente en 4.2 o en la biblioteca de soporte.

Anidamiento sin ChildManager - Solución

¡Solución, claro! He estado haciendo esto durante mucho tiempo (desde que ViewPagerse anunció).

Vea abajo; Este es un Fragmentque difiere la carga, por lo que Fragmentse pueden cargar s dentro de él.

Es bastante simple, Handleres una clase realmente muy útil, efectivamente el controlador espera un espacio para ejecutar en el hilo principal después de que la transacción del fragmento actual haya terminado de confirmarse (ya que los fragmentos interfieren 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 he tenido ningún problema con él.

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

Chris.Jenkins
fuente
¿Cómo se manejan los fragmentos anidados de diseño?
pablisco
La única forma en que pude ver que esto funciona es mediante el uso de CustomLayoutInflater, a medida que se encuentre con el fragmentelemento, anularía la súper implementación e intentaría analizarlo / inflarlo usted mismo. Pero eso será MUCHO esfuerzo, fuera del alcance de una pregunta de StackOverflow.
Chris.Jenkins
Hola, ¿alguien puede ayudarme en este tema? Estoy realmente atascado .. stackoverflow.com/questions/32240138/…
Nicks
2

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

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

2 - mueva su diseño xml FrameLayout de su Fragmento existente al diseño de Actividad y ocúltelo 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 principal

    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 principal

La clase pública YourActivity amplía Activity implementa 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 - Disfrute, con este método obtiene la misma funcionalidad fluida que con la función getChildFragmentManager () en un entorno anterior a la API 17. Como habrá notado, el fragmento secundario ya no es realmente un elemento secundario del fragmento principal, sino que ahora es un elemento secundario de la actividad, esto realmente no se puede evitar.

Alex
fuente
1

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 admitir la API mínima de JellyBean 4.1, por lo que usar fragmentos anidados con getChildFragmentManager no era una opción.

Entonces mi problema se puede destilar a ...

TabHost (para el nivel superior)
+ ViewPager (solo para uno de los fragmentos con pestañas de nivel superior)
= necesidad de fragmentos anidados (que JellyBean 4.1 no admite)

Mi solución fue crear la ilusión de fragmentos anidados sin realmente anidar fragmentos. Hice esto haciendo que la actividad principal usara 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 permitió que mi "Fragmento anidado" falso funcionara como una vista independiente siempre que gestionara 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 de '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. De todos modos, sus pesos se manipularán en código durante el tiempo de ejecución.

Kain Shin
fuente
Hola, tengo curiosidad por saber por qué eligió usar TabHost en lugar de ActionBar con pestañas. Yo mismo cambié de TabHost a solo ActionBar, y mi código se volvió más limpio y compacto ...
IgorGanapolsky
Por lo que recuerdo, un inconveniente de usar pestañas en ActionBar es que decide mostrarlas automáticamente como un menú desplegable giratorio (en el caso de una pantalla pequeña) y eso no fue bueno para mí. Pero no estoy 100% seguro.
WindRider
@Igor, leí en algún lugar de aquí SOque usar ActionBarpestañas con a Navigation Drawerno es bueno porque colocará automáticamente las pestañas sobre la vista de su cajón. Lo siento, no tengo el enlace para hacer una copia de seguridad.
Azurespot
1
@NoniA. blog.xamarin.com/android-tips-hello-toolbar-goodbye-action-bar ¿ Sigues el desarrollo de Android?
IgorGanapolsky
1
¡Vaya, gracias por el enlace @Igor! Comprobaré esto con seguridad. Todavía soy un principiante, así que tengo un millón de otras cosas que aprender con Android, ¡pero esta parece una joya! Gracias de nuevo.
Azurespot
1

Sobre la base de la respuesta de @ Chris.Jenkins, esta es la solución que me ha funcionado bien, para eliminar fragmentos durante los eventos del ciclo de vida (que tienen una tendencia a lanzar IllegalStateExceptions). Esto usa una combinación del enfoque Handler y una verificación Activity.isFinishing () (de lo contrario, arrojará un error para "No se puede realizar esta acción después de 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();
                }
            }
        });
    }
}

Uso:

class MyFragment extends Fragment {
    @Override
    public void onDestroyView() {
        removeFragments(mFragment1, mFragment2, mFragment3);
        super.onDestroyView();
    }
}
HolaImKevo
fuente
1

Aunque el OP puede tener circunstancias especiales que le impiden 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 la mayor audiencia posible.

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

ingrese la descripción de la imagen aquí

Suragch
fuente
Yo era el OP. La razón para no usar la biblioteca de soporte fue porque era una aplicación interna de la empresa, donde el hardware utilizado estaba claramente definido como> = 4.0 y <= 4.1. No había necesidad de llegar a una audiencia amplia, era personal interno y no había intención de utilizar la aplicación fuera de la empresa. La única razón de la biblioteca de soporte es que sea compatible con versiones anteriores, pero uno esperaría que todo lo que pueda hacer con la biblioteca de soporte, pueda lograrlo "de forma nativa" sin ella. ¿Por qué una versión superior "nativa" tendría menos funciones que una biblioteca de soporte cuyo único propósito es ser compatible con versiones anteriores?
Mathias Conradt
1
Sin embargo, por supuesto, podrías usar la biblioteca de soporte y yo también. Simplemente no entendía por qué Google ofrece funciones ÚNICAMENTE en la biblioteca de soporte pero no fuera, o por qué incluso lo llamarían biblioteca de soporte y no lo convertirían en el estándar general, si es la mejor práctica de todos modos. Aquí hay un buen artículo sobre la biblioteca de soporte: martiancraft.com/blog/2015/06/android-support-library
Mathias Conradt
@Mathias, buen artículo.
Suragch