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.app.Fragment; 21 import android.app.FragmentManager; 22 import android.app.FragmentTransaction; 23 import android.os.Parcelable; 24 import android.support.v4.app.FragmentPagerAdapter; 25 import android.support.v4.view.PagerAdapter; 26 import android.util.Log; 27 import android.util.LruCache; 28 import android.view.View; 29 30 /** 31 * NOTE: This is a direct copy of {@link FragmentPagerAdapter} with four very important 32 * modifications. 33 * <p> 34 * <ol> 35 * <li>The method {@link #makeFragmentName(int, int)} is declared "protected" 36 * in our class. We need to be able to re-define the fragment's name according to data 37 * only available to sub-classes.</li> 38 * <li>The method {@link #isViewFromObject(View, Object)} has been reimplemented to search 39 * the entire view hierarchy for the given view.</li> 40 * <li>In method {@link #destroyItem(View, int, Object)}, the fragment is detached and 41 * added to a cache. If the fragment is evicted from the cache, it will be deleted. 42 * An album may contain thousands of photos and we want to avoid having thousands of 43 * fragments.</li> 44 * </ol> 45 */ 46 public abstract class BaseFragmentPagerAdapter extends PagerAdapter { 47 /** The default size of {@link #mFragmentCache} */ 48 private static final int DEFAULT_CACHE_SIZE = 5; 49 private static final String TAG = "FragmentPagerAdapter"; 50 private static final boolean DEBUG = false; 51 52 private final FragmentManager mFragmentManager; 53 private FragmentTransaction mCurTransaction = null; 54 private Fragment mCurrentPrimaryItem = null; 55 /** A cache to store detached fragments before they are removed */ 56 private LruCache<String, Fragment> mFragmentCache = new FragmentCache(DEFAULT_CACHE_SIZE); 57 BaseFragmentPagerAdapter(FragmentManager fm)58 public BaseFragmentPagerAdapter(FragmentManager fm) { 59 mFragmentManager = fm; 60 } 61 62 /** 63 * Return the Fragment associated with a specified position. 64 */ getItem(int position)65 public abstract Fragment getItem(int position); 66 67 @Override startUpdate(View container)68 public void startUpdate(View container) { 69 } 70 71 @Override instantiateItem(View container, int position)72 public Object instantiateItem(View container, int position) { 73 if (mCurTransaction == null) { 74 mCurTransaction = mFragmentManager.beginTransaction(); 75 } 76 77 // Do we already have this fragment? 78 String name = makeFragmentName(container.getId(), position); 79 80 // Remove item from the cache 81 mFragmentCache.remove(name); 82 83 Fragment fragment = mFragmentManager.findFragmentByTag(name); 84 if (fragment != null) { 85 if (DEBUG) Log.v(TAG, "Attaching item #" + position + ": f=" + fragment); 86 mCurTransaction.attach(fragment); 87 } else { 88 fragment = getItem(position); 89 if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment); 90 mCurTransaction.add(container.getId(), fragment, 91 makeFragmentName(container.getId(), position)); 92 } 93 if (fragment != mCurrentPrimaryItem) { 94 fragment.setMenuVisibility(false); 95 } 96 97 return fragment; 98 } 99 100 @Override destroyItem(View container, int position, Object object)101 public void destroyItem(View container, int position, Object object) { 102 if (mCurTransaction == null) { 103 mCurTransaction = mFragmentManager.beginTransaction(); 104 } 105 if (DEBUG) Log.v(TAG, "Detaching item #" + position + ": f=" + object 106 + " v=" + ((Fragment)object).getView()); 107 108 Fragment fragment = (Fragment) object; 109 String name = fragment.getTag(); 110 if (name == null) { 111 // We prefer to get the name directly from the fragment, but, if the fragment is 112 // detached before the add transaction is committed, this could be 'null'. In 113 // that case, generate a name so we can still cache the fragment. 114 name = makeFragmentName(container.getId(), position); 115 } 116 117 mFragmentCache.put(name, fragment); 118 mCurTransaction.detach(fragment); 119 } 120 121 @Override setPrimaryItem(View container, int position, Object object)122 public void setPrimaryItem(View container, int position, Object object) { 123 Fragment fragment = (Fragment) object; 124 if (fragment != mCurrentPrimaryItem) { 125 if (mCurrentPrimaryItem != null) { 126 mCurrentPrimaryItem.setMenuVisibility(false); 127 } 128 if (fragment != null) { 129 fragment.setMenuVisibility(true); 130 } 131 mCurrentPrimaryItem = fragment; 132 } 133 134 } 135 136 @Override finishUpdate(View container)137 public void finishUpdate(View container) { 138 if (mCurTransaction != null) { 139 mCurTransaction.commitAllowingStateLoss(); 140 mCurTransaction = null; 141 mFragmentManager.executePendingTransactions(); 142 } 143 } 144 145 @Override isViewFromObject(View view, Object object)146 public boolean isViewFromObject(View view, Object object) { 147 // Ascend the tree to determine if the view is a child of the fragment 148 View root = ((Fragment) object).getView(); 149 for (Object v = view; v instanceof View; v = ((View) v).getParent()) { 150 if (v == root) { 151 return true; 152 } 153 } 154 return false; 155 } 156 157 @Override saveState()158 public Parcelable saveState() { 159 return null; 160 } 161 162 @Override restoreState(Parcelable state, ClassLoader loader)163 public void restoreState(Parcelable state, ClassLoader loader) { 164 } 165 166 /** Creates a name for the fragment */ makeFragmentName(int viewId, int index)167 protected String makeFragmentName(int viewId, int index) { 168 return "android:switcher:" + viewId + ":" + index; 169 } 170 171 /** 172 * A cache of detached fragments. 173 */ 174 private class FragmentCache extends LruCache<String, Fragment> { FragmentCache(int size)175 public FragmentCache(int size) { 176 super(size); 177 } 178 179 @Override entryRemoved(boolean evicted, String key, Fragment oldValue, Fragment newValue)180 protected void entryRemoved(boolean evicted, String key, 181 Fragment oldValue, Fragment newValue) { 182 // remove the fragment if it's evicted OR it's replaced by a new fragment 183 if (evicted || (newValue != null && oldValue != newValue)) { 184 mCurTransaction.remove(oldValue); 185 } 186 } 187 } 188 } 189