Letting me start by explaining the desired effect I was trying to achieve. On the Android Google+ application if you hit refresh there is a spinner animation that replaces the refresh ActionBar item when pressed. I was able to achieve this but setting the pressed MenuItem to a new layout by using “MenuItem.setActionView()”.
However, on ICS if you have a two ore more MenuItems always available in the Android ActionBar (i.e. you set android:showAsAction=”ifRoom” in the mainmenu.xml file below) swapping the the icon on one MenuItem when pressed causes a slight ghost selection of the other.
The MenuItem next to the one pressed immediately shows a highlight as you stop pressing the refresh icon. This ghost selection behavior is produced because the default ICS background selector contains a fade animation. Swapping the icon causes the animation up/fade to transfer to the next non-customized ActionBar MenuItem, the one you didn’t select.
Here is a code example (not complete) on how to swap out the MenuItem with a custom spinner:
private View mRefreshIndeterminateProgressView; // save inflated layout for reference
private MenuItem refreshItem; // reference to actionbar menu item we want to swap
if (mRefreshIndeterminateProgressView == null) {
LayoutInflater inflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mRefreshIndeterminateProgressView = inflater.inflate(R.layout.actionbar_indeterminate_progress, null);
}
refreshItem.setActionView(mRefreshIndeterminateProgressView); // replace actionbar menu item with progress
// doing refreshItem.setActionView(null) removes the animation
Here is what the actionbar_indeterminate_progress.xml looks like:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center">
<ProgressBar android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:layout_gravity="center"
style="?android:attr/indeterminateProgressStyle"/>
</FrameLayout>
Here is your mainmenu.xml:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/detail_menu_refresh"
android:title="Refresh"
android:icon="@drawable/ic_action_refresh"
android:showAsAction="ifRoom"/>
<item android:id="@+id/main_menu_share"
android:title="Share"
android:icon="@drawable/ic_action_share"
android:showAsAction="ifRoom"/>
<item android:id="@+id/main_menu_about"
android:title="About"
android:icon="@drawable/ic_action_about"
android:showAsAction="never"/>
</menu>
The solution is to set your own custom selector background for the ActionBar so you can remove the fade. To do this, I grabbed the abs__item_background_holo_dark.xml from (ActionBarSherlock > Library > res > drawable) and the associated graphics to add to my project. You could easily do this without using ActionBarSherlock and just ActionBar and regular themes for your project. I then removed the following line from the nod in the xml which disables the fade:
android:exitFadeDuration="@android:integer/config_mediumAnimTime"
Here is modified abs__item_background_holo_dark.xml:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Even though these two point to the same resource, have two states so the drawable will invalidate itself when coming out of pressed state. -->
<item android:state_focused="true" android:state_enabled="false" android:state_pressed="true" android:drawable="@drawable/abs__list_selector_disabled_holo_dark" />
<item android:state_focused="true" android:state_enabled="false" android:drawable="@drawable/abs__list_selector_disabled_holo_dark" />
<item android:state_focused="true" android:state_pressed="true" android:drawable="@drawable/abs__list_selector_background_transition_holo_dark" />
<item android:state_focused="false" android:state_pressed="true" android:drawable="@drawable/abs__list_selector_background_transition_holo_dark" />
<item android:state_focused="true" android:drawable="@drawable/abs__list_focused_holo" />
<item android:drawable="@android:color/transparent" />
</selector>
Here is how I set it up in my styles.xml file:
<style name="Dark" parent="Theme.Sherlock.Light.DarkActionBar">
<item name="actionBarStyle">@style/Widget.Styled.ActionBar</item>
<item name="android:actionBarStyle">@style/Widget.Styled.ActionBar</item>
<item name="actionBarItemBackground">@drawable/abs__item_background_holo_dark</item>
<item name="android:actionBarItemBackground">@drawable/abs__item_background_holo_dark</item>
</style>
<style name="Widget.Styled.ActionBar" parent="Widget.Sherlock.Light.ActionBar.Solid.Inverse"/>
I got a great tip on how to fix this from Jake Wharton, the creator of ActionBarSherlock. I recommend using his library if you want to have a consistent ActionBar across all versions of your Android applications.
- Mister
Hi Michael, I exactly had the same problem with you. Your article is really great…. Thanks…
I get an error saying that “?android:attr/indeterminateProgressStyle” is not available for API 11. How would you do this for API 10?
There is a new library available on GitHub that does a nice job of showing the progress bar, maybe that would be a good library to use for API 10 and lower. https://github.com/ManuelPeinado/RefreshActionItem
Great! Thank you!
Great! Thank you!!