Skip to content

Commit a15d3ea

Browse files
wcshipekingme
authored andcommitted
Update TabLayout so it takes into account TabGravity in MODE_SCROLLABLE.
Add TabGravity.GRAVITY_START to maintain existing (default) MODE_SCROLLABLE behavior. MODE_SCROLLABLE currently supports GRAVITY_CENTER and GRAVITY_START MODE_FIXED does not support GRAVITY_START. PiperOrigin-RevId: 289910847
1 parent 439a1d8 commit a15d3ea

File tree

7 files changed

+234
-7
lines changed

7 files changed

+234
-7
lines changed

catalog/java/io/material/catalog/tabs/TabsAutoDemoFragment.java

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import android.os.Bundle;
2222
import androidx.annotation.LayoutRes;
23+
import androidx.annotation.NonNull;
2324
import androidx.annotation.Nullable;
2425
import android.view.LayoutInflater;
2526
import android.view.View;
@@ -32,8 +33,11 @@
3233
/** A fragment that displays a scrollable tabs demo for the Catalog app. */
3334
public class TabsAutoDemoFragment extends DemoFragment {
3435

36+
private static final String KEY_TABS = "TABS";
37+
3538
private int numTabs = 0;
3639
private String[] tabTitles;
40+
private TabLayout autoScrollableTabLayout;
3741

3842
@Nullable
3943
@Override
@@ -47,8 +51,18 @@ public View onCreateDemoView(
4751
View tabsContent = layoutInflater.inflate(getTabsContent(), content, false /* attachToRoot */);
4852
content.addView(tabsContent, 0);
4953

50-
TabLayout autoScrollableTabLayout = tabsContent.findViewById(R.id.auto_tab_layout);
51-
numTabs = autoScrollableTabLayout.getChildCount();
54+
autoScrollableTabLayout = tabsContent.findViewById(R.id.auto_tab_layout);
55+
56+
if (bundle != null) {
57+
autoScrollableTabLayout.removeAllTabs();
58+
// Restore saved tabs
59+
String[] tabLabels = bundle.getStringArray(KEY_TABS);
60+
for (int i = 0; i < tabLabels.length; i++) {
61+
autoScrollableTabLayout.addTab(autoScrollableTabLayout.newTab().setText(tabLabels[i]), i);
62+
}
63+
}
64+
65+
numTabs = autoScrollableTabLayout.getTabCount();
5266

5367
tabTitles = getContext().getResources().getStringArray(R.array.cat_tabs_titles);
5468

@@ -77,4 +91,14 @@ public View onCreateDemoView(
7791
protected int getTabsContent() {
7892
return R.layout.cat_tabs_auto_content;
7993
}
94+
95+
@Override
96+
public void onSaveInstanceState(@NonNull Bundle bundle) {
97+
super.onSaveInstanceState(bundle);
98+
String[] tabLabels = new String[autoScrollableTabLayout.getTabCount()];
99+
for (int i = 0; i < autoScrollableTabLayout.getTabCount(); i++) {
100+
tabLabels[i] = autoScrollableTabLayout.getTabAt(i).getText().toString();
101+
}
102+
bundle.putStringArray(KEY_TABS, tabLabels);
103+
}
80104
}

catalog/java/io/material/catalog/tabs/TabsScrollableDemoFragment.java

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,24 +20,98 @@
2020

2121
import android.os.Bundle;
2222
import androidx.annotation.LayoutRes;
23+
import androidx.annotation.NonNull;
2324
import androidx.annotation.Nullable;
2425
import android.view.LayoutInflater;
2526
import android.view.View;
2627
import android.view.ViewGroup;
28+
import android.widget.Button;
29+
import android.widget.RadioButton;
30+
import com.google.android.material.tabs.TabLayout;
31+
import com.google.android.material.tabs.TabLayout.Tab;
2732
import io.material.catalog.feature.DemoFragment;
2833

2934
/** A fragment that displays a scrollable tabs demo for the Catalog app. */
3035
public class TabsScrollableDemoFragment extends DemoFragment {
3136

37+
private static final String KEY_TABS = "TABS";
38+
private static final String KEY_TAB_GRAVITY = "TAB_GRAVITY";
39+
40+
private int numTabs = 0;
41+
private String[] tabTitles;
42+
private TabLayout scrollableTabLayout;
43+
3244
@Nullable
3345
@Override
3446
public View onCreateDemoView(
3547
LayoutInflater layoutInflater, @Nullable ViewGroup viewGroup, @Nullable Bundle bundle) {
36-
return layoutInflater.inflate(getTabsContent(), viewGroup, false /* attachToRoot */);
48+
// return layoutInflater.inflate(getTabsContent(), viewGroup, false /* attachToRoot */);
49+
View view =
50+
layoutInflater.inflate(
51+
R.layout.cat_tabs_scrollable_fragment, viewGroup, false /* attachToRoot */);
52+
53+
ViewGroup content = view.findViewById(R.id.content);
54+
View tabsContent = layoutInflater.inflate(getTabsContent(), content, false /* attachToRoot */);
55+
content.addView(tabsContent, 0);
56+
57+
scrollableTabLayout = tabsContent.findViewById(R.id.scrollable_tab_layout);
58+
59+
RadioButton tabGravityStartButton = view.findViewById(R.id.tabs_gravity_start_button);
60+
tabGravityStartButton.setOnClickListener(
61+
v -> scrollableTabLayout.setTabGravity(TabLayout.GRAVITY_START));
62+
63+
RadioButton tabGravityCenterButton = view.findViewById(R.id.tabs_gravity_center_button);
64+
tabGravityCenterButton.setOnClickListener(
65+
v -> scrollableTabLayout.setTabGravity(TabLayout.GRAVITY_CENTER));
66+
67+
if (bundle != null) {
68+
scrollableTabLayout.removeAllTabs();
69+
scrollableTabLayout.setTabGravity(bundle.getInt(KEY_TAB_GRAVITY));
70+
// Restore saved tabs
71+
String[] tabLabels = bundle.getStringArray(KEY_TABS);
72+
73+
for (String label : tabLabels) {
74+
scrollableTabLayout.addTab(scrollableTabLayout.newTab().setText(label));
75+
}
76+
}
77+
78+
numTabs = scrollableTabLayout.getTabCount();
79+
80+
tabTitles = getContext().getResources().getStringArray(R.array.cat_tabs_titles);
81+
82+
Button addButton = view.findViewById(R.id.add_tab_button);
83+
addButton.setOnClickListener(
84+
v -> {
85+
scrollableTabLayout.addTab(
86+
scrollableTabLayout.newTab().setText(tabTitles[numTabs % tabTitles.length]));
87+
numTabs++;
88+
});
89+
90+
Button removeButton = view.findViewById(R.id.remove_tab_button);
91+
removeButton.setOnClickListener(
92+
v -> {
93+
Tab tab = scrollableTabLayout.getTabAt(scrollableTabLayout.getTabCount() - 1);
94+
if (tab != null) {
95+
scrollableTabLayout.removeTab(tab);
96+
}
97+
numTabs = Math.max(0, numTabs - 1);
98+
});
99+
return view;
37100
}
38101

39102
@LayoutRes
40103
protected int getTabsContent() {
41104
return R.layout.cat_tabs_scrollable_content;
42105
}
106+
107+
@Override
108+
public void onSaveInstanceState(@NonNull Bundle bundle) {
109+
super.onSaveInstanceState(bundle);
110+
String[] tabLabels = new String[scrollableTabLayout.getTabCount()];
111+
for (int i = 0; i < scrollableTabLayout.getTabCount(); i++) {
112+
tabLabels[i] = scrollableTabLayout.getTabAt(i).getText().toString();
113+
}
114+
bundle.putStringArray(KEY_TABS, tabLabels);
115+
bundle.putInt(KEY_TAB_GRAVITY, scrollableTabLayout.getTabGravity());
116+
}
43117
}

catalog/java/io/material/catalog/tabs/res/layout/cat_tabs_scrollable_content.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
android:layout_height="match_parent">
2323

2424
<com.google.android.material.tabs.TabLayout
25+
android:id="@+id/scrollable_tab_layout"
2526
android:layout_width="match_parent"
2627
android:layout_height="wrap_content"
2728
app:tabMode="scrollable">
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
Copyright 2020 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+
18+
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
19+
xmlns:tools="http://schemas.android.com/tools"
20+
android:layout_width="match_parent"
21+
android:layout_height="match_parent">
22+
23+
<FrameLayout
24+
android:id="@+id/content"
25+
android:layout_width="match_parent"
26+
android:layout_height="match_parent" />
27+
28+
<LinearLayout
29+
android:layout_width="wrap_content"
30+
android:layout_height="wrap_content"
31+
android:layout_gravity="center|bottom"
32+
android:layout_marginBottom="@dimen/cat_tabs_standard_spacing"
33+
android:orientation="vertical">
34+
35+
<LinearLayout
36+
android:layout_width="wrap_content"
37+
android:layout_height="wrap_content"
38+
android:layout_gravity="center|bottom"
39+
android:layout_marginBottom="@dimen/cat_tabs_standard_spacing"
40+
android:orientation="horizontal"
41+
tools:ignore="ButtonStyle">
42+
<Button
43+
android:id="@+id/add_tab_button"
44+
android:layout_width="wrap_content"
45+
android:layout_height="wrap_content"
46+
android:layout_marginEnd="@dimen/cat_tabs_small_spacing"
47+
android:layout_marginRight="@dimen/cat_tabs_small_spacing"
48+
android:text="@string/cat_tabs_add_tab_button_label" />
49+
<Button
50+
android:id="@+id/remove_tab_button"
51+
android:layout_width="wrap_content"
52+
android:layout_height="wrap_content"
53+
android:text="@string/cat_tabs_remove_tab_button_label" />
54+
</LinearLayout>
55+
56+
57+
<LinearLayout
58+
android:layout_width="wrap_content"
59+
android:layout_height="wrap_content"
60+
android:layout_gravity="center|bottom"
61+
android:layout_marginBottom="@dimen/cat_tabs_standard_spacing"
62+
android:orientation="horizontal">
63+
<TextView
64+
android:layout_width="wrap_content"
65+
android:layout_height="wrap_content"
66+
android:layout_gravity="center_vertical"
67+
android:text="@string/cat_tabs_gravity_selector_label" />
68+
<RadioGroup
69+
android:layout_width="wrap_content"
70+
android:layout_height="wrap_content"
71+
android:checkedButton="@+id/tabs_gravity_start_button"
72+
android:orientation="horizontal">
73+
<RadioButton
74+
android:id="@+id/tabs_gravity_start_button"
75+
android:layout_width="wrap_content"
76+
android:layout_height="wrap_content"
77+
android:layout_marginEnd="@dimen/cat_tabs_small_spacing"
78+
android:layout_marginRight="@dimen/cat_tabs_small_spacing"
79+
android:text="@string/cat_tabs_gravity_start" />
80+
<RadioButton
81+
android:id="@+id/tabs_gravity_center_button"
82+
android:layout_width="wrap_content"
83+
android:layout_height="wrap_content"
84+
android:layout_marginEnd="@dimen/cat_tabs_small_spacing"
85+
android:layout_marginRight="@dimen/cat_tabs_small_spacing"
86+
android:text="@string/cat_tabs_gravity_center" />
87+
</RadioGroup>
88+
</LinearLayout>
89+
</LinearLayout>
90+
</FrameLayout>

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
<string name="cat_tabs_icon_switch_label">Show icons</string>
3131
<string name="cat_tabs_labels_switch_label">Show labels</string>
3232
<string name="cat_tabs_gravity_selector_label">Gravity</string>
33+
<string name="cat_tabs_gravity_start">Start</string>
3334
<string name="cat_tabs_gravity_fill">Fill</string>
3435
<string name="cat_tabs_gravity_center">Center</string>
3536
<string name="cat_tabs_inline_switch_label">Inline</string>

lib/java/com/google/android/material/tabs/TabLayout.java

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
import android.text.Layout;
7373
import android.text.TextUtils;
7474
import android.util.AttributeSet;
75+
import android.util.Log;
7576
import android.util.TypedValue;
7677
import android.view.Gravity;
7778
import android.view.LayoutInflater;
@@ -202,6 +203,8 @@ public class TabLayout extends HorizontalScrollView {
202203

203204
private static final String ACCESSIBILITY_CLASS_NAME = "androidx.appcompat.app.ActionBar.Tab";
204205

206+
private static final String LOG_TAG = "TabLayout";
207+
205208
/**
206209
* Scrollable tabs display a subset of tabs at any given moment, and can contain longer tab labels
207210
* and a larger number of tabs. They are best used for browsing contexts in touch interfaces when
@@ -276,11 +279,19 @@ public class TabLayout extends HorizontalScrollView {
276279
*/
277280
public static final int GRAVITY_CENTER = 1;
278281

282+
/**
283+
* Gravity used to lay out the tabs aligned to the start of the {@link TabLayout}.
284+
*
285+
* @see #setTabGravity(int)
286+
* @see #getTabGravity()
287+
*/
288+
public static final int GRAVITY_START = 1 << 1;
289+
279290
/** @hide */
280291
@RestrictTo(LIBRARY_GROUP)
281292
@IntDef(
282293
flag = true,
283-
value = {GRAVITY_FILL, GRAVITY_CENTER})
294+
value = {GRAVITY_FILL, GRAVITY_CENTER, GRAVITY_START})
284295
@Retention(RetentionPolicy.SOURCE)
285296
public @interface TabGravity {}
286297

@@ -428,7 +439,7 @@ public interface BaseOnTabSelectedListener<T extends Tab> {
428439

429440
private int contentInsetStart;
430441

431-
int tabGravity;
442+
@TabGravity int tabGravity;
432443
int tabIndicatorAnimationDuration;
433444
@TabIndicatorGravity int tabIndicatorGravity;
434445
@Mode int mode;
@@ -1647,7 +1658,7 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
16471658
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
16481659

16491660
if (getChildCount() == 1) {
1650-
// If we're in fixed mode then we need to make the tab strip is the same width as us
1661+
// If we're in fixed mode then we need to make sure the tab strip is the same width as us
16511662
// so we don't scroll
16521663
final View child = getChildAt(0);
16531664
boolean remeasure = false;
@@ -1857,16 +1868,41 @@ private void applyModeAndGravity() {
18571868
switch (mode) {
18581869
case MODE_AUTO:
18591870
case MODE_FIXED:
1871+
if (tabGravity == GRAVITY_START) {
1872+
Log.w(
1873+
LOG_TAG,
1874+
"GRAVITY_START is not supported with the current tab mode, GRAVITY_CENTER will be"
1875+
+ " used instead");
1876+
}
18601877
slidingTabIndicator.setGravity(Gravity.CENTER_HORIZONTAL);
18611878
break;
18621879
case MODE_SCROLLABLE:
1863-
slidingTabIndicator.setGravity(GravityCompat.START);
1880+
applyGravityForModeScrollable(tabGravity);
18641881
break;
18651882
}
18661883

18671884
updateTabViews(true);
18681885
}
18691886

1887+
private void applyGravityForModeScrollable(int tabGravity) {
1888+
switch (tabGravity) {
1889+
case GRAVITY_CENTER:
1890+
slidingTabIndicator.setGravity(Gravity.CENTER_HORIZONTAL);
1891+
break;
1892+
case GRAVITY_FILL:
1893+
Log.w(
1894+
LOG_TAG,
1895+
"MODE_SCROLLABLE + GRAVITY_FILL is not supported, GRAVITY_START will be used"
1896+
+ " instead");
1897+
// Fall through
1898+
case GRAVITY_START:
1899+
slidingTabIndicator.setGravity(GravityCompat.START);
1900+
break;
1901+
default:
1902+
break;
1903+
}
1904+
}
1905+
18701906
void updateTabViews(final boolean requestLayout) {
18711907
for (int i = 0; i < slidingTabIndicator.getChildCount(); i++) {
18721908
View child = slidingTabIndicator.getChildAt(i);

lib/java/com/google/android/material/tabs/res/values/attrs.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
<attr name="tabGravity">
6363
<enum name="fill" value="0"/>
6464
<enum name="center" value="1"/>
65+
<enum name="start" value="2"/>
6566
</attr>
6667
<!-- Whether to display tab labels horizontally inline with icons, or underneath icons. -->
6768
<attr name="tabInlineLabel" format="boolean"/>

0 commit comments

Comments
 (0)