diff --git a/lib/java/com/google/android/material/tabs/ElasticTabIndicatorInterpolator.java b/lib/java/com/google/android/material/tabs/ElasticTabIndicatorInterpolator.java index 499f52cd840..e43f8d5b8c1 100644 --- a/lib/java/com/google/android/material/tabs/ElasticTabIndicatorInterpolator.java +++ b/lib/java/com/google/android/material/tabs/ElasticTabIndicatorInterpolator.java @@ -44,7 +44,7 @@ private static float accInterp(@FloatRange(from = 0.0, to = 1.0) float fraction) } @Override - void setIndicatorBoundsForOffset( + void updateIndicatorForOffset( TabLayout tabLayout, View startTitle, View endTitle, diff --git a/lib/java/com/google/android/material/tabs/FadeTabIndicatorInterpolator.java b/lib/java/com/google/android/material/tabs/FadeTabIndicatorInterpolator.java new file mode 100644 index 00000000000..ba0d45ad406 --- /dev/null +++ b/lib/java/com/google/android/material/tabs/FadeTabIndicatorInterpolator.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2021 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.tabs; + +import static com.google.android.material.animation.AnimationUtils.lerp; + +import android.graphics.RectF; +import android.graphics.drawable.Drawable; +import android.view.View; +import androidx.annotation.NonNull; + +/** + * An implementation of {@link TabIndicatorInterpolator} that sequentially fades out the selected + * tab indicator from the current destination and fades it back in at its new destination. + */ +class FadeTabIndicatorInterpolator extends TabIndicatorInterpolator { + + // When the indicator will disappear from the current tab and begin to reappear at the newly + // selected tab. + private static final float FADE_THRESHOLD = 0.5F; + + @Override + void updateIndicatorForOffset( + TabLayout tabLayout, + View startTitle, + View endTitle, + float offset, + @NonNull Drawable indicator) { + View tab = offset < FADE_THRESHOLD ? startTitle : endTitle; + RectF bounds = calculateIndicatorWidthForTab(tabLayout, tab); + float alpha = offset < FADE_THRESHOLD + ? lerp(1F, 0F, 0F, FADE_THRESHOLD, offset) + : lerp(0F, 1F, FADE_THRESHOLD, 1F, offset); + + indicator.setBounds( + (int) bounds.left, + indicator.getBounds().top, + (int) bounds.right, + indicator.getBounds().bottom + ); + indicator.setAlpha((int) (alpha * 255F)); + } +} diff --git a/lib/java/com/google/android/material/tabs/TabIndicatorInterpolator.java b/lib/java/com/google/android/material/tabs/TabIndicatorInterpolator.java index 5e7bb10c4e8..52167a768a4 100644 --- a/lib/java/com/google/android/material/tabs/TabIndicatorInterpolator.java +++ b/lib/java/com/google/android/material/tabs/TabIndicatorInterpolator.java @@ -37,9 +37,9 @@ * TabLayout#isTabIndicatorFullWidth()} and linearly move the indicator between tabs. * *
Subclasses can override {@link #setIndicatorBoundsForTab(TabLayout, View, Drawable)} and - * {@link #setIndicatorBoundsForOffset(TabLayout, View, View, float, Drawable)} (TabLayout, View, - * View, float, Drawable)} to define how the indicator should be drawn for a single tab or at any - * point between two tabs. + * {@link #updateIndicatorForOffset(TabLayout, View, View, float, Drawable)} (TabLayout, View, View, + * float, Drawable)} to define how the indicator should be drawn for a single tab or at any point + * between two tabs. * *
Additionally, subclasses can use the provided helpers {@link
* #calculateIndicatorWidthForTab(TabLayout, View)} and {@link
@@ -152,7 +152,7 @@ void setIndicatorBoundsForTab(TabLayout tabLayout, View tab, @NonNull Drawable i
* @param indicator The drawable to be drawn to indicate the selected tab. Update the drawable's
* bounds, color, etc as {@code offset} changes to show the indicator in the correct position.
*/
- void setIndicatorBoundsForOffset(
+ void updateIndicatorForOffset(
TabLayout tabLayout,
View startTitle,
View endTitle,
diff --git a/lib/java/com/google/android/material/tabs/TabLayout.java b/lib/java/com/google/android/material/tabs/TabLayout.java
index c8c1e18b900..3d027db578a 100644
--- a/lib/java/com/google/android/material/tabs/TabLayout.java
+++ b/lib/java/com/google/android/material/tabs/TabLayout.java
@@ -383,9 +383,23 @@ public class TabLayout extends HorizontalScrollView {
*/
public static final int INDICATOR_ANIMATION_MODE_ELASTIC = 1;
+ /**
+ * Indicator animation mode used to switch the selected tab indicator from one tab to another
+ * by sequentially fading it out from the current destination and in at its new destination.
+ *
+ * @see #setTabIndicatorAnimationMode(int)
+ * @see #getTabIndicatorAnimationMode()
+ * @attr ref com.google.android.material.R.styleable#TabLayout_tabIndicatorAnimationMode
+ */
+ public static final int INDICATOR_ANIMATION_MODE_FADE = 2;
+
/** @hide */
@RestrictTo(LIBRARY_GROUP)
- @IntDef(value = {INDICATOR_ANIMATION_MODE_LINEAR, INDICATOR_ANIMATION_MODE_ELASTIC})
+ @IntDef(value = {
+ INDICATOR_ANIMATION_MODE_LINEAR,
+ INDICATOR_ANIMATION_MODE_ELASTIC,
+ INDICATOR_ANIMATION_MODE_FADE
+ })
@Retention(RetentionPolicy.SOURCE)
public @interface TabIndicatorAnimationMode {}
@@ -1071,6 +1085,9 @@ public void setTabIndicatorAnimationMode(
case INDICATOR_ANIMATION_MODE_ELASTIC:
this.tabIndicatorInterpolator = new ElasticTabIndicatorInterpolator();
break;
+ case INDICATOR_ANIMATION_MODE_FADE:
+ this.tabIndicatorInterpolator = new FadeTabIndicatorInterpolator();
+ break;
default:
throw new IllegalArgumentException(
tabIndicatorAnimationMode + " is not a valid TabIndicatorAnimationMode");
@@ -3136,7 +3153,7 @@ private void jumpIndicatorToSelectedPosition() {
private void tweenIndicatorPosition(View startTitle, View endTitle, float fraction) {
boolean hasVisibleTitle = startTitle != null && startTitle.getWidth() > 0;
if (hasVisibleTitle) {
- tabIndicatorInterpolator.setIndicatorBoundsForOffset(
+ tabIndicatorInterpolator.updateIndicatorForOffset(
TabLayout.this, startTitle, endTitle, fraction, tabSelectedIndicator);
} else {
// Hide the indicator by setting the drawable's width to 0 and off screen.
diff --git a/lib/java/com/google/android/material/tabs/res/values/attrs.xml b/lib/java/com/google/android/material/tabs/res/values/attrs.xml
index 03505b85a1c..e3621918a1a 100644
--- a/lib/java/com/google/android/material/tabs/res/values/attrs.xml
+++ b/lib/java/com/google/android/material/tabs/res/values/attrs.xml
@@ -66,6 +66,10 @@
This causes the indicator to look like it stretches between destinations
an then shrinks back down to fit the size of it's target tab. -->