Speed up 'Navigation Drawer' animation speed on closing?
Asked Answered
A

7

17

Implemented and working as expect, as such there really is no code that is worth posting here, simply looking to find out if anyone has experience with speeding up the time it takes the drawer to open and close? The YouTube app for instance is much faster!

Ashelman answered 18/10, 2013 at 23:49 Comment(0)
F
24

You can definitely adjust the duration of the animation, but it will require you to copy over the classes from the support library, then edit them accordingly.

ViewDragHelper

The duration is determined here in ViewDragHelper

Then is applied to the DrawerLayout when ViewDragHelper.smoothSlideViewTo is called

You'll need to create a modified version of ViewDragHelper.forceSettleCapturedViewAt that passes in a duration param.

forceSettleCapturedViewAt(... int duration)

Then create your version of ViewDragHelper.smoothSlideViewTo.

public boolean smoothSlideViewTo(... int duration) {
        ...
        return forceSettleCapturedViewAt(... int duration);
    }

DrawerLayout

Next you'll need to modify DrawerLayout.closeDrawer and DrawerLayout.closeDrawers to match your new ViewDragHelper modifications.

ActionBarDrawerToggle

You'll also have to copy over ActionBarDrawerToggle and ActionBarDrawerToggleHoneycomb. These files won't require any editing though.

Fredkin answered 19/10, 2013 at 1:9 Comment(3)
Ouch! Lots to do! Perfect answer none the less.Ashelman
Thanks a lot for this! I wonder why they don't expose the value?Warison
can you give me the hint for open drawer layout fastly ?. Because when I click on humberg icon my drawer layout take some and after than open. and it is happend only first time when I click on humber icon. after that my drawer layout open and close smothly. but problem is opening drawer layout first time. please help. thanks in advanced...Chalybite
A
7

An alternative to speeding up the animation and waiting for it to finish is to simply avoid the animation in the first place: just call startActivity() without calling closeDrawer(). Although you don't see the drawer close, the activity transition animation still provides a pretty nice effect, and it occurs immediately, with no need to wait for the drawer close animation to finish settling first, no choppiness, and a much shorter perceptual delay.


Details

(You can skip past this explanation if you just want to see the code.)

To make this work you need a way to close the drawer without any close animation when navigating back to the activity with the back button. (Not calling closeDrawer() will leave the drawer open in that activity instance; a relatively wasteful workaround would be to just force the activity to recreate() when navigating back, but it's possible to solve this without doing that.) You also need to make sure you only close the drawer if you're returning after navigating, and not after an orientation change, but that's easy.

Although calling closeDrawer() from onCreate() will make the drawer start out closed without any animation, the same is not true from onResume(). Calling closeDrawer() from onResume() will close the drawer with an animation that is momentarily visible to the user. DrawerLayout doesn't provide any method to close the drawer without that animation, but it's possible to add one.

As @syesilova points out, closing the drawer actually just slides it off the screen. So you can effectively skip the animation by moving the drawer directly to its "closed" position. The translation direction will vary according to the gravity (whether it's a left or right drawer), and the exact position depends on the size of the drawer once it's laid out with all its children.

However, simply moving it isn't quite enough, as DrawerLayout keeps some internal state in extended LayoutParams that it uses to know whether the drawer is open. If you just move the drawer off screen, it won't know that it's closed, and that will cause other problems. (For example, the drawer will reappear on the next orientation change.)

Since you're compiling the support library into your app, you can create a class in the android.support.v4.widget package to gain access to its default (package-private) parts, or extend DrawerLayout without copying over any of the other classes it needs. This will also reduce the burden of updating your code with future changes to the support library. (It's always best to insulate your code from implementation details as much as possible.) You can use moveDrawerToOffset() to move the drawer, and set the LayoutParams so it will know that the drawer is closed.


Code

This is the code that'll skip the animation:

// move drawer directly to the closed position
moveDrawerToOffset(drawerView, 0.f); 

/* EDIT: as of v23.2.1 this direct approach no longer works
         because the LayoutParam fields have been made private...
// set internal state so DrawerLayout knows it's closed
final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
lp.onScreen = 0.f;
lp.knownOpen = false;

invalidate();
/*/
// ...however, calling closeDrawer will set those LayoutParams
//    and invalidate the view.
closeDrawer(drawerView);
/**/

Note: if you just call moveDrawerToOffset() without changing the LayoutParams, the drawer will move back to its open position on the next orientation change.


Option 1 (use existing DrawerLayout)

This approach adds a utility class to the support.v4 package to gain access to the package-private parts we need inside DrawerLayout.

Place this class into /src/android/support/v4/widget/:

package android.support.v4.widget;

import android.support.annotation.IntDef;
import android.support.v4.view.GravityCompat;
import android.view.Gravity;
import android.view.View;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

public class Support4Widget {

    /** @hide */
    @IntDef({Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END})
    @Retention(RetentionPolicy.SOURCE)
    private @interface EdgeGravity {}

    public static void setDrawerClosed(DrawerLayout drawerLayout, @EdgeGravity int gravity) {
        final View drawerView = drawerLayout.findDrawerWithGravity(gravity);
        if (drawerView == null) {
            throw new IllegalArgumentException("No drawer view found with gravity " +
                    DrawerLayout.gravityToString(gravity));
        }

        // move drawer directly to the closed position
        drawerLayout.moveDrawerToOffset(drawerView, 0.f); 
        
        /* EDIT: as of v23.2.1 this no longer works because the
                 LayoutParam fields have been made private, but
                 calling closeDrawer will achieve the same result.
        
        // set internal state so DrawerLayout knows it's closed
        final DrawerLayout.LayoutParams lp = (DrawerLayout.LayoutParams) drawerView.getLayoutParams();
        lp.onScreen = 0.f;
        lp.knownOpen = false;

        drawerLayout.invalidate();
        /*/
        // Calling closeDrawer updates the internal state so DrawerLayout knows it's closed
        // and invalidates the view for us.
        drawerLayout.closeDrawer(drawerView);
        /**/
    }
}

Set a boolean in your activity when you navigate away, indicating the drawer should be closed:

public static final String CLOSE_NAV_DRAWER = "CLOSE_NAV_DRAWER";
private boolean mCloseNavDrawer;

@Override
public void onCreate(Bundle savedInstanceState) {
    // ...
    if (savedInstanceState != null) {
        mCloseNavDrawer = savedInstanceState.getBoolean(CLOSE_NAV_DRAWER);
    }
}

@Override
public boolean onNavigationItemSelected(MenuItem menuItem) {

    // ...

    startActivity(intent);
    mCloseNavDrawer = true;
}

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    savedInstanceState.putBoolean(CLOSE_NAV_DRAWER, mCloseNavDrawer);
    super.onSaveInstanceState(savedInstanceState);
}   

...and use the setDrawerClosed() method to shut the drawer in onResume() with no animation:

@Overrid6e
protected void onResume() {
    super.onResume();

    if(mCloseNavDrawer && mDrawerLayout != null && mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
        Support4Widget.setDrawerClosed(mDrawerLayout, GravityCompat.START);
        mCloseNavDrawer = false;
    }
}

Option 2 (extend from DrawerLayout)

This approach extends DrawerLayout to add a setDrawerClosed() method.

Place this class into /src/android/support/v4/widget/:

package android.support.v4.widget;

import android.content.Context;
import android.support.annotation.IntDef;
import android.support.v4.view.GravityCompat;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

public class CustomDrawerLayout extends DrawerLayout {

    /** @hide */
    @IntDef({Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END})
    @Retention(RetentionPolicy.SOURCE)
    private @interface EdgeGravity {}
    
    public CustomDrawerLayout(Context context) {
        super(context);
    }

    public CustomDrawerLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomDrawerLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }
    
    public void setDrawerClosed(View drawerView) {
        if (!isDrawerView(drawerView)) {
            throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer");
        }
        
        // move drawer directly to the closed position
        moveDrawerToOffset(drawerView, 0.f); 
        
        /* EDIT: as of v23.2.1 this no longer works because the
                 LayoutParam fields have been made private, but
                 calling closeDrawer will achieve the same result.
        
        // set internal state so DrawerLayout knows it's closed
        final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
        lp.onScreen = 0.f;
        lp.knownOpen = false;
        
        invalidate();
        /*/
        // Calling closeDrawer updates the internal state so DrawerLayout knows it's closed
        // and invalidates the view for us.
        closeDrawer(drawerView);
        /**/
    }

    public void setDrawerClosed(@EdgeGravity int gravity) {
        final View drawerView = findDrawerWithGravity(gravity);
        if (drawerView == null) {
            throw new IllegalArgumentException("No drawer view found with gravity " +
                    gravityToString(gravity));
        }

        // move drawer directly to the closed position
        moveDrawerToOffset(drawerView, 0.f); 
        
        /* EDIT: as of v23.2.1 this no longer works because the
                 LayoutParam fields have been made private, but
                 calling closeDrawer will achieve the same result.
        
        // set internal state so DrawerLayout knows it's closed
        final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
        lp.onScreen = 0.f;
        lp.knownOpen = false;

        invalidate();
        /*/
        // Calling closeDrawer updates the internal state so DrawerLayout knows it's closed
        // and invalidates the view for us.
        closeDrawer(drawerView);
        /**/
    }
}

Use CustomDrawerLayout instead of DrawerLayout in your activity layouts:

<android.support.v4.widget.CustomDrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    >

...and set a boolean in your activity when you navigate away, indicating the drawer should be closed:

public static final String CLOSE_NAV_DRAWER = "CLOSE_NAV_DRAWER";
private boolean mCloseNavDrawer;

@Override
public void onCreate(Bundle savedInstanceState) {
    // ...
    if (savedInstanceState != null) {
        mCloseNavDrawer = savedInstanceState.getBoolean(CLOSE_NAV_DRAWER);
    }
}

@Override
public boolean onNavigationItemSelected(MenuItem menuItem) {

    // ...

    startActivity(intent);
    mCloseNavDrawer = true;
}

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    savedInstanceState.putBoolean(CLOSE_NAV_DRAWER, mCloseNavDrawer);
    super.onSaveInstanceState(savedInstanceState);
}   

...and use the setDrawerClosed() method to shut the drawer in onResume() with no animation:

@Overrid6e
protected void onResume() {
    super.onResume();

    if(mCloseNavDrawer && mDrawerLayout != null && mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
        mDrawerLayout.setDrawerClosed(GravityCompat.START);
        mCloseNavDrawer = false;
    }
}

I found this the best way to avoid the choppiness without any long perceptual delays.

You could almost use a similar technique to simulate closing the drawer after arriving at an activity, by passing a value in the intent to tell the new activity to open its drawer with no animation from onCreate() and then animate it closed after the activity's layout is finished, however in my experiments the activity transition ruined the effect of the simulation, so you'd also need to disable that.

Angloamerican answered 15/7, 2015 at 1:22 Comment(0)
A
2

First from below links download the sourcode files

DrawerLayout.java

And

ViewDrawerHelper.java

and paste the above two files in your applications util package (or where you want) and take reference of this drawer layout in your activity not of android.support.v4.widget.DrawerLayout change your drawer layout reference in activity's layout file,

Now adjust

private static final int MAX_SETTLE_DURATION = 600; // ms

of ViewDrawerHelper, to speed up just increase the value and to down just decrease the value.

If you want to add action on action bar toggle button then from below links download the source files of

ActionBarDrawerToggle.java

ActionBarDrawerToggleJellybeanMR2.java

ActionBarDrawerToggleHoneycomb.java

and paste the above files in your applications util package (or where you want) Note:- Make sure the imported packages of each newly added file refers to the file which is in your application project, not of android.support.v4.widget.*;

if the above links are not working please add http://

Antislavery answered 3/10, 2014 at 8:22 Comment(1)
This helped me. Definitely an easiest solution. Thanks a lot!Contradictory
V
2

This does not allow you to change the animation speed, but if all you want is to instantly close a drawer you can use the new DrawerLayout.closeDrawer(int/View, bool) methods in v24 of the support library:

drawerLayout.closeDrawer(Gravity.LEFT, false);
Veranda answered 28/6, 2016 at 1:36 Comment(0)
J
1

If you want to force immediatelly disappear the left panel without any animation, you can simply set its x value. When drawer layout opened, its left panel's x value becomes 0, and when closed becomes -1*(its width). So if you set x value -2*width while it is opened, left panel immediatelly disappears. And of course, don't forget to set x to -1*width after it is closed. For Example:

DisplayMetrics metrics = new DisplayMetrics();
this.getWindowManager().getDefaultDisplay().getMetrics(metrics);
//obtain left panel's width in px
private float mToggleStartX=-260*metrics.density; //260 is the width of left panel in dpi

//while drawer layout is opened, to disappear left panel
ll_drawerLayoutMenuPanel.setX(mToggleStartX*2); //ll_drawerLayoutMenuPanel is the left panel layout
mDrawerLayout.closeDrawers();

//don't forget reset x value in the onDrawerClosed method.
mDrawerToggle = new ActionBarDrawerToggle(this,mDrawerLayout,mainToolBar,R.string.drawer_open,R.string.drawer_close) {
    public void onDrawerClosed(View view) {
        super.onDrawerClosed(view);

        ll_drawerLayoutMenuPanel.setX(mToggleStartX);
    }
    ......
};
Jeanettajeanette answered 15/2, 2015 at 13:5 Comment(0)
R
0

I believe that the real meaning of your question is how can I make animation run more smoothly after I click menu in drawerlayout that starts the new activity.
If this is the meaning of your question, this is how I do it.

mLeftDrawer.ItemClick += delegate (object sender, Android.Widget.AdapterView.ItemClickEventArgs e)
        {
            // Mark that item is selected and ask redraw
            e.View.Selected = true;
            adapter.NotifyDataSetChanged();

            var handler = new Handler();
            handler.PostDelayed(new Java.Lang.Runnable(() =>
            {
                _mLeftDrawerLayout.CloseDrawers();

                // Here you should call your activity
            }), 100);

        };
Refection answered 3/8, 2016 at 20:33 Comment(1)
To be honest, i'm not entirely sure WHAT i meant as i asked this question 2 years ago!Ashelman
M
-2

Close drawer after some delay as per following

@Override
public boolean onNavigationItemSelected(MenuItem item) {
        // Handle navigation view item clicks here.

        yourFunction();

        Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                drawer.closeDrawer(GravityCompat.START);
            }
        }, 100);
        return true;
}
Mournful answered 9/2, 2018 at 13:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.