Реализация правильной обратной навигации и обработки кнопок home с помощью панели инструментов в Android
Я использую одно действие и несколько фрагментов(скриншот прилагается) в пределах одного действия, чтобы обеспечить плавную навигацию. Но после внедрения последней панели инструментов и навигационного представления, кажется, трудно обрабатывать кнопки навигации и дома. У меня проблемы со следующими вещами.
- управление кнопкой гамбургер / назад в левом верхнем углу. Переключение значка и функциональности в меню и обратно nav.
- заголовок страницы-изменение заголовков страниц всякий раз, когда осколок в толчке и хлопнул.
Я пробовал несколько вещей, таких как переопределение onBackPressed (), setHomeAsUpIndicator, всплывающие фрагменты вручную. Раньше я использовал переключатель ActionBarDrawer для обработки этого, но сейчас он как-то не работает. Я проверил образцы google, они, похоже, используют отдельные действия в большинстве мест.
может ли кто-нибудь указать мне, как реализовать правильную обратную навигацию для обработки кнопки NavigationView, Back в внутренние фрагменты и названия страниц? Я использую AppCompatActivity, android.приложение.Фрагмент, NavigationView и панели инструментов.
6 ответов
это гораздо проще проиллюстрировать с каким-то разделением ответственности за ваш Activity
и Fragment
.
Проблема 1: управление кнопкой гамбургер / назад в левом верхнем углу. Переключение значка и функциональности в меню и обратно nav.
на иллюстрации решение должно быть инкапсулировано с помощью 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 и CommentsFragment выглядят примерно так:
@Override
public void onStart() {
super.onStart();
// where mText is the title you want on your toolbar/actionBar
getActivity().setTitle(mText);
}
наверное стоит setRetainInstance(true)
на onCreate
ваши фрагменты.
tl; dr
смотреть на это: 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 где происходит волшебство. Вы замечаете форматирование обычного текста методов.. Я огромный Код fan-если вам нужно написать комментарии, ваш код не чист. В основном вам нужно действительно иметь центральное место, где вы можете бежать, когда вы не уверены, почему нажатие кнопки назад не происходит так, как вы ожидаете. Этот метод и есть то место.
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();
}
}
перезагрузка при обратном нажатии всегда добавляйте транзакцию фрагмента в задний стек следующим образом:
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
SomeFragment fragmentToBeLoaded = new SomeFragment();
fragmentTransaction.replace(R.id.fragment_container, fragmentToBeLoaded,
fragmentToBeLoaded.getName());
fragmentTransaction.addToBackStack(fragmentToBeLoaded.getName());
fragmentTransaction.commit();
чтобы динамически изменить заголовок страницы, вы можете вызвать это из каждого Fragment
s 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
}
}
"функциональность меню и обратно nav"
предложение: мы должны полагаться на навигационную систему android по умолчанию. Если мы используем addToBackStack()
для наших фрагментов теоретически нам вообще не нужно переопределять onBackPressed ().
- " App не переопределяет ожидаемую функцию системного значка (например, кнопки "Назад")."
- "приложение поддерживает стандартную навигацию по системной кнопке "Назад" и не использует никаких пользовательских, экранных подсказок "назад"."
Core Качество Приложения: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, но я не думаю, что это что-то изменит здесь.
Я не использую 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
.