如何加快“导航抽屉”关闭的动画速度?

18

实现并按预期工作,因此没有值得在此发布的代码,只是想知道有没有人有加快抽屉打开和关闭所需时间的经验?例如,YouTube 应用程序要快得多!

7个回答

24

你可以调整动画的持续时间,但需要复制支持库中的类,并进行相应的编辑。

ViewDragHelper

ViewDragHelper中确定了这里的持续时间

然后在调用ViewDragHelper.smoothSlideViewTo时,将其应用于DrawerLayout

您需要创建修改版的ViewDragHelper.forceSettleCapturedViewAt,并传入持续时间参数。

forceSettleCapturedViewAt(... int duration)

然后创建你自己版本的ViewDragHelper.smoothSlideViewTo

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

抽屉布局 (DrawerLayout)

接下来,您需要修改DrawerLayout.closeDrawerDrawerLayout.closeDrawers以配合您的新ViewDragHelper修改。

操作栏切换器 (ActionBarDrawerToggle)

您还需要复制ActionBarDrawerToggleActionBarDrawerToggleHoneycomb。但这些文件不需要进行任何编辑。


5
哎呀!有很多事情要做!尽管如此,答案还是完美的。 - Broak
非常感谢!我想知道为什么他们不公开这个值? - theblang
你能给我打开抽屉布局的提示吗?因为当我点击汉堡图标时,我的抽屉布局需要一些时间才能打开。这只发生在我第一次点击汉堡图标时。之后,我的抽屉布局可以平稳地打开和关闭。但问题是第一次打开抽屉布局。请帮忙。谢谢。 - Ganpat Kaliya

7
除了加速动画并等待其完成之外,避免动画就是一个替代方法:只需调用startActivity()而不调用closeDrawer()。虽然你看不到抽屉关闭,但活动过渡动画仍然提供了一个相当不错的效果,并且它立即发生,无需等待抽屉关闭动画先完成,没有卡顿,感知延迟也大大缩短。
详细信息: 为了使其正常工作,需要一种在使用返回按钮导航回活动时无需任何关闭动画即可关闭抽屉的方法(如果不调用closeDrawer()将使该活动实例中的抽屉保持打开状态;一个相对浪费的解决方法是在导航后强制该活动重新创建(recreate()),但可以通过其他方法解决这个问题)。你还需要确保只在导航后关闭抽屉,而不是在方向更改后关闭抽屉,但这很容易解决。
虽然从onCreate()调用closeDrawer()将使抽屉开始处于关闭状态,但从onResume()调用closeDrawer()将关闭带有动画的抽屉,该动画对用户短暂可见。DrawerLayout没有提供任何在没有动画的情况下关闭抽屉的方法,但可以添加一个。
正如@syesilova所指出的那样,关闭抽屉实际上只是将其滑出屏幕。因此,您可以通过直接将抽屉移动到其“关闭”位置来有效地跳过动画。翻译方向将根据引力(左侧或右侧抽屉)而变化,确切的位置取决于抽屉在使用所有子项布局后的大小。
但是,仅仅移动还不够,因为DrawerLayout在扩展的LayoutParams中保留了一些内部状态,用于知道抽屉是否打开。如果您只是将抽屉移出屏幕,则它将无法知道它已关闭,这将导致其他问题。(例如,在下一个方向更改时抽屉将重新出现。)
由于您将支持库编译到应用程序中,因此可以创建一个位于android.support.v4.widget包中的类,以访问其默认(包私有)部分,或者扩展DrawerLayout,而无需复制它需要的任何其他类。这也将减轻更新代码以适应支持库未来更改的负担。(尽可能隔离代码与实现细节之间的联系总是最好的。) 您可以使用moveDrawerToOffset()来移动抽屉,并设置LayoutParams,使其知道抽屉已关闭。
代码如下:
// 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);
/**/

注意:如果仅调用moveDrawerToOffset()而不更改LayoutParams,则抽屉将在下一个方向更改时返回到其打开的位置。


选项1(使用现有的DrawerLayout)

这种方法添加了一个实用程序类到support.v4包中,以获得访问我们需要的DrawerLayout内部的package-private部分。

将此类放入/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);
        /**/
    }
}

当您导航离开时,在活动中设置一个布尔值,指示抽屉应该关闭:

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);
}   

onResume()中使用setDrawerClosed()方法关闭抽屉,且不带动画:

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

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

选项2(从DrawerLayout扩展)

此方法扩展了DrawerLayout,以添加setDrawerClosed()方法。

将此类放入/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);
        /**/
    }
}

在您的活动布局中使用CustomDrawerLayout而不是DrawerLayout:

<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"
    >

当您导航离开时,请在活动中设置一个布尔值,指示抽屉应该关闭:

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);
}   

onResume() 中使用 setDrawerClosed() 方法关闭抽屉,无需动画效果:

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

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

我发现这是避免卡顿而没有任何长时间感知延迟的最佳方法。

你几乎可以使用类似的技术来模拟在到达活动后关闭抽屉,通过在意图中传递一个值告诉新活动从onCreate()打开其抽屉而不带任何动画,然后在活动布局完成后将其关闭动画化,但在我的实验中,活动转换破坏了模拟效果,所以你还需要禁用它。


2

这不允许您更改动画速度,但是如果您只想立即关闭抽屉,您可以使用支持库v24中的新DrawerLayout.closeDrawer(int/View, bool)方法:

drawerLayout.closeDrawer(Gravity.LEFT, false);

2

首先从以下链接下载源代码文件:

DrawerLayout.java

ViewDrawerHelper.java

然后将以上两个文件复制到您的应用程序util包中(或任何您想要的位置),并在您的活动中引用此抽屉布局,而不是android.support.v4.widget.DrawerLayout。在活动的布局文件中更改抽屉布局引用。

现在进行调整。

private static final int MAX_SETTLE_DURATION = 600; // ms

关于ViewDrawerHelper,要加快速度只需增加值,要减慢速度只需降低值。

如果您想在操作栏切换按钮上添加动作,则从以下链接下载源文件:

ActionBarDrawerToggle.java

ActionBarDrawerToggleJellybeanMR2.java

ActionBarDrawerToggleHoneycomb.java

将上述文件复制到应用程序的util包中(或者您想要的任何位置)注意:确保每个新添加文件的导入包都引用了您的应用程序项目中的文件,而不是android.support.v4.widget.*;

如果以上链接无法使用,请添加http://。


这帮了我很多。绝对是最简单的解决方案。非常感谢! - Andrew Dunai

1

如果您想立即强制隐藏左侧面板而不进行任何动画,您可以简单地设置其x值。当抽屉布局打开时,其左侧面板的x值变为0,关闭时变为-1 *(其宽度)。因此,如果您在打开时将x值设置为-2 *宽度,则左侧面板会立即消失。当然,在关闭后不要忘记将x设置为-1 *宽度。

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);
    }
    ......
};

0

我相信你问题的真正意思是,如何在点击抽屉菜单中启动新活动后使动画更加流畅。
如果这是你的问题意思,那么这就是我的做法。

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);

        };

说实话,我现在不太确定我两年前提出这个问题时到底想表达什么! - Broak

-2

根据以下内容,在一定延迟后关闭抽屉

@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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接