web-dev-qa-db-fra.com

Implémenter une navigation arrière et une gestion des boutons d'accueil appropriées à l'aide de la barre d'outils dans Android

J'utilise une seule activité et plusieurs fragments (capture d'écran ci-jointe) au sein de la même activité pour fournir une navigation transparente. Mais après avoir implémenté la dernière barre d'outils et la dernière vue de navigation, il semble difficile de gérer les boutons de navigation et d'accueil. J'ai des problèmes avec les choses suivantes.

  • Gestion du bouton Hamburger/Retour en haut à gauche. Basculer l'icône et les fonctionnalités vers Menu et Navigation arrière.
    • Titre de page - Changement des titres de page chaque fois qu'un fragment est poussé et sauté.

J'ai essayé plusieurs choses comme remplacer onBackPressed (), setHomeAsUpIndicator, popping des fragments manuellement. Plus tôt, j'utilisais la bascule ActionBarDrawer pour gérer cela, mais cela échoue en quelque sorte maintenant. J'ai vérifié les échantillons google qui semblent utiliser des activités distinctes dans la plupart des endroits.

Quelqu'un peut-il me guider sur la façon d'implémenter une navigation arrière appropriée pour gérer le bouton NavigationView, Retour dans les fragments internes et les titres de page? J'utilise AppCompatActivity, Android.app.Fragment, NavigationView et Barre d'outils .

Fragment 1 -> Fragment 2 -> Fragment 3

19
Ajith Memana

Il est beaucoup plus facile d'illustrer avec une sorte de division des responsabilités pour votre Activity et Fragment.

Division of responsibilities for Activity and Fragment Problème 1: Gestion du bouton Hamburger/Retour en haut à gauche. Basculer l'icône et la fonctionnalité vers Menu et Navigation arrière.

D'après l'illustration, la solution doit être encapsulée par le Activity, qui ressemblera à ceci:

public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {

    private ActionBarDrawerToggle mDrawerToggle;
    private DrawerLayout mDrawer;
    private ActionBar mActionBar;

    private boolean mToolBarNavigationListenerIsRegistered = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        mActionBar = getSupportActionBar();

        mDrawer = (DrawerLayout) findViewById(R.id.drawer_layout);
        mDrawerToggle = new ActionBarDrawerToggle(this, mDrawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
        mDrawer.addDrawerListener(mDrawerToggle);
        mDrawerToggle.syncState();

        NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
        navigationView.setNavigationItemSelectedListener(this);

        // On orientation change savedInstanceState will not be null.
        // Use this to show hamburger or up icon based on fragment back stack.
        if(savedInstanceState != null){
            resolveUpButtonWithFragmentStack();
        } else {
            // You probably want to add your ListFragment here.
        }
    }

    @Override
    public void onBackPressed() {

        if (mDrawer.isDrawerOpen(GravityCompat.START)) {
            mDrawer.closeDrawer(GravityCompat.START);

        } else {
            int backStackCount = getSupportFragmentManager().getBackStackEntryCount();

            if (backStackCount >= 1) {
                getSupportFragmentManager().popBackStack();
                // Change to hamburger icon if at bottom of stack
                if(backStackCount == 1){
                    showUpButton(false);
                }
            } else {
                super.onBackPressed();
            }
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;

        } else if (id == Android.R.id.home) {
            // Home/Up logic handled by onBackPressed implementation
            onBackPressed();
        }

        return super.onOptionsItemSelected(item);
    }

    @SuppressWarnings("StatementWithEmptyBody")
    @Override
    public boolean onNavigationItemSelected(MenuItem item) {
        // Handle navigation view item clicks here.
        int id = item.getItemId();

        // Navigation drawer item selection logic goes here

        mDrawer.closeDrawer(GravityCompat.START);
        return true;
    }

    private void replaceFragment() {
        /**
        * Your fragment replacement logic goes here
        * e.g.
        * FragmentTransaction ft = getFragmentManager().beginTransaction();
        * String tag = "MyFragment";
        * ft.replace(R.id.content, MyFragment.newInstance(tag), tag).addToBackStack(null).commit();
        */

        // The part that changes the hamburger icon to the up icon
        showUpButton(true);
    }

    private void resolveUpButtonWithFragmentStack() {
        showUpButton(getSupportFragmentManager().getBackStackEntryCount() > 0);
    }

    private void showUpButton(boolean show) {
        // To keep states of ActionBar and ActionBarDrawerToggle synchronized,
        // when you enable on one, you disable on the other.
        // And as you may notice, the order for this operation is disable first, then enable - VERY VERY IMPORTANT.
        if(show) {
            // Remove hamburger
            mDrawerToggle.setDrawerIndicatorEnabled(false);
            // Show back button
            mActionBar.setDisplayHomeAsUpEnabled(true);
            // when DrawerToggle is disabled i.e. setDrawerIndicatorEnabled(false), navigation icon
            // clicks are disabled i.e. the UP button will not work.
            // We need to add a listener, as in below, so DrawerToggle will forward
            // click events to this listener.
            if(!mToolBarNavigationListenerIsRegistered) {
                mDrawerToggle.setToolbarNavigationClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        onBackPressed();
                    }
                });

                mToolBarNavigationListenerIsRegistered = true;
            }

        } else {
            // Remove back button
            mActionBar.setDisplayHomeAsUpEnabled(false);
            // Show hamburger
            mDrawerToggle.setDrawerIndicatorEnabled(true);
            // Remove the/any drawer toggle listener 
            mDrawerToggle.setToolbarNavigationClickListener(null);
            mToolBarNavigationListenerIsRegistered = false;
        }

        // So, one may think "Hmm why not simplify to:
        // .....
        // getSupportActionBar().setDisplayHomeAsUpEnabled(enable);
        // mDrawer.setDrawerIndicatorEnabled(!enable);
        // ......
        // To re-iterate, the order in which you enable and disable views IS important #dontSimplify.
    }
}

Problème 2: Titre de page - Changement des titres de page chaque fois qu'un fragment est poussé et sauté.

Essentiellement, cela peut être géré dans le onStart pour chaque Fragment c'est-à-dire que votre ListFragment, DetailsFragment et CommentsFragment ressemblent à ceci:

@Override
public void onStart() {
    super.onStart();
    // where mText is the title you want on your toolbar/actionBar
    getActivity().setTitle(mText);
}

Cela vaut probablement la peine d'avoir setRetainInstance(true) dans le onCreate de vos fragments.

16
ade.akinyede

tl; dr

Regardez ceci: https://youtu.be/ANpBWIT3vl

Clonez ceci: https://github.com/shredderskelton/androidtemplate .

C'est un problème très courant et que j'ai surmonté en créant une sorte de projet de modèle que j'utilise chaque fois que je démarre un nouveau projet Android. L'idée est d'abstraire autant de logique qui gère le bouton de retour, l'indicateur "hamburger" et la gestion des fragments en classes réutilisables:

Commencez par créer une classe BaseActivity et BaseFragment. C'est là que vous allez utiliser autant de code réutilisable que possible.

Commençons par votre BaseActivity

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    fragmentManager = getSupportFragmentManager();
    fragmentHandler = new AddFragmentHandler(fragmentManager);
    fragmentManager.addOnBackStackChangedListener(backStackListener);
}

Le FragmentManager est la clé pour posséder la pile arrière, vous devez donc écouter les modifications apportées à la pile arrière à partir d'ici. AddFramentHandler est une petite classe que j'ai préparée pour faciliter l'ajout de fragments, à partir de fragments. Plus sur cela plus tard.

@Override
public void onBackPressed() {
    if (sendBackPressToDrawer()) {
        //the drawer consumed the backpress
        return;
    }

    if (sendBackPressToFragmentOnTop()) {
        // fragment on top consumed the back press
        return;
    }

    //let the Android system handle the back press, usually by popping the fragment
    super.onBackPressed();

    //close the activity if back is pressed on the root fragment
    if (fragmentManager.getBackStackEntryCount() == 0) {
        finish();
    }
}

onBackPressed est l'endroit où la plupart de la magie se produit. Vous remarquez le formatage en texte brut des méthodes .. Je suis un grand Clean Code fan - si vous avez besoin d'écrire des commentaires, votre code n'est pas propre. Fondamentalement, vous devez vraiment avoir un endroit central où vous pouvez courir lorsque vous ne savez pas pourquoi une pression sur le bouton retour ne se produit pas comme vous l'attendez. Cette méthode est cet endroit.

private void syncDrawerToggleState() {
    ActionBarDrawerToggle drawerToggle = getDrawerToggle();
    if (getDrawerToggle() == null) {
        return;
    }
    if (fragmentManager.getBackStackEntryCount() > 1) {
        drawerToggle.setDrawerIndicatorEnabled(false);
        drawerToggle.setToolbarNavigationClickListener(navigationBackPressListener); //pop backstack
    } else {
        drawerToggle.setDrawerIndicatorEnabled(true);
        drawerToggle.setToolbarNavigationClickListener(drawerToggle.getToolbarNavigationClickListener()); //open nav menu drawer
    }
}

Il s'agit de l'autre élément clé de BaseActivity. Fondamentalement, cette méthode vérifie si vous êtes sur le fragment racine et configure l'indicateur en conséquence. Notez qu'il modifie l'écouteur en fonction du nombre de fragments dans la pile arrière.

Ensuite, il y a le BaseFragment:

@Override
public void onResume() {
    super.onResume();
    getActivity().setTitle(getTitle());
}

protected abstract String getTitle();

Le code ci-dessus montre comment le titre est géré par les fragments.

5
shredder

Essayez quelque chose comme ceci:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);
    if (getSupportActionBar()!=null) {
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    }

    drawer = (DrawerLayout) findViewById(R.id.drawer_layout);

    final ActionBarDrawerToggle drawerToggle = new ActionBarDrawerToggle(
            this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
    drawer.addDrawerListener(drawerToggle);
    drawerToggle.syncState();

    final View.OnClickListener originalToolbarListener = drawerToggle.getToolbarNavigationClickListener();

    final View.OnClickListener navigationBackPressListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            getFragmentManager().popBackStack();
        }
    };

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

    // Though below steps are not related but I have included to show drawer close on Navigation Item click. 

    navigationView = (NavigationView) findViewById(R.id.nav_view);
    navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
        @Override
        public boolean onNavigationItemSelected(MenuItem item) {
            int id = item.getItemId();
            /**
             * handle item clicks using id
             */
            drawer.closeDrawer(GravityCompat.START);
            return true;
        }
    });
}

Gérer l'état du tiroir onBackPressed:

@Override
public void onBackPressed() {
    if (drawer.isDrawerOpen(GravityCompat.START)) {
        drawer.closeDrawer(GravityCompat.START);
    } else {
        super.onBackPressed();
    }
}

Pour recharger le fragment précédent à la presse arrière, ajoutez toujours la transaction de fragment à la pile arrière comme ceci:

FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
SomeFragment fragmentToBeLoaded = new SomeFragment();
fragmentTransaction.replace(R.id.fragment_container, fragmentToBeLoaded,
                fragmentToBeLoaded.getName());
fragmentTransaction.addToBackStack(fragmentToBeLoaded.getName());
fragmentTransaction.commit();

Pour modifier dynamiquement le titre de la page, vous pouvez l'appeler à partir de chaque méthode Fragments onStart ou onResume:

@Override
public void onStart() {
   super.onStart();
   getActivity().setTitle("Title for fragment");
}

Remarque: J'ai considéré la déclaration de mise en page standard et donc je n'ai inclus aucune mise en page.

1
Rohit Arya

"Titre de page - Changer les titres de page chaque fois qu'un fragment est poussé et sauté"

Lorsque vous supprimez un fragment, il existe la méthode isRemoving(). Cela aide à changer le titre.

@Override
public void onStop() {
    super.onStop();
    if (isRemoving()) {
        // Change your title here
    }
}

"fonctionnalité pour le menu et la navigation arrière"

Suggestion: nous devons nous fier au système de navigation par défaut Android. Si nous utilisons addToBackStack() pour nos fragments, en théorie, nous n'avons pas du tout à remplacer onBackPressed ().

  1. "L'application ne redéfinit pas la fonction attendue d'une icône système (comme le bouton Retour)."
  2. "L'application prend en charge la navigation standard du bouton Retour du système et n'utilise aucune invite personnalisée de" bouton Retour "à l'écran."

Qualité de l'application principale: https://developer.Android.com/distribute/essentials/quality/core.html

"Gestion du bouton Hamburger/Retour en haut à gauche"

Je suggère d'utiliser l'activité au lieu de "MainActivityDetailFragment" pour éviter les complications.

1
Dmitry

Ajoutez ceci dans votre MainActivity où vous appelez Fragments. getBackStackEntryCount () Renvoie le nombre de fragments dans la pile arrière. où le fragment en bas de la pile a l'index 0. popBackStack () Retirez le fragment supérieur de la pile arrière

 @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();

        if (id == Android.R.id.home) {
            if (getSupportFragmentManager().getBackStackEntryCount() == 1) {
                getSupportFragmentManager().popBackStack();
            } else {
                super.onBackPressed();
            }
        }
        return true;
    }

Et dans votre Fragment où vous voulez retourner, utilisez cette fonction

  @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        if (id == Android.R.id.home) {
            getActivity().onBackPressed();
        }
        return true;
    }
0
Umer

Ok, après de nombreux tests, j'ai finalement réussi à configurer une bonne navigation. J'avais besoin exactement de la même chose que vous, la seule différence est que j'utilise des fragments v4, mais je ne pense pas que cela changera quoi que ce soit ici.

Je n'utilise pas ActionBarDrawerToggle car les derniers exemples de Google n'utilisent plus ce composant.

La solution ci-dessous fonctionne également pour la navigation profonde: activité parent -> fragment -> fragment etc.

Le seul changement nécessaire dans les Fragments est de changer le titre:

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);

    getActivity().setTitle(R.string.targets);
}

Dans la méthode parent Activity onCreate, j'initialise ce qui suit:

    mNavigationView = (NavigationView) findViewById(R.id.navigation_view);
    setupDrawerContent(mNavigationView);

    final Toolbar toolbar = (Toolbar) findViewById(R.id.drawer_toolbar);
    setSupportActionBar(toolbar);

    getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_menu_24);// Set the hamburger icon
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);// Set home button pressable

    // Handle the changes on the actionbar
    getSupportFragmentManager().addOnBackStackChangedListener(
            new FragmentManager.OnBackStackChangedListener() {
                public void onBackStackChanged() {
                    // When no more fragments to remove, we display back the hamburger icon and the original activity title
                    if (getSupportFragmentManager().getBackStackEntryCount() <= 0) {
                        getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_menu_24);
                        setTitle(R.string.app_name);
                    }
                    // Else displays the back arrow
                    else {
                        getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_arrow_back_24);
                    }
                }
            });

Voici maintenant le code pour gérer l'action sur le bouton Accueil:

@Override
public boolean onOptionsItemSelected(MenuItem item){
    // Close the soft keyboard right away
    Tools.setSoftKeyboardVisible(mViewPager, false);

    switch (item.getItemId()) {
        case Android.R.id.home:

            // When no more fragments to remove, open the navigation drawer
            if (getSupportFragmentManager().getBackStackEntryCount() <= 0) {
                mDrawerLayout.openDrawer(GravityCompat.START);
            }
            // Removes the latest fragment
            else {
                getSupportFragmentManager().popBackStack();
            }

            return true;
    }
    return super.onOptionsItemSelected(item);
}

Et enfin le code pour gérer l'action de la presse arrière:

@Override
public void onBackPressed() {
    // When no more fragments to remove, closes the activity
    if (getSupportFragmentManager().getBackStackEntryCount() <= 0) {
        super.onBackPressed();
    }
    // Else removes the latest fragment
    else {
        getSupportFragmentManager().popBackStack();
    }
}

NOTE : j'utilise un AppCompatActivity, un NavigationView et le thème Theme.AppCompat.Light.NoActionBar.

0
Yoann Hercouet