What I want to achieve is disabling all items on ActionBar except one. I have a custom ActionBar
with Menu
,several TextViews
, one Button
and a Spinner
from ListNavigation.
Spinner is created because of bar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
like this :
SpinnerAdapter spinnerAdapter = new ArrayAdapter<String>(this.getActivity(), R.layout.action_bar_spinner, names);
// "listener" for spinner
bar.setListNavigationCallbacks(spinnerAdapter, new OnNavigationListener() {
@Override
public boolean onNavigationItemSelected(int position, long itemId) {
// do some stuff
return true;
}
});
I want to disable the Spinner
, so that it is inactive, but still visible with last item selected before disabling it.In short I need something like bar.getNavigationSpinner.setEnabled(false)
if it existed. Question is: Is there some kind of workaround ? If not, is there a way to disable whole ActionBar
,but keep it visible ?
Note: I want to achieve it in a Fragment.
Yes, it is possible to disable the Spinner
used in list navigation in ActionBar
. But is't not a straightforward solution, rather a hack. ActionBar doesn't provide a direct access to the Spinner
view. Unfortunately the Spinner
is created in a private code without any id.
So how to access the Spinner
instance? One solution could be to access it via Java reflection API, but I wouldn't recommend that.
A better solution is to get the root view for the current Activity
, traverse it's child views (action bar and all it's views are present in the view hierarchy) and find the proper Spinner
. Since the Spinner
in action bar is presumably the only one you haven't created yourself, you should be able to distinguish it from the others.
Getting the root View
is described in this SO question.
The traversal is rather simple, just bear in mind that if you are using ActionBarSherlock, you have to look for IcsSpinner
instead of Spinner
(IcsSpinner
does not extend from Spinner
).
private View findActionBarSpinner() {
View rootView = findViewById(android.R.id.content).getRootView();
List<View> spinners = traverseViewChildren( (ViewGroup) rootView );
return findListNavigationSpinner(spinners); // implement according to your layouts
}
private List<View> traverseViewChildren(ViewGroup parent) {
List<View> spinners = new ArrayList<View>();
for (int i = 0; i < parent.getChildCount(); i++) {
View child = parent.getChildAt(i);
if (child instanceof Spinner) {
spinners.add( child );
} else if (child instanceof IcsSpinner) { // add this if you are using ActionBarSherlock
spinners.add( child );
} else if (child instanceof ViewGroup) {
spinners.addAll( traverseViewChildren( (ViewGroup) child ) );
}
}
return spinners;
}
The function findListNavigationSpinner
should be implemented in a way that you are able to distinguish the other spinners. If you are not using any Spinner
(or any view derived from it), the returned list should contain just one item.
The code above describes how to get the Spinner
in an Activity
. Naturally, it is not a problem to disable the Spinner
from within a Fragment
. The fragment has a reference to it's activity, so the activity can expose the code to the fragment via some interface.
The above code does not work with Action Bar compat (v7 support library). I needed to enable/disable the action bar spinner of my app (API 2.2) using this support library but I have noticed that the name of the class is SpinnerICS
instead Spinner
.
The full package name is
android.support.v7.internal.widget.SpinnerICS
Because of this class is not visible and can not be imported in my code, I can not use:
if (child instanceof SpinnerICS) ...
So finally I solved my issue using getClass().getName
. Therefore the code for enabling/disabling the spinner using Action Bar compat support library (v7) will be:
private List<View> traverseViewChildren(ViewGroup parent) {
List<View> spinners = new ArrayList<View>();
for (int i = 0; i < parent.getChildCount(); i++) {
View child = parent.getChildAt(i);
String className = child.getClass().getName();
if (child instanceof Spinner || className.equals("android.support.v7.internal.widget.SpinnerICS")) {
spinners.add( child );
} else if (child instanceof ViewGroup) {
spinners.addAll( traverseViewChildren( (ViewGroup) child ) );
}
}
return spinners;
}
Also, I have modified my findListNavigationSpinner method to check if the spinners list has elements to prevent ArrayIndexOutOfBoundsException
. Because I only have one spinner in my app (in the Action Bar compat), so my code is this (you should adapt it if you would have more than one spinner):
private View findListNavigationSpinner(List<View> spinners) {
// We only have one spinner in the app
View result = null;
if (spinners != null && spinners.size() > 0) {
result = spinners.get(0);
}
return result;
}
This can be simplified by noting that the ActionBar (and Spinner) is between the root view and the content (with id == android.R.id.content). So you can implement a breadth-first search and return the first Spinner you find, no longer adding to the queue once you hit the view with id == android.R.id.content.
Or do the depth-first search, but stop recursing when you hit the view with id == android.R.id.content as the Spinner isn't contained in there.
So there's no need to maintain any list, just return the first Spinner found.