/* * 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 android.content.Context; import android.content.res.Resources; import androidx.appcompat.view.menu.MenuBuilder; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import androidx.annotation.NonNull; import androidx.annotation.RestrictTo; import androidx.core.view.ViewCompat; import com.google.android.material.navigation.NavigationBarItemView; import com.google.android.material.navigation.NavigationBarMenuView; /** @hide For internal use only. */ @RestrictTo(LIBRARY_GROUP) public class BottomNavigationMenuView extends NavigationBarMenuView { private final int inactiveItemMaxWidth; private final int inactiveItemMinWidth; private final int activeItemMaxWidth; private final int activeItemMinWidth; private boolean itemHorizontalTranslationEnabled; private int[] tempChildWidths; public BottomNavigationMenuView(@NonNull Context context) { super(context); FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); params.gravity = Gravity.CENTER; setLayoutParams(params); final Resources res = getResources(); inactiveItemMaxWidth = res.getDimensionPixelSize(R.dimen.design_bottom_navigation_item_max_width); inactiveItemMinWidth = res.getDimensionPixelSize(R.dimen.design_bottom_navigation_item_min_width); activeItemMaxWidth = res.getDimensionPixelSize(R.dimen.design_bottom_navigation_active_item_max_width); activeItemMinWidth = res.getDimensionPixelSize(R.dimen.design_bottom_navigation_active_item_min_width); tempChildWidths = new int[BottomNavigationView.MAX_ITEM_COUNT]; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final MenuBuilder menu = getMenu(); final int width = MeasureSpec.getSize(widthMeasureSpec); // Use visible item count to calculate widths final int visibleCount = menu.getVisibleItems().size(); // Use total item counts to measure children final int totalCount = getChildCount(); int parentHeight = MeasureSpec.getSize(heightMeasureSpec); final int heightSpec = MeasureSpec.makeMeasureSpec(parentHeight, MeasureSpec.EXACTLY); if (isShifting(getLabelVisibilityMode(), visibleCount) && isItemHorizontalTranslationEnabled()) { final View activeChild = getChildAt(getSelectedItemPosition()); int activeItemWidth = activeItemMinWidth; if (activeChild.getVisibility() != View.GONE) { // Do an AT_MOST measure pass on the active child to get its desired width, and resize the // active child view based on that width activeChild.measure( MeasureSpec.makeMeasureSpec(activeItemMaxWidth, MeasureSpec.AT_MOST), heightSpec); activeItemWidth = Math.max(activeItemWidth, activeChild.getMeasuredWidth()); } final int inactiveCount = visibleCount - (activeChild.getVisibility() != View.GONE ? 1 : 0); final int activeMaxAvailable = width - inactiveCount * inactiveItemMinWidth; final int activeWidth = Math.min(activeMaxAvailable, Math.min(activeItemWidth, activeItemMaxWidth)); final int inactiveMaxAvailable = (width - activeWidth) / (inactiveCount == 0 ? 1 : inactiveCount); final int inactiveWidth = Math.min(inactiveMaxAvailable, inactiveItemMaxWidth); int extra = width - activeWidth - inactiveWidth * inactiveCount; for (int i = 0; i < totalCount; i++) { if (getChildAt(i).getVisibility() != View.GONE) { tempChildWidths[i] = (i == getSelectedItemPosition()) ? activeWidth : inactiveWidth; // Account for integer division which sometimes leaves some extra pixel spaces. // e.g. If the nav was 10px wide, and 3 children were measured to be 3px-3px-3px, there // would be a 1px gap somewhere, which this fills in. if (extra > 0) { tempChildWidths[i]++; extra--; } } else { tempChildWidths[i] = 0; } } } else { final int maxAvailable = width / (visibleCount == 0 ? 1 : visibleCount); final int childWidth = Math.min(maxAvailable, activeItemMaxWidth); int extra = width - childWidth * visibleCount; for (int i = 0; i < totalCount; i++) { if (getChildAt(i).getVisibility() != View.GONE) { tempChildWidths[i] = childWidth; if (extra > 0) { tempChildWidths[i]++; extra--; } } else { tempChildWidths[i] = 0; } } } int totalWidth = 0; for (int i = 0; i < totalCount; i++) { final View child = getChildAt(i); if (child.getVisibility() == GONE) { continue; } child.measure( MeasureSpec.makeMeasureSpec(tempChildWidths[i], MeasureSpec.EXACTLY), heightSpec); ViewGroup.LayoutParams params = child.getLayoutParams(); params.width = child.getMeasuredWidth(); totalWidth += child.getMeasuredWidth(); } setMeasuredDimension(totalWidth, parentHeight); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { final int count = getChildCount(); final int width = right - left; final int height = bottom - top; int used = 0; for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() == GONE) { continue; } if (ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL) { child.layout(width - used - child.getMeasuredWidth(), 0, width - used, height); } else { child.layout(used, 0, child.getMeasuredWidth() + used, height); } used += child.getMeasuredWidth(); } } /** * Sets whether the menu items horizontally translate on selection when the combined item widths * fill the screen. * * @param itemHorizontalTranslationEnabled whether the menu items horizontally translate on * selection * @see #isItemHorizontalTranslationEnabled() */ public void setItemHorizontalTranslationEnabled(boolean itemHorizontalTranslationEnabled) { this.itemHorizontalTranslationEnabled = itemHorizontalTranslationEnabled; } /** * Returns whether the menu items horizontally translate on selection when the combined item * widths fill the screen. * * @return whether the menu items horizontally translate on selection * @see #setItemHorizontalTranslationEnabled(boolean) */ public boolean isItemHorizontalTranslationEnabled() { return itemHorizontalTranslationEnabled; } @Override @NonNull protected NavigationBarItemView createNavigationBarItemView(@NonNull Context context) { return new BottomNavigationItemView(context); } }