/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.material.bottomnavigation; import com.google.android.material.R; import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; import static java.lang.Math.min; import android.content.Context; import android.os.Build.VERSION; import androidx.appcompat.widget.TintTypedArray; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RestrictTo; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.core.content.ContextCompat; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; import com.google.android.material.behavior.HideBottomViewOnScrollBehavior; import com.google.android.material.internal.ThemeEnforcement; import com.google.android.material.internal.ViewUtils; import com.google.android.material.internal.ViewUtils.RelativePadding; import com.google.android.material.navigation.NavigationBarMenuView; import com.google.android.material.navigation.NavigationBarView; import com.google.android.material.shape.MaterialShapeDrawable; /** * Represents a standard bottom navigation bar for application. It is an implementation of material design bottom * navigation. * *
Bottom navigation bars make it easy for users to explore and switch between top-level views in * a single tap. They should be used when an application has three to five top-level destinations. * *
The bar can disappear on scroll, based on {@link * com.google.android.material.behavior.HideBottomViewOnScrollBehavior}, when it is placed within a * {@link CoordinatorLayout} and one of the children within the {@link CoordinatorLayout} is * scrolled. This behavior is only set if the {@code layout_behavior} property is set to {@link * HideBottomViewOnScrollBehavior}. * *
The bar contents can be populated by specifying a menu resource file. Each menu item title, * icon and enabled state will be used for displaying bottom navigation bar items. Menu items can * also be used for programmatically selecting which destination is currently active. It can be done * using {@code MenuItem#setChecked(true)} * *
* layout resource file: * <com.google.android.material.bottomnavigation.BottomNavigationView * xmlns:android="http://schemas.android.com/apk/res/android" * xmlns:app="http://schema.android.com/apk/res/res-auto" * android:id="@+id/navigation" * android:layout_width="match_parent" * android:layout_height="56dp" * android:layout_gravity="start" * app:menu="@menu/my_navigation_items" /> * * res/menu/my_navigation_items.xml: * <menu xmlns:android="http://schemas.android.com/apk/res/android"> * <item android:id="@+id/action_search" * android:title="@string/menu_search" * android:icon="@drawable/ic_search" /> * <item android:id="@+id/action_settings" * android:title="@string/menu_settings" * android:icon="@drawable/ic_add" /> * <item android:id="@+id/action_navigation" * android:title="@string/menu_navigation" * android:icon="@drawable/ic_action_navigation_menu" /> * </menu> **/ public class BottomNavigationView extends NavigationBarView { static final int MAX_ITEM_COUNT = 5; public BottomNavigationView(@NonNull Context context) { this(context, null); } public BottomNavigationView(@NonNull Context context, @Nullable AttributeSet attrs) { this(context, attrs, R.attr.bottomNavigationStyle); } public BottomNavigationView( @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, R.style.Widget_Design_BottomNavigationView); } public BottomNavigationView( @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); // Ensure we are using the correctly themed context rather than the context that was passed in. context = getContext(); /* Custom attributes */ TintTypedArray attributes = ThemeEnforcement.obtainTintedStyledAttributes( context, attrs, R.styleable.BottomNavigationView, defStyleAttr, defStyleRes); setItemHorizontalTranslationEnabled( attributes.getBoolean( R.styleable.BottomNavigationView_itemHorizontalTranslationEnabled, true)); if (attributes.hasValue(R.styleable.BottomNavigationView_android_minHeight)) { setMinimumHeight( attributes.getDimensionPixelSize(R.styleable.BottomNavigationView_android_minHeight, 0)); } attributes.recycle(); if (shouldDrawCompatibilityTopDivider()) { addCompatibilityTopDivider(context); } applyWindowInsets(); } private void applyWindowInsets() { ViewUtils.doOnApplyWindowInsets( this, new ViewUtils.OnApplyWindowInsetsListener() { @NonNull @Override public WindowInsetsCompat onApplyWindowInsets( View view, @NonNull WindowInsetsCompat insets, @NonNull RelativePadding initialPadding) { // Apply the bottom, start, and end padding for a BottomNavigationView // to dodge the system navigation bar initialPadding.bottom += insets.getSystemWindowInsetBottom(); boolean isRtl = ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_RTL; int systemWindowInsetLeft = insets.getSystemWindowInsetLeft(); int systemWindowInsetRight = insets.getSystemWindowInsetRight(); initialPadding.start += isRtl ? systemWindowInsetRight : systemWindowInsetLeft; initialPadding.end += isRtl ? systemWindowInsetLeft : systemWindowInsetRight; initialPadding.applyToView(view); return insets; } }); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int minHeightSpec = makeMinHeightSpec(heightMeasureSpec); super.onMeasure(widthMeasureSpec, minHeightSpec); } private int makeMinHeightSpec(int measureSpec) { int minHeight = getSuggestedMinimumHeight(); if (MeasureSpec.getMode(measureSpec) != MeasureSpec.EXACTLY && minHeight > 0) { minHeight += getPaddingTop() + getPaddingBottom(); return MeasureSpec.makeMeasureSpec( min(MeasureSpec.getSize(measureSpec), minHeight), MeasureSpec.EXACTLY); } return measureSpec; } /** * Sets whether the menu items horizontally translate on selection when the combined item widths * fill up the screen. * * @param itemHorizontalTranslationEnabled whether the items horizontally translate on selection * @see #isItemHorizontalTranslationEnabled() */ public void setItemHorizontalTranslationEnabled(boolean itemHorizontalTranslationEnabled) { BottomNavigationMenuView menuView = (BottomNavigationMenuView) getMenuView(); if (menuView.isItemHorizontalTranslationEnabled() != itemHorizontalTranslationEnabled) { menuView.setItemHorizontalTranslationEnabled(itemHorizontalTranslationEnabled); getPresenter().updateMenuView(false); } } /** * Returns whether the items horizontally translate on selection when the item widths fill up the * screen. * * @return whether the menu items horizontally translate on selection * @see #setItemHorizontalTranslationEnabled(boolean) */ public boolean isItemHorizontalTranslationEnabled() { return ((BottomNavigationMenuView) getMenuView()).isItemHorizontalTranslationEnabled(); } @Override public int getMaxItemCount() { return MAX_ITEM_COUNT; } /** @hide */ @RestrictTo(LIBRARY_GROUP) @Override @NonNull protected NavigationBarMenuView createNavigationBarMenuView(@NonNull Context context) { return new BottomNavigationMenuView(context); } /** * Returns true a divider must be added in place of shadows to maintain compatibility in pre-21 * legacy backgrounds. */ private boolean shouldDrawCompatibilityTopDivider() { return VERSION.SDK_INT < 21 && !(getBackground() instanceof MaterialShapeDrawable); } /** * Adds a divider in place of shadows to maintain compatibility in pre-21 legacy backgrounds. If a * pre-21 background has been updated to a MaterialShapeDrawable, MaterialShapeDrawable will draw * shadows instead. */ private void addCompatibilityTopDivider(@NonNull Context context) { View divider = new View(context); divider.setBackgroundColor( ContextCompat.getColor(context, R.color.design_bottom_navigation_shadow_color)); FrameLayout.LayoutParams dividerParams = new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, getResources().getDimensionPixelSize(R.dimen.design_bottom_navigation_shadow_height)); divider.setLayoutParams(dividerParams); addView(divider); } /** * Set a listener that will be notified when a bottom navigation item is selected. This listener * will also be notified when the currently selected item is reselected, unless an {@link * OnNavigationItemReselectedListener} has also been set. * * @param listener The listener to notify * @see #setOnNavigationItemReselectedListener(OnNavigationItemReselectedListener) * @deprecated Use {@link NavigationBarView#setOnItemSelectedListener(OnItemSelectedListener)} * instead. */ @Deprecated public void setOnNavigationItemSelectedListener( @Nullable OnNavigationItemSelectedListener listener) { setOnItemSelectedListener(listener); } /** * Set a listener that will be notified when the currently selected bottom navigation item is * reselected. This does not require an {@link OnNavigationItemSelectedListener} to be set. * * @param listener The listener to notify * @see #setOnNavigationItemSelectedListener(OnNavigationItemSelectedListener) * @deprecated Use {@link NavigationBarView#setOnItemReselectedListener(OnItemReselectedListener)} * instead. */ @Deprecated public void setOnNavigationItemReselectedListener( @Nullable OnNavigationItemReselectedListener listener) { setOnItemReselectedListener(listener); } /** * Listener for handling selection events on bottom navigation items. * * @deprecated Use {@link NavigationBarView.OnItemSelectedListener} instead. */ @Deprecated public interface OnNavigationItemSelectedListener extends OnItemSelectedListener {} /** * Listener for handling reselection events on bottom navigation items. * * @deprecated Use {@link NavigationBarView.OnItemReselectedListener} instead. */ @Deprecated public interface OnNavigationItemReselectedListener extends OnItemReselectedListener {} }