Home | History | Annotate | Download | only in adapters
      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 android.support.v4.app.Fragment;
     22 import android.support.v4.app.FragmentManager;
     23 import android.support.v4.app.FragmentTransaction;
     24 import android.support.v4.util.LruCache;
     25 import android.support.v4.view.PagerAdapter;
     26 import android.util.Log;
     27 import android.view.View;
     28 
     29 /**
     30  * NOTE: This is a direct copy of {@link android.support.v4.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 
     57     public BaseFragmentPagerAdapter(android.support.v4.app.FragmentManager fm) {
     58         mFragmentManager = fm;
     59     }
     60 
     61     /**
     62      * Return the Fragment associated with a specified position.
     63      */
     64     public abstract Fragment getItem(int position);
     65 
     66     @Override
     67     public void startUpdate(View container) {
     68     }
     69 
     70     @Override
     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
    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
    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
    140     public void finishUpdate(View container) {
    141         if (mCurTransaction != null) {
    142             mCurTransaction.commitAllowingStateLoss();
    143             mCurTransaction = null;
    144             mFragmentManager.executePendingTransactions();
    145         }
    146     }
    147 
    148     @Override
    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
    161     public Parcelable saveState() {
    162         return null;
    163     }
    164 
    165     @Override
    166     public void restoreState(Parcelable state, ClassLoader loader) {
    167     }
    168 
    169     /** Creates a name for the fragment */
    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> {
    178         public FragmentCache(int size) {
    179             super(size);
    180         }
    181 
    182         @Override
    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