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

3

我已经实现了导航抽屉,它工作得很好。我有一个动作栏项,可以将用户带到下一个活动。当通过单击该动作栏图标进入第二个活动时,如果导航抽屉是打开的,则即使用户返回到第一个活动,它仍然保持打开状态。我尝试使用

drawerLayout.closeDrawer(drawerListView);

在调用intent之后,第二个活动会在关闭第一个活动的动画完成后启动。这会导致糟糕的用户体验,即使我也不喜欢它。

那么有没有办法在第二个活动创建后关闭抽屉?我的意思是从第二个活动的onCreate或其他地方关闭?


1
在开始其他活动时,您可以关闭它。尝试在关闭它时加快速度或忽略动画。请参见此链接:https://dev59.com/-WIk5IYBdhLWcg3wPL7R - Devrim
谢谢,但这是否意味着没有办法从另一个活动关闭抽屉? - vishalmullur
你可以这样做,但最好不要。从另一个活动中与一个活动的组件进行交互并不是一个好决定。在内存不足时,后台活动将被销毁,如果您尝试使用其组件进行某些操作,则会出现NPE错误。因此,请在相关的活动中完成应该完成的工作。 - Devrim
谢谢。我会这样做的。 - vishalmullur
4个回答

7
您可以在支持库v24中使用新的DrawerLayout.closeDrawer(int/View, bool)方法来立即关闭抽屉。
drawerLayout.closeDrawer(Gravity.LEFT, false);

如果您希望在单击项目时抽屉自动关闭,但在从其他活动返回到该活动时保持关闭状态,请将其放置在onResume中。


1
这个新的 animate = false 参数非常棒! - WSBT

5

在浏览了DrawerLayout.java的源代码之后,我找到了一个方法。当用户返回到第一个活动并关闭抽屉时,运行此操作以避免运行动画:

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

解释

首先找到对应于导航视图的视图,这将是drawerLayout的最后一个子视图。将其左侧位置设置为其宽度的负值(用于左侧抽屉)。

正如Lorne Laliberte所提到的那样,您还需要将LayoutParams.knownOpen更改为false才能使其工作,但是除了创建appcompat-v4的本地副本并进行编辑之外,没有其他访问方式,因为这是一个私有字段。这就是我的技巧所在。在Java中,默认布尔值设置为false。使用旧宽度和高度创建一个新的LayoutParams将导致已知打开设置为false。然后,我们可以将此设置为覆盖旧LayoutParams的视图。如果视图尚未布局,则需要将其放置在predraw侦听器中,例如在屏幕旋转后。

如果有人遇到问题,请告诉我。


不错的技巧!但请注意,您不需要支持v4或appcompat的本地副本,只需在相同的包命名空间中创建一个类即可。无需复制文件。您只需要在/src/android/support/v4/widget/中创建一个单独的文件即可。 - Lorne Laliberte

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

不行。我已经尝试过了。虽然比调用意图后关闭要快一点,但仍然有一些迟缓的感觉。我认为如果根本不向用户显示关闭操作,那么效果会更好。 - vishalmullur

0

你可以通过在意图中传递一个值来告诉新活动在 onCreate() 中不使用动画打开抽屉,然后在活动布局完成后将其关闭以模拟到达活动后关闭抽屉的效果,但是在我的实验中,活动转换破坏了模拟效果。

另一种方法是避免抽屉动画,只需调用 startActivity() 而不调用 closeDrawer()。活动转换动画仍然提供了很好的效果,并且它立即发生,无需等待抽屉关闭动画先完成,没有卡顿,没有长时间的感知延迟。

但是,当使用返回按钮导航回原始活动时,您需要一种关闭抽屉而不使用动画的方法。


详细信息

(如果您只想看代码,请跳过此解释。)

为使此功能正常工作,您需要一种方法在使用返回按钮导航回活动时关闭抽屉而不进行任何关闭动画。(不调用closeDrawer()将使抽屉在该活动实例中保持打开状态;一个相对低效的解决方法是强制活动在导航回来时重新创建(),但可以不这样做解决这个问题。)您还需要确保仅在导航后关闭抽屉,而不是在方向更改后关闭抽屉,但这很容易。

尽管从onCreate()调用closeDrawer()会使抽屉默认关闭而没有任何动画,但从onResume()调用它则不然。从onResume()调用closeDrawer()会以短暂可见的动画关闭抽屉。DrawerLayout并未提供任何方法来关闭抽屉而没有该动画,但添加一个不困难。

关闭抽屉实际上只是将其从屏幕上滑出。因此,您可以通过直接将抽屉移动到其“关闭”位置来有效地跳过动画。翻译方向将根据重力而变化(无论是左侧还是右侧抽屉),确切的位置取决于抽屉在布局中与所有子项一起放置后的大小。
然而,仅仅移动它还不够,因为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;
    }
}

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