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

时间:2014-11-09 21:52:16

标签: android navigation navigation-drawer

我已经关注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);
                }
            }
        }
    });

当用户使用原生后退按钮向后导航时,如何关闭导航抽屉?任何建议表示赞赏谢谢!

9 个答案:

答案 0 :(得分:55)

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

  

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

所以你可以得到这样的代码:

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

如果打开,此方法将关闭它,否则将回退到默认行为。

答案 1 :(得分:10)

您需要覆盖活动中的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();
  }
}

要替换抽屉文档中的伪代码外观。我知道这两种方法都存在。

答案 2 :(得分:4)

UPDATE:

从支持库24.0.0开始,无需任何解决方法即可实现。两个新的openDrawercloseDrawer方法have been addedDrawerLayout,允许在没有动画的情况下打开或关闭抽屉。

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

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

为了使这项工作更好,您需要一种方法来关闭抽屉而不用关闭动画导航回使用后退按钮的活动。 (相对浪费的解决方法是在导航时强制将活动强制到recreate(),但是如果不这样做就可以解决这个问题。)

您还需要确保只有在导航后返回时才关闭抽屉,而不是在更改方向后,但这很容易。

详细

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

虽然从closeDrawer()调用onCreate()会使抽屉在没有任何动画的情况下关闭,但onResume()也是如此。从closeDrawer()调用onResume()将使用用户暂时可见的动画关闭抽屉。 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_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;
    }
}

答案 3 :(得分:3)

使用@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时,无论是否使用向上按钮或向后按钮,抽屉都会关闭,没有动画。可能有更好的方法来做到这一点。我的应用程序目前相对简单而且有效,但是等待一个更有效的方法,如果有人有。

答案 4 :(得分:3)

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

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

答案 5 :(得分:1)

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

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

答案 6 :(得分:1)

为什么麻烦?单击抽屉项目时,只需关闭抽屉。这就是官方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); 
    }
}

答案 7 :(得分:0)

简单示例:

抽屉resultDrawer;

public void onBackPressed(){

if (this.resultDrawer.isDrawerOpen()) {    
    this.resultDrawer.closeDrawer();    
} else {    
    super.onBackPressed();    
}

}

答案 8 :(得分:0)

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

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