现在很多APP,如微信、QQ、微博等等,它们的主页面都无一例外的选择使用底部Tab导航, 通过这种方式,可以很好的把页面层级分化,很好的提高用户体验。相信,很多Android开发者,都使用到过这种经典的设计,可是您你能保证您的设计真的没问题么?
为啥我会有这个疑问呢? 因为我日前就遇到了这么一个情况,发现我做的APP导航页有问题。 具体可以参考这篇博客:【Android】保存Fragment切换状态 , 首先说明的是,我的项目是从之前就沿用下来的框架,页面底部tab的实现,就是采用前面博客提到的方式, 可是在测试的时候,竟然发现,使用这种方式来实现,经常会发生tab重叠情况: 比如,此刻选中的事“首页”tab,可是内容确实“活动”tab,尤其是在你的app在二级页面发生崩溃返回到一级页面时,这种情况经常发生! 当然,这篇博客的评论里面,也提到了这个问题,所以,最后大家建议大家采用的是;"推荐直接使用ViewPager,通过自定义ViewPager禁用掉左右滑动和自动销毁即可"
在我接触的APP中,我觉得新浪微博的设计当然是最经典的,为啥呢?就因为它多了一个功能,“底部tab的双击,来实现列表滚动到最上方并刷新博客列表”,要知道,这样的设计,可以极大提高用户体验的(避免了用户手动滚动到最上方,然后下拉刷新...),接下来,将带着大家学习如何去实现吧。
先看效果图(尤其是日志):
1. 直接定义tab页面,一个ViewPager,四个RadioButton:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <com.lnyp.vf.ContainerViewPager android:id="@+id/viewpager" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginBottom="60dp" /> <RadioGroup android:id="@+id/radiogroup" android:layout_width="fill_parent" android:layout_height="60dp" android:background="#ececec" android:layout_alignParentBottom="true" android:orientation="horizontal"> <RadioButton android:id="@+id/radio_main" style="@style/navigation_style" android:checked="true" android:drawableTop="@drawable/selector_main_bottom_tab_first" android:paddingLeft="0dp" android:text="首页" /> <RadioButton android:id="@+id/radio_projects" style="@style/navigation_style" android:checked="false" android:drawableTop="@drawable/selector_main_bottom_tab_second" android:paddingLeft="0dp" android:text="活动" /> <RadioButton android:id="@+id/radio_studys" style="@style/navigation_style" android:checked="false" android:drawableTop="@drawable/selector_main_bottom_tab_third" android:paddingLeft="0dp" android:text="社区" /> <RadioButton android:id="@+id/radio_user_center" style="@style/navigation_style" android:checked="false" android:drawableTop="@drawable/selector_main_bottom_tab_forth" android:paddingLeft="0dp" android:text="我的" /> </RadioGroup> </RelativeLayout>
2. 自定义PagerAdapter,为ViewPager添加布局(Fragment),要求可以实现Fragment切换,状态的保存:
import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.support.v4.view.PagerAdapter; import android.view.View; import android.view.ViewGroup; import java.util.List; /** * 为ViewPager添加布局(Fragment),绑定和处理fragments和viewpager之间的逻辑关系 * 可保持Fragment切换状态 */ public class FragmentViewPagerAdapter extends PagerAdapter implements MyViewPager.OnPageChangeListener { private List<Fragment> fragments; // 每个Fragment对应一个Page private FragmentManager fragmentManager; private ContainerViewPager viewPager; // viewPager对象 private int currentPageIndex = 0; // 当前page索引(切换之前) private OnExtraPageChangeListener onExtraPageChangeListener; // ViewPager切换页面时的额外功能添加接口 public FragmentViewPagerAdapter(FragmentManager fragmentManager, ContainerViewPager viewPager, List<Fragment> fragments) { this.fragments = fragments; this.fragmentManager = fragmentManager; this.viewPager = viewPager; this.viewPager.setAdapter(this); this.viewPager.setOnPageChangeListener(this); } @Override public int getCount() { return fragments.size(); } @Override public boolean isViewFromObject(View view, Object o) { return view == o; } @Override public void destroyItem(ViewGroup container, int position, Object object) { container.removeView(fragments.get(position).getView()); // 移出viewpager两边之外的page布局 } @Override public Object instantiateItem(ViewGroup container, int position) { Fragment fragment = fragments.get(position); if (!fragment.isAdded()) { // 如果fragment还没有added FragmentTransaction ft = fragmentManager.beginTransaction(); ft.add(fragment, fragment.getClass().getSimpleName()); ft.commit(); /** * 在用FragmentTransaction.commit()方法提交FragmentTransaction对象后 * 会在进程的主线程中,用异步的方式来执行。 * 如果想要立即执行这个等待中的操作,就要调用这个方法(只能在主线程中调用)。 * 要注意的是,所有的回调和相关的行为都会在这个调用中被执行完成,因此要仔细确认这个方法的调用位置。 */ fragmentManager.executePendingTransactions(); } if (fragment.getView().getParent() == null) { container.addView(fragment.getView()); // 为viewpager增加布局 } return fragment.getView(); } /** * 当前page索引(切换之前) * * @return */ public int getCurrentPageIndex() { return currentPageIndex; } public OnExtraPageChangeListener getOnExtraPageChangeListener() { return onExtraPageChangeListener; } /** * 设置页面切换额外功能监听器 * * @param onExtraPageChangeListener */ public void setOnExtraPageChangeListener(OnExtraPageChangeListener onExtraPageChangeListener) { this.onExtraPageChangeListener = onExtraPageChangeListener; } @Override public void onPageScrolled(int i, float v, int i2) { if (null != onExtraPageChangeListener) { // 如果设置了额外功能接口 onExtraPageChangeListener.onExtraPageScrolled(i, v, i2); } } @Override public void onPageSelected(int i) { fragments.get(currentPageIndex).onPause(); // 调用切换前Fargment的onPause() if (fragments.get(i).isAdded()) { fragments.get(i).onResume(); // 调用切换后Fargment的onResume() } currentPageIndex = i; if (null != onExtraPageChangeListener) { // 如果设置了额外功能接口 onExtraPageChangeListener.onExtraPageSelected(i); } } @Override public void onPageScrollStateChanged(int i) { if (null != onExtraPageChangeListener) { // 如果设置了额外功能接口 onExtraPageChangeListener.onExtraPageScrollStateChanged(i); } } /** * page切换额外功能接口 */ public static class OnExtraPageChangeListener { public void onExtraPageScrolled(int i, float v, int i2) { } public void onExtraPageSelected(int i) { } public void onExtraPageScrollStateChanged(int i) { } } }
注: 上述代码中,有个ContainerViewPager,该ContainerViewPager是继承ViewPager,主要是为了去除ViewPager左右滑动功能,大家可以再源码里直接看到。
4. 实现MainActivity.java,为ViewPager设置PageAdapter, 并且,在MainActivity中实现双击tab功能:
import android.os.Bundle; import android.os.SystemClock; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import android.view.View; import android.widget.RadioButton; import java.util.ArrayList; import java.util.List; import butterknife.Bind; import butterknife.ButterKnife; import butterknife.OnClick; public class MainActivity extends FragmentActivity { public static final int TAB_HOME = 0; public static final int TAB_PROJECTS = 1; public static final int TAB_STUDYS = 2; public static final int TAB_USER_CENTER = 3; @Bind(R.id.viewpager) public ContainerViewPager viewPager; @Bind(R.id.radio_main) public RadioButton radioMain; @Bind(R.id.radio_projects) public RadioButton radioProjects; @Bind(R.id.radio_studys) public RadioButton radioStudys; @Bind(R.id.radio_user_center) public RadioButton radioUserCenter; FragmentMain fragmentMain; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this); initView(); addPageChangeListener(); } private void initView() { List<Fragment> fragments = new ArrayList<Fragment>(); fragmentMain = new FragmentMain(); FragmentHuodong fragmentHuodong = new FragmentHuodong(); FragmentShequ fragmentShequ = new FragmentShequ(); FragmentMy fragmentMy = new FragmentMy(); fragments.add(fragmentMain); fragments.add(fragmentHuodong); fragments.add(fragmentShequ); fragments.add(fragmentMy); this.viewPager.setOffscreenPageLimit(0); FragmentViewPagerAdapter adapter = new FragmentViewPagerAdapter(this.getSupportFragmentManager(), viewPager, fragments); } private void addPageChangeListener() { viewPager.setOnPageChangeListener(new MyViewPager.OnPageChangeListener() { @Override public void onPageSelected(int id) { switch (id) { case TAB_HOME: radioMain.setChecked(true); break; case TAB_PROJECTS: radioProjects.setChecked(true); break; case TAB_STUDYS: radioStudys.setChecked(true); break; case TAB_USER_CENTER: radioUserCenter.setChecked(true); break; } } @Override public void onPageScrolled(int arg0, float arg1, int arg2) { } @Override public void onPageScrollStateChanged(int arg0) { } }); } @OnClick({R.id.radio_main, R.id.radio_projects, R.id.radio_studys, R.id.radio_user_center}) public void onClick(View v) { switch (v.getId()) { case R.id.radio_main: viewPager.setCurrentItem(TAB_HOME, false); doubleClick(v); break; case R.id.radio_projects: viewPager.setCurrentItem(TAB_PROJECTS, false); break; case R.id.radio_studys: viewPager.setCurrentItem(TAB_STUDYS, false); break; case R.id.radio_user_center: viewPager.setCurrentItem(TAB_USER_CENTER, false); break; } } long firstClickTime = 0; long secondClickTime = 0; public void doubleClick(View view) { if (firstClickTime > 0) { secondClickTime = SystemClock.uptimeMillis(); if (secondClickTime - firstClickTime < 500) { fragmentMain.ScrollToTop(); } firstClickTime = 0; return; } firstClickTime = SystemClock.uptimeMillis(); new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(500); firstClickTime = 0; } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } }
注:在处首页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研发村 )