添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
这是一个系列文章,在这个系列里,我会按打造一个 Material Design App 的路线介绍所有应当掌握和值得掌握的系统组件。 你会在这些文章里了解到这些组件的使用和内部实现原理,以及它们背后所反映的 Material Design 的设计思想,希望你会喜欢。 Material Design Part 1 · TextInput详解 Material Design Part 2 · Toolbar详解 Material Design Part 3 · Drawer详解 如页面内容较多,请善用搜索—— Ctrl + F(Command + F) Thanks for reading~! 上一篇文章介绍了 Navigation Drawer ,在文章的最后我说要将它与 Bottom Navigation 进行比较,那么这一篇我就来介绍下 Bottom Navigation 。 如果我没有弄错的话,Bottom Navigation 是在 2016 年上半年被加入 Material Design 里的,这之后加入了 Bottom Navigation 的新版 Google Photos 更是引起了轩然大波,遭到大批 Android 用户的吐槽。 (ಥ_ಥ) 在这篇文章中,我会先介绍 Bottom Navigation ,以及 BottomNavigationView。 在文章的末尾,我会在将它与 Navigation Drawer 作比较的时候,写下自己对于这一设计规范以及产品设计的一些看法。
首先请看下面这张图,了解一下在 MD 的世界里各个组件的“层级关系”。 关于Bottom Navigation BottomNavigationView方法详解 MenuItem点击动效解析( 源码解析 代码示例 BottomNav + ViewPager + Fragment懒加载 Navigation Drawer与Bottom Navigation的比较 关于Bottom Navigation 底部导航栏对于大家来说其实并不陌生,即便在 Android 不推荐使用底部导航栏的年代,依然有很多知名的 App 使用了它,像微信、QQ、淘宝、Instagram 等。终于,Google 也在大力推广的 Material Design 中,加入了自己的底部导航栏—— Bottom Navigation。 首先介绍它的用处, Bottom Navigation 用于用户在 App 的顶级视图之间进行切换。 它的特点(同时也是它的好处)在于它位于 页面底部 ,手指能方便的触碰到,且大多数情况下为 可见 状态,只需要用户 简单直接的点击 即可完成操作。 对于它的用法,官方文档的建议是,如果你有 3 到 5 个同级视图,你就可以使用 Bottom Navigation 作为你的 App 导航方式。同时,这 3 到 5 个视图都需要能够直接的进入,点击 Item 时,不能打开菜单选择或者有其他弹窗等行为,只应该刷新当前视图的内容。 至于它具体的规格,看图。更多的规范细节,可以点链接查看 Bottom-Navigation-Specs 或者 底部导航栏(Bottom navigation)规范指南 。 这里有一点我要特别提出,我在上一篇关于 Drawer 的文章,谈到它和 Bottom Navigation 的取舍时,有朋友在评论里指出,3 到 5 个 Item 用 Bottom Navigation, 5 个以上就用侧边栏。这句话前半句我是不同意的。 首先,官方文档里的用法,只是说 3 到 5 个同级视图才可以用Bottom Navigation ,不属于这个范围不应该使用。至于最终是否使用 Bottom Nav,不仅仅要考虑该因素,更要考虑 App 的导航风格,你是希望你的 App 让用户尽可能的看到更多的内容,还是希望专注于自己的核心功能,给用户提供更加“沉浸”的体验。这应该是一个综合多方因素考虑的问题,而不应该只有一个 if else 。更多的比较,会在本文的最后一节。 BottomNavigationView方法详解 虽然 Bottom Navigation 今年上半年就加入了MD Guideline,但直到 design 包 25.0.0,Google 才提供了 Bottom Navigation 的官方实现。 (注:本文使用"com.android.support:design:25.1.0") 它的用法十分简单,先看一个简单的示例代码: BottomNavigationView_效果视频1—在线播放—优酷网,视频高清在线观看 http://v.youku.com/v_show/id_XMTg3NDI3NjkxNg==.html
activity_bottom_navgation_d.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout 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/activity_bottom_navigation_d" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="bugdev.blogsource.activity.BottomNavigationDActivity"> <android.support.design.widget.BottomNavigationView android:id="@+id/bottom_nav_view_3" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_above="@+id/bottom_nav_view_5" android:layout_marginBottom="30dp" android:background="@color/colorPrimary" android:elevation="8dp" app:itemIconTint="@color/item_bottom_nav_selector" app:itemTextColor="@color/item_bottom_nav_selector" app:menu="@menu/menu_bottom_nav" /> <android.support.design.widget.BottomNavigationView android:id="@+id/bottom_nav_view_5" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:background="@color/colorWhite" android:backgroundTint="@color/colorPrimary" android:elevation="8dp" app:itemIconTint="@color/item_bottom_nav_selector" app:itemTextColor="@color/item_bottom_nav_selector" app:menu="@menu/menu_bottom_nav_4" /> </RelativeLayout> 
item_bottom_nav_selector.xml
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:color="@color/colorWhite" android:state_checked="true" /> <item android:color="@color/colorWhite" android:state_pressed="true" /> <item android:color="@color/colorPrimaryLight"/> </selector> 
menu_bottom_nav.xml
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/item_bottom_home" android:icon="@drawable/ic_home_black_24dp" android:orderInCategory="0" android:title="@string/item_home" /> <item android:id="@+id/item_bottom_explore" android:icon="@drawable/ic_explore_black_24dp" android:orderInCategory="1" android:title="@string/item_explore" /> <item android:id="@+id/item_bottom_mine" android:icon="@drawable/ic_perm_identity_black_24dp" android:orderInCategory="2" android:title="@string/item_mine" /> </menu> 
menu_bottom_nav_5.xml
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/item_bottom_home" android:icon="@drawable/ic_home_black_24dp" android:orderInCategory="0" android:title="@string/item_home" /> <item android:id="@+id/item_bottom_phone" android:icon="@drawable/ic_phone_black_24dp" android:orderInCategory="1" android:title="@string/item_phone" /> <item android:id="@+id/item_bottom_work" android:icon="@drawable/ic_work_black_24dp" android:orderInCategory="2" android:title="@string/item_work" /> <item android:id="@+id/item_bottom_security" android:icon="@drawable/ic_security_black_24dp" android:orderInCategory="3" android:title="@string/item_security" /> <item android:id="@+id/item_bottom_mine" android:icon="@drawable/ic_perm_identity_black_24dp" android:orderInCategory="4" android:title="@string/item_mine" /> </menu> 
BottomNavigationDActivity.java
public class BottomNavigationDActivity extends AppCompatActivity implements BottomNavigationView.OnNavigationItemSelectedListener { @BindView(R.id.bottom_nav_view_3) BottomNavigationView bottomNavView3; @BindView(R.id.bottom_nav_view_5) BottomNavigationView bottomNavView5; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_bottom_navigation_d); ButterKnife.bind(this); bottomNavView3.setOnNavigationItemSelectedListener(this); bottomNavView5.setOnNavigationItemSelectedListener(this); } @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { ToastUtils.showShortToast(item.getTitle().toString()); return true; } } 
代码很简单,无非就是 用menu文件来设置底部栏每个 item 的标题和图标,通过 selector 来设置 item 不同状态时的颜色。 常用方法 & XML属性详解 app:itemBackground: 给 menu的Item 设置 background。但是,这个方法有问题,设置完后水波纹的点击效果就没了,所以暂时请直接使用 background(如示例)。
对应方法: setItemBackgroundResource ( int resId )
app:itemIconTint: 给 Item 的图标设置各种状态下的颜色。
对应方法: setItemIconTintList ( ColorStateList tint )
app:itemTextColor: 给 Item 的标题设置各种状态下的颜色。
对应方法: setItemTextColor ( ColorStateList textColor )
app:menu: 设置 BottomNavigationView 的 menu。 inflateMenu(int resId): 设置 BottomNavigationView 的 menu。
对应方法: Menu getMenu(): 获取设置的 menu。
int getMaxItemCount(): 返回 BottomNavgation 能显示的最大 Item 个数,源码里固定为5。
setOnNavigationItemSelectedListener ( BottomNavigationView.OnNavigationItemSelectedListener listener ):给 BottomNavigationView 设置监听器,监听 Item 的选中情况。 MenuItem点击动效解析 在 Google 推出官方的 Bottom Navigation 组件以前,我使用过 Ashok-Varma/BottomNavigation ,我知道的类似库还有 tyzlmjj/PagerBottomTabStrip sephiroth74/Material-BottomNavigation 。不得不说,相比于这些第三方开源库,BottomNavigationView 还是功能比较简单的。 BottomNavigationView 本身是个继承自 FrameLayout 的 View,我觉得比较值得看看的,就是它的 Item 点击的动画效果。 首先要知道的是,BottomNavigationView 的 Item 有两种形式: Fixed Shifted ,如图所示: 查看源码会发现,设置动画效果的核心代码都在 BottomNavigationItemView setChecked(boolean checked) 方法里,我将分析写成注释写进代码里,这样看起来应该容易理解一些:
@Override public void setChecked(boolean checked) { /**  * 首先,我们要分析 Fixed 和 Shifted 模式的动画效果:  *  * Fixed 模式里 Item 的selected状态和非selected状态,其实仅仅是 Item 的文字大小变了,图标并没有变  * Item 的 title 就是这里的 mLargeLabel 和 mSmallLabel,他们是两个TextView。  *  * Shifted 模式里 Item 的selected状态为图标和文字均可见,非selected状态仅为图标可见  * 同时,可见的 Item 占有一块较大的区域,且图标和文字居中显示。  */ /**  * 首先设置大文字和小文字的坐标  */ ViewCompat.setPivotX(mLargeLabel, mLargeLabel.getWidth() / 2); ViewCompat.setPivotY(mLargeLabel, mLargeLabel.getBaseline()); ViewCompat.setPivotX(mSmallLabel, mSmallLabel.getWidth() / 2); ViewCompat.setPivotY(mSmallLabel, mSmallLabel.getBaseline()); /**  * 这里判断是否为 Shifted 模式,进行不同的设置  *   * 再根据item是否 checked,进行不同的设置  *  */ if (mShiftingMode) { if (checked) { FrameLayout.LayoutParams iconParams = (FrameLayout.LayoutParams) mIcon.getLayoutParams(); iconParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP; iconParams.topMargin = mDefaultMargin; mIcon.setLayoutParams(iconParams); mLargeLabel.setVisibility(VISIBLE); ViewCompat.setScaleX(mLargeLabel, 1f); ViewCompat.setScaleY(mLargeLabel, 1f); } else { FrameLayout.LayoutParams iconParams = (FrameLayout.LayoutParams) mIcon.getLayoutParams(); iconParams.gravity = Gravity.CENTER; iconParams.topMargin = mDefaultMargin; mIcon.setLayoutParams(iconParams); mLargeLabel.setVisibility(INVISIBLE); ViewCompat.setScaleX(mLargeLabel, 0.5f); ViewCompat.setScaleY(mLargeLabel, 0.5f); } mSmallLabel.setVisibility(INVISIBLE); } else { if (checked) { FrameLayout.LayoutParams iconParams = (FrameLayout.LayoutParams) mIcon.getLayoutParams(); iconParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP; iconParams.topMargin = mDefaultMargin + mShiftAmount; mIcon.setLayoutParams(iconParams); mLargeLabel.setVisibility(VISIBLE); mSmallLabel.setVisibility(INVISIBLE); ViewCompat.setScaleX(mLargeLabel, 1f); ViewCompat.setScaleY(mLargeLabel, 1f); ViewCompat.setScaleX(mSmallLabel, mScaleUpFactor); ViewCompat.setScaleY(mSmallLabel, mScaleUpFactor); } else { FrameLayout.LayoutParams iconParams = (FrameLayout.LayoutParams) mIcon.getLayoutParams(); iconParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP; iconParams.topMargin = mDefaultMargin; mIcon.setLayoutParams(iconParams); mLargeLabel.setVisibility(INVISIBLE); mSmallLabel.setVisibility(VISIBLE); ViewCompat.setScaleX(mLargeLabel, mScaleDownFactor); ViewCompat.setScaleY(mLargeLabel, mScaleDownFactor); ViewCompat.setScaleX(mSmallLabel, 1f); ViewCompat.setScaleY(mSmallLabel, 1f); } } /**  * 设置完后,调用 refreshDrawableState() 方法强制 View 刷新 Drawable 状态  */ refreshDrawableState(); } 
BottomNavigationView + ViewPager + Fragment 懒加载 首先要说的是,这个示例其实是不大符合 Material Design 规范的, 在 Bottom Navigation 的规范里,明确指出在应当避免在内容区域使用滑动手势来切换视图,两个视图之间的切换应通过点击 BottomNavigationView 的 Item 来实现,在切换时,视图之间的过渡应有淡入淡出效果。 为什么我还要写这个代码呢?....先看吧,最后我会说明原因。 首先是 效果视频 (知乎专栏不支持 gif 动图,只有给大家看视频了) BottomNavigationView_效果视频2—在线播放—优酷网,视频高清在线观看 http://v.youku.com/v_show/id_XMTg3NDYwNjEwOA==.html 接下来是代码部分,这个 Demo 主要有两部分,一个是 Fragment 的懒加载,还有个就是将 BottomNavigationView 与 ViewPager 联系起来。 首先,Fragment 懒加载部分,先定义一个 BaseFragment:
/**  * @author bugdev  * @email [email protected]  */ public abstract class BaseFragment extends Fragment { protected Activity mActivity; protected View mRootView; private Unbinder unbinder; /**  * 说明:在此处保存全局的Context  *  * @param context 上下文  */ @Override public void onAttach(Context context) { super.onAttach(context); mActivity = (Activity) context; } @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { mRootView = inflater.inflate(getLayoutId(), container, false); unbinder = ButterKnife.bind(this, mRootView); init(); return mRootView; } /**  * @return 返回该Fragment的layout id  */ protected abstract int getLayoutId(); /**  * 说明:创建视图时的初始化操作均写在该方法  */ protected abstract void init(); /**  * 获取控件对象  *  * @param id 控件id  * @return 控件对象  */ public View findViewById(int id) { if (getContentView() != null) { return getContentView().findViewById(id); } else { return null; } } /**  * 说明:返回当前View  *  * @return view  */ protected View getContentView() { return mRootView; } @Override public void onDestroyView() { super.onDestroyView(); unbinder.unbind(); } } 
接下来将LazyLoadFragment继承自Fragment:
/**  * 懒加载Fragment  *  * 可以加载数据的条件:  * 1.视图已经初始化  * 2.视图对用户可见  *  * @author bugdev  * @email [email protected]  */ public abstract class LazyLoadFragment extends BaseFragment { public boolean isInit = false;//视图是否已经初始化 public boolean isLoad = false;//视图是否已经加载过 /**  * 初始化  */ @Override protected void init() { isInit = true; isCanLoadData(); } /**  * 判断是否可以加载数据,如果可以便进行数据的加载  */ private void isCanLoadData() { if (!isInit) { return; } if (getUserVisibleHint()) { lazyLoad(); isLoad = true; } else { if (isLoad) { stopLoad(); } } } /**  * 当视图初始化并且对可见时加载数据  */ public abstract void lazyLoad(); /**  * 当该视图对用户不可见并且已经加载过数据的时候,如果需要在切换到其他页面时停止加载数据,通过覆写此方法实现  */ public void stopLoad() { } /**  * 说明:当前视图可见性发生变化时调用该方法  *  * @param isVisibleToUser 当前视图是否可见  */ @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); isCanLoadData(); } /**  * 视图销毁时将Fragment是否初始化的状态变为false  */ @Override public void onDestroyView() { super.onDestroyView(); isInit = false; isLoad = false; } } 
以上就完成了一个懒加载的Fragment,下面编写Activity部分,首先是布局文件:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout 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/activity_bottom_navigation_t" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="bugdev.blogsource.activity.BottomNavigationTActivity"> <android.support.design.widget.AppBarLayout android:id="@+id/appbar_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/AppTheme.AppBarOverlay"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:popupTheme="@style/AppTheme.PopupOverlay"> <TextView android:id="@+id/toolbar_title_tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:fontFamily="cursive" android:text="@string/app_name" android:textColor="@color/colorPrimaryLight" android:textSize="28sp" android:textStyle="bold" /> </android.support.v7.widget.Toolbar> </android.support.design.widget.AppBarLayout> <android.support.v4.view.ViewPager android:id="@+id/home_view_pager" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> <android.support.design.widget.BottomNavigationView android:id="@+id/bottom_nav_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/colorPrimary" android:elevation="8dp" app:itemIconTint="@color/item_bottom_nav_selector" app:itemTextColor="@color/item_bottom_nav_selector" app:menu="@menu/menu_bottom_nav" /> </LinearLayout> 
接下来就是Activity部分:
public class BottomNavigationTActivity extends AppCompatActivity { @BindView(R.id.toolbar_title_tv) TextView toolbarTitleTv; @BindView(R.id.toolbar) Toolbar toolbar; @BindView(R.id.home_view_pager) ViewPager homeViewPager; @BindView(R.id.bottom_nav_view) BottomNavigationView bottomNavView; @BindView(R.id.activity_bottom_navigation_t) LinearLayout activityBottomNavigationT; MenuItem prevMenuItem; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_bottom_navigation_t); ButterKnife.bind(this); //初始化Toolbar initToolbar(); //初始化Viewpager initViewPager(); //初始化Bottom Navigation initBottomNav(); } private void initToolbar() { toolbar.setTitle(""); setSupportActionBar(toolbar); if (getSupportActionBar() != null) getSupportActionBar().setDisplayHomeAsUpEnabled(false); } private void initViewPager() { homeViewPager.setAdapter(new FragmentStatePagerAdapter(getSupportFragmentManager()) { @Override public Fragment getItem(int position) { Fragment fragment = null; switch (position) { case 0: fragment = new HomeFragment(); break; case 1: fragment = new ExploreFragment(); break; case 2: fragment = new MineFragment(); break; } return fragment; } @Override public int getCount() { return 3; } }); homeViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int position) { invalidateOptionsMenu(); /**  * 该方法只有在有新的页面被选中时才会回调  *  * 如果 preMenuItem 为 null,说明该方法还没有被回调过  * 则ViewPager从创建到现在都处于 position 为 0 的页面  * 所以当该方法第一次被回调的时候,直接将 position 为 0 的页面的 selected 状态设为 false 即可  *  * 如果 preMenuItem 不为 null,说明该方法内的  * "prevMenuItem = bottomNavView.getMenu().getItem(position);"  * 之前至少被调用过一次  * 所以当该方法再次被回调的时候,直接将上一个 prevMenuItem 的 selected 状态设为 false 即可  * 在做完上一句的事情后,将当前页面设为 prevMenuItem,以备下次调用  *  * 我注释写这么详细,是不是要给我搭个赏~ (ಥ_ಥ)  */ if (prevMenuItem == null) { bottomNavView.getMenu().getItem(0).setChecked(false); } else { prevMenuItem.setChecked(false); } bottomNavView.getMenu().getItem(position).setChecked(true); prevMenuItem = bottomNavView.getMenu().getItem(position); } @Override public void onPageScrollStateChanged(int state) { } }); } private void initBottomNav() { bottomNavView.setOnNavigationItemSelectedListener( new BottomNavigationView.OnNavigationItemSelectedListener() { @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { homeViewPager.setCurrentItem(item.getOrder()); return true; } }); } } 
Fragment部分很简单,我就不放出来了,以上就是一个 BottomNavigationView + ViewPager + Fragment 懒加载的例子。 我在代码里将注释都写的很详细,所以也不用再过多的讲解。 讲道理注释这么良心,应该打个赏。 讲道理注释这么良心,应该打个赏。 讲道理注释这么良心,应该打个赏。 (ಥ_ಥ) 最后总结一下思路 懒加载 Fragment 的思路: 通过两个布尔值变量来分别控制 Fragment 是否初始化和是否已经加载过。 ViewPager + BottomNavigationView 的思路: 通过监听 ViewPager 的滑动来将 BottomNavigationView 相应位置上的 Item 设为 selected 状态,同时监听 BottomNavigationView ,在 Item 被点击的时候也将 ViewPager 调至相应的页面。 需要注意的是: 由于 BottomNavigationView 没有提供公开的API,可以对 Item 的布局模式进行设置,所以如果你的视图超过了 3 个,就只有改写或弃用这种滑动切换视图的方式了。 Navigation Drawer 与 Bottom Navigation的比较 首先说些大方向上的东西 第一,设计规范是不断演化更新的; 第二,规范是由一部分比较擅长这方面的人写出来的,但他们同时也受到其他方面的影响,所以他们正确的概率比较大,但不是100%; 第三,规范存在的意义是为了帮助开发者创造出更好的应用,用专业的理解来帮助其他人,使得整个应用环境更好,让用户体验更棒; 第四,在产品设计上,我们做出的所有决定都应该基于“为用户带来更好的使用体验”,在这之上才是商业或其他方面的东西。 所以当我们面临类似 Navigation Drawer 与 Bottom Navigation 的取舍,又或是否可以允许通过滑动配合 Bottom Navigation 来切换页面的选择的时候,我们首先想到的应该是,这样做了好不好?这样是否真的给 App 带来了体验的提升? 就我来说,我很喜欢微信 Android 端可以滑动切换页面的方式,我并不 care 它是否符合规范,它只是让我的使用更加舒服了。 所以,我希望看到这段话的人,在接下来做自己产品的时候,除了参考规范,更要有自己的思考。说到底,App 是你的作品。 再说下 Bottom Navigation 与 Navigation Drawer 的比较 前段时间我去了北京的 2016 Google 开发者大会,听了其中一场有关 Material Design 的演讲,讲者正好就提到了这二者的比较,深得我心。我加入自己的观点总结一下就是: Drawer 的好处在于,在手机端上,它是可以隐藏的,不会始终占用屏幕。但它的坏处也是这个,它会隐藏。所以如果你希望自己的 App 多提高一些内容模块的展示率,你可以考虑使用 Bottom Navigation,用户可以方便的点击查看更多的内容。那么如果你做了个类似 Gmail 的邮件应用,Drawer 或许是个更好的导航选择。这些都不需要非常的死板,完全取决于你希望自己 App 的导航风格是怎样的,取决于你认为自己的作品怎样做才能给用户最好的使用体验。 最后,希望大家都可以做出优秀的作品。 以上:-) Bottom Navigation | Material Design BottomNavigationView | Android Developers Easy Way To Connect BottomNavigationView & ViewPager in Android Android中ViewPager+Fragment取消(禁止)预加载延迟加载(懒加载)问题解决方案 Tab Bars - iOS Human Interface Guidelines weixin.qq.com/r/yTgrM-D (二维码自动识别) 微信公众号: BugDev「bugdev」 欢迎关注啦~