继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

【Android】ViewPager深入解析(一)

去吧皮卡丘
关注TA
已关注
手记 5
粉丝 34
获赞 749

话说小伙伴们在使用App的时候有没有注意到很多App的首页都是可以左右滑动的页面呢?很多App还有绚丽的轮播图广告。那么如何实现这样的效果呢?相信很多小伙伴都知道可以用ViewPager来完成!不过也许很多小伙伴也跟皮卡丘一样,虽然一直在使用ViewPager,但是大部分时间都是在ctrl+c、ctrl+v~~~
图片描述
但是,很多小伙伴也许并没有详细地了解过ViewPager,所谓“知其然知其所以然”,那么今天皮卡丘就和大家一起来学习下ViewPager吧~~~

每个小标题后面的句子,素材来自于一款网络游戏,大家随便看看就可以了,跟主题无关,我只是觉得好玩......

1、江湖相逢

犹记与你初逢扬州湖畔,你只回眸一笑,我却惊艳了时光。

关于ViewPager,我们先来看看api中的继承关系
图片描述
从图里可以看出,ViewPager继承自ViewGroup,也就是ViewPager是一个容器类,可以包含其他的View类。然后我们在看看api中的定义:

Layout manager that allows the user to flip left and right through pages of data. You supply an implementation of a PagerAdapter to generate the pages that the view shows.

Note this class is currently under early design and development. The API will likely change in later updates of the compatibility library, requiring changes to the source code of apps when they are compiled against the newer version.

ViewPager is most often used in conjunction with Fragment, which is a convenient way to supply and manage the lifecycle of each page. There are standard adapters implemented for using fragments with the ViewPager, which cover the most common use cases. These are FragmentPagerAdapter and FragmentStatePagerAdapter; each of these classes have simple code showing how to build a full user interface with them.

有一些英语没有学好的小伙伴可能不是很能理解这一段描述。快去跟你们的英语老师道歉啊!嘛,这里皮卡丘就先给你们进行一下翻译吧,不过因为水平有限,可能会有一些翻译错误,如果大家发现错误的话欢迎留言哦。

ViewPager是一个允许使用者左右滑动数据页面的布局管理器。你可以通过一个适配器(PagerAdapter)来管理要显示的页面。

不过要注意的是,这个类目前还处于初期的设计和开发。随着今后兼容库的更新,API文档也会进行更改,同时应用程序在编译时也需要对代码进行一定的修改。

ViewPager更多的时候会与Fragment一起使用,这是一种很好的方法来管理各个页面的生命周期。Android提供了一些专门的适配器来让ViewPager与Fragment一起工作,也就是FragmentPagerAdapter与FragmentStatePagerAdapter。他们基本上可以满足大部分常见的永续需求,并且他们都有简单的代码样例来展示如何用他们来建立一个完整的用户页面。

通过这一段描述,大家应该对ViewPager有了一个大致的了解,皮卡丘大致把常用的信息再总结一下:

1、ViewPager主要用来左右滑动。(类似图片轮播)
2、ViewPager要用适配器来连接“视图”和“数据”。(大家可以联想下listview的使用方法,原理是类似的)
3、官方推荐ViewPager与Fragment一起使用,并且有专门的适配器。

好啦,看到这里,小伙伴们是不是对这么一个神奇的组件充满了兴趣呢?那我们赶紧来写一个最简单的ViewPager吧。

2、相见恨晚

大家都说我逍遥此身君子意,但与你把酒言欢之时,我却知道,逍遥的心亦有了牵挂。所谓相见恨晚,大概就是如此吧。

在创建项目之前,我们先大致来描述下该项目的功能:viewpager中包含3个视图,并且这3个视图可以通过左右滑动来进行切换。很简便吧?我们先创建一个ViewPagerDemo的工程。
既然我们需要包含3个视图,我们就先准备3个页面的布局吧(很简单的布局,就是居中显示一个图片):
page1.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <ImageView 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:src="@drawable/view1" />

</RelativeLayout>

page2.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <ImageView 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:src="@drawable/view2" />

</RelativeLayout>

page3.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <ImageView 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:src="@drawable/view3" />

</RelativeLayout>

然后是我们的主页面布局,只有一个ViewPager:
activity_main.xml

<LinearLayout 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.support.v4.view.ViewPager
        android:id="@+id/myViewPager"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        ></android.support.v4.view.ViewPager>

</LinearLayout>

这里需要注意的是,ViewPager引入的时候必须写完整android.support.v4.view.ViewPager,如果我们按照一般控件那样直接写ViewPager,就会报以下的错误
图片描述
其实从报错描述中我们大致能看出来是因为无法在android.view下面找到ViewPager这个类,这里的具体原因涉及到兼容包的问题,如果有机会的话皮卡丘也会以后再分享,不过这篇文章就先不深入研究啦。

好啦,我们已经把该准备的布局文件都准备好了,接下来我们开始来敲代码。
从API中我们看出来,viewpager是通过适配器来进行管理的(数据源---适配器---视图)。我们分别来看看:
数据源:这里的数据比较简单,就是包含3个View的一个list

private View page1, page2, page3; // ViewPager包含的页面
private List<View> pageList; // ViewPager包含的页面列表,一般给adapter传的是一个list

MainActivity.java
......

LayoutInflater inflater = getLayoutInflater();
page1 = inflater.inflate(R.layout.page1, null);
page2 = inflater.inflate(R.layout.page2, null);
page3 = inflater.inflate(R.layout.page3, null);

pageList = new ArrayList<View>();
pageList.add(page1);
pageList.add(page2);
pageList.add(page3);

视图:一般视图都是比较简单的,这里也不例外,就是一个ViewPager

private ViewPager myViewPager; // 要使用的ViewPager

......

myViewPager = (ViewPager) findViewById(R.id.myViewPager);

适配器:在大多数使用适配器的控件里,适配器相对于数据源和视图来说都更加复杂,同时也决定了这个控件主要的功能。ViewPager也不例外,所以我们有必要对PagerAdapter深入了解一下。
我们先来看看API中对PagerAdapter的描述:

Base class providing the adapter to populate pages inside of a ViewPager. You will most likely want to use a more specific implementation of this, such as FragmentPagerAdapter or FragmentStatePagerAdapter.

When you implement a PagerAdapter, you must override the following methods at minimum:

  • instantiateItem(ViewGroup, int)
    • destroyItem(ViewGroup, int, Object)
    • getCount()
    • isViewFromObject(View, Object)

PagerAdapter is more general than the adapters used for AdapterViews. Instead of providing a View recycling mechanism directly ViewPager uses callbacks to indicate the steps taken during an update. A PagerAdapter may implement a form of View recycling if desired or use a more sophisticated method of managing page Views such as Fragment transactions where each page is represented by its own Fragment.

ViewPager associates each page with a key Object instead of working with Views directly. This key is used to track and uniquely identify a given page independent of its position in the adapter. A call to the PagerAdapter method startUpdate(ViewGroup) indicates that the contents of the ViewPager are about to change. One or more calls to instantiateItem(ViewGroup, int) and/or destroyItem(ViewGroup, int, Object) will follow, and the end of an update will be signaled by a call to finishUpdate(ViewGroup). By the time finishUpdate returns the views associated with the key objects returned by instantiateItem should be added to the parent ViewGroup passed to these methods and the views associated with the keys passed to destroyItem should be removed. The method isViewFromObject(View, Object) identifies whether a page View is associated with a given key object.

A very simple PagerAdapter may choose to use the page Views themselves as key objects, returning them from instantiateItem(ViewGroup, int) after creation and adding them to the parent ViewGroup. A matching destroyItem(ViewGroup, int, Object) implementation would remove the View from the parent ViewGroup and isViewFromObject(View, Object) could be implemented as return view == object;.

PagerAdapter supports data set changes. Data set changes must occur on the main thread and must end with a call to notifyDataSetChanged() similar to AdapterView adapters derived from BaseAdapter. A data set change may involve pages being added, removed, or changing position. The ViewPager will keep the current page active provided the adapter implements the method getItemPosition(Object).

又到了皮卡丘的英语小讲堂啦,依然是大致的翻译:

PagerAdapter是用于“将多个页面填充到ViewPager”的适配器的一个基类,大多数情况下,你们可能更倾向于使用一个实现了PagerAdapter并且更加具体的适配器,例如FragmentPagerAdapter或者FragmentStatePagerAdapter。

当你实现一个PagerAdapter时,你至少需要重写下面的几个方法:

  • instantiateItem(ViewGroup, int)
    • destroyItem(ViewGroup, int, Object)
    • getCount()
    • isViewFromObject(View, Object)

PagerAdapter比很多AdapterView的适配器更加通用。ViewPager使用回调机制来显示一个更新步骤,而不是直接使用视图回收机制。如果需要时,PagerAdapter也可以实现视图回收方法,或者直接使用一种更加巧妙的方法来管理页面,比如直接使用能够管理自身事务的Fragment。

ViewPager并不直接管理页面,而是通过一个key将每个页面联系起来。这个key用来跟踪和唯一标识一个给定的页面,且该key独立于adapter之外。PagerAdapter中的startUpdate(ViewGroup)方法一旦被执行,就说明ViewPager的内容即将开始改变。紧接着,instantiateItem(ViewGroup, int)和/或destroyItem(ViewGroup, int, Object)方法将会被执行,然后finishUpdate(ViewGroup)的执行就意味着这一次刷新的完成。当finishUpdate(ViewGroup)方法执行完时,与instantiateItem(ViewGroup, int)方法返回的key相对应的视图将会被加入到父ViewGroup中,而与传递给destroyItem(ViewGroup, int, Object)方法的key相对应的视图将会被移除。isViewFromObject(View, Object)方法则判断一个视图是否与一个给定的key相对应。

一个简单的PagerAdapter会选择将视图本身作为key,在将视图创建并加入父ViewGroup之后通过instantiateItem(ViewGroup, int)返回。这种情况下,destroyItem(ViewGroup, int, Object) 的实现方法只需要将View从ViewGroup中移除即可,而isViewFromObject(View, Object)的实现方法可以直接写成return view == object;。

PagerAdapter支持数据集的改变。数据集的改变必须放在主线程中,并且在结束时调用notifyDataSetChanged()方法,这与通过BaseAdapter适配的AdapterView类似。一个数据集的改变包含了页面的添加、移除或者位移。ViewPager可以通过在适配器中实现getItemPosition(Object)方法来保持当前页面处于运行状态。

呼,看完了这一段描述,小伙伴们是不是有点累了呢~~~不过同时也一定对ViewPager的适配器有了更多的了解了吧?我们来筛选下初学时常用的知识点(如果要深入了解的话,还是要把其他的知识也好好掌握哦):

  • instantiateItem(ViewGroup, int)负责初始化指定位置的页面,并且需要返回当前页面本身(其实不一定要View本身,只要是能唯一标识该页面的key都可以,不过初学者一般就先用View本身作为key就可以啦);
  • destroyItem(ViewGroup, int, Object)负责移除指定位置的页面;
  • isViewFromObject(View, Object)里直接写“return view == object;”即可(当然,如果你在instantiateItem(ViewGroup, int)里返回的不是View本身,那就不能这么写哦);
  • 在描述中并未提及到getCount()方法,不过这个比较简单,也很常见,就是返回要展示的页面数量。

看了这么多理论知识,小伙伴们有没有想立刻动手写一下代码呢?于是我们回到刚才的项目,开始写一个适配器。我们新建一个类MyPagerAdapter。

MyPagerAdapter.java

package com.example.viewpagerdemo;

import java.util.List;

import android.support.v4.view.PagerAdapter;
import android.view.View;
import android.view.ViewGroup;

public class MyPagerAdapter extends PagerAdapter {

    private List<View> pageList;

    public MyPagerAdapter(List<View> pageList) {
        this.pageList = pageList;
    }

    @Override
    public int getCount() {
        // TODO Auto-generated method stub

        // 返回要展示的图片数量
        return pageList.size();
    }

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

        // 刚开始用viewpager就直接写“return arg0 == arg1;”就好啦
        return arg0 == arg1;
    }

   ......

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        // TODO Auto-generated method stub

        // 将当前位置的View移除
        container.removeView(pageList.get(position));
    }
}

结合之前的翻译,相信小伙伴们都能比较好地理解这一段代码吧。接下来就简单了,和所有的适配器一样,将viewpager与适配器绑定就可以了。

MainActivity.java

private MyPagerAdapter myPagerAdapter; // 适配器

......

myPagerAdapter = new MyPagerAdapter(pageList);
myViewPager.setAdapter(myPagerAdapter);

好啦,是时候运行程序了,我们来看看效果吧~~~
图片描述

哈哈哈哈哈哈哈哈哈哈是不是很好玩,当然篇幅所限,这只是ViewPager最最简单的功能,接下来皮卡丘还会继续写ViewPager的一些更高级的用法,欢迎小伙伴们继续支持~~~

图片描述
皮卡~皮卡丘~~~


接下来贴出本次demo的完整代码(慕课网暂时不能上传压缩包,所以先把代码都贴在文章里啦,不过其实建议是不要看demo,直接按照文章自己敲是最好哒)
MainActivity.java

package com.example.viewpagerdemo;

import java.util.ArrayList;
import java.util.List;

import android.os.Bundle;
import android.app.Activity;
import android.support.v4.view.ViewPager;
import android.view.LayoutInflater;
import android.view.View;

public class MainActivity extends Activity {

    private ViewPager myViewPager; // 要使用的ViewPager

    private View page1, page2, page3; // ViewPager包含的页面

    private List<View> pageList; // ViewPager包含的页面列表,一般给adapter传的是一个list

    private MyPagerAdapter myPagerAdapter; // 适配器

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();
    }

    private void initView() {
        // TODO Auto-generated method stub
        myViewPager = (ViewPager) findViewById(R.id.myViewPager);

        LayoutInflater inflater = getLayoutInflater();
        page1 = inflater.inflate(R.layout.page1, null);
        page2 = inflater.inflate(R.layout.page2, null);
        page3 = inflater.inflate(R.layout.page3, null);

        pageList = new ArrayList<View>();
        pageList.add(page1);
        pageList.add(page2);
        pageList.add(page3);

        myPagerAdapter = new MyPagerAdapter(pageList);
        myViewPager.setAdapter(myPagerAdapter);
    }

}

剩下的完整代码都已经在文章中有,这里就不再另外贴了,至于@drawable/view1,@drawable/view2,@drawable/view3,就麻烦小伙伴们自己准备3张图片吧~~~

打开App,阅读手记
75人推荐
发表评论
随时随地看视频慕课网APP

热门评论

如果我要显示100张图片,难道要写100个页面布局文件么。

每次显示的都是相同的布局文件,只是左右滑动时切换了图片而已,并不需要每个图片一个布局,楼主的方法并不是很好!

皮卡丘老师忘记重写这个方法了 @Override public Object instantiateItem(ViewGroup container, int position) { //每次滑动的时候把视图添加到viewpager container.addView(pageList.get(position)); return pageList.get(position); }

如果我要显示100张图片,难道要写100个页面布局文件么。

每次显示的都是相同的布局文件,只是左右滑动时切换了图片而已,并不需要每个图片一个布局,楼主的方法并不是很好!

查看全部评论