Created
October 29, 2015 01:08
-
-
Save AndroidT/46ebf977813e12e15439 to your computer and use it in GitHub Desktop.
fragmentstack
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright 2013 Simon Vig Therkildsen | |
* | |
* 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 net.simonvt.fragmentstack; | |
import android.app.Activity; | |
import android.os.Bundle; | |
import android.os.Handler; | |
import android.support.v4.app.Fragment; | |
import android.support.v4.app.FragmentActivity; | |
import android.support.v4.app.FragmentManager; | |
import android.support.v4.app.FragmentTransaction; | |
import java.util.HashSet; | |
import java.util.LinkedList; | |
import java.util.Set; | |
/** A class that manages a stack of {@link Fragment}s in a single container. */ | |
public final class FragmentStack { | |
public interface Callback { | |
void onStackChanged(int stackSize, Fragment topFragment); | |
} | |
/** Create an instance for a specific container. */ | |
public static FragmentStack forContainer(FragmentActivity activity, int containerId, | |
Callback callback) { | |
return new FragmentStack(activity, containerId, callback); | |
} | |
private static final String STATE_STACK = "net.simonvt.util.FragmentStack.stack"; | |
private LinkedList<Fragment> stack = new LinkedList<Fragment>(); | |
private Set<String> topLevelTags = new HashSet<String>(); | |
private Activity activity; | |
private FragmentManager fragmentManager; | |
private FragmentTransaction fragmentTransaction; | |
private int containerId; | |
private Callback callback; | |
private Handler handler; | |
private int enterAnimation; | |
private int exitAnimation; | |
private int popStackEnterAnimation; | |
private int popStackExitAnimation; | |
private final Runnable execPendingTransactions = new Runnable() { | |
@Override | |
public void run() { | |
if (fragmentTransaction != null) { | |
fragmentTransaction.commit(); | |
fragmentManager.executePendingTransactions(); | |
fragmentTransaction = null; | |
dispatchOnStackChangedEvent(); | |
} | |
} | |
}; | |
private FragmentStack(FragmentActivity activity, int containerId, Callback callback) { | |
this.activity = activity; | |
fragmentManager = activity.getSupportFragmentManager(); | |
this.containerId = containerId; | |
this.callback = callback; | |
handler = new Handler(); | |
} | |
/** Removes all added fragments and clears the stack. */ | |
public void destroy() { | |
ensureTransaction(); | |
fragmentTransaction.setCustomAnimations(enterAnimation, exitAnimation); | |
final Fragment topFragment = stack.peekFirst(); | |
for (Fragment f : stack) { | |
if (f != topFragment) removeFragment(f); | |
} | |
stack.clear(); | |
for (String tag : topLevelTags) { | |
removeFragment(fragmentManager.findFragmentByTag(tag)); | |
} | |
fragmentTransaction.commit(); | |
fragmentTransaction = null; | |
} | |
public void saveState(Bundle outState) { | |
executePendingTransactions(); | |
final int stackSize = stack.size(); | |
String[] stackTags = new String[stackSize]; | |
int i = 0; | |
for (Fragment f : stack) { | |
stackTags[i++] = f.getTag(); | |
} | |
outState.putStringArray(STATE_STACK, stackTags); | |
} | |
public void restoreState(Bundle state) { | |
String[] stackTags = state.getStringArray(STATE_STACK); | |
for (String tag : stackTags) { | |
Fragment f = fragmentManager.findFragmentByTag(tag); | |
stack.add(f); | |
} | |
dispatchOnStackChangedEvent(); | |
} | |
public int size() { | |
return stack.size(); | |
} | |
public Fragment peek() { | |
return stack.peekLast(); | |
} | |
/** Replaces the entire stack with this fragment. */ | |
public void replace(Class fragment, String tag) { | |
replace(fragment, tag, null); | |
} | |
/** | |
* Replaces the entire stack with this fragment. | |
* | |
* @param args Arguments to be set on the fragment using {@link Fragment#setArguments(android.os.Bundle)}. | |
*/ | |
public void replace(Class fragment, String tag, Bundle args) { | |
Fragment first = stack.peekFirst(); | |
if (first != null && tag.equals(first.getTag())) { | |
if (stack.size() > 1) { | |
ensureTransaction(); | |
fragmentTransaction.setCustomAnimations(popStackEnterAnimation, popStackExitAnimation); | |
while (stack.size() > 1) { | |
removeFragment(stack.pollLast()); | |
} | |
attachFragment(stack.peek(), tag); | |
} | |
return; | |
} | |
Fragment f = fragmentManager.findFragmentByTag(tag); | |
if (f == null) { | |
f = Fragment.instantiate(activity, fragment.getName(), args); | |
} | |
ensureTransaction(); | |
fragmentTransaction.setCustomAnimations(enterAnimation, exitAnimation); | |
clear(); | |
attachFragment(f, tag); | |
stack.add(f); | |
topLevelTags.add(tag); | |
} | |
public void push(Class fragment, String tag) { | |
push(fragment, tag, null); | |
} | |
/** Adds a new fragment to the stack and displays it. */ | |
public void push(Class fragment, String tag, Bundle args) { | |
ensureTransaction(); | |
fragmentTransaction.setCustomAnimations(enterAnimation, exitAnimation); | |
detachTop(); | |
Fragment f = fragmentManager.findFragmentByTag(tag); | |
if (f == null) { | |
f = Fragment.instantiate(activity, fragment.getName(), args); | |
} | |
attachFragment(f, tag); | |
stack.add(f); | |
} | |
/** | |
* Removes the fragment at the top of the stack and displays the previous one. This will not do | |
* anything if there is | |
* only one fragment in the stack. | |
* | |
* @return Whether a transaction has been enqueued. | |
*/ | |
public boolean pop() { | |
return pop(false); | |
} | |
/** | |
* Removes the fragment at the top of the stack and displays the previous one. This will not do | |
* anything if there is | |
* only one fragment in the stack. | |
* | |
* @param commit Whether the transaction should be committed. | |
* @return Whether a transaction has been enqueued. | |
*/ | |
public boolean pop(boolean commit) { | |
if (stack.size() > 1) { | |
ensureTransaction(); | |
fragmentTransaction.setCustomAnimations(popStackEnterAnimation, popStackExitAnimation); | |
removeFragment(stack.pollLast()); | |
Fragment f = stack.peekLast(); | |
attachFragment(f, f.getTag()); | |
if (commit) commit(); | |
return true; | |
} | |
return false; | |
} | |
private void detachTop() { | |
Fragment f = stack.peekLast(); | |
detachFragment(f); | |
} | |
private void clear() { | |
Fragment first = stack.peekFirst(); | |
for (Fragment f : stack) { | |
if (f == first) { | |
detachFragment(f); | |
} else { | |
removeFragment(f); | |
} | |
} | |
stack.clear(); | |
} | |
private void dispatchOnStackChangedEvent() { | |
if (callback != null && stack.size() > 0) { | |
callback.onStackChanged(stack.size(), stack.peekLast()); | |
} | |
} | |
private FragmentTransaction ensureTransaction() { | |
if (fragmentTransaction == null) fragmentTransaction = fragmentManager.beginTransaction(); | |
handler.removeCallbacks(execPendingTransactions); | |
return fragmentTransaction; | |
} | |
private void attachFragment(Fragment fragment, String tag) { | |
if (fragment != null) { | |
if (fragment.isDetached()) { | |
ensureTransaction(); | |
fragmentTransaction.attach(fragment); | |
} else if (!fragment.isAdded()) { | |
ensureTransaction(); | |
fragmentTransaction.add(containerId, fragment, tag); | |
} | |
} | |
} | |
private void detachFragment(Fragment fragment) { | |
if (fragment != null && !fragment.isDetached()) { | |
ensureTransaction(); | |
fragmentTransaction.detach(fragment); | |
} | |
} | |
private void removeFragment(Fragment fragment) { | |
if (fragment != null && fragment.isAdded()) { | |
ensureTransaction(); | |
fragmentTransaction.remove(fragment); | |
} | |
} | |
public void setDefaultAnimation(int enter, int exit, int popEnter, int popExit) { | |
enterAnimation = enter; | |
exitAnimation = exit; | |
popStackEnterAnimation = popEnter; | |
popStackExitAnimation = popExit; | |
} | |
/** | |
* Commit pending transactions. This will be posted, not executed immediately. | |
* | |
* @return Whether there were any transactions to commit. | |
*/ | |
public boolean commit() { | |
if (fragmentTransaction != null && !fragmentTransaction.isEmpty()) { | |
handler.removeCallbacks(execPendingTransactions); | |
handler.post(execPendingTransactions); | |
return true; | |
} | |
return false; | |
} | |
public boolean executePendingTransactions() { | |
if (fragmentTransaction != null && !fragmentTransaction.isEmpty()) { | |
handler.removeCallbacks(execPendingTransactions); | |
fragmentTransaction.commit(); | |
fragmentTransaction = null; | |
boolean result = fragmentManager.executePendingTransactions(); | |
if (result) { | |
dispatchOnStackChangedEvent(); | |
return true; | |
} | |
} | |
return false; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment