【问题标题】:Closing the navigation drawer after opening another activity打开另一个活动后关闭导航抽屉
【发布时间】:2014-02-23 20:53:33
【问题描述】:

所以我已经实现了导航抽屉,它工作正常。我有用于下一个活动的操作栏项目。通过单击该操作栏图标进入第二个活动时,如果导航抽屉已打开,那么即使用户返回到第一个活动,它仍保持打开状态。我尝试使用

drawerLayout.closeDrawer(drawerListView);

intent 被调用之后,但是第二个 Activity 是在关闭第一个 Activity 的动画完成后开始的。这会造成糟糕的用户体验,甚至我都不喜欢。

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

【问题讨论】:

  • 您可以在开始其他活动时关闭它。尝试在关闭动画时设置加速或忽略动画。看到这个:stackoverflow.com/questions/19460683/…
  • 谢谢,但这是否意味着无法从其他活动中关闭抽屉?
  • 你可以,但你不应该。从另一个活动中交互一个活动的组件不是一个好的决定。在内存不足的情况下,您的后台活动将被破坏,如果您尝试对其组件进行某些操作,您将拥有 NPE。所以做相关活动中应该做的事情。
  • 谢谢。我会这样做的。

标签: android navigation-drawer


【解决方案1】:

您可以使用支持库 v24 中的新 DrawerLayout.closeDrawer(int/View, bool) 方法来立即关闭抽屉:

drawerLayout.closeDrawer(Gravity.LEFT, false);

如果您希望抽屉在单击项目时以动画方式关闭,但在您从另一个活动返回活动时关闭,请放置在您的 onResume 中。

【讨论】:

  • 这个新的animate = false 参数很棒!
【解决方案2】:

在浏览了 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 将导致 knownOpen 设置为 false。然后我们可以将其设置为覆盖旧 LayoutParams 的视图。这需要放在预绘制侦听器中,以防视图尚未布局,例如在屏幕旋转之后。

请询问我是否有人在执行此操作时遇到问题。

【讨论】:

  • 好招!但请注意,您不需要 support-v4 或 appcompat 的本地副本,您只需要在同一包命名空间中创建一个类即可。不需要复制文件。您只需要在/src/android/support/v4/widget/ 中创建一个文件。
【解决方案3】:

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

【讨论】:

  • 不。我试过了。它比调用意图后关闭要快一点,但仍然有一点迟钝(?)。我认为这会更好,这就是为什么根本不向用户显示关闭的原因。
【解决方案4】:

您可以几乎在到达 Activity 后模拟关闭抽屉,方法是在 Intent 中传递一个值来告诉新 Activity 打开它的抽屉,而不使用来自 onCreate() 的动画,然后对其进行动画处理在活动布局完成后关闭,但在我的实验中,活动转换破坏了模拟的效果。

另一种方法是避免抽屉动画,只调用startActivity()而不调用closeDrawer()。 Activity 的过渡动画效果还是不错的,而且是立即发生的,不需要等抽屉关闭动画先完成,没有断断续续的感觉,没有长时间的感知延迟。

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


详情

(如果你只是想看代码,可以跳过这个解释。)

要完成这项工作,您需要一种在使用后退按钮导航回活动时关闭抽屉而没有任何关闭动画的方法。 (不调用closeDrawer() 将使该活动实例中的抽屉保持打开状态;一个相对浪费的解决方法是在导航返回时将活动强制为recreate(),但不这样做也可以解决这个问题。)您还需要确保仅在导航后返回而不是在方向更改后才关闭抽屉,但这很容易。

虽然从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 中需要的包私有部分。

把这个类放到/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);
}   

...并使用setDrawerClosed() 方法在没有动画的情况下关闭onResume() 中的抽屉:

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

...并使用setDrawerClosed() 方法在没有动画的情况下关闭onResume() 中的抽屉:

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

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

【讨论】:

    猜你喜欢
    • 2015-05-17
    • 2023-03-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-25
    • 2020-03-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多