导航抽屉:在平板电脑上设置为始终打开

61
6个回答

47

基于大型设备可以有不同的布局文件的想法,我创建了以下项目。

https://github.com/jiahaoliuliu/ABSherlockSlides

亮点:

由于大型设备的抽屉始终可见,因此无需具有抽屉。相反,一个带有两个相同名称元素的LinearLayout就足够了。

<LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="horizontal">
     <ListView
             android:id="@+id/listview_drawer"
             android:layout_width="@dimen/drawer_size"
             android:layout_height="match_parent"
             android:layout_gravity="start"
             android:choiceMode="singleChoice"
             android:divider="@android:color/transparent"
             android:dividerHeight="0dp"
             android:background="@color/drawer_background"/>
    <FrameLayout
            android:id="@+id/content_frame"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginLeft="@dimen/drawer_content_padding"
            />
</LinearLayout>

因为在布局文件中没有抽屉,当应用程序尝试在布局中查找元素时,它将返回 null。因此,没有必要使用额外的布尔变量来查看使用哪个布局。

DrawerLayout mDrawerLayout = (DrawerLayout)findViewById(R.id.drawer_layout);

if (mDrawerLayout != null) {
    // Set a custom shadow that overlays the main content when the drawer opens
    mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
    // Enable ActionBar app icon to behave as action to toggle nav drawer
    getSupportActionBar().setHomeButtonEnabled(true);
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);

    // ActionBarDrawerToggle ties together the proper interactions
    // between the sliding drawer and the action bar app icon
    mDrawerToggle = new ActionBarDrawerToggle(
            this,
            mDrawerLayout,
            R.drawable.ic_drawer,
            R.string.drawer_open,
            R.string.drawer_close) {

        public void onDrawerClosed(View view) {
            super.onDrawerClosed(view);
        }

        public void onDrawerOpened(View drawerView) {
            // Set the title on the action when drawer open
            getSupportActionBar().setTitle(mDrawerTitle);
            super.onDrawerOpened(drawerView);
        }
    };

    mDrawerLayout.setDrawerListener(mDrawerToggle);
}

这里是将其用作布尔值的示例。

@Override
protected void onPostCreate(Bundle savedInstanceState) {
    super.onPostCreate(savedInstanceState);
    if (mDrawerLayout != null) {
        mDrawerToggle.syncState();
    }
}

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    if (mDrawerLayout != null) {
        // Pass any configuration change to the drawer toggles
        mDrawerToggle.onConfigurationChanged(newConfig);
    }
}

1
怎样确定它是一个平板设备?使用res/layout-sw600dp-land就足够了吗? - Alexander Farber
1
这是一个古老但有趣的问题。你可能会在这里找到答案:https://dev59.com/u2gu5IYBdhLWcg3wbGil 请注意,仅通过创建此文件夹并将布局放入其中,并不意味着该布局将被使用。如果您想要,您可以检测它是否为平板电脑,然后通过编程方式强制其处于横向模式。然后这将起作用。祝你好运! - jiahao

32

在CommonsWare的回答基础上,您可以通过进行几个调整来实现这一点。第一个是设置以下三行:

drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN);
drawerLayout.setScrimColor(getResources().getColor(R.color.drawerNoShadow));
isDrawerLocked = true;

drawerNoShadow颜色可以是无alpha通道的颜色(例如0x00000000)。这样就可以得到一个没有背景遮罩的打开抽屉。

第二件要做的事情是调整FrameLayout的padding_left值。为此,您可以设置一个维度来控制此值(默认为0dp)- 在此示例中为R.dimen.drawerContentPadding。您还需要一个R.dimen.drawerSize值,它将是DrawerLayout的宽度。

这使您可以检查FrameLayout的paddingLeft值以调用这些行。

FrameLayout frameLayout = (FrameLayout)findViewById(R.id.content_frame);
if(frameLayout.getPaddingLeft() == (int)getResources().getDimension(R.dimen.drawerSize) {
    drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN);
    drawerLayout.setScrimColor(getResources().getColor(R.color.drawerNoShadow));
    isDrawerLocked = true;
}

你可以将所有不想启用的功能都包含在一个 if(!isDrawerLocked) 语句中。这将包括:

  • drawerLayout.setDrawerListener(drawerToggle);
  • getActionBar().setDisplayHomeAsUpEnabled(true);

最后,你需要为具有静态抽屉的视图设置备用布局。例如:

<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.DrawerLayout

    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- The navigation drawer -->
    <ListView
        android:id="@+id/left_drawer"
        android:layout_width="@dimen/drawerSize"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:choiceMode="singleChoice"
        android:divider="@android:color/transparent"
        android:dividerHeight="0dp"
        android:background="#111"/>

</android.support.v4.widget.DrawerLayout>
<FrameLayout
    android:id="@+id/content_frame"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginLeft="@dimen/drawerContentPadding"/>

这里的美妙之处在于您可以通过为要针对的设备设置备用dimen.xml文件来控制所有逻辑,并且您唯一需要更改的是drawerContentPadding的值并提供修改过的布局。

注意: 我最终使用margin_left而不是padding_left,因为在新布局中它会覆盖抽屉。有关该技术的更深入的博客文章,请参见http://derekrwoods.com/2013/09/creating-a-static-navigation-drawer-in-android/


2
我通过将这段代码移动到onPostCreate()中解决了大部分问题,但如果您在抽屉锁定为打开状态时尝试向左滑动抽屉以将其关闭,则仍会发生崩溃。 - Tony Maro
让我看看能否找到这段代码 - 我已经在两个应用程序中完全集成了它。 - methodin
我在http://derekrwoods.com/2013/09/creating-a-static-navigation-drawer-in-android/中添加了更详细的关于我使用的过程的解释。 - methodin
1
@methodin 如果您能在博客中添加完整的代码,我将不胜感激。 - eddy
1
@eddy 如果你知道如何运行这段代码 http://stackoverflow.com/a/28809296/2148631 - zaPlayer
显示剩余11条评论

14

尝试使用setDrawerLockMode()来锁定大屏设备上的抽屉打开。

正如我在评论中所指出的那样,我认为DrawerLayout并不适用于您的场景(虽然我认为这不是一个坏主意)。要么使用一个承载相同ListView和内容的不同布局,或者可能下载并修改自己的DrawerLayout,在大屏设备上打开时将内容滑动而不是重叠。


1
抽屉已经打开,但是在我的实际布局前面。 - Waza_Be
@Waza_Be:哦,对了。我认为DrawerLayout并不适合你的情况。要么使用一个不同的布局来承载相同的ListView和内容,要么下载并修改自己的DrawerLayout副本,在大屏幕设备上打开时将内容滑动而不是重叠。 - CommonsWare
我接受了你的答案,但如果你能将你的评论复制到答案中会更有用;-) - Waza_Be

9

为平板电脑提供一个备用的布局文件。这样,您可以保存 NavigationView 的所有默认行为。


步骤1

只需创建与此类似的备用布局文件,用于平板设备,并将其放置在 layout-w600dp-land 资源目录中即可。

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:openDrawer="start">

    <!--
    NavigationView and the content is placed in a horizontal LinearLayout
    rather than as the direct children of DrawerLayout.
    This makes the NavigationView always visible.
    -->

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <android.support.design.widget.NavigationView
            android:id="@+id/nav"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:headerLayout="@layout/nav_header_main"
            app:menu="@menu/activity_main_drawer"/>
        <include
            layout="@layout/app_bar_main"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </LinearLayout>

</android.support.v4.widget.DrawerLayout>

步骤2

在这一步中,我们将进行足够的更改,以确保抽屉的打开和关闭仅适用于非平板设备。

步骤2.1

将以下内容添加到值目录中的新值资源文件中,并将其命名为config_ui.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <bool name="isDrawerFixed">false</bool>
</resources>

这是针对非平板设备的。对于平板设备,请创建另一个同名文件并将其放置在values-w600dp-land中。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <bool name="isDrawerFixed">true</bool>
</resources>

在抽屉所属活动的类中创建一个新字段,如下所示:
private boolean isDrawerFixed; 并将其初始化为
isDrawerFixed = getResources().getBoolean(R.bool.isDrawerFixed);
现在我们可以简单地检查设备是平板还是非平板:if (isDrawerFixed){}
步骤2.2:
像这样使用if语句包装设置操作栏上切换按钮的代码。
if (!isDrawerFixed) {
    ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
    drawer.addDrawerListener(toggle);
    toggle.syncState();
}

请将关闭抽屉时的代码与另一个 if 语句包装在一起,如下所示。

if (!isDrawerFixed) {
    drawer.closeDrawer(GravityCompat.START);
}

最终结果会有点像这样。

output


1
这是一个很好的答案。比其他提供的答案更简单明智。谢谢!我使用了 RelativeLayout,而不是 LinearLayout,我不能只使用 LinearLayout 来显示主/详细片段。再次感谢您。额外加分,因为您提供了截图作为证据,并与我的设备进行了比较。 - Sakiboy
1
最佳答案。简单高效。 - Sam Chen

7

之前的回答都很好,但是在我的项目中实现时遇到了一些问题,所以我想分享我的解决方案。 首先,我们需要定义一个自定义抽屉:

public class MyDrawerLayout extends DrawerLayout {
    private boolean m_disallowIntercept;

    public MyDrawerLayout (Context context) {
        super(context);
    }

    @Override
    public boolean onInterceptTouchEvent(final MotionEvent ev) {
        // as the drawer intercepts all touches when it is opened
        // we need this to let the content beneath the drawer to be touchable
        return !m_disallowIntercept && super.onInterceptTouchEvent(ev);
    }

    @Override
    public void setDrawerLockMode(int lockMode) {
        super.setDrawerLockMode(lockMode);
        // if the drawer is locked, then disallow interception
        m_disallowIntercept = (lockMode == LOCK_MODE_LOCKED_OPEN);
    }
}

然后我们将它放到一个基本的活动布局中(不包括之前答案中任意的布局),像这样:
<MyDrawerLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:openDrawer="start">

    <!--We must define left padding for content-->
    <FrameLayout
        android:id="@+id/content_frame"
        android:paddingStart="@dimen/content_padding"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <android.support.design.widget.NavigationView
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:fitsSystemWindows="true"
        app:menu="@menu/menu_nav" />

</MyDrawerLayout>

在纵向方向上,NavigationView的内容填充为0dp,在横向方向上大约为300dp(通过经验确定)。我们在适当的values文件夹中定义它们:

values/dimens.xml -

<dimen name="content_padding">0dp</dimen>

values-land/dimens.xml -

<dimen name="content_padding">300dp</dimen>

最后,我们在活动中锁定抽屉:
    if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
        mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN);
        mDrawerLayout.setScrimColor(0x00000000); // or Color.TRANSPARENT
        isDrawerLocked = true;
    } else if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
        mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
        mDrawerLayout.setScrimColor(0x99000000); // default shadow
        isDrawerLocked = false;
    }

1
我认为这是迄今为止最好的答案,因为它没有涉及重复代码,并且利用了NavigationView API,为Navigation Graphs铺平了道路。干得好。 - John Shelley

0
在2022年,使用导航组件时,只需创建一个备用文件,例如layout-w600dp-land/activity_navdrawer.xml,并使用以下代码(注意include的位置):
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:openDrawer="start">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

    <com.google.android.material.navigation.NavigationView
        android:id="@+id/nav_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:fitsSystemWindows="true"
        app:headerLayout="@layout/nav_header_navdrawer"
        app:menu="@menu/activity_main_drawer" />

        <include
            android:id="@+id/app_bar_navdrawer"
            layout="@layout/app_bar_navdrawer"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </LinearLayout>

</androidx.drawerlayout.widget.DrawerLayout>

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