【问题标题】:How to provide navigation for an app that supports tablets and handsets如何为支持平板电脑和手机的应用提供导航
【发布时间】:2015-03-05 12:37:05
【问题描述】:

我知道以前可能有人问过这个问题,但由于我没有找到任何令人信服的东西,所以我需要再问一次。

当您还需要提供某种导航面板(例如Navigation Drawer)时,如何设计一款支持平板电脑和手机的应用?

我想我知道如何在手机和平​​板电脑上纵向使用导航抽屉,因为在这些情况下,我当时只显示一个窗格(片段)。但是对于横向的平板电脑,情况就完全不同了,因为您有足够的空间来显示至少两个窗格(片段),大多数情况下这些窗格(片段)将以一种“主要细节”的方式关联。

编辑#1


只是为了提供一些关于我的应用程序的上下文以及为什么我认为我需要在导航列表之外有两个窗格。

我的应用程序的主要目标是帮助服务员接受订单,这种情况在以下情况下发挥作用:

  1. 服务员从左侧菜单(最好是导航抽屉)中选择“接受订单”选项

  2. 在我称之为“内容部分”的第一部分(左窗格)中,服务员可以在他们提供的所有食物类别中进行选择(假设它是顶部的列表)并根据选择的类别,下面的列表显示了该特定类别下的所有菜肴。

  3. 服务员点击菜品后,会自动将其添加到“内容部分”的第二部分(右窗格)


知道导航抽屉的基本结构是这样的:

<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <!-- The main content view -->
    <FrameLayout
        android:id="@+id/content_frame"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    <!-- The navigation drawer -->
    <ListView android:id="@+id/left_drawer"
        android:layout_width="240dp"
        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>

您如何处理“横向平板电脑”场景?您是否将嵌套片段用于在这种情况下可能需要使用的多窗格布局?(这听起来非常复杂)您是否使用自定义库?你知道 Git 中有什么开源应用程序可以实现这一点并且我可以用作参考吗?

编辑#2

在尝试了一些想法后,我意识到为多个活动实现相同的抽屉导航最方便的方法是创建一个 BaseActivity 来处理抽屉的所有功能,并且我的所有活动都可以从它继承。

这是我目前得到的:

基础活动

import android.app.Activity;
import android.content.res.Configuration;
import android.os.Bundle;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v4.widget.DrawerLayout;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.ListView;

public class BaseActivity extends Activity {

    private DrawerLayout mDrawerLayout;
    private ActionBarDrawerToggle mDrawerToggle;
    private ListView mDrawerListView;

    protected void onCreate(Bundle savedInstanceState, int resLayoutID) {

        setContentView(resLayoutID);
        super.onCreate(savedInstanceState);
        setupNavDrawer();
    }

    private void setupNavDrawer() {
        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        mDrawerListView = (ListView) findViewById(R.id.drawer);
        mDrawerListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);

        String[] rows = getResources().getStringArray(R.array.drawer_rows);
        mDrawerListView.setAdapter(new ArrayAdapter<String>(
                this, R.layout.drawer_row, rows));
        mDrawerToggle =
                new ActionBarDrawerToggle(this, mDrawerLayout,
                        R.string.drawer_open,
                        R.string.drawer_close);

        mDrawerLayout.setDrawerListener(mDrawerToggle);
        mDrawerListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> listView, View row,
                                    int position, long id) {
                if (position == 0) {
                    Log.d("Menu", "European Union");
                } else {
                    Log.d("Menu", "Other Option");
                }
                mDrawerLayout.closeDrawers();
            }

        });
        getActionBar().setDisplayHomeAsUpEnabled(true);
        getActionBar().setHomeButtonEnabled(true);
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        mDrawerToggle.syncState();
    }


    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        mDrawerToggle.onConfigurationChanged(newConfig);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (mDrawerToggle.onOptionsItemSelected(item)) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
}

每个需要导航抽屉的活动现在都需要扩展BaseActivity

EuropeanUnionActivity

public class EuropeanUnionActivity extends BaseActivity{
    //...
}

此外,每个活动布局都需要在其代码中包含一个 android.support.v4.widget.DrawerLayout 元素,如下所示:

activity_europeanunion

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

    <include layout="@layout/main" />
    <ListView
        android:id="@+id/drawer"
        android:layout_width="240dp"
        android:layout_height="match_parent"
        android:layout_gravity="left"
        android:background="#111"
        android:choiceMode="singleChoice"
        android:divider="@android:color/transparent"
        android:dividerHeight="0dp" />
</android.support.v4.widget.DrawerLayout>

这里需要提一下,DrawerLayout(drawer_layout) 和 ListView(drawer) 的 ID 在所有将具有导航抽屉的活动布局中都必须相同,因为这些 ID 由BaseActivity

为了简洁和快速测试这种方法,我使用了@CommonsWare 的this example(我希望他不介意)。在那里你可以找到@layout/main 的实现。

现在,尽管几乎一切都按预期工作,但我无法理解为什么如果我将 ListView 的代码放在一个单独的文件中 listview_options 导航抽屉简单不会关闭并点击 ActionBarDrawerToggle 只会使应用程序崩溃并在 LogCat 中显示:

FATAL EXCEPTION: main
    Process: com.idealsolution.simplenavigationdrawer, PID: 17725
    java.lang.IllegalArgumentException: No drawer view found with gravity LEFT
            at android.support.v4.widget.DrawerLayout.openDrawer(DrawerLayout.java:1293)
            at android.support.v7.app.ActionBarDrawerToggle.toggle(ActionBarDrawerToggle.java:290)
            at android.support.v7.app.ActionBarDrawerToggle.onOptionsItemSelected(ActionBarDrawerToggle.java:280)
            at com.idealsolution.simplenavigationdrawer.BaseActivity.onOptionsItemSelected(BaseActivity.java:86)
            at android.app.Activity.onMenuItemSelected(Activity.java:2608)
            at com.android.internal.widget.ActionBarView$3.onClick(ActionBarView.java:167)
            at android.view.View.performClick(View.java:4456)
            at android.view.View$PerformClick.run(View.java:18465)
            at android.os.Handler.handleCallback(Handler.java:733)
            at android.os.Handler.dispatchMessage(Handler.java:95)
            at android.os.Looper.loop(Looper.java:136)
            at android.app.ActivityThread.main(ActivityThread.java:5086)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:515)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
            at dalvik.system.NativeStart.main(Native Method)

这是第 86 行:

public boolean onOptionsItemSelected(MenuItem item) {

    if (mDrawerToggle.onOptionsItemSelected(item)) { //Line 86
        return true;
    }
    return super.onOptionsItemSelected(item);
}

这是相同的代码 activity_europeanunion,但对ListView 使用include 标签

<include layout="@layout/main" />
<include layout="@layout/listview_options" />

listview_options.xml的代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/menu_layout"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ListView
        android:id="@+id/drawer"
        android:layout_width="240dp"
        android:layout_height="match_parent"
        android:layout_gravity="left"
        android:background="#111"
        android:choiceMode="singleChoice"
        android:divider="@android:color/transparent"
        android:dividerHeight="0dp" />
</LinearLayout>

这是我在BaseActivity 中尝试检索ListView 的代码,但这显然不起作用:

    private void setupNavDrawer() {
            mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);


           //Please pay special attention to the following two lines
            mDrawerLinearLayout = (LinearLayout) findViewById(R.id.menu_layout);
            mDrawerListView = (ListView) mDrawerLinearLayout.findViewById(R.id.drawer);


            mDrawerListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);

            String[] rows = getResources().getStringArray(R.array.drawer_rows);
            mDrawerListView.setAdapter(new ArrayAdapter<String>(
                    this, R.layout.drawer_row, rows));
            mDrawerToggle =
                    new ActionBarDrawerToggle(this, mDrawerLayout,
                            R.string.drawer_open,
                            R.string.drawer_close);
            mDrawerLayout.setDrawerListener(mDrawerToggle);
            mDrawerListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> listView, View row,
                                        int position, long id) {
                    //...
                    mDrawerLayout.closeDrawers();
                }

            });
            getActionBar().setDisplayHomeAsUpEnabled(true);
            getActionBar().setHomeButtonEnabled(true);
        }

欢迎任何想法和建议。 谢谢。

附:我找到了this question,其中说不建议使用导航抽屉。

【问题讨论】:

  • “你如何处理“横向平板电脑”场景?”——嗯,用水平的LinearLayout 替换FrameLayout 来容纳你的两个窗格?我看不到导航抽屉如何影响问题。您会以与没有导航抽屉相同的方式实现主/详细信息。您甚至可以将LinearLayout 放在一个单独的XML 文件中,然后使用&lt;include&gt; 将其与DrawerLayout 一起放在整体布局中,如果它有助于您可视化问题。
  • “服务员从左侧菜单中选择“接受订单”选项(最好是导航抽屉)”——根据 Google 的设计指南,导航抽屉中的条目应该是名词,而不是动词/命令. “我想我必须使用导航列表之外的两个窗格”——是的,但是这些窗格可以包含在水平的LinearLayout 中。 DrawerLayout 是什么限制你在 UI 中除了抽屉之外有一个“单一”的东西,但它没有说明那个“单一”的东西里面有什么。如果你想要两个FrameLayouts,去吧。
  • 当您不需要时,您可以随时通过setVisibilitity(View.GONE) 让其中一个窗格消失。如果您正确使用权重,则另一个窗格将展开以填充所有可用空间。
  • “那么最终将 DrawerLayout 用于手机和平板电脑(任何方向)是否完全可行?” - 当然。 “你会用那种方法吗?” - 不适用于您描述的用法,因为我会坚持使用更符合 Google 设计指南(名词,而不是动词)的抽屉。但是,除此之外,当然。
  • 我怀疑很多抽屉式导航应用都采用“单一活动”模型,其中一切都只是来来往往的片段,而不是在活动之间转换。话虽如此,如果您的方法令您满意,我认为您的方法没有特别的问题。

标签: android android-fragments navigation-drawer android-ui landscape-portrait


【解决方案1】:

对于手机和平板电脑纵向模式,您可以使用如下布局:

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

    <include layout="@layout/fragments_layout" />
    <include layout="@layout/drawer_list" />
</android.support.v4.widget.DrawerLayout>

这可能是平板电脑横向模式的布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <include layout="@layout/drawer_list" />
    <include layout="@layout/fragments_layout" />
</LinearLayout>

这是抽屉列表:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/drawer_layout"
    android:layout_width="260dp"
    android:layout_height="match_parent"
    android:layout_gravity="start" >

    <ListView
        android:id="@+id/drawer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:choiceMode="singleChoice"
        android:background="#111"
        android:divider="@android:color/darker_gray"
        android:dividerHeight="0dp" />

</RelativeLayout>

和fragments_layout:

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

    <fragment android:id="+@id/master_fragment" />
    <fragment android:id="+@id/detail_fragment" />
</FrameLayout>

在活动中,您必须检查您是否处于平板电脑横向模式并显示两个片段。否则,你会先显示主片段,隐藏细节

如果导航抽屉在所有活动上都可见,则声明一个负责处理抽屉的抽象活动。然后,所有活动都继承自这个抽象活动。您发布的 BaseActivity 就是一个很好的例子

【讨论】:

  • 让我看看我明白你的意思。手机和平板模式的布局可以放在res\layout\main.xml 内并成为默认布局,而平板电脑横向模式的布局可以放在@987654326 内@,然后呢? java代码是真正让我感到困惑的部分。扩展任一 main.xml 版本的 Activity 是否同样负责加载整体布局,指示应加载哪些片段并使用适当的片段填充这些“插槽”并处理由 UI 工作触发的任何事件在一个片段中?
  • 系统负责加载 layout\main.xml 或 \layout-w720dp\main.xml 但您将负责(在代码上)显示两个片段(平板电脑横向)或仅一次一个(电话)
  • 以及drawer_list? 中选择的选项?如果是这样的话,对于单个 Activity 来说,这听起来需要做很多工作,但正如我在问题中提到的那样,我对 Fragments 真的很陌生,我可能正在从一个鼹鼠山中造出一座山
  • 如果导航抽屉在所有活动上都可见,我有一个抽象活动来处理抽屉。然后,所有的活动都继承自这个抽象活动
  • 是的,我想我的所有活动都需要抽屉。
猜你喜欢
  • 2018-05-10
  • 2017-03-06
  • 2013-02-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多