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.所以当我们遇到问题,调试是一个很好的办法。

时间: 2024-10-06 11:33:09

Android控件的新宠儿——ViewPager的相关文章

Android控件——ViewPager

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

android控件篇:ViewPager+Fragment+GridView的使用(与AndroidQuery框架结合)

最近看了一个AndroidQuery的框架,里面的Demo,有个界面,让博主很喜欢.左右滑动十分顺畅,手感很好,于是拿来和大家分享一下.先看一下效果图: 从图中可以看出,上面的布局是一个Layout里面嵌套有个ViewPager,ViewPager中包含着Fragment,Fragment的布局文件包含了一个简单的GridView,GridView的Item布局很简单,就是一个100*100大小的图片.好啦,先说这么多,然后咱们看代码吧. 最外层Activity的布局文件 <?xml versi

Android 控件:使用下拉列表框--Spinner

---恢复内容开始--- 一.前段代码 <Spinner android:id="@+id/spin" android:paddingTop="10px" android:layout_width="fill_parent" android:layout_height="50sp"/> <Button android:id="@+id/addList" android:layout_wid

Android控件上添加图片

项目中有一个点赞功能,点赞的小图标添加在点赞列表旁边,在xml里可以进行设置,也可以在代码中进行绘图. 下面是两种方法的设置: 1.xml里:一些控件:button.textView等等里面有个属性是android:drawableLeft 就可以将pic设置到text的左边.good.... 2.代码中: TextView txtlikedList = new TextView(this.getContext()); Drawable drawable= getResources().getD

android控件 下拉刷新pulltorefresh

外国人写的下拉刷新控件,我把他下载下来放在网盘,有时候访问不了github 支持各种控件下拉刷新 ListView.ViewPager.WevView.ExpandableListView.GridView.(Horizontal )ScrollView.Fragment上下左右拉动刷新,比下面johannilsson那个只支持ListView的强大的多.并且他实现的下拉刷新ListView在item不足一屏情况下也不会显示刷新提示,体验更好. 国内网盘地址:http://www.400gb.c

Robotium之Android控件定位实践和建议(Appium/UIAutomator姊妹篇)

本人之前曾经撰文描述Appium和UIAutomator框架是如何定位Android界面上的控件的. UIAutomator定位Android控件的方法实践和建议 Appium基于安卓的各种FindElement的控件定位方法实践和建议 今天我们换一个渊源更留长,当今更盛行的框架Robotium,实践下看它又是如何对控件进行定位的. 1. 背景 为保持这个系列的一致性,我们继续用SDK自带的NotePad实例应用作为我们的试验目标应用,但是这次不仅仅是像以前一样主要围绕Menu Option里面

Android控件架构与自定义控件详解(二)——自定义View

在自定义View时,我们通常会去重写onDraw()方法来绘制View的显示内容.如果该View还需要使用wrap_content属性,那么还必须重写onMeasure()方法.另外,通过自定义attrs属性,还可以设置新的属性配置值. 在View中通常有一些比较重要的回调方法. onFinishInflate():从XML加载组件后回调. onSizeChanged(;:组件大小改变时. onMeasure():回调该方法来进行测量. onLayout():回调该方法来确定显示的位置. onT

Android控件介绍

Android控件介绍 多选按钮(CheckBox) CheckBox有两个常用的事件,OnClickListener事件和OnClickChangeListener事件 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_w

Android控件(2)RadioButton&amp;RadioGroup

抄自: http://www.cnblogs.com/wt616/archive/2011/06/20/2085531.html 学习目的: 1.掌握在Android中如何建立RadioGroup和RadioButton 2.掌握RadioGroup的常用属性 3.理解RadioButton和CheckBox的区别 4.掌握RadioGroup选中状态变换的事件(监听器) RadioButton和CheckBox的区别: 1.单个RadioButton在选中后,通过点击无法变为未选中 单个Che