blob: f5518ed96cd98f5a97d74505bc18c13b488f9905 [file] [log] [blame]
/*
* Copyright (C) 2011 Google Inc.
* Licensed to The Android Open Source Project.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ex.photo.adapters;
import android.os.Parcelable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.collection.LruCache;
import androidx.viewpager.widget.PagerAdapter;
import android.util.Log;
import android.view.View;
/**
* NOTE: This is a direct copy of {@link androidx.fragment.app.FragmentPagerAdapter}
* with four very important modifications.
* <p>
* <ol>
* <li>The method {@link #makeFragmentName(int, int)} is declared "protected"
* in our class. We need to be able to re-define the fragment's name according to data
* only available to sub-classes.</li>
* <li>The method {@link #isViewFromObject(View, Object)} has been reimplemented to search
* the entire view hierarchy for the given view.</li>
* <li>In method {@link #destroyItem(View, int, Object)}, the fragment is detached and
* added to a cache. If the fragment is evicted from the cache, it will be deleted.
* An album may contain thousands of photos and we want to avoid having thousands of
* fragments.</li>
* </ol>
*/
public abstract class BaseFragmentPagerAdapter extends PagerAdapter {
/** The default size of {@link #mFragmentCache} */
private static final int DEFAULT_CACHE_SIZE = 5;
private static final String TAG = "FragmentPagerAdapter";
private static final boolean DEBUG = false;
private final FragmentManager mFragmentManager;
private FragmentTransaction mCurTransaction = null;
private Fragment mCurrentPrimaryItem = null;
/** A cache to store detached fragments before they are removed */
private LruCache<String, Fragment> mFragmentCache = new FragmentCache(DEFAULT_CACHE_SIZE);
public BaseFragmentPagerAdapter(androidx.fragment.app.FragmentManager fm) {
mFragmentManager = fm;
}
/**
* Return the Fragment associated with a specified position.
*/
public abstract Fragment getItem(int position);
@Override
public void startUpdate(View container) {
}
@Override
public Object instantiateItem(View container, int position) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
// Do we already have this fragment?
String name = makeFragmentName(container.getId(), position);
// Remove item from the cache
mFragmentCache.remove(name);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
if (DEBUG) Log.v(TAG, "Attaching item #" + position + ": f=" + fragment);
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position);
if(fragment == null) {
if (DEBUG) Log.e(TAG, "NPE workaround for getItem(). See b/7103023");
return null;
}
if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), position));
}
if (fragment != mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
}
return fragment;
}
@Override
public void destroyItem(View container, int position, Object object) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
if (DEBUG) Log.v(TAG, "Detaching item #" + position + ": f=" + object
+ " v=" + ((Fragment)object).getView());
Fragment fragment = (Fragment) object;
String name = fragment.getTag();
if (name == null) {
// We prefer to get the name directly from the fragment, but, if the fragment is
// detached before the add transaction is committed, this could be 'null'. In
// that case, generate a name so we can still cache the fragment.
name = makeFragmentName(container.getId(), position);
}
mFragmentCache.put(name, fragment);
mCurTransaction.detach(fragment);
}
@Override
public void setPrimaryItem(View container, int position, Object object) {
Fragment fragment = (Fragment) object;
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
mCurrentPrimaryItem.setMenuVisibility(false);
}
if (fragment != null) {
fragment.setMenuVisibility(true);
}
mCurrentPrimaryItem = fragment;
}
}
@Override
public void finishUpdate(View container) {
if (mCurTransaction != null && !mFragmentManager.isDestroyed()) {
mCurTransaction.commitAllowingStateLoss();
mCurTransaction = null;
mFragmentManager.executePendingTransactions();
}
}
@Override
public boolean isViewFromObject(View view, Object object) {
// Ascend the tree to determine if the view is a child of the fragment
View root = ((Fragment) object).getView();
for (Object v = view; v instanceof View; v = ((View) v).getParent()) {
if (v == root) {
return true;
}
}
return false;
}
@Override
public Parcelable saveState() {
return null;
}
@Override
public void restoreState(Parcelable state, ClassLoader loader) {
}
/** Creates a name for the fragment */
protected String makeFragmentName(int viewId, int index) {
return "android:switcher:" + viewId + ":" + index;
}
/**
* A cache of detached fragments.
*/
private class FragmentCache extends LruCache<String, Fragment> {
public FragmentCache(int size) {
super(size);
}
@Override
protected void entryRemoved(boolean evicted, String key,
Fragment oldValue, Fragment newValue) {
// remove the fragment if it's evicted OR it's replaced by a new fragment
if (evicted || (newValue != null && oldValue != newValue)) {
mCurTransaction.remove(oldValue);
}
}
}
}