Внедрение правильной обратной навигации и управление домашней кнопкой с помощью панели инструментов в Android

Я использую одно действие и несколько фрагментов (прикрепленных к скриншотам) в рамках одного и того же действия, чтобы обеспечить плавную навигацию. Но после внедрения последней панели инструментов и навигации, кажется, сложно справиться с навигационными и домашними кнопками. У меня возникают проблемы со следующими вещами.

  • Управление кнопкой «Гамбургер / Назад» слева вверху. Переключение значка и функций в меню и Назад.
    • Название страницы – изменение заголовков страниц всякий раз, когда фрагмент помещается и выталкивается.

Я попробовал несколько вещей, например, переопределить onBackPressed (), setHomeAsUpIndicator, вырезать фрагменты вручную. Раньше я использовал переключатель ActionBarDrawer, чтобы справиться с этим, но он как-то не работает. Я проверил образцы Google, они, похоже, используют отдельные действия в большинстве мест.

Может ли кто-нибудь указать мне, как реализовать правильную обратную навигацию для обработки кнопки NavigationView, Back во внутренних фрагментах и ​​названиях страниц? Я использую AppCompatActivity , android.app.Fragment , NavigationView и панель инструментов .

Фрагмент 1 -> Фрагмент 2 -> Фрагмент 3

Solutions Collecting From Web of "Внедрение правильной обратной навигации и управление домашней кнопкой с помощью панели инструментов в Android"

Гораздо проще проиллюстрировать какое-то разделение ответственности за вашу Activity и Fragment .

Разделение обязанностей на деятельность и фрагмент Задача 1: Управление кнопкой «Гамбургер / Назад» слева вверху. Переключение значка и функций в меню и Назад.

Из иллюстрации решение должно быть инкапсулировано Activity , которое будет выглядеть примерно так:

 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 * eg * 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 ie setDrawerIndicatorEnabled(false), navigation icon // clicks are disabled ie 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. } } 

Задача 2: Название страницы. Изменение заголовков страниц всякий раз, когда фрагмент нажат и выскочил.

По сути, это может быть обработано в onStart для каждого Fragment т.е. ваш ListFragment, DetailsFragment и комментарииFragment выглядят примерно так:

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

Вероятно, стоит setRetainInstance(true) в onCreate ваших фрагментов.

ТЛ; др

Смотрите это: https://youtu.be/ANpBWIT3vlU

Клонировать это: https://github.com/shredderskelton/androidtemplate .

Это очень распространенная проблема, которую я преодолел, создав своего рода шаблонный проект, который я использую всякий раз, когда начинаю новый проект Android. Идея состоит в том, чтобы абстрагировать большую часть логики, которая обрабатывает кнопку «Назад», индикатор «гамбургер» и управление фрагментами в классы повторного использования:

Начните с создания класса BaseActivity и BaseFragment. Здесь вы можете как можно больше использовать код многократного использования.

Давайте начнем с вашего BaseActivity

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

FragmentManager – это ключ к владению задним стеком, поэтому вам нужно прослушивать изменения в заднем стеке отсюда. AddFramentHandler – это небольшой класс, который я приготовил для упрощения добавления фрагментов из фрагментов. Об этом позже.

 @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 – это место, где происходит большая часть магии. Вы замечаете форматирование текста в обычном формате. Я – огромный вентилятор чистого кода. Если вам нужно писать комментарии, ваш код не будет чистым. В основном вам нужно иметь центральное место, где вы можете работать, когда не знаете, почему нажатие кнопки «Назад» не происходит так, как вы ожидаете. Этот метод – это место.

 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 } } 

Это другая ключевая часть BaseActivity. В основном этот метод проверяет, находитесь ли вы в корневом фрагменте и соответствующим образом настраиваете индикатор. Обратите внимание, что он меняет слушателя в зависимости от количества фрагментов в стеке.

Тогда есть BaseFragment:

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

В приведенном выше коде показано, как обрабатывается заголовок фрагментами.

Попробуйте что-то вроде этого:

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

Обрабатывать состояние выдвижного ящика при помощи onBackPressed :

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

Чтобы перезагрузить предыдущий fragment при повторном нажатии, всегда добавляйте транзакцию фрагмента в задний стек следующим образом:

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

Чтобы динамически изменить заголовок страницы, вы можете вызвать это из каждого метода onStart или onResume :

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

Примечание. Я рассмотрел стандартную декларацию макета, и поэтому я не включил никаких макетов.

«Название страницы – изменение названий страниц всякий раз, когда фрагмент нажат и выскочил»

Когда вы удаляете фрагмент, существует метод isRemoving() . Это помогает сменить название.

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

«Функциональность для меню и обратно навигация»

Предложение: мы должны полагаться на навигационную систему Android по умолчанию. Если мы используем addToBackStack() для наших фрагментов, теоретически нам не нужно переопределять onBackPressed () вообще.

  1. «Приложение не переопределяет ожидаемую функцию значка системы (например, кнопку« Назад »)».
  2. «Приложение поддерживает стандартную навигацию по кнопкам системы и не использует никаких пользовательских экранных подсказок« Назад ».

Качество основного приложения: https://developer.android.com/distribute/essentials/quality/core.html

«Управление кнопкой« Гамбургер / Назад »слева вверху»

Я предлагаю использовать активность вместо «MainActivityDetailFragment», чтобы избежать осложнений.

Добавьте это в свою MainActivity, где вы вызываете фрагменты. GetBackStackEntryCount () Возвращает количество фрагментов в обратном стеке. Где фрагмент на нижней части стека имеет индекс 0. popBackStack () Вырезает верхний фрагмент из заднего стека

  @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; } 

И в вашем фрагменте, где вы хотите вернуться, используйте эту функцию

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

Хорошо, после многих тестов мне наконец удалось настроить хорошую навигацию. Мне нужно было точно так же, как и вы, только разница в том, что я использую v4 Fragments, но я не думаю, что это что-то изменит.

Я не использую ActionBarDrawerToggle поскольку последние примеры из Google больше не используют этот компонент.

Решение ниже также работает для глубокой навигации: родительская активность -> фрагмент -> фрагмент и т. Д.

Единственное изменение, необходимое во Фрагментах, заключается в изменении названия:

 @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); getActivity().setTitle(R.string.targets); } 

В методе родительского действия onCreate я инициализирую следующее:

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

Вот код для обработки действия на кнопке Home:

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

И, наконец, код для обработки обратного действия:

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

ПРИМЕЧАНИЕ . Я использую AppCompatActivity , NavigationView и тему Theme.AppCompat.Light.NoActionBar .