如何为支持平板电脑和手机的应用程序提供导航
How to provide navigation for an app that supports tablets and handsets
我知道以前可能有人问过这个问题,但由于我没有找到任何有说服力的东西,我需要再问一次。
当您还需要提供某种导航面板(例如 Navigation Drawer)时,您如何设计一个支持平板电脑和手机的应用程序?
我想我知道如何在纵向手机和平板电脑上使用导航抽屉,因为在那些情况下我当时只显示一个窗格(片段)。但是对于横向的平板电脑来说,这是完全不同的,因为你有足够的空间来显示至少两个窗格(片段),大部分时间将以某种 "mater-detail" 的方式相关。
编辑 #1
只是为了提供一些关于我的应用程序的背景信息,以及为什么我认为我需要除了导航列表之外还有两个窗格。
好吧,我的应用程序的主要目标是帮助服务员 接单 ,这种情况会在以下时间发挥作用:
服务员从左侧菜单(最好是导航抽屉)中选择选项"Take order"
在我称之为 "Content Part" 的第一部分(左窗格)中,服务员可以在他们提供的所有食物类别中进行选择(假设它是top) 然后根据所选的类别,下面的列表显示了该特定类别下的所有菜肴。
一旦服务员点击一道菜,它就会自动添加到 "Content Part"
的第二部分(右窗格)
知道导航抽屉的基本结构是这样的:
<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>
你如何处理 "tablet-in-landscape-orientation" 场景?您是否将嵌套片段用于在这种情况下可能需要使用的多窗格布局?(这听起来非常复杂)您是否使用自定义库?您是否知道 Git 中实现此功能且我可以用作参考的任何开源应用程序?
编辑#2
在尝试了一些想法之后,我意识到为多个活动实现同一个导航抽屉的最方便的方法是创建一个 BaseActivity
来处理所有抽屉功能并且我的所有活动都可以从中进行继承。
这是我目前得到的:
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);
}
}
每个需要导航抽屉的 activity 现在都需要扩展 BaseActivity
:
EuropeanUnionActivity
public class EuropeanUnionActivity extends BaseActivity{
//...
}
此外,每个 activity 布局都需要在其代码中包含一个 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
(抽屉) 的 ID 在所有 activity 布局中必须相同将有一个导航抽屉,因为这些 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.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);
}
欢迎提出任何想法和建议。
谢谢
P.S。我发现 this question 中说使用导航抽屉是不可取的。
对于 phone 和平板纵向模式,您可以使用这样的布局:
<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>
这里是drawer_list:
<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>
在 activity 上,您必须检查您是否处于平板电脑横向模式并显示两个片段。否则,您将首先显示主片段,隐藏细节
如果导航抽屉在所有活动中都可见,请声明一个负责抽屉的抽象 activity。然后,所有的活动都继承自这个抽象activity。您发布的 BaseActivity 就是一个很好的例子
我知道以前可能有人问过这个问题,但由于我没有找到任何有说服力的东西,我需要再问一次。
当您还需要提供某种导航面板(例如 Navigation Drawer)时,您如何设计一个支持平板电脑和手机的应用程序?
我想我知道如何在纵向手机和平板电脑上使用导航抽屉,因为在那些情况下我当时只显示一个窗格(片段)。但是对于横向的平板电脑来说,这是完全不同的,因为你有足够的空间来显示至少两个窗格(片段),大部分时间将以某种 "mater-detail" 的方式相关。
编辑 #1
只是为了提供一些关于我的应用程序的背景信息,以及为什么我认为我需要除了导航列表之外还有两个窗格。
好吧,我的应用程序的主要目标是帮助服务员 接单 ,这种情况会在以下时间发挥作用:
服务员从左侧菜单(最好是导航抽屉)中选择选项"Take order"
在我称之为 "Content Part" 的第一部分(左窗格)中,服务员可以在他们提供的所有食物类别中进行选择(假设它是top) 然后根据所选的类别,下面的列表显示了该特定类别下的所有菜肴。
一旦服务员点击一道菜,它就会自动添加到 "Content Part"
的第二部分(右窗格)
知道导航抽屉的基本结构是这样的:
<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>
你如何处理 "tablet-in-landscape-orientation" 场景?您是否将嵌套片段用于在这种情况下可能需要使用的多窗格布局?(这听起来非常复杂)您是否使用自定义库?您是否知道 Git 中实现此功能且我可以用作参考的任何开源应用程序?
编辑#2
在尝试了一些想法之后,我意识到为多个活动实现同一个导航抽屉的最方便的方法是创建一个 BaseActivity
来处理所有抽屉功能并且我的所有活动都可以从中进行继承。
这是我目前得到的:
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);
}
}
每个需要导航抽屉的 activity 现在都需要扩展 BaseActivity
:
EuropeanUnionActivity
public class EuropeanUnionActivity extends BaseActivity{
//...
}
此外,每个 activity 布局都需要在其代码中包含一个 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
(抽屉) 的 ID 在所有 activity 布局中必须相同将有一个导航抽屉,因为这些 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.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);
}
欢迎提出任何想法和建议。 谢谢
P.S。我发现 this question 中说使用导航抽屉是不可取的。
对于 phone 和平板纵向模式,您可以使用这样的布局:
<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>
这里是drawer_list:
<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>
在 activity 上,您必须检查您是否处于平板电脑横向模式并显示两个片段。否则,您将首先显示主片段,隐藏细节
如果导航抽屉在所有活动中都可见,请声明一个负责抽屉的抽象 activity。然后,所有的活动都继承自这个抽象activity。您发布的 BaseActivity 就是一个很好的例子