现在很多APP,如微信、QQ、微博等等,它们的主页面都无一例外的选择使用底部Tab导航, 通过这种方式,可以很好的把页面层级分化,很好的提高用户体验。相信,很多Android开发者,都使用到过这种经典的设计,可是您你能保证您的设计真的没问题么?

为啥我会有这个疑问呢? 因为我日前就遇到了这么一个情况,发现我做的APP导航页有问题。 具体可以参考这篇博客:【Android】保存Fragment切换状态 , 首先说明的是,我的项目是从之前就沿用下来的框架,页面底部tab的实现,就是采用前面博客提到的方式,
可是在测试的时候,竟然发现,使用这种方式来实现,经常会发生tab重叠情况: 比如,此刻选中的事“首页”tab,可是内容确实“活动”tab,尤其是在你的app在二级页面发生崩溃返回到一级页面时,这种情况经常发生! 当然,这篇博客的评论里面,也提到了这个问题,所以,最后大家建议大家采用的是;"推荐直接使用ViewPager,通过自定义ViewPager禁用掉左右滑动和自动销毁即可"

在我接触的APP中,我觉得新浪微博的设计当然是最经典的,为啥呢?就因为它多了一个功能,“底部tab的双击,来实现列表滚动到最上方并刷新博客列表”,要知道,这样的设计,可以极大提高用户体验的(避免了用户手动滚动到最上方,然后下拉刷新...),接下来,将带着大家学习如何去实现吧。

先看效果图(尤其是日志):

1. 直接定义tab页面,一个ViewPager,四个RadioButton:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:orientation="vertical">
  6.  
  7. <com.lnyp.vf.ContainerViewPager
  8. android:id="@+id/viewpager"
  9. android:layout_width="match_parent"
  10. android:layout_height="match_parent"
  11. android:layout_marginBottom="60dp" />
  12.  
  13. <RadioGroup
  14. android:id="@+id/radiogroup"
  15. android:layout_width="fill_parent"
  16. android:layout_height="60dp"
  17. android:background="#ececec"
  18. android:layout_alignParentBottom="true"
  19. android:orientation="horizontal">
  20.  
  21. <RadioButton
  22. android:id="@+id/radio_main"
  23. style="@style/navigation_style"
  24. android:checked="true"
  25. android:drawableTop="@drawable/selector_main_bottom_tab_first"
  26. android:paddingLeft="0dp"
  27. android:text="首页" />
  28.  
  29. <RadioButton
  30. android:id="@+id/radio_projects"
  31. style="@style/navigation_style"
  32. android:checked="false"
  33. android:drawableTop="@drawable/selector_main_bottom_tab_second"
  34. android:paddingLeft="0dp"
  35. android:text="活动" />
  36.  
  37. <RadioButton
  38. android:id="@+id/radio_studys"
  39. style="@style/navigation_style"
  40. android:checked="false"
  41. android:drawableTop="@drawable/selector_main_bottom_tab_third"
  42. android:paddingLeft="0dp"
  43. android:text="社区" />
  44.  
  45. <RadioButton
  46. android:id="@+id/radio_user_center"
  47. style="@style/navigation_style"
  48. android:checked="false"
  49. android:drawableTop="@drawable/selector_main_bottom_tab_forth"
  50. android:paddingLeft="0dp"
  51. android:text="我的" />
  52. </RadioGroup>
  53.  
  54. </RelativeLayout>

2. 自定义PagerAdapter,为ViewPager添加布局(Fragment),要求可以实现Fragment切换,状态的保存:

  1. import android.support.v4.app.Fragment;
  2. import android.support.v4.app.FragmentManager;
  3. import android.support.v4.app.FragmentTransaction;
  4. import android.support.v4.view.PagerAdapter;
  5. import android.view.View;
  6. import android.view.ViewGroup;
  7.  
  8. import java.util.List;
  9.  
  10. /**
  11. * 为ViewPager添加布局(Fragment),绑定和处理fragments和viewpager之间的逻辑关系
  12. * 可保持Fragment切换状态
  13. */
  14. public class FragmentViewPagerAdapter extends PagerAdapter implements MyViewPager.OnPageChangeListener {
  15. private List<Fragment> fragments; // 每个Fragment对应一个Page
  16. private FragmentManager fragmentManager;
  17. private ContainerViewPager viewPager; // viewPager对象
  18. private int currentPageIndex = 0; // 当前page索引(切换之前)
  19.  
  20. private OnExtraPageChangeListener onExtraPageChangeListener; // ViewPager切换页面时的额外功能添加接口
  21.  
  22. public FragmentViewPagerAdapter(FragmentManager fragmentManager, ContainerViewPager viewPager, List<Fragment> fragments) {
  23. this.fragments = fragments;
  24. this.fragmentManager = fragmentManager;
  25. this.viewPager = viewPager;
  26. this.viewPager.setAdapter(this);
  27.  
  28. this.viewPager.setOnPageChangeListener(this);
  29. }
  30.  
  31. @Override
  32. public int getCount() {
  33. return fragments.size();
  34. }
  35.  
  36. @Override
  37. public boolean isViewFromObject(View view, Object o) {
  38. return view == o;
  39. }
  40.  
  41. @Override
  42. public void destroyItem(ViewGroup container, int position, Object object) {
  43. container.removeView(fragments.get(position).getView()); // 移出viewpager两边之外的page布局
  44. }
  45.  
  46. @Override
  47. public Object instantiateItem(ViewGroup container, int position) {
  48. Fragment fragment = fragments.get(position);
  49. if (!fragment.isAdded()) { // 如果fragment还没有added
  50. FragmentTransaction ft = fragmentManager.beginTransaction();
  51. ft.add(fragment, fragment.getClass().getSimpleName());
  52. ft.commit();
  53. /**
  54. * 在用FragmentTransaction.commit()方法提交FragmentTransaction对象后
  55. * 会在进程的主线程中,用异步的方式来执行。
  56. * 如果想要立即执行这个等待中的操作,就要调用这个方法(只能在主线程中调用)。
  57. * 要注意的是,所有的回调和相关的行为都会在这个调用中被执行完成,因此要仔细确认这个方法的调用位置。
  58. */
  59. fragmentManager.executePendingTransactions();
  60. }
  61.  
  62. if (fragment.getView().getParent() == null) {
  63. container.addView(fragment.getView()); // 为viewpager增加布局
  64. }
  65.  
  66. return fragment.getView();
  67. }
  68.  
  69. /**
  70. * 当前page索引(切换之前)
  71. *
  72. * @return
  73. */
  74. public int getCurrentPageIndex() {
  75. return currentPageIndex;
  76. }
  77.  
  78. public OnExtraPageChangeListener getOnExtraPageChangeListener() {
  79. return onExtraPageChangeListener;
  80. }
  81.  
  82. /**
  83. * 设置页面切换额外功能监听器
  84. *
  85. * @param onExtraPageChangeListener
  86. */
  87. public void setOnExtraPageChangeListener(OnExtraPageChangeListener onExtraPageChangeListener) {
  88. this.onExtraPageChangeListener = onExtraPageChangeListener;
  89. }
  90.  
  91. @Override
  92. public void onPageScrolled(int i, float v, int i2) {
  93. if (null != onExtraPageChangeListener) { // 如果设置了额外功能接口
  94. onExtraPageChangeListener.onExtraPageScrolled(i, v, i2);
  95. }
  96. }
  97.  
  98. @Override
  99. public void onPageSelected(int i) {
  100. fragments.get(currentPageIndex).onPause(); // 调用切换前Fargment的onPause()
  101. if (fragments.get(i).isAdded()) {
  102. fragments.get(i).onResume(); // 调用切换后Fargment的onResume()
  103. }
  104. currentPageIndex = i;
  105.  
  106. if (null != onExtraPageChangeListener) { // 如果设置了额外功能接口
  107. onExtraPageChangeListener.onExtraPageSelected(i);
  108. }
  109. }
  110.  
  111. @Override
  112. public void onPageScrollStateChanged(int i) {
  113. if (null != onExtraPageChangeListener) { // 如果设置了额外功能接口
  114. onExtraPageChangeListener.onExtraPageScrollStateChanged(i);
  115. }
  116. }
  117.  
  118. /**
  119. * page切换额外功能接口
  120. */
  121. public static class OnExtraPageChangeListener {
  122. public void onExtraPageScrolled(int i, float v, int i2) {
  123. }
  124.  
  125. public void onExtraPageSelected(int i) {
  126. }
  127.  
  128. public void onExtraPageScrollStateChanged(int i) {
  129. }
  130. }
  131. }

注: 上述代码中,有个ContainerViewPager,该ContainerViewPager是继承ViewPager,主要是为了去除ViewPager左右滑动功能,大家可以再源码里直接看到。

3. 自定义四个(随便几个)Fragment, 每个Fragment就是ViewPager里要承载的View,负责显示每个Tab页面

4. 实现MainActivity.java,为ViewPager设置PageAdapter, 并且,在MainActivity中实现双击tab功能:

  1. import android.os.Bundle;
  2. import android.os.SystemClock;
  3. import android.support.v4.app.Fragment;
  4. import android.support.v4.app.FragmentActivity;
  5. import android.view.View;
  6. import android.widget.RadioButton;
  7.  
  8. import java.util.ArrayList;
  9. import java.util.List;
  10.  
  11. import butterknife.Bind;
  12. import butterknife.ButterKnife;
  13. import butterknife.OnClick;
  14.  
  15. public class MainActivity extends FragmentActivity {
  16.  
  17. public static final int TAB_HOME = 0;
  18. public static final int TAB_PROJECTS = 1;
  19. public static final int TAB_STUDYS = 2;
  20. public static final int TAB_USER_CENTER = 3;
  21.  
  22. @Bind(R.id.viewpager)
  23. public ContainerViewPager viewPager;
  24.  
  25. @Bind(R.id.radio_main)
  26. public RadioButton radioMain;
  27.  
  28. @Bind(R.id.radio_projects)
  29. public RadioButton radioProjects;
  30.  
  31. @Bind(R.id.radio_studys)
  32. public RadioButton radioStudys;
  33.  
  34. @Bind(R.id.radio_user_center)
  35. public RadioButton radioUserCenter;
  36.  
  37. FragmentMain fragmentMain;
  38.  
  39. @Override
  40. protected void onCreate(Bundle savedInstanceState) {
  41. super.onCreate(savedInstanceState);
  42. setContentView(R.layout.activity_main);
  43.  
  44. ButterKnife.bind(this);
  45.  
  46. initView();
  47. addPageChangeListener();
  48. }
  49.  
  50. private void initView() {
  51.  
  52. List<Fragment> fragments = new ArrayList<Fragment>();
  53.  
  54. fragmentMain = new FragmentMain();
  55.  
  56. FragmentHuodong fragmentHuodong = new FragmentHuodong();
  57.  
  58. FragmentShequ fragmentShequ = new FragmentShequ();
  59.  
  60. FragmentMy fragmentMy = new FragmentMy();
  61.  
  62. fragments.add(fragmentMain);
  63. fragments.add(fragmentHuodong);
  64. fragments.add(fragmentShequ);
  65. fragments.add(fragmentMy);
  66.  
  67. this.viewPager.setOffscreenPageLimit(0);
  68.  
  69. FragmentViewPagerAdapter adapter = new FragmentViewPagerAdapter(this.getSupportFragmentManager(), viewPager, fragments);
  70.  
  71. }
  72.  
  73. private void addPageChangeListener() {
  74. viewPager.setOnPageChangeListener(new MyViewPager.OnPageChangeListener() {
  75.  
  76. @Override
  77. public void onPageSelected(int id) {
  78. switch (id) {
  79. case TAB_HOME:
  80. radioMain.setChecked(true);
  81. break;
  82. case TAB_PROJECTS:
  83. radioProjects.setChecked(true);
  84. break;
  85. case TAB_STUDYS:
  86. radioStudys.setChecked(true);
  87. break;
  88. case TAB_USER_CENTER:
  89. radioUserCenter.setChecked(true);
  90. break;
  91. }
  92. }
  93.  
  94. @Override
  95. public void onPageScrolled(int arg0, float arg1, int arg2) {
  96.  
  97. }
  98.  
  99. @Override
  100. public void onPageScrollStateChanged(int arg0) {
  101.  
  102. }
  103. });
  104. }
  105.  
  106. @OnClick({R.id.radio_main, R.id.radio_projects, R.id.radio_studys, R.id.radio_user_center})
  107. public void onClick(View v) {
  108. switch (v.getId()) {
  109.  
  110. case R.id.radio_main:
  111. viewPager.setCurrentItem(TAB_HOME, false);
  112. doubleClick(v);
  113. break;
  114. case R.id.radio_projects:
  115. viewPager.setCurrentItem(TAB_PROJECTS, false);
  116. break;
  117. case R.id.radio_studys:
  118. viewPager.setCurrentItem(TAB_STUDYS, false);
  119. break;
  120. case R.id.radio_user_center:
  121. viewPager.setCurrentItem(TAB_USER_CENTER, false);
  122. break;
  123. }
  124. }
  125.  
  126. long firstClickTime = 0;
  127. long secondClickTime = 0;
  128.  
  129. public void doubleClick(View view) {
  130.  
  131. if (firstClickTime > 0) {
  132. secondClickTime = SystemClock.uptimeMillis();
  133. if (secondClickTime - firstClickTime < 500) {
  134. fragmentMain.ScrollToTop();
  135. }
  136. firstClickTime = 0;
  137. return;
  138. }
  139.  
  140. firstClickTime = SystemClock.uptimeMillis();
  141.  
  142. new Thread(new Runnable() {
  143.  
  144. @Override
  145. public void run() {
  146. try {
  147. Thread.sleep(500);
  148. firstClickTime = 0;
  149. } catch (InterruptedException e) {
  150. e.printStackTrace();
  151. }
  152. }
  153. }).start();
  154. }
  155. }

注:在处首页Tab的点击事件时,除了要设置viewPager.setCurrentItem(TAB_HOME, false);外,还需要设置doubleClick(v);它主要是处理了双击事件。

通过以上四个步骤,已经可以实现tab导航,双击tab调用Fragment中方法了。接下来,让我们看下日志:仔细看下ViewPager+Fragment的生命周期:(我设置ViewPager取消了预加载功能)

1. 第一次进入到主页面:加载FragmentMain,执行生命周期方法

2. 分别点击其他的Tab:

3. 之后再点击FragmentMain,我们发现,并未在执行任何生命周期的方法;

4. 点击FragmentMain页面中的Button,进入新的Activity:

我们发现,刚刚启动的几个Fragment(首页、活动、社区),都执行了onPause和onStop方法;

5. 返回到上一级页面,也就是首页:

刚刚停止的几个Fragment(首页、活动、社区),执行了onStart和onResume方法。

6. 接着,测试下首页tab的双击事件:

会调用我们在FragmentMain中定义ScrollToTop方法,在该方法中,我们可以处理一些相应的逻辑。

7. 退出APP,看下:

看到这里,不知道大家是否明白了如何定义使用Tab了,如果有疑问,可以再多看看源码,也欢迎一起讨论。

github源码地址:https://github.com/zuiwuyuan/ViewpagerFragmentTab

如此这般,就OK啦!欢迎指正!

  如有疑问,欢迎进QQ群:487786925( Android研发村 )

Android 高仿新浪微博底部导航栏,实现双击首页Tab,页面的ListView滚动、刷新的更多相关文章

  1. uni-app h5端跳转到底部导航栏的时候使用方法uni.switchTab跳转刷新页面更新数据

    h5端的uni-app项目 需求:uni-app h5端跳转到底部导航栏的时候使用方法uni.switchTab跳转刷新页面更新数据 百度的方法如下: uni.switchTab({ url: '/p ...

  2. [Android]--RadioGroup+RadioButton实现底部导航栏

    RadioGroup+RadioButton组合方式打造简单实用的底部导航栏 代码块: <?xml version="1.0" encoding="utf-8&qu ...

  3. Android之framework修改底部导航栏NavigationBar动态显示和隐藏

     原文链接 http://blog.csdn.net/way_ping_li/article/details/45727335 git diff diff --git a/frameworks/bas ...

  4. Android (争取做到)最全的底部导航栏实现方法

    本文(争取做到)Android 最全的底部导航栏实现方法. 现在写了4个主要方法. 还有一些个人感觉不完全切题的方法也会简单介绍一下. 方法一. ViewPager + List<View> ...

  5. Android学习笔记- Fragment实例 底部导航栏的实现

    1.要实现的效果图以及工程目录结构: 先看看效果图吧: 接着看看我们的工程的目录结构: 2.实现流程: Step 1:写下底部选项的一些资源文件 我们从图上可以看到,我们底部的每一项点击的时候都有不同 ...

  6. Android开源项目——带图标文字的底部导航栏IconTabPageIndicator

    接下来的博客计划是,在<Android官方技术文档翻译>之间会发一些Android开源项目的介绍,直接剩下的几篇Android技术文档发完,然后就是Android开源项目和Gradle翻译 ...

  7. android底部导航栏实现

    第一种用radiobutton实现 https://wizardforcel.gitbooks.io/w3school-android/content/75.html 布局文件,使用radiogrou ...

  8. Android应用底部导航栏(选项卡)实例

    现在很多android的应用都采用底部导航栏的功能,这样可以使得用户在使用过程中随意切换不同的页面,现在我采用TabHost组件来自定义一个底部的导航栏的功能. 我们先看下该demo实例的框架图: 其 ...

  9. Android 修改底部导航栏navigationbar的颜色

    Android 修改底部导航栏navigationbar的颜色 getWindow().setNavigationBarColor(Color.BLUE); //写法一 getWindow().set ...

随机推荐

  1. js创建svg元素的方法

    需要JQuery <!DOCTYPE html> <html lang="en"> <head> <meta charset=" ...

  2. php数字转人民币金额大写

    numToRmb.php <?php header("content-type:text/html;charset=utf-8"); function numToRmb($n ...

  3. 集训队日常训练20180518-DIV1

    A.3583 n根木棍是否能分成相等两堆. 背包dp,首先求和sum,如果为偶数就说明不行,否则考虑做一个sum/2大小的背包. #include<bits/stdc++.h> using ...

  4. 有趣的HTML5 Web 存储

    HTML5 web 存储,一个比cookie更好的本地存储方式. 什么是 HTML5 Web 存储? 使用HTML5可以在本地存储用户的浏览数据. 早些时候,本地存储使用的是 cookie.但是Web ...

  5. UE4物理模块(三)---碰撞查询(下)SAP/MBP/BVH算法简介

    在上一文中介绍了碰撞查询的配置方法: Jerry:UE4物理模块(三)---碰撞查询(上)​zhuanlan.zhihu.com 本篇介绍下UE4的各种零大小的射线检测,以及非零大小(带体积)的射线检 ...

  6. AndroidStudio离线打包MUI集成JPush极光推送并在java后端管理推送

    1.AndroidStudio离线打包MUI 如何离线打包请参看上篇随笔<AndroidStudio离线打包MUI> 2.集成极光推送 官方文档:https://docs.jiguang. ...

  7. [运维]VMware vSphere介绍 标签: 运维 2017-04-21 19:48 532人阅读 评论(17)

    大部分的程序员,应该是使用过vmware workstation的,我们用这款软件来创建虚拟机,满足我们学习或者工作的一些问题,今天介绍的是vmware家的另一款,不算是软件,比软件范围更大,VMwa ...

  8. Linux ar命令介绍 和常用示例

    制作静态库要用到ar命令,命令格式: ar [-]{dmpqrtx}[abcfilNoPsSuvV] [membername] [count] archive files... {dmpqrtx}中的 ...

  9. 三.BP神经网络

    BP神经网络是包含多个隐含层的网络,具备处理线性不可分问题的能力.以往主要是没有适合多层神经网络的学习算法,,所以神经网络的研究一直处于低迷期. 20世纪80年代中期,Rumelhart,McClel ...

  10. 将centos 7 自带的 php 5.4升级为 5.6

    1.进入终端后查看php版本 php -v 输出可能如下: PHP 5.4.35 (cli) (built: Nov 14 2014 07:04:10) Copyright (c) 1997-2014 ...