简体   繁体   English

打开另一个活动后关闭导航抽屉

[英]Closing the navigation drawer after opening another activity

So I have implemented the navigation drawer and it works fine. 所以我已经实现了导航抽屉,它工作正常。 I have action bar item that takes the uses to next activity. 我有一个动作栏项目,用于下一个活动。 When entering the second activity by clicking that action bar icon, if the navigation drawer is open then it remains open even after the user returns back to first activity. 通过单击该操作栏图标进入第二个活动时,如果导航抽屉打开,则即使用户返回到第一个活动,它仍然保持打开状态。 I tried using 我试过用

drawerLayout.closeDrawer(drawerListView);

after the intent is called but what happens is the second activity starts after the animation to close first activity is completed. 在调用intent之后但发生的事情是在关闭第一个活动的动画完成后开始的第二个活动。 This creates a bad user experience and even I dont like it. 这会造成糟糕的用户体验,甚至我也不喜欢它。

So it there any way I could close the drawer after the second activity gets created? 所以在创建第二个活动后我可以用任何方式关闭抽屉吗? I mean from the second activtiy's onCreate or somewhere? 我的意思是从第二个活动的onCreate或某个地方?

You can use the new DrawerLayout.closeDrawer(int/View, bool) methods in v24 of the support library to instantly close a drawer: 您可以在支持库的DrawerLayout.closeDrawer(int/View, bool)使用新的DrawerLayout.closeDrawer(int/View, bool)方法来立即关闭抽屉:

drawerLayout.closeDrawer(Gravity.LEFT, false);

Place in your onResume if you want the drawer to animate closed on item click, but be closed when you go back to the activity from another one. 如果您希望抽屉在项目单击时关闭动画,则放在onResume ,但在从另一个活动返回活动时关闭。

After too long looking through the source of DrawerLayout.java I have found a way. 经过长时间浏览DrawerLayout.java的源代码后,我找到了一种方法。 Run this when the user returns to the first activity to close the drawer without running the animation: 当用户返回第一个活动以关闭抽屉而不运行动画时运行此命令:

View view = drawerLayout.getChildAt(drawerLayout.getChildCount() - 1);
 ViewTreeObserver vto = view.getViewTreeObserver();

 vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
                @Override
                public boolean onPreDraw() {
                    final DrawerLayout.LayoutParams lp = new DrawerLayout.LayoutParams(view.getWidth(), view.getHeight());
                    lp.gravity = Gravity.LEFT;
                    view.setLayoutParams(lp);
                    view.setLeft(-view.getMeasuredWidth());
                    view.getViewTreeObserver().removeOnPreDrawListener(this);
                    return true;
                }
            });

Explanation 说明

First find the view that corresponds to the navigation view, this will be the last child of drawerLayout. 首先找到与导航视图对应的视图,这将是drawerLayout的最后一个子节点。 Set the left position to minus its width (for left side drawers). 将左侧位置设为减去其宽度(对于左侧抽屉)。

As Lorne Laliberte mentioned, you also need to change LayoutParams.knownOpen to false for this to work, however there is no way to access this short of creating a local copy of appcompat-v4 and editing it - as this is a private field. 正如Lorne Laliberte所提到的,你还需要将LayoutParams.knownOpen更改为false才能使其工作,但是无法访问创建appcompat-v4的本地副本并对其进行编辑的缺点 - 因为这是一个私有字段。 This is where my trick comes in. In java a default boolean is set to false. 这就是我的技巧所在。在java中,默认布尔值设置为false。 Creating a new LayoutParams with the old width and height will result in one with knownOpen set to false. 使用旧的宽度和高度创建新的LayoutParams将导致将knownOpen设置为false。 We can then set this to the view overwriting the old LayoutParams. 然后我们可以将其设置为覆盖旧LayoutParams的视图。 This needs to be put inside an predraw listener in case the view hasn't been layed out yet, for example after the screen was rotated. 如果视图尚未布局,例如在旋转屏幕之后,则需要将其放入预绘制侦听器内。

Please ask me if anyone has trouble getting this to work. 请问我是否有人在上班时遇到问题。

在调用意图之前关闭抽屉。

You can almost 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. 您可以在到达活动后几乎模拟关闭抽屉,方法是在意图中传递一个值,告诉新活动打开其抽屉而没有来自onCreate()动画,然后在活动布局完成后将其动画关闭,但是我的实验活动过渡破坏了模拟的效果。

An alternative is to avoid the drawer animation and just call startActivity() without calling closeDrawer() . 另一种方法是避免抽屉动画,只需调用startActivity()而不调用closeDrawer() The activity transition animation still provides a nice effect, and it occurs immediately, with no need to wait for the drawer close animation to finish settling first, no choppiness, no long perceptual delays. 活动过渡动画仍然提供了一个很好的效果,它立即发生,无需等待抽屉关闭动画首先完成稳定,没有波动,没有长时间的感知延迟。

However, you will need a way to close the drawer without animation when navigating back to the original activity with the back button. 但是,当使用后退按钮导航回原始活动时,您将需要一种方法来关闭抽屉而不使用动画。


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. (不调用closeDrawer()将使抽屉在该活动实例中保持打开;相对浪费的解决方法是在导航时强制活动recreate() ,但是可以在不执行此操作的情况下解决此问题。)您还需要如果您在导航后返回,请确保只关闭抽屉,而不是在更改方向后,但这很容易。

Although calling closeDrawer() from onCreate() will make the drawer start out closed without any animation, the same is not true from onResume() . 尽管从onCreate()调用closeDrawer()会使抽屉在没有任何动画的情况下关闭,但onResume()却不是这样。 Calling closeDrawer() from onResume() will close the drawer with an animation that is momentarily visible to the user. onResume()调用closeDrawer()将使用一个暂时可见的动画关闭抽屉。 DrawerLayout doesn't provide any method to close the drawer without that animation, but it's not difficult to add one. DrawerLayout没有提供任何方法来关闭没有动画的抽屉,但添加一个并不困难。

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. 但是,简单地移动它是不够的,因为DrawerLayout在扩展的LayoutParams中保留了一些内部状态,用于知道抽屉是否打开。 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. 由于您正在将支持库编译到您的应用程序中,您可以在android.support.v4.widget包中创建一个类来访问其默认(包私有)部分,或者扩展DrawerLayout而不复制任何其他部分。它需要的课程。 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. (最好尽可能地将代码与实现细节隔离开。)您可以使用moveDrawerToOffset()移动抽屉,并设置LayoutParams以便它知道抽屉已关闭。


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. 注意:如果您只是在不更改LayoutParams情况下调用moveDrawerToOffset() ,则抽屉将在下一个方向更改时移回其打开位置。


Option 1 (use existing DrawerLayout) 选项1(使用现有的DrawerLayout)

This approach adds a utility class to the support.v4 package to gain access to the package-private parts we need inside DrawerLayout. 这种方法为support.v4包添加了一个实用程序类,以便访问DrawerLayout中我们需要的包私有部分。

Place this class into /src/android/support/v4/widget/: 将此类放入/ 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: ...并使用setDrawerClosed()方法在没有动画的情况下关闭onResume()的抽屉:

@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) 选项2(从DrawerLayout扩展)

This approach extends DrawerLayout to add a setDrawerClosed() method. 此方法扩展DrawerLayout以添加setDrawerClosed()方法。

Place this class into /src/android/support/v4/widget/: 将此类放入/ 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: 在活动布局中使用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"
    >

...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: ...并使用setDrawerClosed()方法在没有动画的情况下关闭onResume()的抽屉:

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

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM