Вопрос о компоненте архитектуры навигации Android

У меня вопрос. Я использую компоненты архитектуры навигации Android, используя нижний вид навигации с одиночной активностью. Как сделать так, чтобы фрагмент открывался только один раз? Даже если кнопку, вызывающую этот фрагмент, нажать несколько раз? Как сделать так, чтобы фрагмент попадал в задний стек только один раз? Я создал тестовый проект, чтобы попробовать

xml navigation_test

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/test_nav"
    app:startDestination="@id/firstFragment">
    <fragment
        android:id="@+id/firstFragment"
        android:name="ru.artem_nr.navigation_test.FirstFragment"
        android:label="fragment_first"
        tools:layout="@layout/fragment_first" >
        <action
            android:id="@+id/action_firstFragment_to_secondFrag"
            app:destination="@id/navigation"
            app:launchSingleTop="true"
            app:popUpTo="@+id/secondFrag"
            app:popUpToInclusive="false" />
    </fragment>
    <navigation android:id="@+id/navigation"
        app:startDestination="@id/secondFrag">
        <fragment
            android:id="@+id/secondFrag"
            android:name="ru.artem_nr.navigation_test.SecondFrag"
            android:label="fragment_second"
            tools:layout="@layout/fragment_second" />
    </navigation>
</navigation>

основная деятельность

открытый класс MainActivity расширяет AppCompatActivity, реализует NavigationView.OnNavigationItemSelectedListener {

public Toolbar toolbar;
public DrawerLayout drawerLayout;
public NavController navController;
public NavigationView navigationView;

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

    setupNavigation();
}

private void setupNavigation() {

    toolbar = findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    getSupportActionBar().setDisplayShowHomeEnabled(true);

    drawerLayout = findViewById(R.id.drawer_layout);
    navigationView = findViewById(R.id.nav_view);
    navController = Navigation.findNavController(this, R.id.garden_nav_fragment);
    NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout);
    NavigationUI.setupWithNavController(navigationView, navController);
    navigationView.setNavigationItemSelectedListener((NavigationView.OnNavigationItemSelectedListener) this);
}

@Override
public boolean onSupportNavigateUp() {
    return (boolean) NavigationUI.navigateUp(navController, drawerLayout);
}

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

@Override
public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
    menuItem.setChecked(true);

    drawerLayout.closeDrawers();

    int id = menuItem.getItemId();

    switch (id) {
        case R.id.nav_home:
            navController.navigate(R.id.navigation);
            break;
    }
    return true;
}

}

фрагменты - просто заготовка


person Артем Артемов    schedule 19.05.2019    source источник
comment
В вашем вопросе говорится о нижней навигации, но в вашем коде используется NavigationView. Что вы пытаетесь использовать? Пытаетесь справиться со случаем, когда пользователь повторно выбирает элемент в меню, который уже выбран?   -  person ianhanniballake    schedule 20.05.2019
comment
неважно, какую навигацию я использую. В моем реальном проекте нижняя панель навигации, но я создал тестовый проект, как я сказал. В раскладке ящика та же проблема. Да, вы правы, я пытаюсь с этим справиться. Если пользователь повторно выбирает какой-то фрагмент, он возвращается в стек. Как это исправить?   -  person Артем Артемов    schedule 20.05.2019


Ответы (2)


Согласно этой проблеме, BottomNavigationView предлагает OnNavigationItemReselectedListener, который имеет приоритет над OnNavigationItemSelectedListener, установленным NavigationUI :

bottomNavigationView.setOnNavigationItemReselectedListener(
    new BottomNavigationView.OnNavigationReselectedListener() {
        @Override
        public void onNavigationItemReselected(MenuItem item) {
            // By doing nothing, we ignore reselection events
        }
    });

NavigationView не предлагает тот же интерфейс, что и этой другой проблеме, поэтому вам нужно скопируйте поведение по умолчанию (обратите внимание, что он вызывает NavigationUI.onNavDestinationSelected(), а не просто вызывает navigate() напрямую, в отличие от вашего кода) и добавьте свою собственную проверку для isChecked() для защиты от повторного выбора.

person ianhanniballake    schedule 19.05.2019
comment
Итак, на данный момент у платформы нет дефолтного решения этой проблемы? если я не буду - person Артем Артемов; 20.05.2019
comment
@ АртемАртемов - правильно, каждый компонент полностью независим друг от друга. - person ianhanniballake; 20.05.2019
comment
Я пытаюсь использовать NavOptions.Builder для просмотра навигации ботов. Это помогает многократно нажимать на один фрагмент, но не помогает в ситуации, когда я нажимаю на разные значки по очереди много раз. Создает большой задний стек, теперь я вижу один способ - создать isChecked (), как вы сказали, но это не лучшее решение для большого проекта, потому что нам нужно создавать метод isChecked () для каждого фрагмента. - person Артем Артемов; 20.05.2019
comment
Вы не должны устанавливать OnNavigationItemSelectedListener в нижней части навигации - значение по умолчанию NavigationUI.setupWithNavController() работает правильно. - person ianhanniballake; 20.05.2019
comment
@ianhanniballake У меня похожие проблемы с глобальными действиями при использовании drawerlayout. У меня есть 3 пункта назначения верхнего уровня (по одному для каждого предмета ящика). Они просто накладываются друг на друга. Использование launchSingleTop ничего не делает. Кроме того, анимация входа / выхода не работает для глобальных действий. Это ошибка? Известная проблема? WAI? Если нет, то куда обращаться? - person Mark; 11.01.2020

Вам нужно объявить фрагмент как «SingleTop».

Для этого есть опция в меню в правой части окна редактора навигационного графика под опциями анимации.

В качестве альтернативы вы можете установить его программно через класс NavOptions.Builder, используя метод setLaunchSingleTop (true), как подробно описано в ссылке ниже:

https://developer.android.com/reference/kotlin/androidx/navigation/NavOptions.Builder#setLaunchSingleTop(kotlin.Boolean)

Итак, ваш метод выбора должен выглядеть так:

public boolean onNavigationItemSelected(@NonNull MenuItem menuItem){
NavOptions.Builder builder = new NavOptions.Builder()
.setLaunchSingleTop(true);

NavOptions options = builder.build();
navController.navigate(item.getItemId(), null, options);
person Izzy Stannett    schedule 19.05.2019
comment
То есть вы пытались сделать это программно с помощью NavOptions.Builder? Если вы поделитесь своим кодом, мы сможем найти для вас проблему. - person Izzy Stannett; 20.05.2019
comment
нет, я сделал это в интерфейсе. Я создал тестовый проект, чтобы попробовать - person Артем Артемов; 20.05.2019
comment
программно попробовать? - person Izzy Stannett; 20.05.2019
comment
да, спасибо за ответ, но работает не во всех ситуациях - person Артем Артемов; 20.05.2019
comment
вы можете установитьLaunchSingleTop () только для действий, а не для фрагментов - поэтому вы не можете объявить фрагмент как SingleTop, хотя я бы хотел, чтобы вы могли! - person Mark; 11.01.2020