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