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. --> + +