Внедрение правильной обратной навигации и управление домашней кнопкой с помощью панели инструментов в Android
Я использую одно действие и несколько фрагментов (прикрепленных к скриншотам) в рамках одного и того же действия, чтобы обеспечить плавную навигацию. Но после внедрения последней панели инструментов и навигации, кажется, трудно справиться с навигационными и домашними кнопками. У меня возникают проблемы со следующими вещами.
- Управление кнопкой "Гамбургер/Назад" слева вверху. Переключение значка и функций в меню и обратно.
- Название страницы - изменение заголовков страниц, когда фрагмент нажат и выскочил.
Я пробовал несколько вещей, например, переопределять onBackPressed(), setHomeAsUpIndicator, всплывающие фрагменты вручную. Раньше я использовал ActionBarDrawer, чтобы справиться с этим, но он как-то не работает. Я проверил образцы Google, они, похоже, используют отдельные действия в большинстве мест.
Может ли кто-нибудь указать мне, как реализовать правильную обратную навигацию для обработки кнопки NavigationView, Back во внутренних фрагментах и названиях страниц? Я использую AppCompatActivity, android.app.Fragment, NavigationView и Панель инструментов.
![Фрагмент 1 - > Фрагмент 2 - > Фрагмент 3]()
Ответы
Ответ 1
Это гораздо проще проиллюстрировать с каким-то разделением ответственности за ваши 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
* 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.
}
}
Проблема 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
ваших фрагментов.
Ответ 2
ТЛ; дг
Следите за этим:
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 - это место, где происходит большая часть магии. Вы заметили форматирование текста в обычном формате. Я огромный фанат Clean Code - если вам нужно писать комментарии, ваш код isn ' t чистый. В основном вам нужно действительно иметь центральное место, где вы можете работать, когда не знаете, почему нажатие кнопки "Назад" не происходит так, как вы ожидаете. Этот метод - это место.
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();
В приведенном выше коде показано, как обрабатывается название фрагментами.
Ответ 3
"Название страницы - изменение названий страниц всякий раз, когда фрагмент нажат и выскочил"
При удалении фрагмента существует метод isRemoving()
. Это помогает сменить название.
@Override
public void onStop() {
super.onStop();
if (isRemoving()) {
// Change your title here
}
}
"для меню и Back nav"
Предложение: мы должны полагаться на стандартную навигационную систему Android. Если мы используем addToBackStack()
для наших фрагментов, теоретически нам не нужно переопределять onBackPressed() вообще.
- "Приложение не переопределяет ожидаемую функцию значка системы (например, кнопку" Назад ").
- "Приложение поддерживает стандартную навигацию по кнопке" Назад "и не использует никаких пользовательских экранных подсказок" Назад ".
Качество основного приложения: https://developer.android.com/distribute/essentials/quality/core.html
"Управление кнопкой" Гамбургер/Назад "слева вверху
Я предлагаю использовать активность вместо "MainActivityDetailFragment", чтобы избежать осложнений.
Ответ 4
Попробуйте что-то вроде этого:
@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();
Чтобы динамически изменить заголовок страницы, вы можете вызвать это из каждого метода fragment
onStart
или onResume
:
@Override
public void onStart() {
super.onStart();
getActivity().setTitle("Title for fragment");
}
Примечание. Я рассмотрел стандартное объявление макета и, таким образом, я не включил никаких макетов.
Ответ 5
Добавьте это в свою 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;
}
Ответ 6
Хорошо, после множества тестов мне, наконец, удалось настроить хорошую навигацию. Мне нужно было точно так же, как и вы, только разница в том, что я использую 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
, a NavigationView
и тему Theme.AppCompat.Light.NoActionBar
.