Установить выбранный элемент в 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.