AppCompat v7 Toolbar Up/Back Arrow Not Working

7.9k views Asked by At

I have two fragments in an activity. When fragment A is showing, I want the navigation drawer burger icon to show and the navigation drawer to work. When fragment B is showing, I want the back arrow to show and when it's clicked do an up navigation. However, I can't seem to get the new AppCompat v7 toolbar to show the up arrow at all inside my ActionBarActivity unless the nav drawer is open.

In my activity, for my onCreate() method I have...

toolbar = (Toolbar) findViewById(R.id.toolbar);
if (toolbar != null) {
    setSupportActionBar(toolbar);
}
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, toolbar, R.string.drawer_open, R.string.drawer_close);
mDrawerLayout.setDrawerListener(mDrawerToggle);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);

And then I call mDrawerToggle.syncState(); in my onPostCreate()

I've tried searching on how to programatically trigger the toolbar icon to the back arrow but nothing has worked. From what I've gathered, calling

getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);

from my fragment should change the icon but that's not the case. This may be a stupid question, but what am I doing wrong?

4

There are 4 answers

0
Nikola Despotoski On BEST ANSWER

From what I have seen in the source code of v7 ActionBarDrawerToggle, you can animate the icon to different states without having the drawer being opened.

 private enum ActionDrawableState{
        BURGER, ARROW
    }
    private static void toggleActionBarIcon(ActionDrawableState state, final ActionBarDrawerToggle toggle, boolean animate){
        if(animate) {
            float start = state == ActionDrawableState.BURGER ? 0f : 1.0f;
            float end = Math.abs(start - 1);
            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
                ValueAnimator offsetAnimator = ValueAnimator.ofFloat(start, end);
                offsetAnimator.setDuration(300);
                offsetAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
                offsetAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        float offset = (Float) animation.getAnimatedValue();
                        toggle.onDrawerSlide(null, offset);
                    }
                });
               offsetAnimator.start();
            }else{
                //do the same with nine-old-androids lib :)
            }
        }else{
            if(state == ActionDrawableState.BURGER){
                toggle.onDrawerClosed(null);
            }else{
                toggle.onDrawerOpened(null);
            }
        }
    }

Morphing between Burger and Arrow depends on values between 0f and 1.0f, basically these are values that the drawer passes to the ActionBarDrawerToggle.

I used ValueAnimator to animate values in this range, i.e mimicking the drawer toggling.

null arguments are safe because ActionBarDrawerToggle does not care at all about drawer views. Make sure you take a look at new interpolators to do the animation fully-by-the-book of material design guidelines:

fast_out_linear_in  
fast_out_slow_in

Another approach is to access mSlider private field of the ActionBarDrawer through reflection and call setPosition(float position) method to toggle between Burger and Arrow. mSlider is of type (extends) DrawerArrowDrawable.

Personally, I always try to avoid reflection, as long as there is no other way to do your dirty work.

0
kunal.c On

In my case the icon is animating: I have used ActionBarDrawerToggle v7.

MainActivity:

    Toolbar toolbar = (Toolbar) findViewById(R.id.tool1);

    setSupportActionBar(toolbar);
    toolbar.setTitle("ToolBar Demo");
    toolbar.setLogo(R.drawable.ic_launcher);

    mDrawerLayout = (DrawerLayout) findViewById(R.id.drawerLayout);

    mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, toolbar,
            R.string.open_navigation_drawer,
            R.string.close_navigation_drawer) {

        @Override
        public void onDrawerSlide(View drawerView, float slideOffset) {
            // TODO Auto-generated method stub
            super.onDrawerSlide(drawerView, slideOffset);
        }

        /** Called when a drawer has settled in a completely closed state. */
        @Override
        public void onDrawerClosed(View view) {
            super.onDrawerClosed(view);
            getSupportActionBar().setTitle("hello");
        }

        /** Called when a drawer has settled in a completely open state. */
        @Override
        public void onDrawerOpened(View drawerView) {
            super.onDrawerOpened(drawerView);
            getSupportActionBar().setTitle("hi");
        }
    };
    mDrawerLayout.setDrawerListener(mDrawerToggle);



}

@Override
public boolean onOptionsItemSelected(MenuItem item) { // <---- added
    if (mDrawerToggle.onOptionsItemSelected(item)) {
        return true;
    }
    return super.onOptionsItemSelected(item);
}

@Override
protected void onPostCreate(Bundle savedInstanceState) { // <---- added
    super.onPostCreate(savedInstanceState);
    mDrawerToggle.syncState(); // important statetment for drawer to
                                // identify
                                // its state
}

@Override
public void onConfigurationChanged(Configuration newConfig) { // <---- added
    super.onConfigurationChanged(newConfig);
    mDrawerToggle.onConfigurationChanged(newConfig);
}

@Override
public void onBackPressed() {
    if (mDrawerLayout.isDrawerOpen(Gravity.START | Gravity.LEFT)) { // <----
                                                                    // added
        mDrawerLayout.closeDrawers();
        return;
    }
    super.onBackPressed();
}
0
vrbsm On
getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
        @Override
        public void onBackStackChanged() {
            int stackHeight = getSupportFragmentManager().getBackStackEntryCount();
            if (stackHeight > 0) { // if we have something on the stack (doesn't include the current shown fragment)
                getSupportActionBar().setHomeButtonEnabled(true);
                getSupportActionBar().setDisplayHomeAsUpEnabled(true);
            } else {
                getSupportActionBar().setDisplayHomeAsUpEnabled(false);
                getSupportActionBar().setHomeButtonEnabled(false);
            }
        }

    });

After ...

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case android.R.id.home:
            getSupportFragmentManager().popBackStack();
            return true;
     ....
 }
0
ywwynm On

As Support Library updated to 23.0.0, there is a better way to play drawer-arrow animation. So I'm going to improve @Nikola's answer. Here's code:

public static void playDrawerToggleAnim(final DrawerArrowDrawable d) {
    float start = d.getProgress();
    float end = Math.abs(start - 1);
    ValueAnimator offsetAnimator = ValueAnimator.ofFloat(start, end);
    offsetAnimator.setDuration(300);
    offsetAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
    offsetAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            float offset = (Float) animation.getAnimatedValue();
            d.setProgress(offset);
        }
    });
    offsetAnimator.start();
}

And call it whenever you want by:

playDrawerToggleAnim((DrawerArrowDrawable) toolbar.getNavigationIcon());