import android.support.v4.app.Fragment; import android.util.Log; import java.util.HashSet; import java.util.List; import java.util.Set; /** * Fragment with a workaround to handle menus in conjunction with MenuManagerActivity *

* To make it work properly you need to assure that all fragments with option menu and that have nested fragments with option menu extend this fragment. *

* Apparently when you detach a parent fragment the nested fragment is not detached and actually not even paused, nor the visibility hint is set to false. * For this reason I had to handle an hierarchy check between fragments making the parent fragment notify children when he is going away and coming back * and making children ask to be informed when the parent go away. *

* I think I handled any fancy configuration now, not 100% sure tough. I'll keep improving if I found bugs. The nested fragment problem was an hard one to * catch and fix, I wrote a good debug log that can be enabled by just setting a variable to true. Don't worry about performance since when compiling with * that variable to false the code for logging is effectively removed, not even the if is evaluated. */ public class MenuDelegatorFragment extends Fragment { public static final String LTAG = MenuDelegatorFragment.class.getSimpleName(); private boolean mHasMenu; private final Set mChildrenShowingMenu = new HashSet(); private static final boolean DEBUG = false; private void debugLog(String what) { Log.v(LTAG, String.format("%s[%d] (%s): %s", mHasMenu ? "M" : "-", getId(), ((Object) this).getClass().getSimpleName(), what)); } @Override public void setHasOptionsMenu(boolean hasMenu) { if (DEBUG) { debugLog(String.format("setHasOptionsMenu(%b)", hasMenu)); } // Note that I'm not calling the super constructor here! // Cause I'll delegate the menu handling to the MenuManagerActivity instead of letting // the framework do it! mHasMenu = hasMenu; if (mHasMenu && isHierarchyVisible()) { showMenu(); } else { hideMenu(); } } @Override public void setUserVisibleHint(boolean isVisibleToUser) { boolean wasUserVisible = getUserVisibleHint(); super.setUserVisibleHint(isVisibleToUser); if (wasUserVisible == isVisibleToUser) { return; } if (DEBUG) { debugLog(String.format("setUserVisibleHint(%b)", isVisibleToUser)); } if (mHasMenu) { if (isVisibleToUser && isHierarchyVisible()) { showMenu(); } else { hideMenu(); } } if (isVisibleToUser) { notifyChildrenToShowMenu(); } else { notifyChildrenToHideMenu(); } } @Override public void onPause() { super.onPause(); if (DEBUG) { debugLog("onPause()"); } if (mHasMenu) { hideMenu(); } notifyChildrenToHideMenu(); } @Override public void onResume() { if (DEBUG) { debugLog("onResume()"); } super.onResume(); if (mHasMenu && isHierarchyVisible()) { showMenu(); } notifyChildrenToShowMenu(); } private boolean isHierarchyVisible() { boolean visible = getUserVisibleHint() && isResumed(); Fragment parent = getParentFragment(); boolean hierarchyVisible; if (parent == null) { hierarchyVisible = visible; } else if (parent instanceof MenuDelegatorFragment) { hierarchyVisible = visible && ((MenuDelegatorFragment) parent).isHierarchyVisible(); } else { Log.w(LTAG, String.format("non-%s in the hierarchy, can't grant menu will work correctly in every situation: %s", LTAG, ((Object) parent).getClass().getSimpleName())); hierarchyVisible = visible && parent.getUserVisibleHint() && parent.isResumed(); } if (DEBUG) { debugLog(String.format("isHierarchyVisible() -> %b", hierarchyVisible)); } return hierarchyVisible; } private void showMenu() { if (((MenuManagerActivity) getActivity()).registerFragmentForOptionMenu(this)) { if (DEBUG) { debugLog("showMenu()"); } if (getParentFragment() instanceof MenuDelegatorFragment) { ((MenuDelegatorFragment) getParentFragment()).addChildShowingMenu(this); } } } private void hideMenu() { if (((MenuManagerActivity) getActivity()).unregisterFragmentForOptionMenu(this)) { if (DEBUG) { debugLog("hideMenu()"); } if (getParentFragment() instanceof MenuDelegatorFragment) { ((MenuDelegatorFragment) getParentFragment()).removeChildShowingMenu(this); } } } private void parentIsGoingAway() { if (DEBUG) { debugLog("parentIsGoingAway()"); } hideMenu(); } private void parentIsComingBack() { if (DEBUG) { debugLog("parentIsComingBack()"); } if (mHasMenu && isHierarchyVisible()) { showMenu(); } } private void removeChildShowingMenu(MenuDelegatorFragment child) { if (mChildrenShowingMenu.remove(child)) { if (DEBUG) { debugLog(String.format("removeChildShowingMenu([%s] %s)", child.getId(), ((Object) child).getClass().getSimpleName())); } } } private void addChildShowingMenu(MenuDelegatorFragment child) { if (mChildrenShowingMenu.add(child)) { if (DEBUG) { debugLog(String.format("addChildShowingMenu([%s] %s)", child.getId(), ((Object) child).getClass().getSimpleName())); } } } private void notifyChildrenToHideMenu() { if (mChildrenShowingMenu.size() == 0) { return; } if (DEBUG) { debugLog("notifyChildrenToHideMenu():" + mChildrenShowingMenu.size()); } for (MenuDelegatorFragment child : mChildrenShowingMenu) { child.parentIsGoingAway(); } mChildrenShowingMenu.clear(); } private void notifyChildrenToShowMenu() { List children = getChildFragmentManager().getFragments(); if (children == null || children.size() == 0) { return; } if (DEBUG) { debugLog("notifyChildrenToShowMenu():" + children.size()); } for (Fragment child : children) { if (child instanceof MenuDelegatorFragment) { ((MenuDelegatorFragment) child).parentIsComingBack(); } else if (child.hasOptionsMenu()) { Log.w(LTAG, String.format("non-%s in the hierarchy, can't grant menu will work correctly in every situation: %s", LTAG, ((Object) child).getClass().getSimpleName())); } } } }