• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.providers.media.photopicker.ui;
17 
18 import android.os.Bundle;
19 import android.util.Log;
20 import android.view.LayoutInflater;
21 import android.view.View;
22 import android.view.ViewGroup;
23 
24 import androidx.annotation.NonNull;
25 import androidx.annotation.Nullable;
26 import androidx.fragment.app.Fragment;
27 import androidx.fragment.app.FragmentManager;
28 import androidx.fragment.app.FragmentTransaction;
29 import androidx.recyclerview.widget.RecyclerView;
30 import androidx.viewpager2.widget.CompositePageTransformer;
31 import androidx.viewpager2.widget.ViewPager2;
32 
33 import com.android.providers.media.R;
34 
35 import com.google.android.material.bottomsheet.BottomSheetBehavior;
36 import com.google.android.material.tabs.TabLayout;
37 import com.google.android.material.tabs.TabLayoutMediator;
38 
39 import java.lang.ref.WeakReference;
40 import java.lang.reflect.Field;
41 
42 /**
43  * The tab container fragment
44  */
45 public class TabContainerFragment extends Fragment {
46     private static final String TAG = "TabContainerFragment";
47     private static final int PHOTOS_TAB_POSITION = 0;
48     private static final int ALBUMS_TAB_POSITION = 1;
49 
50     private TabContainerAdapter mTabContainerAdapter;
51     private TabLayoutMediator mTabLayoutMediator;
52     private ViewPager2 mViewPager;
53 
54     @Override
55     @NonNull
onCreateView(@onNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)56     public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
57             Bundle savedInstanceState) {
58         super.onCreateView(inflater, container, savedInstanceState);
59         return inflater.inflate(R.layout.fragment_picker_tab_container, container, false);
60     }
61 
62     @Override
onViewCreated(@onNull View view, @Nullable Bundle savedInstanceState)63     public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
64         super.onViewCreated(view, savedInstanceState);
65         mTabContainerAdapter = new TabContainerAdapter(/* fragment */ this);
66         mViewPager = view.findViewById(R.id.picker_tab_viewpager);
67         mViewPager.setAdapter(mTabContainerAdapter);
68 
69         // If the ViewPager2 has more than one page with BottomSheetBehavior, the scrolled view
70         // (e.g. RecyclerView) on the second page can't be scrolled. The workaround is to update
71         // nestedScrollingChildRef to the scrolled view on the current page. b/145334244
72         Field fieldNestedScrollingChildRef = null;
73         try {
74             fieldNestedScrollingChildRef = BottomSheetBehavior.class.getDeclaredField(
75                     "nestedScrollingChildRef");
76             fieldNestedScrollingChildRef.setAccessible(true);
77         } catch (NoSuchFieldException ex) {
78             Log.d(TAG, "Can't get the field nestedScrollingChildRef from BottomSheetBehavior", ex);
79         }
80 
81         final BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(
82                 getActivity().findViewById(R.id.bottom_sheet));
83 
84         final CompositePageTransformer compositePageTransformer = new CompositePageTransformer();
85         mViewPager.setPageTransformer(compositePageTransformer);
86         compositePageTransformer.addTransformer(new AnimationPageTransformer());
87         compositePageTransformer.addTransformer(
88                 new NestedScrollPageTransformer(bottomSheetBehavior, fieldNestedScrollingChildRef));
89 
90         // The BottomSheetBehavior looks for the first nested scrolling child to determine how to
91         // handle nested scrolls, it finds the inner recyclerView on ViewPager2 in this case. So, we
92         // need to work around it by setNestedScrollingEnabled false. b/145351873
93         final View firstChild = mViewPager.getChildAt(0);
94         if (firstChild instanceof RecyclerView) {
95             mViewPager.getChildAt(0).setNestedScrollingEnabled(false);
96         }
97 
98         final TabLayout tabLayout = getActivity().findViewById(R.id.tab_layout);
99         mTabLayoutMediator = new TabLayoutMediator(tabLayout, mViewPager, (tab, pos) -> {
100             if (pos == PHOTOS_TAB_POSITION) {
101                 tab.setText(R.string.picker_photos);
102             } else if (pos == ALBUMS_TAB_POSITION) {
103                 tab.setText(R.string.picker_albums);
104             }
105         });
106         mTabLayoutMediator.attach();
107         // TabLayout only supports colorDrawable in xml. And if we set the color in the drawable by
108         // setSelectedTabIndicator method, it doesn't apply the color. So, we set color in xml and
109         // set the drawable for the shape here.
110         tabLayout.setSelectedTabIndicator(R.drawable.picker_tab_indicator);
111     }
112 
113     @Override
onDestroyView()114     public void onDestroyView() {
115         mTabLayoutMediator.detach();
116         super.onDestroyView();
117     }
118 
119     /**
120      * Create the fragment and add it into the FragmentManager
121      *
122      * @param fm the fragment manager
123      */
show(FragmentManager fm)124     public static void show(FragmentManager fm) {
125         final FragmentTransaction ft = fm.beginTransaction();
126         final TabContainerFragment fragment = new TabContainerFragment();
127         ft.replace(R.id.fragment_container, fragment, TAG);
128         ft.commitAllowingStateLoss();
129     }
130 
131     private static class AnimationPageTransformer implements ViewPager2.PageTransformer {
132 
133         @Override
transformPage(@onNull View view, float pos)134         public void transformPage(@NonNull View view, float pos) {
135             view.setAlpha(1.0f - Math.abs(pos));
136         }
137     }
138 
139     private static class NestedScrollPageTransformer implements ViewPager2.PageTransformer {
140         private Field mFieldNestedScrollingChildRef;
141         private BottomSheetBehavior mBottomSheetBehavior;
142 
NestedScrollPageTransformer(BottomSheetBehavior bottomSheetBehavior, Field field)143         public NestedScrollPageTransformer(BottomSheetBehavior bottomSheetBehavior, Field field) {
144             mBottomSheetBehavior = bottomSheetBehavior;
145             mFieldNestedScrollingChildRef = field;
146         }
147 
148         @Override
transformPage(@onNull View view, float pos)149         public void transformPage(@NonNull View view, float pos) {
150             // If pos != 0, it is not in current page, don't update the nested scrolling child
151             // reference.
152             if (pos != 0 || mFieldNestedScrollingChildRef == null) {
153                 return;
154             }
155 
156             try {
157                 final View childView = view.findViewById(R.id.picker_tab_recyclerview);
158                 if (childView != null) {
159                     mFieldNestedScrollingChildRef.set(mBottomSheetBehavior,
160                             new WeakReference(childView));
161                 }
162             } catch (IllegalAccessException ex) {
163                 Log.d(TAG, "Set nestedScrollingChildRef to BottomSheetBehavior fail", ex);
164             }
165         }
166     }
167 }
168