Android开源框架ViewPageIndicator和ViewPager实现Tab导航

前言:

    关于使用ViewPageIndicator和ViewPager实现Tab导航,在开发社区里已经有一堆的博客对其进行了介绍,如果我还在这里写如何去实现,那简直就是老生常谈,毫无新鲜感,而且,我也不认为自己对ViewPageIndicator的理解会比别人好,毕竟我也是看着大神的帖子,在学习实践着。

    那我还写这个有啥意义呢?其实么,就是想在这里记录下,在使用ViewPageIndicator和ViewPager实现Tab导航时,大家有可能会遇到的坑。这个坑,需要我们开发时尽量去避免的。

    啥?你问我为啥子知道有这些坑?

    因为我刚刚遇到过,刚刚解决了,所以来此分享经验啦!


一、推荐博客

    在介绍具体实现的代码之前,我先介绍两篇博客呗,毕竟我就是根据他们写的来学习的。第一篇《Android 开源框架ViewPageIndicator 和 ViewPager 仿网易新闻客户端Tab标签》这篇博客对如何实现tab已经介绍的非常详细了,包括如何去自定义tab样式,请学习膜拜吧。。。如果你学习能力较差,那好吧,在慕课网航有个教学视频,这可是鸿翔大神录制的哦,4-1 ViewPagerIndicator与ViewPager实现Tab,如果你看了视频还不会,那我,呵呵呵。。。


二、如何在Android Studio中导入ViewPageIndicator

    看过之前我推荐博客和视频的人会问,在Eclipse中,我们直接引入ViewPageIndicator项目,再把就好了Android-support-v4.jar 包进行下处理就好了。 那,如何在Android Studio中导入ViewPageIndicator项目呢?

    好,我来告诉大家,请看下图:


    看到了吧,我们仅需要在此处搜索com.inkapplications.viewpageindicator,选中,点击OK,gradle会自动帮我把改工程加载进来的。

    让我们看下子导入后,源码在哪了:


    当你需要修改资源文件的时候,可以直接在res目录下的资源文件中进行修改,是不是很方便!!!

    此刻有一点要记住,因为你改项目中已经引入了android-support-v4,所以你无需再引入其它的v4包,切记切记!!!


三、实现Tab导航

3.1 主页面布局

 

  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     android:layout_width="fill_parent"  
  3.     android:layout_height="fill_parent"  
  4.     android:orientation="vertical">  
  5.   
  6.     <com.viewpagerindicator.TabPageIndicator  
  7.         android:id="@+id/tab_page_indicator"  
  8.         android:layout_width="fill_parent"  
  9.         android:layout_height="60dp" />  
  10.   
  11.     <android.support.v4.view.ViewPager  
  12.         android:id="@+id/study_viewpager"  
  13.         android:layout_width="fill_parent"  
  14.         android:layout_height="fill_parent" />  
  15.   
  16. </LinearLayout>  

 

3.2 定义ViewPager要显示的Fragment 

 

  1. import android.os.Bundle;  
  2. import android.support.v4.app.Fragment;  
  3. import android.view.LayoutInflater;  
  4. import android.view.View;  
  5. import android.view.ViewGroup;  
  6.   
  7. import com.lidroid.xutils.ViewUtils;  
  8.   
  9. public class FragmentStudy extends Fragment {  
  10.   
  11.     private View view = null;  
  12.   
  13.     @Override  
  14.     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {  
  15.   
  16.         if(view==null){  
  17.             view = inflater.inflate(R.layout.fragment_study, container, false);  
  18.         }  
  19.   
  20.         ViewUtils.inject(this, view);  
  21.   
  22.         return view;  
  23.     }  
  24. }  

fragment_study.xml文件:

 

  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout 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.     <TextView  
  8.         android:id="@+id/text_name"  
  9.         android:layout_width="fill_parent"  
  10.         android:layout_height="fill_parent"  
  11.         android:gravity="center"  
  12.         android:text="无缘"  
  13.         android:textSize="25sp" />  
  14.   
  15. </LinearLayout>  

 

3.3 定义ViewPager的Adapter

 

  1. public class ProjectPagerAdapter extends FragmentPagerAdapter {  
  2.   
  3.     private static String[] titles = {"全部", "计划中", "进行中", "已完成"};  
  4.   
  5.     public ProjectPagerAdapter(FragmentManager fm) {  
  6.         super(fm);  
  7.     }  
  8.   
  9.     @Override  
  10.     public Fragment getItem(int position) {  
  11.         Fragment fragment = null;  
  12.         Bundle bundle = null;  
  13.         switch (position) {  
  14.             case 0:  
  15.                 fragment = new FragmentStudy();  
  16.                 bundle = new Bundle();  
  17.                 bundle.putInt("pos", 0);  
  18.                 fragment.setArguments(bundle);  
  19.                 break;  
  20.             case 1:  
  21.                 fragment = new FragmentStudy();  
  22.                 bundle = new Bundle();  
  23.                 bundle.putInt("pos", 1);  
  24.                 fragment.setArguments(bundle);  
  25.                 break;  
  26.             case 2:  
  27.                 fragment = new FragmentStudy();  
  28.                 bundle = new Bundle();  
  29.                 bundle.putInt("pos", 2);  
  30.                 fragment.setArguments(bundle);  
  31.                 break;  
  32.             case 3:  
  33.                 fragment = new FragmentStudy();  
  34.                 bundle = new Bundle();  
  35.                 bundle.putInt("pos", 3);  
  36.                 fragment.setArguments(bundle);  
  37.                 break;  
  38.         }  
  39.         return fragment;  
  40.     }  
  41.   
  42.     @Override  
  43.     public int getCount() {  
  44.         return titles.length;  
  45.     }  
  46.     @Override  
  47.     public CharSequence getPageTitle(int position) {  
  48.         return titles[position];  
  49.     }  
  50. }  


3.4 设置ViewPageIndicator和ViewPager

 

  1. import android.os.Bundle;  
  2. import android.support.v4.app.FragmentActivity;  
  3. import android.support.v4.view.ViewPager;  
  4.   
  5. import com.lidroid.xutils.ViewUtils;  
  6. import com.lidroid.xutils.view.annotation.ViewInject;  
  7. import com.viewpagerindicator.TabPageIndicator;  
  8.   
  9.   
  10. public class MainActivity extends FragmentActivity {  
  11.   
  12.   
  13.     @ViewInject(R.id.tab_page_indicator)  
  14.     private TabPageIndicator tabPageIndicator;  
  15.   
  16.     @ViewInject(R.id.study_viewpager)  
  17.     private ViewPager studyViewpager;  
  18.   
  19.     private ProjectPagerAdapter mAdatpter;  
  20.   
  21.     @Override  
  22.     protected void onCreate(Bundle savedInstanceState) {  
  23.         super.onCreate(savedInstanceState);  
  24.         setContentView(R.layout.activity_main);  
  25.         ViewUtils.inject(this);  
  26.   
  27.         mAdatpter = new ProjectPagerAdapter(getSupportFragmentManager());// 此处,如果不是继承的FragmentActivity,而是继承的Fragment,则参数应该传入getChildFragmentManager()  
  28.   
  29.         studyViewpager.setAdapter(mAdatpter);  
  30.   
  31.         tabPageIndicator.setViewPager(studyViewpager);  
  32.     }  
  33. }  


这样,一个基本的顶部导航Tab就出来了,接下来,我们还可以定义tab的样式,在style.xml文件中,我们添加:

 

  1. <!-- 去掉tab顶部的黑边 -->  
  2.     <style name="no_title" parent="@android:style/Theme.Light.NoTitleBar">  
  3.         <item name="android:windowContentOverlay">@null</item>  
  4.   
  5.     </style>  
  6.   
  7.     <style name="MyTheme" parent="no_title">  
  8.         <item name="vpiTabPageIndicatorStyle">@style/MyWidget.TabPageIndicator</item>  
  9.         <item name="android:windowNoTitle">true</item>  
  10.         <item name="android:animationDuration">5000</item>  
  11.         <item name="android:windowContentOverlay">@null</item>  
  12.     </style>  
  13.   
  14.     <style name="MyWidget.TabPageIndicator" parent="Widget">  
  15.         <item name="android:gravity">center</item>  
  16.         <item name="android:background">@drawable/vpi__tab_indicator</item>  
  17.         <item name="android:paddingLeft">22dip</item>  
  18.         <item name="android:paddingRight">22dip</item>  
  19.         <item name="android:paddingTop">1dp</item>  
  20.         <item name="android:paddingBottom">1dp</item>  
  21.         <item name="android:textAppearance">@style/MyTextAppearance.TabPageIndicator</item>  
  22.         <item name="android:textSize">14sp</item>  
  23.         <item name="android:maxLines">1</item>  
  24.     </style>  
  25.   
  26.     <style name="MyTextAppearance.TabPageIndicator" parent="Widget">  
  27.         <item name="android:textStyle">bold</item>  
  28.         <item name="android:textColor">#2c3e50</item>  
  29.     </style>  


样式定义好之后,只需要为Activity配置该theme即可:

 

  1.  <activity  
  2.             android:name=".MainActivity"  
  3.             android:label="@string/app_name"  
  4.             android:theme="@style/MyTheme">  
  5.             <intent-filter>  
  6.                 <action android:name="android.intent.action.MAIN" />  
  7.   
  8.                 <category android:name="android.intent.category.LAUNCHER" />  
  9.             </intent-filter>  
  10. </activity>  

好了,快来看下效果:


            怎么样?开心了吧,你想要的tab终于出来了,你满足了么?

    请千万不要满足,因为这个Tab还不稳定,它还有漏洞,下面我们来看下。


四、Tab导航中的坑和解决方案

4.1 Fragment二次加载onCreateView方法时会报异常:java.lang.IllegalStateException

    对于之前实现的tab导航,当我们来回切换tab时,会发现,系统会崩溃:


    错误意思是,当切换tab回到已经加载过的Fragment时,系统不允许之前的Fragment在还未移除的情况下,再次加载该View,一个View只能有一个父控件。请仔细看我们FragmentStudy的代码:

 

  1. if(view==null){  
  2.     view = inflater.inflate(R.layout.fragment_study, container, false);  
  3. }  


    我们为了保证只有一个实例,才会只对其进行是否为null的判断,只进行了一次初始化。

    那该如何解决呢?

    有人说好办啊,把判断view==null直接去掉不就好了。。。额,确实好了,但是你有没有考虑每次都会创建新的view,浪费资源性能呢?   

    那如何是好呢?别急,有办法,让我们修改下代码:

 

  1. import android.os.Bundle;  
  2. import android.support.v4.app.Fragment;  
  3. import android.view.LayoutInflater;  
  4. import android.view.View;  
  5. import android.view.ViewGroup;  
  6.   
  7. import com.lidroid.xutils.ViewUtils;  
  8.   
  9. public class FragmentStudy extends Fragment {  
  10.   
  11.     private View view = null;  
  12.   
  13.     @Override  
  14.     public void onCreate(Bundle savedInstanceState) {  
  15.         // TODO Auto-generated method stub  
  16.         super.onCreate(savedInstanceState);  
  17.   
  18. //        pos = getArguments().getInt("pos");  
  19. //        System.out.println(pos);  
  20.   
  21.         LayoutInflater inflater = getActivity().getLayoutInflater();  
  22.         view = inflater.inflate(R.layout.fragment_study, (ViewGroup) getActivity().findViewById(R.id.study_viewpager), false);  
  23.     }  
  24.   
  25.     @Override  
  26.     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {  
  27.   
  28. //        if(view==null){  
  29. //            view = inflater.inflate(R.layout.fragment_study, container, false);  
  30. //        }  
  31.         // 这句话必须加  
  32.         ViewGroup p = (ViewGroup) view.getParent();  
  33.         if (p != null) {  
  34.             p.removeAllViewsInLayout();  
  35.         }  
  36.   
  37.         ViewUtils.inject(this, view);  
  38.   
  39.         return view;  
  40.     }  
  41. }  

                 有没有发现啥? 没错,我们把加载布局文件创建View的过程,放在了onCreate()方法中,即只在第一次创建该Fragment时加载布局,之后切换时,都不会再加载布局了。

    美哉啊!!!

    在运行下该项目,发现是不是不会再崩溃了?!


4.2 ViewPager预加载Fragment造成的重复请求问题

    不知道大家知不知道,ViewPager有个功能,也被称为ViewPager的一个好处,就是它可以预加载Fragment。可是如果我在每个Fragment中都有网络请求,岂不是加载一个Fragment发送了多个请求?这种体验可是不好的。有没有办法改呢?

    有人说,那不简单啊,设置ViewPager不去预加载不就好了!

    对,就这么简单,ViewPager确实提供了相应的方法,去设置预加载Fragment的数量:

    通过ViewPager的setOffscreenPageLimit(int pagenum)来设置,默认情况下参数是1,比如viewPager.setOffscreenPageLimit(2)会预加载2个页面,viewPager.setOffscreenPageLimit(0)会不预加载页面。

    可是,尼玛,操蛋的事来了,我尝试了,viewPager.setOffscreenPageLimit(0)这个方法根本没有用啊,真让我抓狂。

    该咋办?

    google了一下,还真让我找到了方法,重写Fragment的setUserVisibleHint方法:

 

  1. // 使用fragment+viewpage时会发现设置setOffscreenPageLimit(0)不预加载页面不管用,  
  2. // 可以用下边的方法代替,下边的方法是在子页面(也就是fragment中)复写下边的方法,根据fragment是否可见来判断是否是当前页面,然后执行网络加载数据  
  3.     @Override  
  4.     public void setUserVisibleHint(boolean isVisibleToUser) {  
  5.         super.setUserVisibleHint(isVisibleToUser);  
  6.         if (isVisibleToUser) {  
  7.             //fragment可见时执行加载数据或者进度条等  
  8.             qryDataFromServer();  
  9.         } else {  
  10.             //不可见时不执行操作  
  11.         }  
  12.     }  
  13.   
  14.     private void qryDataFromServer() {  
  15.         System.out.println("第"+pos + "个Fragment is qryDataFromServer");  
  16.     }  


来,我们运行下项目,切换Tab,看下输出:

 

  1. I/System.out﹕ 第0个Fragment is qryDataFromServer  
  2. I/System.out﹕ 第1个Fragment is qryDataFromServer  
  3. I/System.out﹕ 第2个Fragment is qryDataFromServer  
  4. I/System.out﹕ 第3个Fragment is qryDataFromServer  
  5. I/System.out﹕ 第2个Fragment is qryDataFromServer  
  6. I/System.out﹕ 第1个Fragment is qryDataFromServer  
  7. I/System.out﹕ 第0个Fragment is qryDataFromServer  


这就对了,切换到哪个Fragment,才去进行网络请求,而不是一次预加载多个网络请求。

 

总结:

    相信看到这里,大家已经能够掌握如何使用TabPageIndicator和ViewPager开发一个比较完美的顶部导航栏了,其实,TabPageIndicator使用并不难,最重要的还是去避免上文中提到的几个坑,这样才会有较快的开发效率!


源码下载地址(免费): http://download.csdn.net/detail/zuiwuyuan/9039533

posted @ 2017-05-11 15:50  天涯海角路  阅读(369)  评论(0)    收藏  举报