首页 > 代码库 > Android控件的新宠儿——ViewPager

Android控件的新宠儿——ViewPager

1 认识一下ViewPager?

     ViewPager最早出自4.0版本,那么低版本如何能使用ViewPager呢?为了兼容低版本安卓设备,谷歌官方给我们提供了一个的软件包android.support.v4.view。这个V4包囊了只有在安卓3.0以上可以使用的api,而viewpager就是其中之一。利用它,我们可以做很多事情,从最简单的引导页导航,到轮转广告,到页面菜单等等,无不出现ViewPager的身影。应用广泛,简单好用,更好的交互性,这也是ViewPager一出现便大受程序员欢迎的原因。如此好用的控件,你是不是已经蠢蠢欲动了呢?不废话,我们将以项目为向导,由浅入深的讲解ViewPager,开始ViewPager的学习之旅吧。

2 什么时候可以使用ViewPager?

任何新的技术,最难的不是学习如何使用它,而是明白什么时候使用它最合适。正所谓物尽其用,只有正确的技术用在了正确的地方,那么才能发挥该技术最大的功效,做出好的应用。下面结合一些典型场景来让不了解ViewPager的你了解在什么情况下使用ViewPager才是最好的。ViewPager最典型的应用场景主要包括引导页导航,轮转广告,和页面菜单。可以这么说,但凡遇到界面切换的需求,都可以考虑ViewPager。抛砖引玉,剩下的就看读者发挥想象力了。

3  ViewPager的基本入门(和ListView对比学习)

     那如何使用它呢,与ListView类似,我们也需要一个适配器,他就是PagerAdapter。ViewPager采用MVC模式将前段显示与后端数据进行分离,也就是说器装载数据并不是直接添加数据,而是,需要使用PagerAdapter。PagerAdapter相当于,MVC模式中的C(Controller,控制器),ViewPager相当MVC模式中的V(View,视图),为ViewPager提供的数据List,数组或者数据库,就相当于MVC中的M(Mode,模型)。

      学习ViewPager不仅仅是学习ViewPager单一个控件那么简单,我们需要围绕MVC模式,把ViewPager用到的数据(M),视图(V),控制器(C)都理一遍,明白如何把他们,组合在一起,达到ViewPager的切换效果。

      我们通过一个简单的项目来认识一下ViewPager的使用方式。

首先新建项目,引入ViewPager控件

ViewPager,它是google SDk中自带的一个附加包的一个类,可以用来实现屏幕间的切换,在V4包中。

三步曲:

3.1  准备视图 View

        在主布局文件main.xml中添加ViewPager如下:

activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"      xmlns:tools="http://schemas.android.com/tools"      android:layout_width="fill_parent"      android:layout_height="fill_parent"      tools:context="com.example.testviewpage_1.MainActivity" >    <android.support.v4.view.ViewPager      android:id="@+id/viewpager"      android:layout_width="wrap_content"      android:layout_height="wrap_content"      android:layout_gravity="center" />    </RelativeLayout>  

 

其中,其中 <android.support.v4.view.ViewPager /> 是ViewPager对应的组件,要将其放到想要滑动的位置,可以全屏显示,也可以半屏,任意大小,由程序员按需求控制。

3.2   准备数据模型 ,Mode

① 新建三个layout,用于滑动切换的视图:

我们的三个视图都非常简单,里面没有任何的控件,大家当然可以往里添加各种控件,但这里是个DEMO,只详解原理即可,所以我这里仅仅用背景来区别不用layout布局。

layout1.xml

<?xml version="1.0" encoding="utf-8"?>  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"      android:layout_width="match_parent"      android:layout_height="match_parent"      android:background="#ffffff"      android:orientation="vertical" >        </LinearLayout> 

 

 layout2.xml

<?xml version="1.0" encoding="utf-8"?>  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"      android:layout_width="match_parent"      android:layout_height="match_parent"      android:background="#ffff00"      android:orientation="vertical" >        </LinearLayout> 

 

layout3.xml

 

<?xml version="1.0" encoding="utf-8"?>  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"      android:layout_width="match_parent"      android:layout_height="match_parent"      android:background="#ff00ff"      android:orientation="vertical" >  </LinearLayout>

 

② 声明变量:

 

private View view1, view2, view3;  

private List<View> viewList;//view数组  

private ViewPager viewPager;  //对应的viewPager 

我们来看看上面的变量声明:

     首先viewPager对应 <android.support.v4.view.ViewPager/>控件。

view1, view2, view3对应我们的三个layout,即layout1.xml,layout2.xml,layout3.xml

viewList是一个View数组,盛装上面的三个VIEW

③  数据的初始化:

viewPager = (ViewPager) findViewById(R.id.viewpager);  LayoutInflater inflater=getLayoutInflater();  view1 = inflater.inflate(R.layout.layout1, null);  view2 = inflater.inflate(R.layout.layout2,null);  view3 = inflater.inflate(R.layout.layout3, null);    viewList = new ArrayList<View>();// 将要分页显示的View装入数组中  viewList.add(view1);  viewList.add(view2);  viewList.add(view3);

 

获取到找到ViewPager ,赋值给变量,最后将实例化的view1,view2,view3添加到viewList中。

3.3  准备控制器(Controller)—— PagerAdapter 

PagerAdapter 是ViewPager的适配器。

适配器我们在ListView里面早就使用过,listView通过重写GetView()函数来获取当前要加载的Item。而PageAdapter不太相同,毕竟PageAdapter是单个VIew的合集。PagerAdapter在instantiateItem()里面给布局容器添加了将要显示的视图。

PageAdapter 必须重写的四个函数:

boolean isViewFromObject(View arg0, Object arg1)

 

int getCount() 

 

void destroyItem(ViewGroup container, int position,Object object)

 

Object instantiateItem(ViewGroup container, int position)

下面,我们就看看四个主要方法改如何重写,都分别做了什么吧

@Override  public int getCount() {      // TODO Auto-generated method stub      return viewList.size();  }getCount(),返回滑动的View的个数。@Override  public void destroyItem(ViewGroup container, int position,          Object object) {      // TODO Auto-generated method stub      container.removeView(viewList.get(position));  }  destroyItem,从容器中删除指定position的View @Override  public Object instantiateItem(ViewGroup container, int position) {      // TODO Auto-generated method stub          container.addView(viewList.get(position));                return viewList.get(position);      }  };

 

instantiateItem()方法中,我先讲指定position位置的View添加到容器中,末了,将本View返回。

@Override  public boolean isViewFromObject(View arg0, Object arg1) {      // TODO Auto-generated method stub      return arg0 == arg1;  } 

 

这里为什么这么写暂不做讲解,知道这样写即可,后面我们会单独讲解清楚。

这么简单,我们就实现了三个view间的相互滑动。

                   第一个界面想第二个界面滑动                               第二个界面想第三个界面滑动

          

以下是全部核心代码:

package com.example.testviewpage_1;  import java.util.ArrayList;  import java.util.List;  import java.util.zip.Inflater;    import android.app.Activity;  import android.os.Bundle;  import android.support.v4.view.PagerAdapter;  import android.support.v4.view.ViewPager;  import android.view.LayoutInflater;  import android.view.View;  import android.view.ViewGroup;      public class MainActivity extends Activity {        private View view1, view2, view3;      private ViewPager viewPager;  //对应的viewPager            private List<View> viewList;//view数组                @Override      protected void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          setContentView(R.layout.activity_main);                    viewPager = (ViewPager) findViewById(R.id.viewpager);          LayoutInflater inflater=getLayoutInflater();          view1 = inflater.inflate(R.layout.layout1, null);          view2 = inflater.inflate(R.layout.layout2,null);          view3 = inflater.inflate(R.layout.layout3, null);                    viewList = new ArrayList<View>();// 将要分页显示的View装入数组中          viewList.add(view1);          viewList.add(view2);          viewList.add(view3);                              PagerAdapter pagerAdapter = new PagerAdapter() {                            @Override              public boolean isViewFromObject(View arg0, Object arg1) {                  // TODO Auto-generated method stub                  return arg0 == arg1;              }                            @Override              public int getCount() {                  // TODO Auto-generated method stub                  return viewList.size();              }                            @Override              public void destroyItem(ViewGroup container, int position,                      Object object) {                  // TODO Auto-generated method stub                  container.removeView(viewList.get(position));              }                            @Override              public Object instantiateItem(ViewGroup container, int position) {                  // TODO Auto-generated method stub                  container.addView(viewList.get(position));                                                      return viewList.get(position);              }          };                              viewPager.setAdapter(pagerAdapter);                }      }  

    

 至此我们已经基本了解了ViewPager,学会了基本用法,接下来,我们就来详细学习ViewPager的核心PagerAdapter。

4 从PagerAdapter说开去——解读PagerAdapter的四大函数

       4.1 且看官方文档怎么说?

       最权威的讲解是官方文档,都是英文的,不好排版,我就不贴出来了,以下是我根据文档翻译出来的。有不明白的,可以自己看官方文档:

http://developer.android.com/reference/android/support/v4/view/PagerAdapter.html(加红)

      安卓提供一个适配器用于填充ViewPager页面. 你很可能想要使用一个更加具体的实现, 例如:

 FragmentPagerAdapter or FragmentStatePagerAdapter.

当你实现一个PagerAdapter时,至少需要覆盖以下几个方法:

instantiateItem(ViewGroup, int)

 

destroyItem(ViewGroup, int, Object)

 

getCount()

 

isViewFromObject(View, Object)

PagerAdapter比AdapterView的使用更加普通.ViewPager使用回调函数来表示一个更新的步骤,而不是使用一个视图回收机制。在需要的时候pageradapter也可以实现视图的回收或者使用一种更为巧妙的方法来管理视图,比如采用可以管理自身视图的fragment。

① viewpager不直接处理每一个视图而是将各个视图与一个键联系起来。这个键用来跟踪且唯一代表一个页面,不仅如此,该键还独立于这个页面所在adapter的位置。当pageradapter将要改变的时候他会调用startUpdate函数, 接下来会调用一次或多次的instantiateItem或者destroyItem。最后在更新的后期会调用finishUpdate。当finishUpdate返回时 instantiateItem返回的对象应该添加到父ViewGroup destroyItem返回的对象应该被ViewGroup删除。methodisViewFromObject(View, Object)代表了当前的页面是否与给定的键相关联。

 

② 对于非常简单的pageradapter或许你可以选择用page本身作为键,在创建并且添加到viewgroup后instantiateItem方法里返回该page本身即可

destroyItem将会将该page从viewgroup里面移除。isViewFromObject方法里面直接可以返回view == object。

 

pageradapter支持数据集合的改变,数据集合的改变必须要在主线程里面执行,然后还要调用notifyDataSetChanged方法。和baseadapter非常相似。数据集合的改变包括页面的添加删除和修改位置。viewpager要维持当前页面是活动的,所以你必须提供getItemPosition方法。

 

上面的FragmentPagerAdapter 和FragmentStatePagerAdapter非常常用,我们放到后面来讲。

     上面的话,重点只有两段:① ,②

针对上面两段,集中理解两点:

(1)第一段说明了,键(Key)的概念,首先这里要清楚的一点是,每个滑动页面都对应一个Key,而且这个Key值是用来唯一追踪这个页面的,也就是说每个滑动页面都与一个唯一的Key一一对应。大家先有这个概念就好,关于这个Key是怎么来的,下面再讲。

(2)当前page本身可以作为键,直接在destroyItem()返回,用来标示自己。下面,我们来讲讲Key

 

4.2  ViewPager的 key

①    destroyItem(ViewGroup, int, Object)

该方法把给定位置的界面丛容器中移除,负责从容器中删除视图,确保在finishUpdate(viewGroup)返回时视图能够被移除。

来看看我们前面的项目是怎么重写这个方法的:

@Override  public void destroyItem(ViewGroup container, int position,          Object object) {      // TODO Auto-generated method stub      container.removeView(viewList.get(position));  } 

 

将给定位置的视图从container中移除了…… 这个方法必须被实现,而且不能调用父类,否则抛出异常。(说该方法没有被覆盖)

② getCount()   

  返回当前有效视图的个数。

@Override  public int getCount() {      // TODO Auto-generated method stub      return viewList.size();  } 

 

返回了当前需要显示的视图的个数。

接下来的两个方法是重点。

  ③ instantiateItem(ViewGroup, int)

这个方法实现的功能是创建指定位置的视图,同时肩负着添加该创建的视图到指定容器container中,而这一步,要确保在finishUpdate(viewGroup)返回之后完成。

该方法返回一个代表该视图的键(key),没必要非是视图本身,也可以是这个页面的其他容器,我的理解是没必要视图本身,只要这个返回值能代表当前视图,并与视图一意对应即可,比如返回和该视图对应的position可以吗?(接下来我们做个例子试试)

总结:

给container添加一个视图。

返回代表该视图的Key

该方法和destroyItem(ViewGroup, int, Object)一样,在finishUpdate(ViewGroup)这句话执行完之后执行。

我们来看看我们是怎么做的:

@Override  public Object instantiateItem(ViewGroup container, int position) {      // TODO Auto-generated method stub          container.addView(viewList.get(position));                              return viewList.get(position);      }  };

 

没有错,这里我们给container添加了一个View  viewList.get(position),,并将该视图作为key返回了。

回过头来,我们看看第四章的官方文档翻译:

② 对于非常简单的pageradapter或许你可以选择用page本身作为键,在创建并且添加到viewgroup后instantiateItem方法里返回该page本身即可

destroyItem将会将该page从viewgroup里面移除。isViewFromObject方法里面直接可以返回view == object。

就是这里,把当前的View作为key传出去,那么这个key在哪里被使用呢?就得来看看下面的方法了。

④ isViewFromObject(View, Object)

功能:该函数用来判断instantiateItem(ViewGroup, int)函数所返回来的Key与一个页面视图是否是代表的同一个视图(即它俩是否是对应的,对应的表示同一个View)

返回值:如果对应的是同一个View,返回True,否则返回False。

在上面的项目中,我们这样做的:

@Override  public boolean isViewFromObject(View arg0, Object arg1) {      // TODO Auto-generated method stub      return arg0 == arg1;  }  

 

由于在instantiateItem()中,我们作为Key返回来的是当前的View,所以在这里判断时,我们直接将Key与View看是否相等来判断是否是同一个View。

发散思维:如果我们在instantiateItem()返回的是代表当前视图的position而非本身呢?这里该怎么做?接下来我们就解答你的疑问。

4.3   自定义key

上面我们想必对key有个初步认识,下面我们举个例子来说明一下key和View的关系,由于key要和View一一对应,这里我把和View一一对应的position作为key返回,然后在上面的项目的基础上修改。这里只展示需要修改的代码。

我们更改了两个地方:

(1)instantiateItem()

@Override  public Object instantiateItem(ViewGroup container, int position) {      // TODO Auto-generated method stub          container.addView(viewList.get(position));             return  position ;      }  };

 

(2)2、isViewFromObject ()

@Override  public boolean isViewFromObject(View arg0, Object arg1) {      // TODO Auto-generated method stub      //根据传来的key(arg1),找到view,判断与传来的参数View arg0是不是同一个视图      return arg0 == viewList.get((int)Integer.parseInt(arg1.toString()));  }  

 

判断instantiateItem()返回的key与视图是否对应,这里我们返回的是position,我们需要根据position找到对应的View,与传过来的View对比,看看是否对应。注意:这里,我们要先将obect对应转换为int类型:(int)Integer.parseInt(arg1.toString());然后再根据position找到对应的View;

5  ViewPager的进阶,添加标题栏

5.1 PagerTitleStrip

View可以添加标题栏,用来指示当前滑动到哪一页。先来一张效果图:

  

       PagerTabStrip是ViewPager的一个关于当前页面、上一个页面和下一个页面的一个非交互的指示器。它经常作为ViewPager控件的一个子控件被被添加在XML布局文件中。在你的布局文件中,将它作为子控件添加在ViewPager中。而且要将它的 android:layout_gravity 属性设置为TOP或BOTTOM来将它显示在ViewPager的顶部或底部。每个页面的标题是通过适配器的getPageTitle(int)函数提供给ViewPager的。

主要是两点:

①  PagerTabStrip可以作为控件直接添加到xml布局文件中。

②  重写getPageTitle(int)来给PagerTabStrip提供标题。

你也许会发现上面只有上部分一部分的地方才有滑动切换,是因为我更改了布局文件。

(1)  先来看看布局文件:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"      xmlns:tools="http://schemas.android.com/tools"      android:layout_width="match_parent"      android:layout_height="match_parent"      tools:context="com.example.testviewpage_2.MainActivity" >        <android.support.v4.view.ViewPager          android:id="@+id/viewpager"          android:layout_width="wrap_content"          android:layout_height="200dip"          android:layout_gravity="center">                    <android.support.v4.view.PagerTitleStrip              android:id="@+id/pagertitle"                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:layout_gravity="top"              />                </android.support.v4.view.ViewPager>    </RelativeLayout>  

 

这里将layout_height更改为200dip,只所以这么做,是为了告诉大家,只要在想要实现滑动切换的地方添加上<android.support.v4.view.ViewPager />就可以实现切换,无所谓位置和大小,跟普通控件一样!!!!!!

重点是我们将PagerTabStrip作为子控件直接镶嵌在ViewPager中,设置layout_gravity="top" 或者 buttom。

 

(2)  重写适配器的getPageTitle()函数

在元项目基础上我们做了如下更改:

1、定义变量:

private List<String> titleList;  //标题列表数组 

申请了一个标题数组,来存储三个页面所对应的标题、

2、初始化

titleList = new ArrayList<String>();// 每个页面的Title数据  

titleList.add("王鹏");  

titleList.add("姜语");  

titleList.add("结婚");

添加了标题数据

3、重写CharSequence getPageTitle(int )函数

@Override  public CharSequence getPageTitle(int position) {      // TODO Auto-generated method stub      return titleList.get(position);  }

 

根据位置返回当前所对应的标题。

 

5.2 PagerTabStrip

     PagerTabStrip使用方法和上面类似。

先来看看效果:

   

效果和PagerTitleStrip差不多,但是有微小差别:

 PagerTabStrip在当前页面下,标题的下方有一个横线作为导航。

PagerTabStrip的Tab是可以点击的,点击标题可以跳转到对应的页面。

PagerTabStrip是ViewPager的一个关于当前页面、上一个页面和下一个页面的一个可交互的指示器。它经常作为ViewPager控件的一个子控件被被添加在XML布局文件中。在你的布局文件中,将它作为子控件添加在ViewPager中。而且要将它的 android:layout_gravity 属性设置为TOP或BOTTOM来将它显示在ViewPager的顶部或底部。每个页面的标题是通过适配器的getPageTitle(int)函数提供给ViewPager的。

注意:可交互的,这就是PagerTabStrip和PagerTitleStrip最大的不一样。PagerTabStrip是可交互的,PagerTitleStrip是不可交互的。

用法与PagerTitleStrip完全相同,即:

1、首先,文中提到:在你的布局文件中,将它作为子控件添加在ViewPager中。

2、第二,标题的获取,是重写适配器的getPageTitle(int)函数来获取的。

看看实例:

1、XML布局

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"      xmlns:tools="http://schemas.android.com/tools"      android:layout_width="match_parent"      android:layout_height="match_parent"      tools:context="com.example.testviewpage_2.MainActivity" >        <android.support.v4.view.ViewPager          android:id="@+id/viewpager"          android:layout_width="wrap_content"          android:layout_height="wrap_content"          android:layout_gravity="center">                            <android.support.v4.view.PagerTabStrip              android:id="@+id/pagertab"              android:layout_width="match_parent"              android:layout_height="wrap_content"               android:layout_gravity="top"/>                </android.support.v4.view.ViewPager>    </RelativeLayout>  

 

可以看到,同样,是将PagerTabStrip作为ViewPager的一个子控件直接插入其中,当然android:layout_gravity=""的值一样要设置为top或bottom。

2、重写适配器的getPageTitle()函数

代码里面不用改

@Override  public CharSequence getPageTitle(int position) {      // TODO Auto-generated method stub      return titleList.get(position);  }

 

    根据位置返回当前所对应的标题。

 

6  Fragment 和 ViewPager的完美结合—— FragmentPagerAdapter

前面讲解了ViewPager的普通实现方法,但android官方最推荐的一种实现方法却是使用fragment,Fragment的碎片化功能大大的丰富了ViewPager的功能和表现形式。先前我们实现ViewPager使用的是ViewPagerAdapter。而对于fragment,使用的是FragmentPagerAdapter和FragmentStatePagerAdapter。下面我们来学习一下。

6.1  FragmentPagerAdapter

 FragmentPagerAdapter是PagerAdapter的子类,专门用来呈现fragment页面的,这些Fragment会一直保存在FragmentManager,以方便用户随时取用。

FragmentPagerAdapter适用于有限个fragment的页面管理,因为你所访问过的fragment都会保存在内存中。由于,fragment保存着大量的各种状态,这样就造成了比较大的内存开销。故,当遇到大量的页面切换的时候,建议采用FragmentStatePagerAdapter,这个我们会在下面的章节讲到。

FragmentPagerAdapter使用过程:

6.1.1 适配器的实现:

public class FragAdapter extends FragmentPagerAdapter {        private List<Fragment> mFragments;            public FragAdapter(FragmentManager fm,List<Fragment> fragments) {          super(fm);          // TODO Auto-generated constructor stub          mFragments=fragments;      }        @Override      public Fragment getItem(int arg0) {          // TODO Auto-generated method stub          return mFragments.get(arg0);      }        @Override      public int getCount() {          // TODO Auto-generated method stub          return mFragments.size();      }    } 

 

很简单吧,只需要继承FragmentPagerAdapter实现两个方法getItem(int arg)和 getCount(),就可以了。

这里,我们定义了一个fragment的List对象,在构造方法里面初始化了。如下

public FragAdapter(FragmentManager fm,List<Fragment> fragments) {          super(fm);          // TODO Auto-generated constructor stub          mFragments=fragments;      } 

 

接下来我们实现了getCount(),和前面一样返回了页面的个数。这里我们返回了List对象的大小。List就是fragment的集合,有多少个fragment就展示多少个页面,这点很容易理解。如下:

 @Override      public int getCount() {          // TODO Auto-generated method stub          return mFragments.size();      }  

 

最后,根据传过来的键Key参数,返回该当前要显示的fragment,如下:

 

 @Override      public Fragment getItem(int arg0) {          // TODO Auto-generated method stub          return mFragments.get(arg0);      } 

 

6.1.2  构造Fragment类。

  下面我们要分别构造3个Fragment,这里,我们第一个fragment1有一个可以点击的按钮,第二个和第三个fragment2,fragment3分别用不同的背景代替。

第一个Fragment类:

XML:(layout1.xml)

<?xml version="1.0" encoding="utf-8"?>  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"      android:layout_width="match_parent"      android:layout_height="match_parent"      android:background="#ffffff"      android:orientation="vertical" >            <Button android:id="@+id/fragment1_btn"          android:layout_width="wrap_content"          android:layout_height="wrap_content"          android:text="show toast"          />  </LinearLayout>

 

Fragment1的java代码:

public class Fragment1 extends Fragment {            @Override      public View onCreateView(LayoutInflater inflater, ViewGroup container,              Bundle savedInstanceState) {          // TODO Auto-generated method stub          View view= inflater.inflate(R.layout.layout1, container, false);                    //对View中控件的操作方法          Button btn = (Button)view.findViewById(R.id.fragment1_btn);          btn.setOnClickListener(new View.OnClickListener() {                            @Override              public void onClick(View v) {                  // TODO Auto-generated method stub                  Toast.makeText(getActivity(), "点击了第一个fragment的BTN", Toast.LENGTH_SHORT).show();              }          });          return view;      }  }  

 

这里我加入了一个按钮,在onCreateView()方法里面加载了layout1,返回了要显示的View,同时,给按钮添加了一个监听事件,这里为了向读者说明利用了fragment我们可以实现各种各样的交互,ViewPager能做到的不仅仅是动态的图片,而是动态的交互。

第二个Fragment类:

XML代码:(layout2.xml)和上面的代码一样,没有做任何更改

<?xml version="1.0" encoding="utf-8"?>  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"      android:layout_width="match_parent"      android:layout_height="match_parent"      android:background="#ffff00"      android:orientation="vertical" >  </LinearLayout> 

 

java代码:

public class Fragment2 extends Fragment {            @Override      public View onCreateView(LayoutInflater inflater, ViewGroup container,              Bundle savedInstanceState) {          // TODO Auto-generated method stub          View view=inflater.inflate(R.layout.layout2, container, false);          return view;      }    } 

 

第三个Fragment类:

XML代码:(layout3.xml)同样,没做任何更改

<?xml version="1.0" encoding="utf-8"?>  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"      android:layout_width="match_parent"      android:layout_height="match_parent"      android:background="#ff00ff"      android:orientation="vertical" >          </LinearLayout> 

 

Java代码

public class Fragment3 extends Fragment {            @Override      public View onCreateView(LayoutInflater inflater, ViewGroup container,              Bundle savedInstanceState) {          // TODO Auto-generated method stub          View view=inflater.inflate(R.layout.layout3, container, false);          return view;      }    } 

 

6.1.3  主Activity我继承了FragmentActivity,只有FragmentActivity内部才能内嵌Fragment普通Activity是不行的。

public class MainActivity extends FragmentActivity {        @Override      protected void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          setContentView(R.layout.activity_main);            //构造适配器          List<Fragment> fragments=new ArrayList<Fragment>();          fragments.add(new Fragment1());          fragments.add(new Fragment2());          fragments.add(new Fragment3());           FragAdapter adapter = new FragAdapter(getSupportFragmentManager(), fragments);                    //设定适配器          ViewPager vp = (ViewPager)findViewById(R.id.viewpager);          vp.setAdapter(adapter);      }    }  

 

很简单,我们构造了一个适配器,然后为ViewPager设置的适配器,和前面几乎一样。而适配器里面传入的,就是那三个我们准备好的fragment。

看看效果:

                   在第一个页面加一个按钮                   第一页面向第二页面滑动

      

第二页面向第三个页面滑动

6.2 FragmentStatePagerAdapter

和FragmentPagerAdapter相比,它更适用于大量页面的展示,当整个fragment不再被访问,则会被销毁(由于预加载,默认最多保存3个fragment),只保存其状态,这相对于FragmentPagerAdapter占有了更少的内存,为什么大量页面用FragmentStatePagerAdapter?不言而喻了吧。

FragmentStatePagerAdapter的用法和FragmentPagerAdapter一样,这里就不再赘述。

注意:

在初次使用的FragmentPagerAdapter的时候,曾爆出类型转换的异常。这是为什么呢?跟踪代码才发现出错的地方发生在fragments.add(new Fragment1()); 错误提示无法将Fragment1(Fragment的子类)强制转换成Fragment,当时真是莫名其妙,明明Fragment1就是Fragment,为什么说不是呢?经过仔细排查才发现在Fragment1里面导入的是android.app.Fragment,而在Activity类导入的是为android.support.v4.app.Fragment。统一导入之后才消除异常,不细心造成的错误往往难以排查,让人纠结,这里我们在使用FragmentPagerAdapter必须注意导入正确的包android.support.v4.app.Fragment。

7  ViewPager的预加载机制。

ViewPager能够如此流畅的切换页面得益于其预加载的机制,那么什么是ViewPager的预加载呢?

归纳掌握两点:

①  ViewPager会预先加载左右两边的图片,预加载的个数最多3个。前方超出当个数由4个的时候,最前方的会被销毁。预加载和销毁分别回调以下两个方法:

instantiateItem(ViewGroup, int)

destroyItem(ViewGroup, int, Object)

 

②  限制:当左边图片的position小于0的时候,不会预加载;

当右边的图片的position大于或者等于item总数的时候,也不会预加载。

我画了一张示意图:如下左边0位置的被销毁

 

8 学以致用,用ViewPager做个选项卡。

    至此,我们已经基本学完了ViewPager的常用特性。学贵于致用,接下来我们通过一个涵盖面全的例子,来把我们所学的知识用一遍。

做一个选项卡效果,我们立刻想到ViewPagerIndicator,利用我们以上学过的知识,就可以轻易实现这个功能。

先来一张效果图,激发激发热血吧:

 

上图 左右滑动,或者点击文字,界面会切换,同时,页卡文字下方的滑块也会滑动,指示当前显示的页面。

8.1  准备布局

回忆一下,我们在使用ViewPagerIndicator的时候,会在ViewPager的上面添加ViewPagerIndicator,然后通过ViewPagerIndicator的setViewPager(ViewPager mPager)设置ViewPager,使得ViewPagerIndicator指示器与ViewPager相关联。

这里,我们用一个包含几个 TextView的LinearLayout,下边一个ImageView 替换,如下:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="fill_parent"    android:layout_height="fill_parent"    android:orientation="vertical" >     <LinearLayout        android:id="@+id/linearLayout1"        android:layout_width="fill_parent"        android:layout_height="60dip"        android:background="#FFFFFF" >         <TextView            android:id="@+id/text1"            android:layout_width="fill_parent"            android:layout_height="fill_parent"            android:layout_weight="1.0"            android:gravity="center"            android:text="页卡1"            android:textColor="#000000"            android:textSize="22.0dip" />         <TextView            android:id="@+id/text2"            android:layout_width="fill_parent"            android:layout_height="fill_parent"            android:layout_weight="1.0"            android:gravity="center"            android:text="页卡2"            android:textColor="#000000"            android:textSize="22.0dip" />         <TextView            android:id="@+id/text3"            android:layout_width="fill_parent"            android:layout_height="fill_parent"            android:layout_weight="1.0"            android:gravity="center"            android:text="页卡3"            android:textColor="#000000"            android:textSize="22.0dip" />    </LinearLayout>     <ImageView        android:id="@+id/cursor"        android:layout_width="fill_parent"        android:layout_height="wrap_content"        android:scaleType="matrix"        android:src="@drawable/a" />     <android.support.v4.view.ViewPager        android:id="@+id/vPager"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_gravity="center"        android:layout_weight="1.0"        android:background="#000000"        android:flipInterval="30"        android:persistentDrawingCache="animation" /> </LinearLayout>

 

下面是ViewPager。

接下来准备3个切换的布局:(3个布局都是一个RelativeLayout,只是,背景颜色不同而已)

fragment_main_1.xml,fragment_main_2.xml,fragment_main_3.xml

fragment_main_1.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:background="#000000" > </RelativeLayout>

 

fragment_main_2.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:background="#ffffff" > </RelativeLayout>

 

fragment_main_3.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:background="#ff0000" > </RelativeLayout>

 

下面由浅入深,一步步把功能做完:

8.2  先完成ViewPager界面切换,和3 基本入门那一章节一致。

①   初始化 ViewPager布局

   

private void initViewPager() {              vPager = (ViewPager) findViewById(R.id.vPager);              List<View> listViews = new ArrayList<View>();              listViews.add(View.inflate(this, R.layout.fragment_main_1, null));              listViews.add(View.inflate(this, R.layout.fragment_main_2, null));              listViews.add(View.inflate(this, R.layout.fragment_main_3, null));              MyPagerAdapter adapter = new MyPagerAdapter(listViews);              vPager.setAdapter(adapter);        // 给ViewPager设置监听              MyOnPagerChangeListener listener = new MyOnPagerChangeListener();              vPager.setOnPageChangeListener(listener);       }

 

这一部分相信大家很熟悉了,无非做了2步:

(1)给ViewPager设置适配器。(加载了3个我们已经准备好的布局)

(2)给ViewPager设置滑动页面监听。

在此就不再多讲,下面是适配器的实现:

      

 class MyPagerAdapter extends PagerAdapter{              List<View> listViews;                           public MyPagerAdapter(List<View> listViews) {                     super();                     this.listViews = listViews;              }               @Override              public int getCount() {                     // TODO Auto-generated method stub                     return listViews.size();              }               @Override              public boolean isViewFromObject(View arg0, Object arg1) {                     // TODO Auto-generated method stub                     return arg0 == arg1;              }               @Override              public Object instantiateItem(View container, int position) {                     // TODO Auto-generated method stub                     ((ViewPager)container).addView(listViews.get(position));                     return listViews.get(position);              }               @Override              public void destroyItem(View container, int position, Object object) {                     ((ViewPager)container).removeView(listViews.get(position));              }           }

 

至此,我们已经可以切换界面了。可是,我们发现选项卡下面的指示滑动条并不能随着页面的切换而移动,从而标识当前页面。这就是我们下一步要做的。

8.3  这里,我们完成指示滑动条的移动。

思路:

     通过对ViewPager页面切换的监听,用唯一动画相应的距离实现标识滑块的移动。

①  滑块相关数据初始化

      这一段是很重要的,先贴出核心代码,随后详细讲解。

 

     

private void initImageView() {              cursor = (ImageView) findViewById(R.id.cursor);              bmpw = BitmapFactory.decodeResource(getResources(), R.drawable.a).getWidth(); //滑块的宽度              DisplayMetrics dm = new DisplayMetrics();              getWindowManager().getDefaultDisplay().getMetrics(dm);  //给DisplayMetrics赋值              screenW = dm.widthPixels;                           offset = (screenW/3 - bmpw)/2;   //滑块动画初始位置               //设置动画初始位置              Matrix matrix = new Matrix();              matrix.postTranslate(offset, 0);              cursor.setImageMatrix(matrix);      }

 

通过上面的代码主要做了这几个事儿:

(1)

        cursor = (ImageView) findViewById(R.id.cursor);

        //滑块的宽度

              bmpw = BitmapFactory.decodeResource(getResources(), R.drawable.a).getWidth();

加载了滑块,获得了滑块的宽度。

(2)获得了屏幕的宽度

        DisplayMetrics dm = new DisplayMetrics();

              getWindowManager().getDefaultDisplay().getMetrics(dm);  //给DisplayMetrics赋值

              screenW = dm.widthPixels;  //获得屏幕宽度

     上面的代码通过一个类WindowManager获得屏幕的相关信息,保存在  DisplayMetrics 对象里面,然后获取其屏幕宽度。

 

(3)计算滑块的初始位置

offset = (screenW/3 - bmpw)/2;   //滑块动画初始位置

滑块的初始位置的计算,请看如下示意图

 

(4)

       //设置动画初始位置

              Matrix matrix = new Matrix();

              matrix.postTranslate(offset, 0);

              cursor.setImageMatrix(matrix);

 这段代码做的事情也很简单,给滑块设置了初始位置,即当选项页面为第0页的时候滑块的位置。这里是通过Matrix 对象来设置滑块的位置信息。

②  实现页面监听类的方法

class MyOnPagerChangeListener implements OnPageChangeListener{                int one = offset * 2 + bmpw;   //选项卡0 -> 1 的偏移量                int two = one * 2;  // //选项卡1 -> 2 的偏移量              @Override              public void onPageScrollStateChanged(int arg0) {                     // TODO Auto-generated method stub                                  }               @Override              public void onPageScrolled(int arg0, float arg1, int arg2) {                     // TODO Auto-generated method stub                                  }               @Override              public void onPageSelected(int position) {                     Animation  animation = null;                     Toast.makeText(MainActivity.this, "position:"+position, Toast.LENGTH_SHORT).show();                     Log.i("hql-->", "one=="+one+"  two=="+two);                     Log.i("hql-->", "offset=="+offset+"  bmpw=="+bmpw+"screenW"+screenW);                      switch (position) {                     case 0:                            if(currentIndex == 1){                                   animation = new TranslateAnimation(one, 0, 0, 0);                            }else if(currentIndex == 2){                                   animation = new TranslateAnimation(two, 0, 0, 0);                            }                                                       break;                     case 1:                            if(currentIndex == 0){                                   animation = new TranslateAnimation(offset, one, 0, 0);                            }else if(currentIndex == 2){                                   animation = new TranslateAnimation(two, one, 0, 0);                            }                                break;                     case 2:                            if(currentIndex == 0){                                   animation = new TranslateAnimation(offset, two, 0, 0);                            }else if(currentIndex == 1){                                   animation = new TranslateAnimation(one, two, 0, 0);                            }                            break;                     }                      currentIndex = position;  //记录当前的页面号                      animation.setDuration(300);   //设置动画时间                      animation.setFillAfter(true);  //设置停留在动画后                      cursor.startAnimation(animation);              }                    }

 

上面主要做了两件事:

①  计算由第0页到第1页,滑块移动的距离。

 

int one = offset * 2 + bmpw;   //选项卡0 -> 1 的偏移量

int two = one * 2;  // //选项卡1 -> 2 的偏移量

计算方法无非就是数学题,我画了张示意图。

 

②  实现onPageSelected(int Position)方法

@Override              public void onPageSelected(int position) {                     Animation  animation = null;                     Toast.makeText(MainActivity.this, "position:"+position, Toast.LENGTH_SHORT).show();                     Log.i("hql-->", "one=="+one+"  two=="+two);                     Log.i("hql-->", "offset=="+offset+"  bmpw=="+bmpw+"screenW"+screenW);                      switch (position) {                     case 0:                            if(currentIndex == 1){                                   animation = new TranslateAnimation(one, 0, 0, 0);                            }else if(currentIndex == 2){                                   animation = new TranslateAnimation(two, 0, 0, 0);                            }                                                       break;                     case 1:                            if(currentIndex == 0){                                   animation = new TranslateAnimation(offset, one, 0, 0);                            }else if(currentIndex == 2){                                   animation = new TranslateAnimation(two, one, 0, 0);                            }                                break;                     case 2:                            if(currentIndex == 0){                                   animation = new TranslateAnimation(offset, two, 0, 0);                            }else if(currentIndex == 1){                                   animation = new TranslateAnimation(one, two, 0, 0);                            }                            break;                     }                      currentIndex = position;  //记录当前的页面号                      animation.setDuration(300);   //设置动画时间                      animation.setFillAfter(true);  //设置停留在动画后                      cursor.startAnimation(animation);              }

 

通过该方法,我们可以很清晰的看到,这个方法里根据传入的代表当前页面的键,这里是Position,来在滑动的时候使滑块做相应的移动。

做完这一步,我们的滑块已经可以随着页面切换而移动起来了。

8.4  为了更好的交互,完成点击选项卡切换页面。

思路:给3个选项卡(这里是3个TextView)设置点击事件,在点击事件里面通过ViewPager.setCurrentItem(int num)设置当前页面的键(KEY).

private void initTextView() {              TextView text1 = (TextView) findViewById(R.id.text1);              TextView text2 = (TextView) findViewById(R.id.text2);              TextView text3 = (TextView) findViewById(R.id.text3);                           text1.setOnClickListener(this);              text2.setOnClickListener(this);              text3.setOnClickListener(this);       }

 

初始化选项卡文字,这些文字做成控件的时候可以设置,同时给他们设置监听。

 

处理点击事件:

 

@Override       public void onClick(View v) {              switch (v.getId()) {              case R.id.text1:                     vPager.setCurrentItem(0);                     break;              case R.id.text2:                     vPager.setCurrentItem(1);                         break;        case R.id.text3:               vPager.setCurrentItem(2);                         break;              }

 

代码很简单,至此,点击选项卡也可以实现页面切换,实现了双向互动。

这里我再把变量申明和OnCreate()方法里面的调用代码贴出。

   

 private int offset;  //滑块动画初始位置        private int bmpw;   //滑块的宽度       private int currentIndex = 0;  //默认当前也卡号为0       private ImageView cursor;  //滑块       private int screenW;   //屏幕宽度       private ViewPager vPager;  //ViewPager        @Override       protected void onCreate(Bundle savedInstanceState) {              super.onCreate(savedInstanceState);              setContentView(R.layout.activity_main);              initImageView();   //初始化滑块              initTextView();  //初始化文字        initViewPager();  //初始化ViewPager布局       }

 

      好了,一个双向互动的选项卡就完成了,怎么样,是不是很简单?其实ViewPagerIndicator实现的思路和我们做的选项卡差不多,亲爱读者们,花点时间把这个Demo封装一下,提供一些方便的设置方法,比如设置任意长度的title,就是一个精简的选显卡控件了。

     由此我们可以逆推,当然我们要掌握一个开源的控件时并不难,都是由使用到熟悉。通常都是在布局里面引用该控件,然后在java代码通过findViewById()方法找到该空间,之后通过该控件的一些方法,设置相应参数即可使用。当然,熟悉的基础上,下一步,就是深入了解,毕竟市面上的开源控件再怎么样都是人写的,那就很可能不是你想当然那样,所以通过一些方法去了解开源控件很重要。我了解一个控件的特性一般先阅读说明,然后通过调试,打log日志和假设验证的方式,来了解一个控件,这些方式都很普通,也很简单,却多用几次就得心应手了,但是却很实用,重要的是要有求真精神。

      一路来,从认识到灵活运用,由浅入深,我们好像挺顺利,其实不然,一个好的应用都是从bug中产出的,好的程序也是错误中不断优化出来。我们在计算滑块的移动距离的时候,会发生计算结果为0的情况:如下

int one = offset * 2 + bmpw;   //选项卡0 -> 1 的偏移量

int two = one * 2;  // //选项卡1 -> 2 的偏移量

 

这是为什么呢?

     经过代码跟踪,最后才发现我们先初始化ViewPager的监听类,在初始化ViewPager的过程中,我们计算了移动的偏移量,滑块动画初始位置offset 和screenW都是还没有初始化,都是0,此时我们再计算滑块长度和初始值,导致移动距离one和two都为0.所以当我们遇到问题,调试是一个很好的办法。

Android控件的新宠儿——ViewPager