Add a custom Tab selection effect for Viewpager swiping
Mock: https://docs.google.com/presentation/d/1o1CGLwwpega6NlFVvkgIEnxiYsn2qSnTX4axCEXBkVg/edit?ts=60c12e95&resourcekey=0-gwPJERFbm4jFJnCk8ZG0Vg#slide=id.ge040080db2_0_54
Before: https://drive.google.com/file/d/1L-7g_8PiSEKvquuJvntCVoSZR7-CYxI9/view?usp=sharing
After: https://drive.google.com/file/d/1_iFygqxfVc_tO4upX0RF0solwl_3-IYG/view?usp=sharing
Bug: 191388121
Test: Manually
Change-Id: Ieb75b69196283cbe45879230d895ebf02ec5cc71
diff --git a/Android.bp b/Android.bp
index 871e193..7e55969 100644
--- a/Android.bp
+++ b/Android.bp
@@ -27,11 +27,12 @@
static_libs: [
"androidx.appcompat_appcompat",
"androidx.cardview_cardview",
+ "androidx-constraintlayout_constraintlayout",
"androidx.exifinterface_exifinterface",
"androidx.lifecycle_lifecycle-runtime-ktx",
"androidx.recyclerview_recyclerview",
"androidx.slice_slice-view",
- "androidx-constraintlayout_constraintlayout",
+ "androidx.viewpager2_viewpager2",
"com.google.android.material_material",
"glide-prebuilt",
"kotlinx-coroutines-android",
diff --git a/src/com/android/wallpaper/widget/SeparatedTabLayout.java b/src/com/android/wallpaper/widget/SeparatedTabLayout.java
index 793403f..8642485 100644
--- a/src/com/android/wallpaper/widget/SeparatedTabLayout.java
+++ b/src/com/android/wallpaper/widget/SeparatedTabLayout.java
@@ -1,17 +1,31 @@
package com.android.wallpaper.widget;
+import static androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_DRAGGING;
+import static androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_IDLE;
+import static androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_SETTLING;
+
import android.content.Context;
import android.util.AttributeSet;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.viewpager2.widget.ViewPager2;
+import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback;
import com.android.wallpaper.R;
import com.google.android.material.tabs.TabLayout;
-/** Custom {@link TabLayout} for separated tabs. */
-public class SeparatedTabLayout extends TabLayout {
+import java.lang.ref.WeakReference;
+
+/**
+ * Custom {@link TabLayout} for separated tabs.
+ *
+ * <p>Don't use {@code TabLayoutMediator} for the tab layout, which binds the tab scrolling
+ * animation that is unwanted for the separated tab design. Uses {@link
+ * SeparatedTabLayout#setViewPager} to bind a {@link ViewPager2} to use the proper tab effect.
+ */
+public final class SeparatedTabLayout extends TabLayout {
public SeparatedTabLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
@@ -24,4 +38,81 @@
tab.view.setBackgroundResource(R.drawable.separated_tabs_ripple_mask);
return tab;
}
+
+ /** Binds the given {@code viewPager} to the {@link SeparatedTabLayout}. */
+ public void setViewPager(ViewPager2 viewPager) {
+ viewPager.registerOnPageChangeCallback(new SeparatedTabLayoutOnPageChangeCallback(this));
+ addOnTabSelectedListener(new SeparatedTabLayoutOnTabSelectedListener(viewPager));
+ }
+
+ private static class SeparatedTabLayoutOnTabSelectedListener implements
+ OnTabSelectedListener {
+ private final WeakReference<ViewPager2> mViewPagerRef;
+
+ private SeparatedTabLayoutOnTabSelectedListener(ViewPager2 viewPager) {
+ mViewPagerRef = new WeakReference<>(viewPager);
+ }
+
+ @Override
+ public void onTabSelected(Tab tab) {
+ ViewPager2 viewPager = mViewPagerRef.get();
+ if (viewPager != null && viewPager.getCurrentItem() != tab.getPosition()) {
+ viewPager.setCurrentItem(tab.getPosition());
+ }
+ }
+
+ @Override
+ public void onTabUnselected(Tab tab) {}
+
+ @Override
+ public void onTabReselected(Tab tab) {}
+ }
+
+ private static class SeparatedTabLayoutOnPageChangeCallback extends OnPageChangeCallback {
+ private final WeakReference<TabLayout> mTabLayoutRef;
+ private int mPreviousScrollState = SCROLL_STATE_IDLE;
+ private int mScrollState = SCROLL_STATE_IDLE;
+
+ private SeparatedTabLayoutOnPageChangeCallback(TabLayout tabLayout) {
+ mTabLayoutRef = new WeakReference<>(tabLayout);
+ }
+
+ @Override
+ public void onPageSelected(final int position) {
+ if (isUserDragging()) {
+ // Don't update tab position here, wait for page scrolling done to update the tabs.
+ return;
+ }
+ // ViewPager2#setCurrentItem would run into here.
+ updateTabPositionIfNeeded(position);
+ }
+
+ @Override
+ public void onPageScrollStateChanged(final int state) {
+ mPreviousScrollState = mScrollState;
+ mScrollState = state;
+ }
+
+ @Override
+ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ // Update the tab when the scrolling page is full displayed and is user dragging case.
+ if (positionOffset == 0f && isUserDragging()) {
+ updateTabPositionIfNeeded(position);
+ }
+ }
+
+ private boolean isUserDragging() {
+ return mPreviousScrollState == SCROLL_STATE_DRAGGING
+ && mScrollState == SCROLL_STATE_SETTLING;
+ }
+
+ private void updateTabPositionIfNeeded(int position) {
+ TabLayout tabLayout = mTabLayoutRef.get();
+ if (tabLayout != null
+ && tabLayout.getSelectedTabPosition() != position
+ && position < tabLayout.getTabCount()) {
+ tabLayout.selectTab(tabLayout.getTabAt(position), /* updateIndicator= */ true);
+ }
+ }
+ }
}