ViewPager和PageAdapter,FragmentPageAdapter,FragmentStatePageFragment

【Android】ViewPager深入解析(一)

http://www.imooc.com/article/2580

2015-12-07 11:59:28 11830浏览 19评论

话说小伙伴们在使用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张图片吧~~~


【Android】ViewPager深入解析(二)

http://www.imooc.com/article/2742
2015-12-11 15:09:35 9380浏览 24评论

话说上一话我们讲到,两人虽是萍水相逢,却把酒言欢,一见如故......
......
额不好意思,拿错剧本了......
话说在上一篇文章里面,我们详细地解释了ViewPager的定义以及最基础的用法,感兴趣的小伙伴们可以去围观一下哦
【Android】ViewPager深入解析(一)
不过这么简单的功能,在实际使用中是不够的,那么接下来,我们就在上一篇文章的基础上继续研究ViewPager的更多用法。今天这篇文章的知识,在实际项目中的应用就比较多了,难度也提高了不少,而且因为涉及的知识点开始增加,所以很多简单的知识皮卡丘可能就一带而过了,小伙伴们一定要好好学习啊。

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

3、策马同游

莺鸣柳上,风来吴山......这杭州城内处处美景,我只愿与你策马同游。

不知道小伙伴们还记不记得在上一篇文章中提到的

ViewPager更多的时候会与Fragment一起使用

没错,在大部分时候,项目中的ViewPager会和Fragment同时出现,每一个ViewPager的页面就是一个Fragment。既然如此,我们当然也要来看看这个知识!继续参考API。

Android提供了一些专门的适配器来让ViewPager与Fragment一起工作,也就是FragmentPagerAdapter与FragmentStatePagerAdapter。

好哒,看来是时候了解下FragmentPagerAdapter和FragmentStatePagerAdapter这一对磨人的小妖精了。我们先来看看FragmentPagerAdapter的API:

Implementation of PagerAdapter that represents each page as a Fragment that is persistently kept in the fragment manager as long as the user can return to the page.

This version of the pager is best for use when there are a handful of typically more static fragments to be paged through, such as a set of tabs. The fragment of each page the user visits will be kept in memory, though its view hierarchy may be destroyed when not visible. This can result in using a significant amount of memory since fragment instances can hold on to an arbitrary amount of state. For larger sets of pages, consider FragmentStatePagerAdapter.

When using FragmentPagerAdapter the host ViewPager must have a valid ID set.

Subclasses only need to implement getItem(int) and getCount() to have a working adapter.

好,皮卡丘来翻译一下这段话

FragmentPagerAdapter继承自PagerAdapter ,主要用来展示多个Fragment页面,并且每一个Fragment都会被保存在fragment manager中。

FragmentPagerAdapter最适用于那种少量且相对静态的页面,例如几个tab页。每一个用户访问过的fragment都会被保存在内存中,尽管他的视图层级可能会在不可见时被销毁。这可能导致大量的内存因为fragment实例能够拥有任意数量的状态。对于较多的页面集合,更推荐使用FragmentStatePagerAdapter。

当使用FragmentPagerAdapter的时候对应的ViewPager必须拥有一个有效的ID集。

FragmentPagerAdapter的派生类只需要实现getItem(int)和getCount()即可。

再来看看FragmentStatePagerAdapter的API:

Implementation of PagerAdapter that uses a Fragment to manage each page. This class also handles saving and restoring of fragment's state.

This version of the pager is more useful when there are a large number of pages, working more like a list view. When pages are not visible to the user, their entire fragment may be destroyed, only keeping the saved state of that fragment. This allows the pager to hold on to much less memory associated with each visited page as compared to FragmentPagerAdapter at the cost of potentially more overhead when switching between pages.

When using FragmentPagerAdapter the host ViewPager must have a valid ID set.

Subclasses only need to implement getItem(int) and getCount() to have a working adapter.

接下来是翻译

FragmentStatePagerAdapter继承自PagerAdapter,主要使用Fragment来管理每个页面。这个类同样用来保存和恢复fragment页面的状态。

FragmentStatePagerAdapter更多用于大量页面,例如视图列表。当某个页面对用户不再可见时,他们的整个fragment就会被销毁,仅保留fragment状态。相比于FragmentPagerAdapter,这样做的好处是在访问各个页面时能节约大量的内存开销,但代价是在页面切换时会增加非常多的开销。

当使用FragmentPagerAdapter(注:API里这里写的是FragmentPagerAdapter,不过貌似应该是FragmentStatePagerAdapter?)的时候对应的ViewPager必须拥有一个有效的ID集。

FragmentStatePagerAdapter的派生类只需要实现getItem(int)和getCount()即可。

从这两段API中我们可以看出:FragmentPagerAdapter与FragmentStatePagerAdapter存在着大量相似之处,用法也差不多,他们之间最大的不同在于:用户访问过的页面不可见之后是否会保留在内存中,而这个区别也构成了他们使用场景的不同。

至于这两个适配器的使用其实是很简单的,我们新建一个适配器类MyFragmentAdapter(这篇文章的demo是在上一篇文章的基础上修改的)

MyFragmentAdapter.java

package com.example.viewpagerdemo;

import java.util.List;

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;

public class MyFragmentAdapter extends FragmentPagerAdapter {

    private List<Fragment> fragmentList;
    public MyFragmentAdapter(FragmentManager fm, List<Fragment> fragmentList) {
        super(fm);
        // TODO Auto-generated constructor stub

        this.fragmentList = fragmentList;
    }

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

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

}

接下来我们创建3个Fragment,布局的话就使用上一篇文章中的3个page。

MyFragment1.java

package com.example.viewpagerdemo;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

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

MyFragment2和MyFragment3与之类似,就是把布局文件换一下就可以了,当然,实际项目中的fragment是不会这么简单的,我们可以在每个fragment页里面放入各种功能,配合ViewPager就能轻松构成一个APP的首页。
接下来我们只需要在MainActivity中将“数据源”、“视图”、“适配器”关联起来即可,这个和上一篇文章的内容差不多,就是将原来的3个普通页面换成Fragment页面,这里皮卡丘就直接贴代码啦。

MainActivity.java

        // ViewPager中包含的页面为普通页面
        /*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);*/

        // ViewPager中包含的页面为Fragment,用法与前面的普通适配器一模一样
        MyFragment1 myFragment1 = new MyFragment1();
        MyFragment2 myFragment2 = new MyFragment2();
        MyFragment3 myFragment3 = new MyFragment3();

        List<Fragment> fragmentList = new ArrayList<Fragment>();
        fragmentList.add(myFragment1);
        fragmentList.add(myFragment2);
        fragmentList.add(myFragment3);

        MyFragmentAdapter myFragmentAdapter = new MyFragmentAdapter(getSupportFragmentManager(), fragmentList);
        myViewPager.setAdapter(myFragmentAdapter);

代码很简单吧,哦,有一点别忘记,如果要使用FragmentPagerAdapter的话,我们的MainActivity必须继承FragmentActivity而不是Activity。OK,大家可以运行一下项目,效果应该是跟上一篇文章里一样的,不过因为这里的每个页面都是Fragment,所以相比于普通的View,你可以在每个页面自定义非常多的功能。

4、肝胆相照

曾以为你云裳一舞,只有那一份女儿柔情,然而相识越久,方才知你剑转流云,亦不输儿郎。

小伙伴们有没有觉得,相比于很多实际项目,我们的demo还是少了什么?没错,一般的APP在使用viewpager的时候,都会有一个tab页(只不过有一些在页面顶部,有一些在页面底部)。PagerTabStrip与PagerTitleStrip这两个类就是专门用来实现ViewPager的标题的,不过实际使用中基本上没他们什么事情(摸摸这两个孩子),所以我们这里也就不讲解了。一般会自己重新写一个Tab页,我们赶紧来实现一下吧。
我们先来准备一个tab页的布局,很简单,就是3个TextView。

tab.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="50dp"
    android:orientation="horizontal" >

    <TextView
        android:id="@+id/tv_tab0"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:text="小火龙"
        android:textSize="16sp"
        android:gravity="center"
        />

    <TextView 
        android:id="@+id/tv_tab1"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:text="杰尼龟"
        android:textSize="16sp"
        android:gravity="center"
        />

    <TextView
        android:id="@+id/tv_tab2"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:text="妙蛙种子"
        android:textSize="16sp"
        android:gravity="center"
        />

</LinearLayout>

很简单吧?这个皮卡丘就不解释了。
既然有了tab页,我们就要考虑Tab页与ViewpPger的交互了,一般情况下我们需要实现下面2中功能:
1、点击一个选项卡的时候,ViewPager滑动到对应的页面。
2、滑动ViewPager页面时,Tab滑动到对应的选项卡。
我们先来看第一个功能,这个功能其实很简单,涉及到的知识点就是setCurrentItem(int position),我们直接来看代码吧

    @Override
    public void onClick(View v) {
        // TODO Auto-generated method stub
        switch (v.getId()) {
        case R.id.tv_tab0:
            myViewPager.setCurrentItem(0);
            break;
        case R.id.tv_tab1:
            myViewPager.setCurrentItem(1);
            break;
        case R.id.tv_tab2:
            myViewPager.setCurrentItem(2);
            break;
        default:
            break;
        }

接着是第二个功能,这个功能涉及到的知识点就复杂一些了,这里我们需要实现一个接口OnPageChangeListener。我们来看看OnPageChangeListener的API
图片描述
我们总共需要实现3个方法,这3个方法基本上看名字就知道是什么作用,皮卡丘就不翻译啦,直接来讲解一下他们的用法:

onPageScrollStateChanged(int state):当页面的滑动状态改变时该方法会被触发,页面的滑动状态有3个:“0”表示什么都不做,“1”表示开始滑动,“2”表示结束滑动。

onPageScrolled(int position, float positionOffset, int positionOffsetPixels):页面在滑动过程中不停触发该方法:“position”按照api的解释是“目前显示在屏幕上的第一个页面,只要positionOffset不为0,那么他后面的页面同样是可见的”(这一点非常重要!!!皮卡丘因为当初并没有看API,而是想当然得觉得position是当前页面,所以绕了不少弯路,在做了大量的debug和log之后才发现错误在哪......所以小伙伴们一定要好好掌握基础知识啊!),“positionOffset”指的是偏移量的百分比,“positionOffsetPixels”指的是偏移量的数值。

onPageSelected(int position):ViewPager跳转到新页面时触发该方法,position表示新页面的位置。

我们继续看我们之前提的需求2,小伙伴们是不是已经觉得很简单啦?没错,只需要在onPageSelected(int position)中设置对应的方法即可。

MainActivity.java

    ......

    @Override
    public void onPageSelected(int position) {
        // TODO Auto-generated method stub
        switch (position) {
        case 0:
            tv_tab0.setTextColor(Color.RED);
            tv_tab1.setTextColor(Color.BLACK);
            tv_tab2.setTextColor(Color.BLACK);
            break;
        case 1:
            tv_tab0.setTextColor(Color.BLACK);
            tv_tab1.setTextColor(Color.BLUE);
            tv_tab2.setTextColor(Color.BLACK);
            break;
        case 2:
            tv_tab0.setTextColor(Color.BLACK);
            tv_tab1.setTextColor(Color.BLACK);
            tv_tab2.setTextColor(Color.GREEN);
            break;
        default:
            break;
        }

    ......

好啦,我们来看看效果
图片描述
怎么样!是不是变得更有趣了!我们不仅能看到每只神奇宝贝的样子,还能看到他们的名字了!
不过总觉得,名字和图片之间是不是应该有条下划线来区分下比较好呢?那么这条下划线应该具有怎么样的功能呢?很多APP的实现方式是,当ViewPager的图片页面切换完成之后,下划线就从原来的位置滑动到新的位置。这个实现是比较简单的......前提是你对属性动画比较了解的话......
我们先把下划线在布局中画出来,因为在布局文件里面我们没办法确定实际屏幕的宽度,所以我们可以先给下划线随意设置一个宽度,然后在Java代码中将其设置为整个屏幕宽度的三分之一。
activity_main.xml

...

<ImageView
        android:id="@+id/line_tab"
        android:layout_width="100dp"
        android:layout_height="1dp"
        android:background="#6E0000FF"
        />

...

MainActivity.java


    ......

    /**
     * 重新设定line的宽度
     */
    private void initLineImage() {
        // TODO Auto-generated method stub

        /**
         * 获取屏幕的宽度
         */
        DisplayMetrics dm = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(dm);
        int screenW = dm.widthPixels;

        /**
         * 重新设置下划线的宽度
         */
        LayoutParams lp = line_tab.getLayoutParams();
        lp.width = screenW / 3;
        line_tab.setLayoutParams(lp);

        moveOne = lp.width; // 滑动一个页面的距离
    }

    ......

然后我们来写一个动画方法

MainActivity.java

    ......

    private void movePositionX(int toPosition) {
        // TODO Auto-generated method stub
        float curTranslationX = line_tab.getTranslationX();
        float toPositionX = moveOne * toPosition;
        ObjectAnimator animator = ObjectAnimator.ofFloat(line_tab, "translationX", curTranslationX, toPositionX);
                animator.setDuration(500);
        animator.start();
    }

    ......

这个方法的功能就是让下划线滑动到新的页面。我们把他加入到onPageSelected方法中:

MainActivity.java


    ......
    @Override
    public void onPageSelected(int position) {
        // TODO Auto-generated method stub
        switch (position) {
        case 0:
            tv_tab0.setTextColor(Color.RED);
            tv_tab1.setTextColor(Color.BLACK);
            tv_tab2.setTextColor(Color.BLACK);
            movePositionX(0);
            break;
        case 1:
            tv_tab0.setTextColor(Color.BLACK);
            tv_tab1.setTextColor(Color.BLUE);
            tv_tab2.setTextColor(Color.BLACK);
            movePositionX(1);
            break;
        case 2:
            tv_tab0.setTextColor(Color.BLACK);
            tv_tab1.setTextColor(Color.BLACK);
            tv_tab2.setTextColor(Color.GREEN);
            movePositionX(2);
            break;
        default:
            break;
        }
    }

    ......

好,我们的Demo又增加了新的功能,我们来看看这一次的效果
图片描述

这样的效果基本上已经可以满足大部分的需求了。但是!其实我们可以考虑下,能不能让这个效果更加好玩:当我们滑动页面,但是页面并未真正切换的时候,下划线能否也跟着滑动呢?如果能实现这样的效果,那么用户体验一定非常的不错呢!
事不宜迟,我们赶紧来实现一下。大家觉得!这个功能应该考虑什么问题呢?
5、4、3、2、1
现在来揭晓答案啦
1、手指滑动的时候,下划线要跟着滑动的偏移量而进行相应的移动
2、手指停止滑动的时候,下划线要逐渐移动到目标位置(如果滑动偏移量不大的话就会回到原来位置)
所以我们需要判断手指什么时候开始滑动,什么时候停止滑动,大家还记得API中的描述吗?没错,就是onPageScrollStateChanged。

MainActivity.java


    ......

    @Override
    public void onPageScrollStateChanged(int state) {
        // TODO Auto-generated method stub
        switch (state) {
        case 1:
            isScrolling = true;
            isBackScrolling = false;
            break;
        case 2:
            isScrolling = false;
            isBackScrolling = true;
            break;
        default:
            isScrolling = false;
            isBackScrolling = false;
            break;
        }

    }

    ......

isScrolling代表的是手指正在滑动,isBackScrolling代表的是手指离开,页面继续滚动或者回滚。这个逻辑并不复杂,小伙伴们自己好好理解下吧。
接下来,是在onPageScrolled中加入滑动的动画,我们在原来的滑动动画上进行一定的修改

MainActivity.java


    ......

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        // TODO Auto-generated method stub
        if (isScrolling) {
            movePositionX(position, moveOne * positionOffset);
        }

        if (isBackScrolling) {
            movePositionX(position);
        }

    }

    ......

    /**
     * 下划线跟随手指的滑动而移动
     * @param toPosition
     * @param positionOffsetPixels
     */
    private void movePositionX(int toPosition, float positionOffsetPixels) {
        // TODO Auto-generated method stub
        float curTranslationX = line_tab.getTranslationX();
        float toPositionX = moveOne * toPosition + positionOffsetPixels;
        ObjectAnimator animator = ObjectAnimator.ofFloat(line_tab, "translationX", curTranslationX, toPositionX);
        animator.setDuration(500);
        animator.start();
    }

    /**
     * 下划线滑动到新的选项卡中
     * @param toPosition
     */
    private void movePositionX(int toPosition) {
        // TODO Auto-generated method stub
        movePositionX(toPosition, 0);
    }

    ......

这里的逻辑其实还是有点小复杂的,但主要麻烦在计算动画的目标坐标的数学逻辑,代码上的逻辑倒是蛮简单的,这里皮卡丘就不再具体分析啦。

好啦,这一下本章所有的功能都已经被加到这个demo上了!皮卡丘已经迫不及待得想看看效果了。
图片描述
功能顺利实现!我们把这个demo放到真机上看看效果。
然而!在模拟器上运行成功的demo,放到真机上却出了问题:下划线居然不跟随手指的滑动而移动!图片描述
这不可能!一定是手机出问题了!没错,反正不是我的锅~~~好吧,还是不自欺欺人了,出问题了自然要去打印日志来检查问题,在打印log的过程中,我们可以发现,在真机上onPageScrolled方法的触发频率比模拟器上高了不知道多少倍,这让我想到一开始设计的时候考虑过的问题:是否需要设定一个时间间隔,每隔了这个时间间隔才让动画执行一次(当时考虑这个问题的时候更多的是从执行效率的角度来考虑,不过当时在模拟器上打印日志的时候发现onPageScrolled触发的频率本来就不高,所以就没加这个限制)。OK,那让我们在onPageScrolled中增加一个时间间隔:
MainActivity.java


    ......

    currentTime = System.currentTimeMillis();
    if (isScrolling && (currentTime - startTime > 200)) {
        Log.i("MainActivity", "position = " + position);
        movePositionX(position, moveOne * positionOffset);
        startTime = currentTime;
    }

    ......

好啦,执行代码,在真机上运行~功能果然能正常运行啦,可喜可贺可喜可贺啊。
呼,这一章的内容就讲解到这里啦,相比于皮卡丘之前写的文章,这一篇文章包含的知识和内容多了不少,大家辛苦啦。接下来请继续跟着皮卡丘学习Android的各种知识吧。
图片描述


和之前一样贴出这次文章的所有代码
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:orientation="vertical"
     >

    <include layout="@layout/tab" />

    <ImageView
        android:id="@+id/line_tab"
        android:layout_width="100dp"
        android:layout_height="1dp"
        android:background="#6E0000FF"
        />

    <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>

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>

tab.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="50dp"
    android:orientation="horizontal" >

    <TextView
        android:id="@+id/tv_tab0"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:text="小火龙"
        android:textSize="16sp"
        android:gravity="center"
        />

    <TextView 
        android:id="@+id/tv_tab1"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:text="杰尼龟"
        android:textSize="16sp"
        android:gravity="center"
        />

    <TextView
        android:id="@+id/tv_tab2"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:text="妙蛙种子"
        android:textSize="16sp"
        android:gravity="center"
        />

</LinearLayout>

MainActivity.java

package com.example.viewpagerdemo;

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

import android.os.Bundle;
import android.animation.ObjectAnimator;
import android.graphics.Color;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.view.ViewPager;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView;
import android.widget.TextView;

public class MainActivity extends FragmentActivity implements OnPageChangeListener, OnClickListener {

    private ViewPager myViewPager; // 要使用的ViewPager

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

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

    private MyPagerAdapter myPagerAdapter; // 适配器

    private TextView tv_tab0, tv_tab1, tv_tab2; // 3个选项卡

    private ImageView line_tab; // tab选项卡的下划线

    private int moveOne = 0; // 下划线移动一个选项卡

    private boolean isScrolling = false; // 手指是否在滑动

    private boolean isBackScrolling  = false; // 手指离开后的回弹

    private long startTime = 0;

    private long currentTime = 0;

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

        initView();
        initLineImage();
    }

    /**
     * 重新设定line的宽度
     */
    private void initLineImage() {
        // TODO Auto-generated method stub

        /**
         * 获取屏幕的宽度
         */
        DisplayMetrics dm = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(dm);
        int screenW = dm.widthPixels;

        /**
         * 重新设置下划线的宽度
         */
        LayoutParams lp = line_tab.getLayoutParams();
        lp.width = screenW / 3;
        line_tab.setLayoutParams(lp);

        moveOne = lp.width; // 滑动一个页面的距离
    }

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

        // ViewPager中包含的页面为普通页面
        /*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);*/

        // ViewPager中包含的页面为Fragment,用法与前面的普通适配器一模一样
        MyFragment1 myFragment1 = new MyFragment1();
        MyFragment2 myFragment2 = new MyFragment2();
        MyFragment3 myFragment3 = new MyFragment3();

        List<Fragment> fragmentList = new ArrayList<Fragment>();
        fragmentList.add(myFragment1);
        fragmentList.add(myFragment2);
        fragmentList.add(myFragment3);

        MyFragmentAdapter myFragmentAdapter = new MyFragmentAdapter(getSupportFragmentManager(), fragmentList);
        myViewPager.setAdapter(myFragmentAdapter);

        tv_tab0 = (TextView) findViewById(R.id.tv_tab0);
        tv_tab1 = (TextView) findViewById(R.id.tv_tab1);
        tv_tab2 = (TextView) findViewById(R.id.tv_tab2);
        myViewPager.setCurrentItem(0);
        tv_tab0.setTextColor(Color.RED);
        tv_tab1.setTextColor(Color.BLACK);
        tv_tab2.setTextColor(Color.BLACK);

        tv_tab0.setOnClickListener(this);
        tv_tab1.setOnClickListener(this);
        tv_tab2.setOnClickListener(this);

        myViewPager.setOnPageChangeListener(this);

        line_tab = (ImageView) findViewById(R.id.line_tab);
    }

    @Override
    public void onPageScrollStateChanged(int state) {
        // TODO Auto-generated method stub
        switch (state) {
        case 1:
            isScrolling = true;
            isBackScrolling = false;
            break;
        case 2:
            isScrolling = false;
            isBackScrolling = true;
            break;
        default:
            isScrolling = false;
            isBackScrolling = false;
            break;
        }

    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        // TODO Auto-generated method stub
        currentTime = System.currentTimeMillis();
        if (isScrolling && (currentTime - startTime > 200)) {
            movePositionX(position, moveOne * positionOffset);
            startTime = currentTime;
        }

        if (isBackScrolling) {
            movePositionX(position);
        }

    }

    @Override
    public void onPageSelected(int position) {
        // TODO Auto-generated method stub
        switch (position) {
        case 0:
            tv_tab0.setTextColor(Color.RED);
            tv_tab1.setTextColor(Color.BLACK);
            tv_tab2.setTextColor(Color.BLACK);
            movePositionX(0);
            break;
        case 1:
            tv_tab0.setTextColor(Color.BLACK);
            tv_tab1.setTextColor(Color.BLUE);
            tv_tab2.setTextColor(Color.BLACK);
            movePositionX(1);
            break;
        case 2:
            tv_tab0.setTextColor(Color.BLACK);
            tv_tab1.setTextColor(Color.BLACK);
            tv_tab2.setTextColor(Color.GREEN);
            movePositionX(2);
            break;
        default:
            break;
        }
    }

    /**
     * 下划线跟随手指的滑动而移动
     * @param toPosition
     * @param positionOffsetPixels
     */
    private void movePositionX(int toPosition, float positionOffsetPixels) {
        // TODO Auto-generated method stub
        float curTranslationX = line_tab.getTranslationX();
        float toPositionX = moveOne * toPosition + positionOffsetPixels;
        ObjectAnimator animator = ObjectAnimator.ofFloat(line_tab, "translationX", curTranslationX, toPositionX);
        animator.setDuration(500);
        animator.start();
    }

    /**
     * 下划线滑动到新的选项卡中
     * @param toPosition
     */
    private void movePositionX(int toPosition) {
        // TODO Auto-generated method stub
        movePositionX(toPosition, 0);
    }

    @Override
    public void onClick(View v) {
        // TODO Auto-generated method stub
        switch (v.getId()) {
        case R.id.tv_tab0:
            myViewPager.setCurrentItem(0);
            break;
        case R.id.tv_tab1:
            myViewPager.setCurrentItem(1);
            break;
        case R.id.tv_tab2:
            myViewPager.setCurrentItem(2);
            break;
        default:
            break;
        }
    }

}

MyFragment1.java

package com.example.viewpagerdemo;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

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

MyFragment2.java

package com.example.viewpagerdemo;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

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

MyFragment3.java

package com.example.viewpagerdemo;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

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

MyFragmentAdapter.java

package com.example.viewpagerdemo;

import java.util.List;

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;

public class MyFragmentAdapter extends FragmentPagerAdapter {

    private List<Fragment> fragmentList;
    public MyFragmentAdapter(FragmentManager fm, List<Fragment> fragmentList) {
        super(fm);
        // TODO Auto-generated constructor stub

        this.fragmentList = fragmentList;
    }

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

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

}

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 Object instantiateItem(ViewGroup container, int position) {
        // TODO Auto-generated method stub

        // 获取指定位置的控件,页面的事件都可以在这里写
        View view = pageList.get(position);

        // 将指定位置的View加入到ViewGroup
        container.addView(view);

        // 将View作为key返回
        return view;
    }

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

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值