1 /* 2 * Copyright (C) 2011 Google Inc. 3 * Licensed to The Android Open Source Project. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package com.android.ex.photo.adapters; 19 20 import android.os.Parcelable; 21 import androidx.fragment.app.Fragment; 22 import androidx.fragment.app.FragmentManager; 23 import androidx.fragment.app.FragmentTransaction; 24 import androidx.collection.LruCache; 25 import androidx.viewpager.widget.PagerAdapter; 26 import android.util.Log; 27 import android.view.View; 28 29 /** 30 * NOTE: This is a direct copy of {@link androidx.fragment.app.FragmentPagerAdapter} 31 * with four very important modifications. 32 * <p> 33 * <ol> 34 * <li>The method {@link #makeFragmentName(int, int)} is declared "protected" 35 * in our class. We need to be able to re-define the fragment's name according to data 36 * only available to sub-classes.</li> 37 * <li>The method {@link #isViewFromObject(View, Object)} has been reimplemented to search 38 * the entire view hierarchy for the given view.</li> 39 * <li>In method {@link #destroyItem(View, int, Object)}, the fragment is detached and 40 * added to a cache. If the fragment is evicted from the cache, it will be deleted. 41 * An album may contain thousands of photos and we want to avoid having thousands of 42 * fragments.</li> 43 * </ol> 44 */ 45 public abstract class BaseFragmentPagerAdapter extends PagerAdapter { 46 /** The default size of {@link #mFragmentCache} */ 47 private static final int DEFAULT_CACHE_SIZE = 5; 48 private static final String TAG = "FragmentPagerAdapter"; 49 private static final boolean DEBUG = false; 50 51 private final FragmentManager mFragmentManager; 52 private FragmentTransaction mCurTransaction = null; 53 private Fragment mCurrentPrimaryItem = null; 54 /** A cache to store detached fragments before they are removed */ 55 private LruCache<String, Fragment> mFragmentCache = new FragmentCache(DEFAULT_CACHE_SIZE); 56 BaseFragmentPagerAdapter(androidx.fragment.app.FragmentManager fm)57 public BaseFragmentPagerAdapter(androidx.fragment.app.FragmentManager fm) { 58 mFragmentManager = fm; 59 } 60 61 /** 62 * Return the Fragment associated with a specified position. 63 */ getItem(int position)64 public abstract Fragment getItem(int position); 65 66 @Override startUpdate(View container)67 public void startUpdate(View container) { 68 } 69 70 @Override instantiateItem(View container, int position)71 public Object instantiateItem(View container, int position) { 72 if (mCurTransaction == null) { 73 mCurTransaction = mFragmentManager.beginTransaction(); 74 } 75 76 // Do we already have this fragment? 77 String name = makeFragmentName(container.getId(), position); 78 79 // Remove item from the cache 80 mFragmentCache.remove(name); 81 82 Fragment fragment = mFragmentManager.findFragmentByTag(name); 83 if (fragment != null) { 84 if (DEBUG) Log.v(TAG, "Attaching item #" + position + ": f=" + fragment); 85 mCurTransaction.attach(fragment); 86 } else { 87 fragment = getItem(position); 88 if(fragment == null) { 89 if (DEBUG) Log.e(TAG, "NPE workaround for getItem(). See b/7103023"); 90 return null; 91 } 92 if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment); 93 mCurTransaction.add(container.getId(), fragment, 94 makeFragmentName(container.getId(), position)); 95 } 96 if (fragment != mCurrentPrimaryItem) { 97 fragment.setMenuVisibility(false); 98 } 99 100 return fragment; 101 } 102 103 @Override destroyItem(View container, int position, Object object)104 public void destroyItem(View container, int position, Object object) { 105 if (mCurTransaction == null) { 106 mCurTransaction = mFragmentManager.beginTransaction(); 107 } 108 if (DEBUG) Log.v(TAG, "Detaching item #" + position + ": f=" + object 109 + " v=" + ((Fragment)object).getView()); 110 111 Fragment fragment = (Fragment) object; 112 String name = fragment.getTag(); 113 if (name == null) { 114 // We prefer to get the name directly from the fragment, but, if the fragment is 115 // detached before the add transaction is committed, this could be 'null'. In 116 // that case, generate a name so we can still cache the fragment. 117 name = makeFragmentName(container.getId(), position); 118 } 119 120 mFragmentCache.put(name, fragment); 121 mCurTransaction.detach(fragment); 122 } 123 124 @Override setPrimaryItem(View container, int position, Object object)125 public void setPrimaryItem(View container, int position, Object object) { 126 Fragment fragment = (Fragment) object; 127 if (fragment != mCurrentPrimaryItem) { 128 if (mCurrentPrimaryItem != null) { 129 mCurrentPrimaryItem.setMenuVisibility(false); 130 } 131 if (fragment != null) { 132 fragment.setMenuVisibility(true); 133 } 134 mCurrentPrimaryItem = fragment; 135 } 136 137 } 138 139 @Override finishUpdate(View container)140 public void finishUpdate(View container) { 141 if (mCurTransaction != null && !mFragmentManager.isDestroyed()) { 142 mCurTransaction.commitAllowingStateLoss(); 143 mCurTransaction = null; 144 mFragmentManager.executePendingTransactions(); 145 } 146 } 147 148 @Override isViewFromObject(View view, Object object)149 public boolean isViewFromObject(View view, Object object) { 150 // Ascend the tree to determine if the view is a child of the fragment 151 View root = ((Fragment) object).getView(); 152 for (Object v = view; v instanceof View; v = ((View) v).getParent()) { 153 if (v == root) { 154 return true; 155 } 156 } 157 return false; 158 } 159 160 @Override saveState()161 public Parcelable saveState() { 162 return null; 163 } 164 165 @Override restoreState(Parcelable state, ClassLoader loader)166 public void restoreState(Parcelable state, ClassLoader loader) { 167 } 168 169 /** Creates a name for the fragment */ makeFragmentName(int viewId, int index)170 protected String makeFragmentName(int viewId, int index) { 171 return "android:switcher:" + viewId + ":" + index; 172 } 173 174 /** 175 * A cache of detached fragments. 176 */ 177 private class FragmentCache extends LruCache<String, Fragment> { FragmentCache(int size)178 public FragmentCache(int size) { 179 super(size); 180 } 181 182 @Override entryRemoved(boolean evicted, String key, Fragment oldValue, Fragment newValue)183 protected void entryRemoved(boolean evicted, String key, 184 Fragment oldValue, Fragment newValue) { 185 // remove the fragment if it's evicted OR it's replaced by a new fragment 186 if (evicted || (newValue != null && oldValue != newValue)) { 187 mCurTransaction.remove(oldValue); 188 } 189 } 190 } 191 } 192