11

I'm having some problems consistently animating a 'refresh' icon in the ActionBar of my app.

I have a container FragmentActivity which swaps fragments in and out as the user navigates through the app (either from within the fragment itself or from a SlidingMenu option). So when the app first loads, my FragmentContainer adds FragA. From FragA the user can navigate to FragB which is then swapped in.

In the action bar I display a static 'refresh' icon. As each Fragment loads, I replace this with an animated 'spinner' icon. When the load completes, I revert to the original refresh icon.

Problem is, this animation only works for the original fragment (FragA, in this case). When the user navigates to FragB and selects the refresh icon, the refresh is triggered, but the animation never happens. Similarly, if the back button is pressed to return to FragA, this now follows the same pattern i.e. the refresh button does not animate when pressed.

Things to note...

  1. I'm using ActionBarSherlock and the SlidingMenu implementation at https://github.com/jfeinstein10/SlidingMenu. So the above activity is actually a SlidingFragmentActivity.
  2. Both Fragments call setHasOptionsMenu(true) - I've debugged through this and onCreateOptionsMenu is being correctly called for each.
  3. The icons are being correctly displayed for both Fragments - the animation is just not happening when I navigate off the 'default' fragment.
  4. I see the same behaviour when using the SlidingMenu to navigate - FragA loads, animation works -> SlidingMenu is used to navigate to a different fragment... animation doesn't work -> Back button to FragA... animation doesn't work here either.
  5. I'm using FragmentTransaction.remove() and add() rather than replace() as I have previously had back-button issues with replace() - I am using the compatibility lib and I read on here that the replace implementation is a bit buggy - and not using it certainly fixed the issues I was seeing.

Code snippets below:

My code to load the original fragment is....

FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(R.id.content_frame, new FragA());
ft.addToBackStack(null);
ft.commit();

To 'swap' FragB for FragA....

public void switchContent(PysoBaseFragment fragment) {
    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    ft.remove(existingFragment);
    ft.add(R.id.content_frame, fragment);
    ft.addToBackStack(null);
    ft.commit();
}

This method is declared in the container activity and is called from FragA i.e....

getFragmentContainer().switchContent(new FragB());

The code to spin the icon is called from the new Fragment as it starts to load. Its something like...

    ImageView spinnerActionView = (ImageView) inflater.inflate(R.layout.refresh_action_view, null);
    Animation rotation = AnimationUtils.loadAnimation(this, R.anim.rotate_animation);
    rotation.setRepeatCount(Animation.INFINITE);
    spinnerActionView.startAnimation(rotation);
    menuItemRefresh = menu.findItem(R.id.menu_refresh);
    menuItemRefresh.setActionView(spinnerActionView);

Where menu is assigned to an instance variable of the container when onCreateOptionsMenu is called.

Update:

I've noticed another weird bug in this area (I'm happy to add this as a separate question, but I'm updating it here in the hope that it will shed some light on my original problem - I believe both are caused by how I have configured my action-bar from my Fragments).

When I first load a fragment I have 1 static refresh icon displayed. If I rotate the screen... another refresh icon appears... when I rotate the screen back, a 3rd refresh icon appears!

Stranger still, clicking the back-button removes each additional icon in turn, before finally (on the 4th click) returning to the previous screen.

Neil
  • 1,821
  • 4
  • 14
  • 27
  • Common error when fragments are dealt with the wrong way. I had the same issues when I was injecting fragments that exact way. Simplest, dirtiest way to fix it is just to clear the menu OnCreateOptionsMenu(). Will post the right answer if this is still not answered later :) – Calvin Park Apr 01 '13 at 23:57
  • Still not fixed. Do you have any code you can post? – Neil Apr 03 '13 at 18:57
  • I tried calling menu.clear() in onCreateOptionsMenu(). Didn't make any difference. – Neil Apr 03 '13 at 21:26
  • Are the menus and items that change to animate part of the activity or come from the fragments? – frozenkoi Apr 04 '13 at 08:55

5 Answers5

5

Never chang menu items somewhere except

onPrepareOtionsMenu(){
}

you should something like this.

in your activity:

boolean mIsRefreshing =false;

public boolean onPrepareOptionsMenu(Menu menu) {
if(mIsRefreshing){
        final MenuItem menuItemRefresh = menu.findItem(R.id.menu_refresh);
    menuItemRefresh.setActionView(spinnerActionView);
}
        return true;
    }

public void setRefreshing(boolean refreshing){
mIsRefreshing = refreshing;
invalidateOptionsMenu();  //supportInvelidateOptionsMenu()
}

So now you can call from your frament

((YourActivity)getActivity()).setRefreshing(true);
((YourActivity)getActivity()).setRefreshing(false);
Artem Zelinskiy
  • 2,201
  • 16
  • 19
  • Didn't work, I'm afraid. I have a feeling that the 'menuItemRefresh' that I am setting the actionView on, is not the one that is being displayed (this could be backed up by the fact that I see more than one icon when I rotate the screen). I need to dig a bit further I think. – Neil Apr 03 '13 at 19:27
  • I was changing my menu items outside of onPrepareOptionsMenu which was causing a bad animation... now it works... thanks! – Charles Woodson Mar 11 '21 at 22:53
1

If you are using the same Animation object each time, you may need to reset your animation before attempting to run it again.

Try adding rotation.reset() above your call to startAnimation().

megabits
  • 145
  • 1
  • 8
  • I'm using AnimationUtils.loadAnimation each time the user clicks on the icon. I believe this returns a new object. I added the reset() call above it, but it is still static. – Neil Mar 29 '13 at 19:47
1
    "I've noticed another weird bug in this area (I'm happy to add this as a separate question, but I'm updating it here in the hope that it will shed some light on my original problem - I believe both are caused by how I have configured my action-bar from my Fragments).   
    When I first load a fragment I have 1 static refresh icon displayed. If I rotate the screen... another refresh icon appears... when I rotate the screen back, a 3rd refresh icon appears! 
    Stranger still, clicking the back-button removes each additional icon in turn, before finally (on the 4th click) returning to the previous screen."

I can give Explanation to this ,When ever you change orientation (Potrait<->landscape).

An activity is restarted when the orientation changes. I'm guessing your code isn't saving needed information before this restart occurs. You can stop this default behavior by handling specific configuration changes yourself (ie: orientation change). A good tutorial on doing this is located here: Handling Runtime Changes

Viswanath Lekshmanan
  • 9,945
  • 1
  • 40
  • 64
1

You are adding an action view to add the animation. If you try to do something else, like changing the menu item's icon (even if it's not animated, just to see if it changes) instead does that work?

frozenkoi
  • 3,228
  • 22
  • 33
  • Didn't work either I'm afraid. The icon was replaced for the first screen, but not for the second. At least this proved that the problem is not with the animation. – Neil Apr 21 '13 at 16:00
  • Actually... ignore the above comment - I tried it again and that does actually make a difference. Calling setIcon() on the MenuItem changes, the icon on both screens whereas calling setActionView() with the animation only works on the first screen. Interesting... any ideas why this may be? – Neil Apr 21 '13 at 16:49
  • On digging a little further... it seems that the code I use to stop the animation once the refresh is complete, is having an effect here. I call menuItemRefresh.getActionView().clearAnimation() then menuItemRefresh.setActionView(null). If remove this code then the animation runs successfully on *both* screens (but obviously never stops). – Neil Apr 21 '13 at 16:56
  • @Neil Does each fragment add its own icon in its own menu? Do both menu items have the same id? I.e. are both R.id.menu_refresh ? From the code snippet in your question you call `menuItemRefresh = menu.findItem(R.id.menu_refresh);` but it's not clear what is `menu`. Try giving each refresh menu item a different id to rule out the 2 menus getting mixed. – frozenkoi Apr 22 '13 at 01:59
  • No, I originally took that approach. However, in order to fix the other problem I was seeing (icons being repeated when rotating the screen) I have removed all menu-specific functionality from the fragments and put it into the activity. I call `supportInvalidateOptionsMenu()` as each fragment is swapped in the parent activity. This ensures that `onCreateOptionsMenu()` is called and that the menu-items are basically re-built. This approach is a little heavy-handed but it did fix my first problem. So, now I have 1 single `R.id.menu_refresh` but I still have the same problem. – Neil Apr 22 '13 at 18:32
  • @Neil In what function do you call `clearAnimation()` and `setActionView(null)`?. Consider not having the code to remove the animation. Have a flag that stores whether or not the animation should be playing. A boolean might be enough. Whenever you need to turn the animation on or off update the flag and call `invalidateOptionsMenu()` Then in `onCreateOptionsMenu` will be called and in that one construct the menu and depending on that flag start the animation or not. – frozenkoi Apr 23 '13 at 20:08
1

Only call onCreateOptionsMenu() in the main activity (I mean Sliding fragment activity).No need of calling onCreateOptionsMenu() from every fragment.I am giving a sample snippet below.

Call from fragment (whenerver loading starts and end)

    yourSlidingFragmnetActivity.refresh = true/false; (u can have this logic of your own)
    getActivity().supportInvalidateOptionsMenu();


    @Override
        public boolean onCreateOptionsMenu(Menu menu) {


            menu.add(BaseListActivity.DEFAULT_GROUP_ID, BaseListActivity.MENU_ITEM_RELOAD_LIST, BaseListActivity.DEFAULT_ORDER,
                    "Refresh").setIcon(R.drawable.ic_menu_refresh_new)
                    .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
            return true;
        }

        @Override
        public boolean onOptionsItemSelected(MenuItem item) {

            if (item.getItemId() == BaseListActivity.MENU_ITEM_RELOAD_LIST) {
                item.setActionView(R.layout.progressbar_layout);
                item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
                return true;
            }
            return true;
        }

        public static boolean refresh = false;

        @Override
        public boolean onPrepareOptionsMenu(Menu menu) {
            MenuItem item = menu.getItem(0);
            if (refresh) {
                item.setActionView(R.layout.progressbar_layout);
            } else {
                item.setIcon(R.drawable.ic_menu_refresh_new);
            }
            return true;
        }

        R.layout.progressbar_layout:
        <?xml version="1.0" encoding="utf-8"?>
        <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                      android:layout_width="wrap_content"
                      android:layout_height="wrap_content"
                      android:addStatesFromChildren="true"
                      android:focusable="true"
                      android:paddingLeft="4dp"
                      android:paddingRight="4dp"
                      android:gravity="center"
                      style="?attr/actionButtonStyle">
        <ProgressBar
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:focusable="true"
                    style="@android:style/Widget.ProgressBar.Small"/>
        </LinearLayout>

I am also using jfeinstein10 slidingmenu lib , I am not adding or removing fragments , I am always replace them and in oncreate menthod iam using like this

private Fragment mContent;
if(mContent == null){
mContent = new SampleFragment();
}
// set the Above View
    setContentView(R.layout.content_frame);
                getSupportFragmentManager()
                .beginTransaction()
                .replace(R.id.content_frame,mContent)
                .commit();      
Handroid
  • 399
  • 1
  • 2
  • 13
  • As I understand it, if any individual fragments wish to add additional items to the action-bar menu, then they need to declare onCreateOptionsMenu() also. So I don't think its incorrect to have fragments declaring this method. – Neil Apr 04 '13 at 11:56
  • Did you experience any back-button problems while using replace() with the SlidingMenu? I was getting weird back button behaviour until I replaced the call to replace() with calls to remove() and add(). – Neil Apr 04 '13 at 11:58
  • Can you explain me exactly what weird behavior you are getting ? – Handroid Apr 04 '13 at 13:12
  • I won't be able to look at my code till tomorrow but, from memory: Some of my screen-loads are quite intensive and this was interfering with the animation of the SlidingMenu (which was very jerky). To fix this I show a 'loading' fragment until the sliding window has closed, then I show the requested Fragment. The loading page was *not* added to the back-stack, however, on pressing the back-button the loading page was being shown again. I had read on here that the replace() method in the support lib was a little buggy - so I replaced this call with add() & remove() and it fixed this issue. – Neil Apr 04 '13 at 13:58