zoukankan      html  css  js  c++  java
  • 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.所以当我们遇到问题,调试是一个很好的办法。

    前面的代码太简单了,就不上传了,这里分享最后的自定义选项卡的Demo大家可以下载来看看:

                                                                                                                           ViewPager自定义选项卡.zip

  • 相关阅读:
    luncence
    git与svn与github与码云的区别
    redis缓存在项目中的使用
    大宗风控体系“药不能停”:一线实战高手解密衍生品交易风险管控的三个层级!
    永恒的风控:大宗商品贸易融资背后的核心风险该如何规避?
    达信:深度解读COSO新版企业风险管理框架(ERM)
    DataOps Reading Notes
    SDN Reading Notes
    给某mooc站点准备的FE大纲
    《财富》杂志推荐的75本商业必读书?
  • 原文地址:https://www.cnblogs.com/duoduohuakai/p/4094249.html
Copyright © 2011-2022 走看看