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.
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 .
Il est beaucoup plus facile d'illustrer avec une sorte de division des responsabilités pour votre Activity
et 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.
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.
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 Fragment
s 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.
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
}
}
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 ().
Qualité de l'application principale: https://developer.Android.com/distribute/essentials/quality/core.html
Je suggère d'utiliser l'activité au lieu de "MainActivityDetailFragment" pour éviter les complications.
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;
}
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
.