Skip to content

Instantly share code, notes, and snippets.

@AndroidT
Created October 29, 2015 01:08
Show Gist options
  • Save AndroidT/46ebf977813e12e15439 to your computer and use it in GitHub Desktop.
Save AndroidT/46ebf977813e12e15439 to your computer and use it in GitHub Desktop.
fragmentstack
/*
* 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