当用户按下后退按钮时隐藏导航抽屉

Posted

技术标签:

【中文标题】当用户按下后退按钮时隐藏导航抽屉【英文标题】:Hide navigation drawer when user presses back button 【发布时间】:2015-01-06 03:54:47 【问题描述】:

我按照 Google 的官方开发者教程 here 创建了一个抽屉式导航。

目前,一切正常,除了用户使用 android 在屏幕底部提供的本机后退按钮(以及主页和最近使用的应用程序按钮)。如果用户使用本机返回按钮向后导航,导航抽屉仍将打开。如果用户使用 ActionBar 导航回来,导航抽屉将按照我想要的方式关闭。

我的代码几乎与官方教程相同,除了我如何处理用户选择抽屉上的项目:

   mDrawerList.setOnItemClickListener(new ListView.OnItemClickListener()
    
        @Override
        public void onItemClick(AdapterView parent, View view, int position, long id)
        
            switch(position)
            
                case 0:
                
                    Intent intent = new Intent(MainActivity.this, NextActivity.class);
                    startActivity(intent);
                
            
        
    );

当用户使用本机后退按钮导航返回时,如何关闭导航抽屉?任何建议表示赞赏。谢谢!

【问题讨论】:

【参考方案1】:

您必须覆盖onBackPressed()。来自文档:

当 Activity 检测到用户按下背部时调用 钥匙。默认实现只是完成当前活动, 但是你可以覆盖它来做任何你想做的事情。

所以你可以有这样的代码:

@Override
public void onBackPressed() 
    if (this.drawerLayout.isDrawerOpen(GravityCompat.START)) 
        this.drawerLayout.closeDrawer(GravityCompat.START);
     else 
        super.onBackPressed();
    

如果是打开的,这个方法会关闭它,否则回退到默认行为。

【讨论】:

如果您想在按下返回按钮时关闭抽屉而不是此时关闭应用程序,请使用isDrawerVisible而不是isDrawerOpen 在打开抽屉项目时关闭抽屉可能会容易得多(并且它会为您节省动画):***.com/a/43559228/888881【参考方案2】:

您需要在您的活动中覆盖onBackPressed() 并检查导航抽屉打开的情况。如果它是打开的,则关闭它,否则执行正常的后按方法。下面是一些混合了一些伪代码的代码来帮助你:

@Override
public void onBackPressed()
  if(drawer.isDrawerOpen()) //replace this with actual function which returns if the drawer is open
   drawer.close();     // replace this with actual function which closes drawer
  
  else
   super.onBackPressed();
  

要替换伪代码,请查看抽屉的文档。我知道这两种方法都存在。

【讨论】:

【参考方案3】:

这是您问题的替代解决方案。

@Override    
public void onBackPressed()    
    if(drawerLayout.isDrawerOpen(navigationView))    
        drawerLayout.closeDrawer(navigationView);    
    else     
        finish();    
        
    

【讨论】:

【参考方案4】:

更新:

从支持库 24.0.0 开始,这无需任何变通方法即可实现。两个新的 openDrawer 和 closeDrawer 方法 have been added 到 DrawerLayout 允许在没有动画的情况下打开或关闭抽屉。

您现在可以使用openDrawer(drawerView, false)closeDrawer(drawerView, false) 毫不延迟地打开和关闭抽屉。


如果您在没有调用closeDrawer() 的情况下调用startActivity(),则当您使用后退按钮导航回该活动实例时,抽屉将保持打开状态。当您调用 startActivity() 时调用 closeDrawer() 有几个问题,从不稳定的动画到较长的感知延迟,具体取决于您使用的解决方法。所以我同意最好的方法是调用startActivity(),然后在返回时关闭抽屉。

为了使这项工作顺利进行,您需要一种在使用后退按钮导航回活动时关闭抽屉而不显示关闭动画的方法。 (一个相对浪费的解决方法是在导航返回时只强制活动到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); 
        
        // set internal state so DrawerLayout knows it's closed
        final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
        lp.onScreen = 0.f;
        lp.knownOpen = false;

        invalidate();

注意:如果您只调用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); 
        
        // 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();
    

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

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); 
        
        // set internal state so DrawerLayout knows it's closed
        final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
        lp.onScreen = 0.f;
        lp.knownOpen = false;
        
        invalidate();
    

    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); 
        
        // set internal state so DrawerLayout knows it's closed
        final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
        lp.onScreen = 0.f;
        lp.knownOpen = false;

        invalidate();
    

在您的活动布局中使用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_
    android:layout_
    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;
    

【讨论】:

【参考方案5】:

使用@James Cross 提供的答案的实现是可行的,但是关闭抽屉的动画是不可取的且无法修复,example.

@Override
public void onResume()

    super.onResume();
    mDrawerLayout.closeDrawers();

一种解决方法是在按下设备后退按钮时重新启动活动。这对我来说似乎并不理想,但它确实有效。按照@mt0s 和@Qazi Ahmed 的建议覆盖onBackPressed(),并传递一个额外的来确定调用活动:

    mDrawerList.setOnItemClickListener(new ListView.OnItemClickListener()
    
        @Override
        public void onItemClick(AdapterView parent, View view, int position, long id)
        
            switch(position)
            
                case 0:
                
                    Intent intent = new Intent(MainActivity.this, NextActivity.class);
                    //pass int extra to determine calling activity
                    intent.putExtra(EXTRA_CALLING_ACTIVITY, CallingActivityInterface.MAIN_ACTIVITY);
                    startActivity(intent);
                
            
        
    );

NextActivity.class,检查调用活动:

@Override
public void onBackPressed()

    int callingActivity = getIntent().getIntExtra(EXTRA_CALLING_ACTIVITY, CallingActivityInterface.MAIN_ACTIVITY);
    switch(callingActivity)
    
        case CallingActivityInterface.MAIN_ACTIVITY:
        
            Intent intent = new Intent(this, MainActivity.class);
            startActivity(intent);
            finish();
        
        ...
    

这样,当我返回MainActivity 时,无论我使用向上按钮还是返回按钮,抽屉都会关闭且没有动画。可能有更好的方法来做到这一点。我的应用程序目前相对简单,并且可行,但如果有人有的话,我等待更有效的方法。

【讨论】:

【参考方案6】:

为什么这么麻烦?单击抽屉项目时只需关闭抽屉。这就是在 Google Play 官方应用中的处理方式。

private class DrawerItemClickListener implements ListView.OnItemClickListener 
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) 
         drawerLayout.closeDrawer(GravityCompat.START, false);
         selectItem(position); 
    

【讨论】:

为什么这么麻烦?因为 closeDrawer 直到 issuetracker.google.com/issues/37087029 才存在 @Damnum “为什么这么麻烦?点击抽屉项目时只需关闭抽屉”,这是最好的答案,它对我有用,尽管我的工作没有第二个参数(假)被移除。哥们点赞!【参考方案7】:

您可能希望确保在打开活动时始终关闭导航绘制。使用它来做到这一点:

@Override
public void onResume()
    mDrawerList.closeDrawer(Gravity.LEFT);

【讨论】:

谢谢,这可行,但有一个小问题。从 ActionBar 导航回到我在启动时看到的屏幕,根本看不到抽屉。使用后退按钮将关闭抽屉,但我仍然看到它正在关闭时的一瞥。文档说它是“动画看不见的”。我怎样才能摆脱动画,让它根本不可见?【参考方案8】:

简单示例:

Drawer resultDrawer;

public void onBackPressed()
    if (this.resultDrawer.isDrawerOpen())     
        this.resultDrawer.closeDrawer();    
     else     
        super.onBackPressed();    
    

【讨论】:

【参考方案9】:

使用androidx.drawerlayout:drawerlayout:1.1.0 或更高版本,您可以使用isOpen 和close() 保持简单。

// YourActivity.kt
override fun onBackPressed() 
    if (drawerLayout.isOpen) 
        drawerLayout.close()
     else 
        super.onBackPressed()
    

【讨论】:

【参考方案10】:

我是这样做的:

@Override
    public void onBackPressed() 

        if(drawerLayout.isDrawerOpen(navigationView))
            drawerLayout.closeDrawer(Gravity.LEFT);
        else
            super.onBackPressed();
        
    

【讨论】:

以上是关于当用户按下后退按钮时隐藏导航抽屉的主要内容,如果未能解决你的问题,请参考以下文章

隐藏导航栏但保留后退按钮 - SwiftUI

如何使向上按钮返回而不是打开导航抽屉

导航抽屉,处理后退按钮以转到以前的片段?

导航栏隐藏标题和后退按钮 iOS 7

PresentViewController 在 iOS 中隐藏导航栏

Android Studio - 带回栈的导航抽屉