Установить выбранный элемент в Android BottomNavigationView


123

Я использую новинку android.support.design.widget.BottomNavigationViewиз библиотеки поддержки. Как я могу установить текущий выбор из кода? Я понял, что после поворота экрана выбор снова меняется на первый элемент. Конечно, также было бы полезно, если бы кто-нибудь мог сказать мне, как «сохранить» текущее состояние BottomNavigationViewв onPauseфункции и как восстановить его в onResume.

Спасибо!


1
Я только что взглянул на исходный код, и у меня такой же вопрос ... Не похоже, что есть способ установить выбранный элемент, и BottomNavigationViewне выполняет никакого внутреннего сохранения состояния. Вероятно, ожидайте, что это будет включено в будущее обновление. Дубликат (с дополнительной информацией) здесь: stackoverflow.com/questions/40236786/…
— Тим Малсид,

Ответы:


171

В API 25.3.0 был представлен метод, setSelectedItemId(int id)позволяющий пометить элемент как выбранный, как если бы он был нажат.

Из документов:

Установите ID выбранного пункта меню. Это ведет себя так же, как нажатие на элемент.

Пример кода:

BottomNavigationView bottomNavigationView;
bottomNavigationView = (BottomNavigationView) findViewById(R.id.bottomNavigationView);
bottomNavigationView.setOnNavigationItemSelectedListener(myNavigationItemListener);
bottomNavigationView.setSelectedItemId(R.id.my_menu_item_id);

ВАЖНЫЙ

Вы ДОЛЖНЫ уже добавить все элементы в меню (если вы делаете это программно) и установить Listener перед вызовом setSelectedItemId (я считаю, что вы хотите, чтобы код в вашем слушателе запускался, когда вы вызываете этот метод). Если вы вызовете setSelectedItemId перед добавлением пунктов меню и установкой слушателя, ничего не произойдет.


3
Это НЕ РАБОТАЕТ, он обновляет только саму вкладку, он не запускает событие
— setOnNavigationItemSelectedListener

1
Если вы не определили поведение в, onTabSelectedочевидно, ничего не будет. Прочтите документацию -> Установите идентификатор выбранного пункта меню. Это ведет себя так же, как нажатие на элемент. Это от разработчиков Android
— Рикардо,

1
Я настоятельно рекомендую вам отладить и проверить, что вы делаете не так. Я уже реализовал три приложения BottomNavigationView и никогда не испытывал проблем с этим методом.
— Рикардо

@Ricardo Когда я добавляю это в свой фрагмент, приложение зависает при входе в этот фрагмент
— Субин Бабу

1
В моем случае это не сработало, потому что я программно создавал NavigationMenu. Во время строительства щелчок не работал. После добавления всех элементов в меню вызов setSelectedItemId () работал.
— Педро Ромао

53

Чтобы программно щелкнуть элемент BottomNavigationBar, который вам нужен:

View view = bottomNavigationView.findViewById(R.id.menu_action_item);
view.performClick();

Это правильно расположит все предметы с их этикетками.


4
Это хакерство относительно проблемы. У вас есть методы из самой библиотеки, которые позволят вам выполнять то, что вам нужно. Если вы не можете, то это потому, что вы делаете это неправильно,
— Рикардо,

47

Для тех, кто все еще пользуется SupportLibrary <25.3.0

Я не уверен, является ли это полным ответом на этот вопрос, но моя проблема была очень похожей - мне пришлось обработать backнажатие кнопки и перевести пользователя на предыдущую вкладку, где он был. Так что, может, мое решение кому-нибудь пригодится:

private void updateNavigationBarState(int actionId){
    Menu menu = bottomNavigationView.getMenu();

    for (int i = 0, size = menu.size(); i < size; i++) {
        MenuItem item = menu.getItem(i);
        item.setChecked(item.getItemId() == actionId);
    }
}

Пожалуйста, имейте в виду, что если пользователь нажмет другую вкладку навигации BottomNavigationView, не очистит текущий выбранный элемент, поэтому вам необходимо вызвать этот метод onNavigationItemSelectedпосле обработки действия навигации:

@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
    switch (item.getItemId()) {
        case R.id.some_id_1:
            // process action
            break;
        case R.id.some_id_2:
            // process action
            break;
        ...
        default:
            return false;
    }

    updateNavigationBarState(item.getItemId());

    return true;
}

Что касается сохранения состояния экземпляра, я думаю, вы могли бы поиграть с тем же action idвидом навигации и найти подходящее решение.


Не Menu menu = bottomNavigationView.getMenu();возвращает копию меню, чтобы bottomNavigationViewобъект не пострадал?
— 最 白 目

1
@dan Согласно источнику, он возвращает атрибут класса mMenu, а не копию android.googlesource.com/platform/frameworks/support/+/master/…
— Вячеслав,

6
Вам не нужно снимать отметки с других пунктов, по крайней мере, 26.1.0 / 27.1.0
— Джемшит Искендеров

не снимайте галочку, они будут выделены
— djdance

19
bottomNavigationView.setSelectedItemId(R.id.action_item1);

где action_item1ID пункта меню.


8
@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);

   bottomNavigationView.setOnNavigationItemSelectedListener(this);
   Menu menu = bottomNavigationView.getMenu();
   this.onNavigationItemSelected(menu.findItem(R.id.action_favorites));
}


5

Добавить android:enabled="true" в элементы BottomNavigationMenu.

А затем установите bottomNavigationView.setOnNavigationItemSelectedListener(mListener)и установите его как выбранное, выполнивbottomNavigationView.selectedItemId = R.id.your_menu_id


3

Вероятно, это будет добавлено в следующих обновлениях. Но пока для этого вы можете использовать отражение.

Создайте собственное представление, расширяющееся от BottomNavigationView, и получите доступ к некоторым из его полей.

public class SelectableBottomNavigationView extends BottomNavigationView {

    public SelectableBottomNavigationView(Context context) {
        super(context);
    }

    public SelectableBottomNavigationView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public SelectableBottomNavigationView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public void setSelected(int index) {
        try {
            Field f = BottomNavigationView.class.getDeclaredField("mMenuView");
            f.setAccessible(true);
            BottomNavigationMenuView menuView = (BottomNavigationMenuView) f.get(this);

            try {
                Method method = menuView.getClass().getDeclaredMethod("activateNewButton", Integer.TYPE);
                method.setAccessible(true);
                method.invoke(menuView, index);
            } catch (SecurityException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }

}

А затем используйте его в своем XML-файле макета.

<com.your.app.SelectableBottomNavigationView
        android:id="@+id/bottom_navigation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:itemBackground="@color/primary"
        app:itemIconTint="@drawable/nav_item_color_state"
        app:itemTextColor="@drawable/nav_item_color_state"
        app:menu="@menu/bottom_navigation_menu"/>

3
Отражение - плохая идея!
— Филипе Брито

PerformClick в этом случае лучше, чем отражение, imho.
— Lassi Kinnunen

3

Просто добавьте еще один способ программно выполнить выбор - вероятно, это было изначально задумано или, может быть, это было добавлено позже.

Menu bottomNavigationMenu = myBottomNavigationMenu.getMenu();
bottomNavigationMenu.performIdentifierAction(selected_menu_item_id, 0);

performIdentifierActionПринимает Menuидентификатор товара и флаг.

См. Документацию для получения дополнительной информации.


Кажется, что это выполняет действие, принадлежащее элементу меню, но не приводит к тому, что элемент меню отображается выбранным. Я думал, что это было то, что хотел OP, но я не уверен.
— LarsH

3

Кажется, исправлено в SupportLibrary 25.1.0 :) Редактировать: Кажется, исправлено, что состояние выделения сохраняется при повороте экрана.


1
Я только что обновился до 25.1.1. Проблема все еще существует.
— Си Вэй

3

Выше API 25 вы можете использовать setSelectedItemId (menu_item_id), но в API 25 вы должны сделать по-другому, пользовательское меню для получения дескриптора, а затем setChecked для отмеченного конкретного элемента


3

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

MenuItem item = mBottomNavView.getMenu().findItem(menu_id);
item.setChecked(true);

2

Я не хочу изменять свой код. Если да, то я рекомендую вам попробовать BottomNavigationViewEx。

Вам просто нужно заменить вызов метода setCurrentItem(index);и getCurrentItem()。

Щелкните здесь, чтобы просмотреть изображение





0
private void setSelectedItem(int actionId) {
    Menu menu = viewBottom.getMenu();
    for (int i = 0, size = menu.size(); i < size; i++) {
        MenuItem menuItem = menu.getItem(i);
        ((MenuItemImpl) menuItem).setExclusiveCheckable(false);
        menuItem.setChecked(menuItem.getItemId() == actionId);
        ((MenuItemImpl) menuItem).setExclusiveCheckable(true);
    }
}

Единственный «минус» решения - это использование MenuItemImpl, которое является «внутренним» для библиотеки (хотя и общедоступным).


0

ЕСЛИ ВАМ НУЖНО ДИНАМИЧЕСКИ ПЕРЕДАТЬ ФРАГМЕНТНЫЕ АРГУМЕНТЫ, СДЕЛАЙТЕ ЭТО

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

Вы не можете динамически передавать различные аргументы загруженному фрагменту с помощью using setSelectedItemId(R.id.second_tab), что приводит к вызову static OnNavigationItemSelectedListener. Чтобы преодолеть это ограничение, я сделал это в моем, MainActivityкоторый содержит вкладки:

fun loadArticleTab(articleId: String) {
    bottomNavigationView.menu.findItem(R.id.tab_article).isChecked = true // use setChecked() in Java
    supportFragmentManager
        .beginTransaction()
        .replace(R.id.main_fragment_container, ArticleFragment.newInstance(articleId))
        .commit()
}

ArticleFragment.newInstance()Метод реализован как обычно:

private const val ARG_ARTICLE_ID = "ARG_ARTICLE_ID"

class ArticleFragment : Fragment() {

    companion object {
        /**
         * @return An [ArticleFragment] that shows the article with the given ID.
         */
        fun newInstance(articleId: String): ArticleFragment {
            val args = Bundle()
            args.putString(ARG_ARTICLE_ID, day)
            val fragment = ArticleFragment()
            fragment.arguments = args
            return fragment
        }
    }

}

0

надеюсь, это поможет

//Setting default selected menu item and fragment
        bottomNavigationView.setSelectedItemId(R.id.action_home);
        getSupportFragmentManager()
                .beginTransaction()
                .replace(R.id.fragment_container, new HomeFragment()).commit();

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


0

Этот метод работает для меня.

private fun selectBottomNavigationViewMenuItem(bottomNavigationView : BottomNavigationView,@IdRes menuItemId: Int) {
            bottomNavigationView.setOnNavigationItemSelectedListener(null)
            bottomNavigationView.selectedItemId = menuItemId
            bottomNavigationView.setOnNavigationItemSelectedListener(this)
        }

пример

 override fun onBackPressed() {
        replaceFragment(HomeFragment())
        selectBottomNavigationViewMenuItem(navView, R.id.navigation_home)
    }



private fun replaceFragment(fragment: Fragment) {
        val transaction: FragmentTransaction = supportFragmentManager.beginTransaction()
        transaction.replace(R.id.frame_container, fragment)
        transaction.commit()
    }

-1

Отражение - плохая идея.

Перейдите к этой сути . Есть метод, который выполняет выбор, но также вызывает обратный вызов:

  @CallSuper
    public void setSelectedItem(int position) {
        if (position >= getMenu().size() || position < 0) return;

        View menuItemView = getMenuItemView(position);
        if (menuItemView == null) return;
        MenuItemImpl itemData = ((MenuView.ItemView) menuItemView).getItemData();


        itemData.setChecked(true);

        boolean previousHapticFeedbackEnabled = menuItemView.isHapticFeedbackEnabled();
        menuItemView.setSoundEffectsEnabled(false);
        menuItemView.setHapticFeedbackEnabled(false); //avoid hearing click sounds, disable haptic and restore settings later of that view
        menuItemView.performClick();
        menuItemView.setHapticFeedbackEnabled(previousHapticFeedbackEnabled);
        menuItemView.setSoundEffectsEnabled(true);


        mLastSelection = position;

    }

Я использую callOnClick()вместо performClick()этого, мне не нужно отключать звук, который все равно не работал performClick().
— arekolek
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.