Cambiar entre la imagen de Android Navigation Drawer y Up caret cuando se usan fragmentos

177

Cuando se utiliza el cajón de navegación, los desarrolladores de Android recomiendan que en la barra de acciones "solo las pantallas que se representan en el cajón de navegación deberían tener la imagen del cajón de navegación" y que "todas las demás pantallas tengan el quilate tradicional".

Ver aquí para más detalles: http://youtu.be/F5COhlbpIbY

Estoy usando una actividad para controlar múltiples niveles de fragmentos y puedo hacer que la imagen del cajón de navegación se muestre y funcione en todos los niveles.

Al crear fragmentos de nivel inferior, puedo llamar ActionBarDrawerToggle setDrawerIndicatorEnabled(false)para ocultar la imagen del cajón de navegación y mostrar el cursor hacia arriba

LowerLevelFragment lowFrag = new LowerLevelFragment();

//disable the toggle menu and show up carat
theDrawerToggle.setDrawerIndicatorEnabled(false);
getSupportFragmentManager().beginTransaction().replace(R.id.frag_layout, 
lowFrag, "lowerFrag").addToBackStack(null).commit();

El problema que tengo es que cuando vuelvo a los fragmentos de nivel superior, el quilate de arriba todavía se muestra en lugar de la imagen original del cajón de navegación. ¿Alguna sugerencia sobre cómo "actualizar" la barra de acciones en los fragmentos de nivel superior para volver a mostrar la imagen del cajón de navegación?


Solución

La sugerencia de Tom funcionó para mí. Esto es lo que hice:

Actividad principal

Esta actividad controla todos los fragmentos de la aplicación.

Al preparar nuevos fragmentos para reemplazar a otros, configuro el DrawerToggle de setDrawerIndicatorEnabled(false)esta manera:

LowerLevelFragment lowFrag = new LowerLevelFragment();

//disable the toggle menu and show up carat
theDrawerToggle.setDrawerIndicatorEnabled(false);
getSupportFragmentManager().beginTransaction().replace(R.id.frag_layout,   
lowFrag).addToBackStack(null).commit();

A continuación, en una anulación de onBackPressed, revertí lo anterior estableciendo el DrawerToggle para que setDrawerIndicatorEnabled(true)así:

@Override
public void onBackPressed() {
    super.onBackPressed();
    // turn on the Navigation Drawer image; 
    // this is called in the LowerLevelFragments
    setDrawerIndicatorEnabled(true)
}

En los fragmentos de nivel inferior

En los fragmentos que modifiqué onCreatey onOptionsItemSelectedasí:

En onCreateagregado setHasOptionsMenu(true)para habilitar la configuración del menú de opciones. También se configura setDisplayHomeAsUpEnabled(true)para habilitar < en la barra de acciones:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // needed to indicate that the fragment would 
    // like to add items to the Options Menu        
    setHasOptionsMenu(true);    
    // update the actionbar to show the up carat/affordance 
    getActivity().getActionBar().setDisplayHomeAsUpEnabled(true);
}

Luego, onOptionsItemSelectedcada vez que se presiona <, se llama onBackPressed()desde la actividad para subir un nivel en la jerarquía y mostrar la imagen del cajón de navegación:

@Override
public boolean onOptionsItemSelected(MenuItem item) {   
    // Get item selected and deal with it
    switch (item.getItemId()) {
        case android.R.id.home:
            //called when the up affordance/carat in actionbar is pressed
            getActivity().onBackPressed();
            return true;
         
    }
EvilAsh
fuente
2
También en su método onBackPressed () puede verificar cuántas entradas hay en la pila con el método getFragmentManager (). GetBackStackEntryCount () y habilitar el indicador del cajón solo si el resultado es 0. En ese caso, no es necesario habilitar homeAsUpIndicator en cada LowerLevelFragments.
pvshnik
2
¡Esto es muy útil! Debes mover la parte de "solución" de tu publicación y convertirla en una "respuesta" real. Obtendrá más puntos por votos a favor y es una respuesta después de todo
Oleksiy
¿Por qué estás reemplazando el fragmento aquí: .replace(R.id.frag_layout. Si este es un nivel jerárquico más, esperaría que .addlo volviera a apilar.
JJD
55
Hermano, ¿cómo se hace referencia al theDrawerToggle.setDrawerIndicatorEnabled(false);interior del fragmento? Creo que se declara en el archivo de clase de actividad principal. No puedo encontrar una manera de hacer referencia a esto. ¿Alguna pista?
Skynet
1
Al usar una barra de herramientas, tuve que cambiar las opciones de visualización para no usar la página principal mientras tanto. De lo contrario, el setDisplayOptions()método dentro del ToolbarWidgetWrapper(del paquete interno android.support.v7.internal.widget) no volvería a crear el icono al ingresar el mismo fragmento por segunda vez. Solo dejo esto aquí para cuando otros tropiecen con este problema también.
Wolfram Rittmeyer

Respuestas:

29

Ha escrito que, para implementar fragmentos de nivel inferior, está reemplazando el fragmento existente, en lugar de implementar el fragmento de nivel inferior en una nueva actividad.

Creo que luego tendría que implementar la funcionalidad de respaldo manualmente: cuando el usuario presionó hacia atrás, tiene un código que muestra la pila (por ejemplo, en Activity::onBackPressedanulación). Entonces, donde sea que hagas eso, puedes revertir el setDrawerIndicatorEnabled.

Tom
fuente
Gracias Tom, eso funcionó! He actualizado la publicación original con la solución que usé.
EvilAsh
66
¿Cómo hacer referencia a theDrawerToggle en un fragmento de nivel inferior? Se ha definido en la actividad Principal, ¡no puedo entender esto!
Skynet
1
Puede obtener la actividad del fragmento. Tan solo agregue un captador en su actividad principal para acceder a la palanca del cajón.
Raphael Royer-Rivard
83

Es fácil como 1-2-3.

Si quieres lograr:

1) Indicador del cajón : cuando no hay fragmentos en la pila posterior o el cajón está abierto

2) Flecha : cuando algunos Fragmentos están en la Pila de Atrás

private FragmentManager.OnBackStackChangedListener
        mOnBackStackChangedListener = new FragmentManager.OnBackStackChangedListener() {
    @Override
    public void onBackStackChanged() {
        syncActionBarArrowState();
    }
};

@Override
protected void onCreate(Bundle savedInstanceState) {
    getSupportActionBar().setDisplayShowHomeEnabled(true);
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    mDrawerToggle = new ActionBarDrawerToggle(
            this,             
            mDrawerLayout,  
            R.drawable.ic_navigation_drawer, 
            0, 
            0  
    ) {

        public void onDrawerClosed(View view) {
            syncActionBarArrowState();
        }

        public void onDrawerOpened(View drawerView) {
            mDrawerToggle.setDrawerIndicatorEnabled(true);
        }
    };

    mDrawerLayout.setDrawerListener(mDrawerToggle);
    getSupportFragmentManager().addOnBackStackChangedListener(mOnBackStackChangedListener);
}

@Override
protected void onDestroy() {
    getSupportFragmentManager().removeOnBackStackChangedListener(mOnBackStackChangedListener);
    super.onDestroy();
}

private void syncActionBarArrowState() {
    int backStackEntryCount = 
        getSupportFragmentManager().getBackStackEntryCount();
    mDrawerToggle.setDrawerIndicatorEnabled(backStackEntryCount == 0);
}

3) Ambos indicadores para actuar según su forma

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    if (mDrawerToggle.isDrawerIndicatorEnabled() && 
        mDrawerToggle.onOptionsItemSelected(item)) {
        return true;
    } else if (item.getItemId() == android.R.id.home && 
               getSupportFragmentManager().popBackStackImmediate()) {
        return true;
    } else {
        return super.onOptionsItemSelected(item);
    }
}

PD: Consulte Crear un cajón de navegación en desarrolladores de Android para obtener otros consejos sobre el comportamiento del indicador de 3 líneas.

riwnodennyk
fuente
3
Terminé con algo similar al tuyo. Creo que esta solución (usando BackStackChangedListener para cambiar entre lo que se muestra) es la más elegante. Sin embargo, tengo dos cambios: 1) No llamo / cambio el indicador del cajón en las llamadas onDrawerClosed / onDrawerOpened - no es necesario, y 2) dejo que los Fragments mismos manejen la navegación Arriba haciendo un AbstractFragment que todos heredan y que implementa onOptionsItemSelected (..) y que siempre llama a setHasOptionsMenu (verdadero);
Espen Riskedal
2
Yout también debe bloquear el gesto de deslizamiento para el cajón de navegación en modo de flecha: mDrawerLayout.setDrawerLockMode (DrawerLayout.LOCK_MODE_LOCKED_CLOSED); y habilitarlo nuevamente en modo indicador de cajón
artkoenig
55
Terminé haciendo todo esto en mi fragmento NavigationDrawer. Funciona como un encanto por cierto, gracias.
frostymarvelous
1
setActionBarArrowDependingOnFragmentsBackStack()... qué nombre tan largo: P
S. Thiongane
2
Esto no me muestra el botón arriba cuando paso del fragmento A a B y agrego A al backstack. :(
aandis
14

He usado lo siguiente:

getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
            @Override
            public void onBackStackChanged() {
                if(getSupportFragmentManager().getBackStackEntryCount() > 0){
                    mDrawerToggle.setDrawerIndicatorEnabled(false);
                    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
                }
                else {
                    getSupportActionBar().setDisplayHomeAsUpEnabled(false);
                    mDrawerToggle.setDrawerIndicatorEnabled(true);
                }
            }
        });
Yuriy Sych
fuente
1
MUCHAS GRACIAS, este es el único que realmente funcionó. Una vez que agrego esto, drawerToggle.setToolbarNavigationClickListener(se llama a este oyente al hacer clic en la flecha
EpicPandaForce
Gracias @Yuriy, esto me ayudó a resolver mi problema
Muhammad Shoaib Murtaza
12

Si el botón de la barra de acción arriba no funciona, no olvide agregar el oyente:

// Navigation back icon listener
mDrawerToggle.setToolbarNavigationClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            onBackPressed();
        }
});

Tengo algunos problemas para implementar una navegación de cajones con un botón de inicio, todo funcionó excepto el botón de acción.

Burrich
fuente
10

Intente manejar la selección de elementos de Inicio en MainActivity dependiendo del estado del DrawerToggle. De esta manera, no tiene que agregar el mismo código a cada fragmento.

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Only handle with DrawerToggle if the drawer indicator is enabled.
    if (mDrawerToggle.isDrawerIndicatorEnabled() &&
            mDrawerToggle.onOptionsItemSelected(item)) {
        return true;
    }
    // Handle action buttons
    switch (item.getItemId()) {
        // Handle home button in non-drawer mode
        case android.R.id.home:
            onBackPressed();
            return true;

        default:
            return super.onOptionsItemSelected(item);
    }
}
dzeikei
fuente
+1 solución ordenada. Para que su respuesta sea totalmente utilizable, debe agregar un cheque en el backstack. Cuando está vacío, configure automáticamente el indicador del cajón en verdadero.
HpTerm
@HpTerm Manejo el backstack onBackPressed()porque quería el mismo comportamiento para ambos.
dzeikei
6

SEGUIMIENTO

La solución dada por @dzeikei es clara, pero puede extenderse, cuando se usan fragmentos, para manejar automáticamente el retroceso del indicador del cajón cuando la pila está vacía.

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Only handle with DrawerToggle if the drawer indicator is enabled.
    if (mDrawerToggle.isDrawerIndicatorEnabled() &&
            mDrawerToggle.onOptionsItemSelected(item)) {
        return true;
    }
    // Handle action buttons
    switch (item.getItemId()) {
        // Handle home button in non-drawer mode
        case android.R.id.home:
            // Use getSupportFragmentManager() to support older devices
            FragmentManager fragmentManager = getFragmentManager();
            fragmentManager.popBackStack();
            // Make sure transactions are finished before reading backstack count
            fragmentManager.executePendingTransactions();
            if (fragmentManager.getBackStackEntryCount() < 1){
                mDrawerToggle.setDrawerIndicatorEnabled(true);  
            }
            return true;

        default:
            return super.onOptionsItemSelected(item);
    }
}

EDITAR

Por la pregunta de @JJD.

Los fragmentos se mantienen / gestionan en una actividad. El código anterior se escribe una vez en esa actividad, pero solo maneja el código de detención onOptionsItemSelected.

En una de mis aplicaciones, también necesitaba manejar el comportamiento del cursor hacia arriba cuando se presionaba el botón Atrás. Esto se puede manejar anulando onBackPressed.

@Override
public void onBackPressed() {
    // Use getSupportFragmentManager() to support older devices
    FragmentManager fragmentManager = getFragmentManager();
    fragmentManager.executePendingTransactions();
    if (fragmentManager.getBackStackEntryCount() < 1){
        super.onBackPressed();
    } else {
        fragmentManager.executePendingTransactions();
        fragmentManager.popBackStack();
        fragmentManager.executePendingTransactions();
        if (fragmentManager.getBackStackEntryCount() < 1){
            mDrawerToggle.setDrawerIndicatorEnabled(true);
        }
    }
};

Tenga en cuenta la duplicación de código entre onOptionsItemSelectedy onBackPressedque puede evitarse creando un método y llamando a ese método en ambos lugares.

También tenga en cuenta que agrego dos veces más, lo executePendingTransactionsque en mi caso fue necesario o, de lo contrario, a veces tuve comportamientos extraños del up caret.

HPTerm
fuente
¿Es este el código completo que debo agregar para implementar el comportamiento Up caret o te estás refiriendo a alguna de las otras publicaciones? Por favor aclara esto.
JJD
Gracias por la edición En realidad lo mantengo mDrawerToggledentro de una NavigationDrawerFragmentclase. Para conseguir que funcione necesito también cambiar el estado del botón de inicio / indicador - ver: NavigationDrawerFragment#toggleDrawerIndicator. Además, no estoy seguro de que necesite el registro inicial onOptionsItemSelected: lo descomenté. - Ejemplo simplificado
JJD
Implementación revisada : tienes razón sobre el check in inicial onOptionsItemSelected. Esto garantiza que el Cajón de navegación aún se abra en la jerarquía de nivel superior. Sin embargo, moví el código al NavigationDrawerFragment#onOptionsItemSelected. Esto me ayuda a no exponerme mDrawerToggleal MainActivity.
JJD
@JJD Recordé haber luchado un poco por tener todo funcionando para el alto nivel para cada nivel de la jerarquía. Mientras funcione para ti también está bien. Por supuesto, como usted dice, puede mover el código a otro lugar para no exponerlo.
HpTerm
2

Creé una interfaz para la actividad de alojamiento para actualizar el estado de visualización del menú de hamburguesas. Para los fragmentos de nivel superior, configuro el conmutador truey para los fragmentos para los que quiero mostrar la flecha hacia arriba <establezco el conmutador false.

public class SomeFragment extends Fragment {

    public interface OnFragmentInteractionListener {
        public void showDrawerToggle(boolean showDrawerToggle);
    }

    private OnFragmentInteractionListener mListener;

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            this.mListener = (OnFragmentInteractionListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement OnFragmentInteractionListener");
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        mListener.showDrawerToggle(false);
    }
}

Entonces en mi actividad ...

public class MainActivity extends Activity implements SomeFragment.OnFragmentInteractionListener {

    private ActionBarDrawerToggle mDrawerToggle;

    public void showDrawerToggle(boolean showDrawerIndicator) {
        mDrawerToggle.setDrawerIndicatorEnabled(showDrawerIndicator);
    }

}
Bill Mote
fuente
2

Esta respuesta estaba funcionando pero había un pequeño problema con ella. No getSupportActionBar().setDisplayHomeAsUpEnabled(false)se llamó explícitamente y estaba causando que el ícono del cajón se ocultara incluso cuando no había elementos en la pila, por lo que cambiar el setActionBarArrowDependingOnFragmentsBackStack()método funcionó para mí.

private void setActionBarArrowDependingOnFragmentsBackStack() {
        int backStackEntryCount = getSupportFragmentManager()
                .getBackStackEntryCount();
        // If there are no items in the back stack
        if (backStackEntryCount == 0) {
            // Please make sure that UP CARAT is Hidden otherwise Drawer icon
            // wont display
            getSupportActionBar().setDisplayHomeAsUpEnabled(false);
            // Display the Drawer Icon
            mDrawerToggle.setDrawerIndicatorEnabled(true);
        } else {
            // Show the Up carat
            getSupportActionBar().setDisplayHomeAsUpEnabled(true);
            // Hide the Drawer Icon
            mDrawerToggle.setDrawerIndicatorEnabled(false);
        }

    }
Sheraz Ahmad Khilji
fuente
en mi caso, la solución correcta estaba usando solo actionBarDrawerToggle.setDrawerIndicatorEnabled (getSupportFragmentManager (). getBackStackEntryCount () <0);
Gorets
1

La lógica es clara. Mostrar el botón de retroceso si la pila de fragmentos de respaldo está limpia. Muestre animación de hamburguesa de material si la pila de fragmentos no es clara

getSupportFragmentManager().addOnBackStackChangedListener(
    new FragmentManager.OnBackStackChangedListener() {
        @Override
        public void onBackStackChanged() {
            syncActionBarArrowState();
        }
    }
);


private void syncActionBarArrowState() {
    int backStackEntryCount = getSupportFragmentManager().getBackStackEntryCount();
    mNavigationDrawerFragment.setDrawerIndicatorEnabled(backStackEntryCount == 0);
}

//add these in Your NavigationDrawer fragment class

public void setDrawerIndicatorEnabled(boolean flag){
    ActionBar actionBar = getActionBar();
    if (!flag) {
        mDrawerToggle.setDrawerIndicatorEnabled(false);
        actionBar.setDisplayHomeAsUpEnabled(true);
        mDrawerToggle.setHomeAsUpIndicator(getColoredArrow());
    } else {
        mDrawerToggle.setDrawerIndicatorEnabled(true);
    }
    mDrawerToggle.syncState();
    getActivity().supportInvalidateOptionsMenu();
}

//download back button from this(https://www.google.com/design/icons/) website and add to your project

private Drawable getColoredArrow() {
    Drawable arrowDrawable = ContextCompat.getDrawable(getActivity(), R.drawable.ic_arrow_back_black_24dp);
    Drawable wrapped = DrawableCompat.wrap(arrowDrawable);

    if (arrowDrawable != null && wrapped != null) {
        // This should avoid tinting all the arrows
        arrowDrawable.mutate();
        DrawableCompat.setTint(wrapped, Color.GRAY);
    }
    return wrapped;
}
kml_ckr
fuente
1

Si echas un vistazo a la aplicación GMAIL y vienes aquí para buscar el ícono de carret / permitdance ..

Le pediría que hiciera esto, ninguna de las respuestas anteriores fue clara. Pude modificar la respuesta aceptada.

  • NavigationDrawer -> Vista de lista contiene subfragmentos.


  • los subfragmentos se enumerarán así

  • firstFragment == position 0 ---> esto tendrá subfragmentos -> fragmento

  • segundo fragmento
  • tercer Fragmento y demás ...

En firstFragment tienes otro fragmento.

Llama a esto en DrawerActivity

getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
        @Override
        public void onBackStackChanged() {
            if (getFragmentManager().getBackStackEntryCount() > 0) {
                mDrawerToggle.setDrawerIndicatorEnabled(false);
            } else {
                mDrawerToggle.setDrawerIndicatorEnabled(true);
            }
        }
    });

y en fragmento

    setHasOptionsMenu(true);    

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Get item selected and deal with it
    switch (item.getItemId()) {
        case android.R.id.home:
            //called when the up affordance/carat in actionbar is pressed
            activity.onBackPressed();
            return true;
    }
    return false;
}

En el método de actividad del cajón OnBackPressed, configure el alternador del cajón en verdadero para habilitar nuevamente el icono de la lista de navegación.

Gracias Pusp

EngineSense
fuente
0

En mi opinión, el uso de onNavigateUp () (como se muestra aquí ) en la solución de riwnodennyk o Tom es más limpio y parece funcionar mejor. Simplemente reemplace el código onOptionsItemSelected con esto:

@Override
public boolean onSupportNavigateUp() {
    if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
        // handle up navigation
        return true;
    } else {
        return super.onSupportNavigateUp();
    }
}
0101100101
fuente