Skip to content

Commit 08c23dc

Browse files
imhappikendrickumstattd
authored andcommitted
[NavigationRail] Navigation rail expansion
PiperOrigin-RevId: 689848271
1 parent 2e95296 commit 08c23dc

File tree

9 files changed

+351
-20
lines changed

9 files changed

+351
-20
lines changed

catalog/java/io/material/catalog/navigationrail/NavigationRailSubMenuDemoFragment.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,18 @@
1919
import io.material.catalog.R;
2020

2121
import android.os.Bundle;
22+
import android.view.Gravity;
2223
import android.view.LayoutInflater;
2324
import android.view.View;
2425
import android.view.ViewGroup;
26+
import android.widget.FrameLayout;
27+
import android.widget.FrameLayout.LayoutParams;
28+
import android.widget.ImageView;
2529
import androidx.annotation.NonNull;
2630
import androidx.annotation.Nullable;
31+
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton;
2732
import com.google.android.material.navigationrail.NavigationRailView;
33+
import com.google.android.material.snackbar.Snackbar;
2834
import io.material.catalog.feature.DemoFragment;
2935

3036
/** A base class that provides a demo screen structure for a single navigation rail demo. */
@@ -42,6 +48,39 @@ public View onCreateDemoView(
4248
layoutInflater.inflate(
4349
R.layout.cat_navigation_rail_submenus_fragment, viewGroup, /* attachToRoot= */ false);
4450
navigationRailView = view.findViewById(R.id.cat_navigation_rail);
51+
// Add extended floating action button
52+
navigationRailView.addHeaderView(R.layout.cat_navigation_rail_efab_header_view);
53+
FrameLayout.LayoutParams lp =
54+
(LayoutParams) navigationRailView.getHeaderView().getLayoutParams();
55+
lp.gravity = Gravity.START;
56+
navigationRailView.getHeaderView().
57+
findViewById(R.id.cat_navigation_rail_efab_container)
58+
.setPadding(
59+
navigationRailView.getItemActiveIndicatorExpandedMarginHorizontal(),
60+
0,
61+
navigationRailView.getItemActiveIndicatorExpandedMarginHorizontal(),
62+
0);
63+
64+
ExtendedFloatingActionButton efab =
65+
navigationRailView.getHeaderView().findViewById(R.id.cat_navigation_rail_efab);
66+
efab.setAnimationEnabled(false);
67+
efab.setExtended(false);
68+
efab.setOnClickListener(v ->
69+
Snackbar.make(v, R.string.cat_navigation_rail_efab_message, Snackbar.LENGTH_SHORT)
70+
.show());
71+
72+
ImageView button =
73+
navigationRailView.getHeaderView().findViewById(R.id.cat_navigation_rail_expand_button);
74+
button.setOnClickListener(
75+
v -> {
76+
if (efab.isExtended()) {
77+
efab.shrink();
78+
navigationRailView.collapse();
79+
} else {
80+
efab.extend();
81+
navigationRailView.expand();
82+
}
83+
});
4584
return view;
4685
}
4786
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
Copyright 2024 The Android Open Source Project
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
https://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
-->
17+
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
18+
xmlns:app="http://schemas.android.com/apk/res-auto"
19+
android:orientation="vertical"
20+
android:id="@+id/cat_navigation_rail_efab_container"
21+
android:layout_width="match_parent"
22+
android:layout_height="wrap_content"
23+
android:clipToPadding="false"
24+
android:clipChildren="false"
25+
android:layout_gravity="top|start">
26+
<ImageView
27+
android:id="@+id/cat_navigation_rail_expand_button"
28+
android:layout_width="56dp"
29+
android:layout_height="56dp"
30+
android:src="@drawable/ic_drawer_menu_24px"
31+
android:contentDescription="@null"
32+
android:scaleType="center" />
33+
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
34+
android:id="@+id/cat_navigation_rail_efab"
35+
android:layout_width="wrap_content"
36+
android:layout_height="wrap_content"
37+
app:icon="@drawable/ic_add_24px"
38+
android:text="@string/cat_navigation_rail_efab_text"/>
39+
</LinearLayout>

catalog/java/io/material/catalog/navigationrail/res/layout/cat_navigation_rail_submenus_fragment.xml

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,29 @@
1414
~ See the License for the specific language governing permissions and
1515
~ limitations under the License.
1616
-->
17-
<com.google.android.material.navigationrail.NavigationRailView
17+
<androidx.constraintlayout.widget.ConstraintLayout
1818
xmlns:android="http://schemas.android.com/apk/res/android"
1919
xmlns:app="http://schemas.android.com/apk/res-auto"
20-
android:id="@+id/cat_navigation_rail"
21-
android:layout_width="wrap_content"
22-
android:layout_height="match_parent"
23-
app:labelVisibilityMode="labeled"
24-
android:fitsSystemWindows="false"
25-
app:scrollingEnabled="true"
26-
app:menu="@menu/navigation_rail_submenus_menu"/>
20+
android:layout_width="match_parent"
21+
android:layout_height="match_parent">
22+
<com.google.android.material.navigationrail.NavigationRailView
23+
android:id="@+id/cat_navigation_rail"
24+
android:layout_width="wrap_content"
25+
android:layout_height="match_parent"
26+
app:labelVisibilityMode="labeled"
27+
android:fitsSystemWindows="false"
28+
app:scrollingEnabled="true"
29+
app:layout_constraintStart_toStartOf="parent"
30+
app:menu="@menu/navigation_rail_submenus_menu"/>
31+
<FrameLayout
32+
android:layout_width="0dp"
33+
android:layout_height="match_parent"
34+
app:layout_constraintStart_toEndOf="@id/cat_navigation_rail"
35+
app:layout_constraintEnd_toEndOf="parent">
36+
<TextView
37+
android:layout_width="wrap_content"
38+
android:layout_height="wrap_content"
39+
android:layout_gravity="center"
40+
android:text="@string/cat_navigation_rail_center_text"/>
41+
</FrameLayout>
42+
</androidx.constraintlayout.widget.ConstraintLayout>

catalog/java/io/material/catalog/navigationrail/res/menu/navigation_rail_submenus_menu.xml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,26 @@
7474
android:title="@string/cat_navigation_rail_page_10_name"/>
7575
</menu>
7676
</item>
77+
78+
<item
79+
android:title="@string/cat_navigation_rail_subheader_2_name">
80+
<menu>
81+
<item
82+
android:id="@+id/action_page_11"
83+
android:enabled="true"
84+
android:icon="@drawable/ic_star_checkable_24"
85+
android:title="@string/cat_navigation_rail_page_11_name"/>
86+
</menu>
87+
</item>
88+
89+
<item
90+
android:title="@string/cat_navigation_rail_subheader_3_name">
91+
<menu>
92+
<item
93+
android:id="@+id/action_page_12"
94+
android:enabled="true"
95+
android:icon="@drawable/ic_star_checkable_24"
96+
android:title="@string/cat_navigation_rail_page_12_name"/>
97+
</menu>
98+
</item>
7799
</menu>

catalog/java/io/material/catalog/navigationrail/res/values/strings.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,17 @@
5454
description="The title of a page in the navigation rail demo. [CHAR_LIMIT=50]">Page 9</string>
5555
<string name="cat_navigation_rail_page_10_name"
5656
description="The title of a page in the navigation rail demo. [CHAR_LIMIT=50]">Page 10</string>
57+
58+
<string name="cat_navigation_rail_page_11_name"
59+
description="The title of a page in the navigation rail demo. [CHAR_LIMIT=50]">Page 11</string>
60+
<string name="cat_navigation_rail_page_12_name"
61+
description="The title of a page in the navigation rail demo. [CHAR_LIMIT=50]">Page 12</string>
5762
<string name="cat_navigation_rail_subheader_1_name"
5863
description="The title of a subheader in the navigation rail demo. [CHAR_LIMIT=50]">Subheader 1</string>
64+
<string name="cat_navigation_rail_subheader_2_name"
65+
description="The title of a subheader in the navigation rail demo. [CHAR_LIMIT=50]">Subheader 2</string>
66+
<string name="cat_navigation_rail_subheader_3_name"
67+
description="The title of a subheader in the navigation rail demo. [CHAR_LIMIT=50]">Subheader 3</string>
5968

6069
<string name="cat_navigation_rail_add_nav_item"
6170
description="Label for the button that will add a destination to the navigation rail. [CHAR_LIMIT=50]">Add destination</string>
@@ -116,4 +125,10 @@
116125
description="Content description for a floating action button in the navigation rail.[CHAR_LIMIT=50]">Navigation Rail FAB</string>
117126

118127
<string name="cat_navigation_rail_compact_message" description="Usage guidance for Navigation Rail advising using a Bottom Navigation Bar on compact screens instead. [CHAR_LIMIT=NONE]">The orientation of this Navigation Rail demo has been locked to portrait mode, because landscape mode results in a compact height on this device. For any compact screen dimensions, use a Bottom Navigation Bar instead.</string>
128+
129+
<string name="cat_navigation_rail_efab_text" description="The text on the button to expand the navigation rail [CHAR_LIMIT=NONE]">Label</string>
130+
<string name="cat_navigation_rail_center_text" description="The text in the content centered in the remaining space beside the navigation rail [CHAR_LIMIT=NONE]">center</string>
131+
<string name="cat_navigation_rail_efab_message" description="The message to display when the extended floating action button is clicked [CHAR_LIMIT=NONE]">Button clicked</string>
132+
<string name="cat_navigation_rail_expand_collapse_button_description" description="The content description for the button that expands and collapses the navigation rail [CHAR_LIMIT=NONE]">Button to expand or collapse the navigation rail</string>
133+
119134
</resources>

lib/java/com/google/android/material/floatingactionbutton/ExtendedFloatingActionButton.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -920,10 +920,7 @@ private boolean isOrWillBeHidden() {
920920

921921
/**
922922
* Set whether or not animations are enabled.
923-
*
924-
* @hide
925923
*/
926-
@RestrictTo(LIBRARY_GROUP)
927924
public void setAnimationEnabled(boolean animationEnabled) {
928925
this.animationEnabled = animationEnabled;
929926
}

lib/java/com/google/android/material/navigation/NavigationBarItemView.java

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -210,11 +210,11 @@ public NavigationBarItemView(@NonNull Context context) {
210210
}
211211
// If item icon gravity is start, we want to update the active indicator width in a layout
212212
// change listener to keep the active indicator size up to date with the content width.
213+
LayoutParams lp = (LayoutParams) innerContentContainer.getLayoutParams();
214+
int newWidth = right - left + lp.rightMargin + lp.leftMargin;
213215
if (itemIconGravity == ITEM_ICON_GRAVITY_START
214216
&& activeIndicatorExpandedDesiredWidth == ACTIVE_INDICATOR_WIDTH_WRAP_CONTENT
215-
&& (right - left) != (oldRight - oldLeft)) {
216-
LayoutParams lp = (LayoutParams) innerContentContainer.getLayoutParams();
217-
int newWidth = right - left + lp.rightMargin + lp.leftMargin;
217+
&& newWidth != activeIndicatorView.getMeasuredWidth()) {
218218
LayoutParams indicatorParams = (LayoutParams) activeIndicatorView.getLayoutParams();
219219
int minWidth =
220220
min(
@@ -308,6 +308,16 @@ public int getItemPosition() {
308308
return itemPosition;
309309
}
310310

311+
@NonNull
312+
public BaselineLayout getLabelGroup() {
313+
return labelGroup;
314+
}
315+
316+
@NonNull
317+
public BaselineLayout getExpandedLabelGroup() {
318+
return expandedLabelGroup;
319+
}
320+
311321
public void setShifting(boolean shifting) {
312322
if (isShifting != shifting) {
313323
isShifting = shifting;
@@ -1103,7 +1113,11 @@ public void setActiveIndicatorExpandedHeight(int height) {
11031113
private void updateActiveIndicatorLayoutParams(int availableWidth) {
11041114
// Set width to the min of either the desired indicator width or the available width minus
11051115
// a horizontal margin.
1106-
if (availableWidth <= 0) {
1116+
if (availableWidth <= 0 && getVisibility() == VISIBLE) {
1117+
// Return early if there's not yet an available width and the view is visible; this will be
1118+
// called again when there is an available width. Otherwise if the available width is 0 due to
1119+
// the view being gone, we still want to set layout params so that when the view appears,
1120+
// there is no jump in animation from turning visible and then adjusting the height/width.
11071121
return;
11081122
}
11091123

@@ -1125,7 +1139,7 @@ private void updateActiveIndicatorLayoutParams(int availableWidth) {
11251139
// If the label visibility is unlabeled, make the active indicator's height equal to its
11261140
// width.
11271141
indicatorParams.height = isActiveIndicatorResizeableAndUnlabeled() ? newWidth : newHeight;
1128-
indicatorParams.width = newWidth;
1142+
indicatorParams.width = max(0, newWidth);
11291143
activeIndicatorView.setLayoutParams(indicatorParams);
11301144
}
11311145

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright (C) 2024 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.google.android.material.navigationrail;
17+
18+
import android.animation.Animator;
19+
import android.animation.ValueAnimator;
20+
import android.view.View;
21+
import android.view.ViewGroup;
22+
import androidx.annotation.NonNull;
23+
import androidx.annotation.Nullable;
24+
import androidx.transition.Transition;
25+
import androidx.transition.TransitionValues;
26+
import com.google.android.material.navigation.NavigationBarMenuItemView;
27+
28+
/**
29+
* A {@link Transition} that animates the {@link NavigationBarMenuItemView} label horizontally when
30+
* the label is fading in.
31+
*/
32+
class LabelMoveTransition extends Transition {
33+
34+
private static final String LABEL_VISIBILITY = "NavigationRailLabelVisibility";
35+
private static final float HORIZONTAL_DISTANCE = -30f;
36+
37+
@Override
38+
public void captureStartValues(@NonNull TransitionValues transitionValues) {
39+
transitionValues.values.put(LABEL_VISIBILITY, transitionValues.view.getVisibility());
40+
}
41+
42+
@Override
43+
public void captureEndValues(@NonNull TransitionValues transitionValues) {
44+
transitionValues.values.put(LABEL_VISIBILITY, transitionValues.view.getVisibility());
45+
}
46+
47+
@Nullable
48+
@Override
49+
public Animator createAnimator(@NonNull ViewGroup sceneRoot,
50+
@Nullable TransitionValues startValues,
51+
@Nullable TransitionValues endValues) {
52+
if (startValues == null || endValues == null
53+
|| startValues.values.get(LABEL_VISIBILITY) == null
54+
|| endValues.values.get(LABEL_VISIBILITY) == null) {
55+
return super.createAnimator(sceneRoot, startValues, endValues);
56+
}
57+
// Only animate if the view is appearing
58+
if ((int) startValues.values.get(LABEL_VISIBILITY) != View.GONE
59+
|| (int) endValues.values.get(LABEL_VISIBILITY) != View.VISIBLE) {
60+
return super.createAnimator(sceneRoot, startValues, endValues);
61+
}
62+
View view = endValues.view;
63+
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
64+
animator.addUpdateListener(
65+
animation -> {
66+
float progress = animation.getAnimatedFraction();
67+
view.setTranslationX(HORIZONTAL_DISTANCE * (1 - progress));
68+
});
69+
return animator;
70+
}
71+
}

0 commit comments

Comments
 (0)