1 package com.android.wallpaper.widget;
2 
3 import static androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_DRAGGING;
4 import static androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_IDLE;
5 import static androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_SETTLING;
6 
7 import android.content.Context;
8 import android.util.AttributeSet;
9 
10 import androidx.annotation.NonNull;
11 import androidx.annotation.Nullable;
12 import androidx.viewpager2.widget.ViewPager2;
13 import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback;
14 
15 import com.android.wallpaper.R;
16 
17 import com.google.android.material.tabs.TabLayout;
18 
19 import java.lang.ref.WeakReference;
20 
21 /**
22  * Custom {@link TabLayout} for separated tabs.
23  *
24  * <p>Don't use {@code TabLayoutMediator} for the tab layout, which binds the tab scrolling
25  * animation that is unwanted for the separated tab design. Uses {@link
26  * SeparatedTabLayout#setViewPager} to bind a {@link ViewPager2} to use the proper tab effect.
27  */
28 public final class SeparatedTabLayout extends TabLayout {
29 
SeparatedTabLayout(Context context, @Nullable AttributeSet attrs)30     public SeparatedTabLayout(Context context, @Nullable AttributeSet attrs) {
31         super(context, attrs);
32     }
33 
34     @Override
35     @NonNull
newTab()36     public Tab newTab() {
37         Tab tab = super.newTab();
38         tab.view.setBackgroundResource(R.drawable.separated_tabs_ripple_mask);
39         return tab;
40     }
41 
42     /** Binds the given {@code viewPager} to the {@link SeparatedTabLayout}. */
setViewPager(ViewPager2 viewPager)43     public void setViewPager(ViewPager2 viewPager) {
44         viewPager.registerOnPageChangeCallback(new SeparatedTabLayoutOnPageChangeCallback(this));
45         addOnTabSelectedListener(new SeparatedTabLayoutOnTabSelectedListener(viewPager));
46     }
47 
48     private static class SeparatedTabLayoutOnTabSelectedListener implements
49             OnTabSelectedListener {
50         private final WeakReference<ViewPager2> mViewPagerRef;
51 
SeparatedTabLayoutOnTabSelectedListener(ViewPager2 viewPager)52         private SeparatedTabLayoutOnTabSelectedListener(ViewPager2 viewPager) {
53             mViewPagerRef = new WeakReference<>(viewPager);
54         }
55 
56         @Override
onTabSelected(Tab tab)57         public void onTabSelected(Tab tab) {
58             ViewPager2 viewPager = mViewPagerRef.get();
59             if (viewPager != null && viewPager.getCurrentItem() != tab.getPosition()) {
60                 viewPager.setCurrentItem(tab.getPosition());
61             }
62         }
63 
64         @Override
onTabUnselected(Tab tab)65         public void onTabUnselected(Tab tab) {}
66 
67         @Override
onTabReselected(Tab tab)68         public void onTabReselected(Tab tab) {}
69     }
70 
71     private static class SeparatedTabLayoutOnPageChangeCallback extends OnPageChangeCallback {
72         private final WeakReference<TabLayout> mTabLayoutRef;
73         private int mPreviousScrollState = SCROLL_STATE_IDLE;
74         private int mScrollState = SCROLL_STATE_IDLE;
75 
SeparatedTabLayoutOnPageChangeCallback(TabLayout tabLayout)76         private SeparatedTabLayoutOnPageChangeCallback(TabLayout tabLayout) {
77             mTabLayoutRef = new WeakReference<>(tabLayout);
78         }
79 
80         @Override
onPageSelected(final int position)81         public void onPageSelected(final int position) {
82             if (isUserDragging()) {
83                 // Don't update tab position here, wait for page scrolling done to update the tabs.
84                 return;
85             }
86             // ViewPager2#setCurrentItem would run into here.
87             updateTabPositionIfNeeded(position);
88         }
89 
90         @Override
onPageScrollStateChanged(final int state)91         public void onPageScrollStateChanged(final int state) {
92             mPreviousScrollState = mScrollState;
93             mScrollState = state;
94         }
95 
96         @Override
onPageScrolled(int position, float positionOffset, int positionOffsetPixels)97         public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
98             // Update the tab when the scrolling page is full displayed and is user dragging case.
99             if (positionOffset == 0f && isUserDragging()) {
100                 updateTabPositionIfNeeded(position);
101             }
102         }
103 
isUserDragging()104         private boolean isUserDragging() {
105             return mPreviousScrollState == SCROLL_STATE_DRAGGING
106                     && mScrollState == SCROLL_STATE_SETTLING;
107         }
108 
updateTabPositionIfNeeded(int position)109         private void updateTabPositionIfNeeded(int position) {
110             TabLayout tabLayout = mTabLayoutRef.get();
111             if (tabLayout != null
112                     && tabLayout.getSelectedTabPosition() != position
113                     && position < tabLayout.getTabCount()) {
114                 tabLayout.selectTab(tabLayout.getTabAt(position), /* updateIndicator= */ true);
115             }
116         }
117     }
118 }
119