ViewPager是Android V4包中给出的一个开源控件,由于它友好的API和良好的流畅度,已经被大部分的软件所使用。但是不明白为什么ViewPager总是跟Fragment扯到一起,仅仅是因为ViewPager里面放了一个PagerFragmentAdapter么?如果仅仅是这样能让你们把这两个东西扯到一起那真是Fragment和ViewPager的忧伤。很多人问说,网上有什么好的代码可以看么?其实我可以很负责任的告诉你,V4的包就是一个非常好的代码。代码量小,但是非常的精致。另外说明一点,我的文章从来不是教你这个东西怎么用的。而是告诉你为什么的。如果你是以工具类的性质来看,对不起,真的不适合你。
在我们分析ViewPager我们需要对ViewPager稍微进行改造,主要为了方便我们后面的调试。我只留了PagerAdapter,FragmentPagerAdapter,ViewPager,三个类。实际我们只留了两个类,因为FragmentPagerAdapter是PagerAdapter的子类。
我假设你已经非常熟悉ViewPager的操作,我们知道ViewPager的管理采用了适配器模式,但是为什么在ViewPager的继承树上并没有看到AdapterView的影子呢?这个问题我们会在后面来解释,主要问题还是在它的实现机制上。
作为源码分析的第一篇,我希望从简单的类入手,这一系列我可能不会写的很多篇,其中会涉及一些Fragment的知识,我希望大家可以去看我对Fragment的源码分析那部分知识,不要去关注网络上Fragment使用方法那种很肤浅的理解。
我们来看一下PagerAdapter和FragmentPagerAdapter两个类,我们先来看一下基类PagerAdapter。
public abstract class PagerAdapter {
private DataSetObservable mObservable = new DataSetObservable();
/**
* Return the number of views available.
*/
public abstract int getCount();
public void startUpdate(ViewGroup container) {
startUpdate((View) container);
}
public Object instantiateItem(ViewGroup container, int position) {
return instantiateItem((View) container, position);
}
public void destroyItem(ViewGroup container, int position, Object object) {
destroyItem((View) container, position, object);
}
public void setPrimaryItem(ViewGroup container, int position, Object object) {
setPrimaryItem((View) container, position, object);
}
public void finishUpdate(ViewGroup container) {
finishUpdate((View) container);
}
public void startUpdate(View container) {}
public Object instantiateItem(View container, int position) {
throw new UnsupportedOperationException(
"Required method instantiateItem was not overridden");
}
public void destroyItem(View container, int position, Object object) {
throw new UnsupportedOperationException("Required method destroyItem was not overridden");
}
public void setPrimaryItem(View container, int position, Object object) {
}
public void finishUpdate(View container) {
}
public abstract boolean isViewFromObject(View view, Object object);
public Parcelable saveState() {
return null;
}
public void restoreState(Parcelable state, ClassLoader loader) {
}
public int getItemPosition(Object object) {
return POSITION_UNCHANGED;
}
public void notifyDataSetChanged() {
mObservable.notifyChanged();
}
public void registerDataSetObserver(DataSetObserver observer) {
mObservable.registerObserver(observer);
}
public void unregisterDataSetObserver(DataSetObserver observer) {
mObservable.unregisterObserver(observer);
}
public CharSequence getPageTitle(int position) {
return null;
}
public float getPageWidth(int position) {
return 1.f;
}
}
我们通过PagerAdapter的类结构看,至少可以看出几点:
1.跟传统的BaseAdapter很相似
2.本身就对控件复用提出接口上的支持
我们先来分析第一点,其实跟BaseAdapter相似无可厚非~毕竟是同一平台下的同一种设计模式,并且ViewPager采用单独的一套PagerAdapter也是无可奈何的事情,因为AdapterView的限制太多了,如果ViewPager继承于它实在难以开展,不如自成一套。其中一个理由在于ViewPager在添加控件的时候需要调用addView方法。但是如果你熟悉Android的部分源码,应该了解,为了杜绝你自己去addView,在AdapterView里面是关闭了这个接口的支持。另外,由于PagerAdapter希望你使用的控件复用,而把你的数据模型放在了第一位,所以它在返回的位置类型使用的是数据模式的Object对象。当然它这种写法其实并不好,因为每一个继承它的类都要有意无意的实现强转。因此,如果在此处引入泛型应该是更好的。
@Override
public void addView(View child, int index, LayoutParams params) {
throw new UnsupportedOperationException("addView(View, int, LayoutParams) "
+ "is not supported in AdapterView");
}
(AdapterView中关闭了对addView的支持)
上面我们分析第一点的时候实际上已经提到了PagerAdapter对控件复用的支持,我们接下来来仔细分析一下。Adapter对控件的支持主要依赖于它所提供的四个方法
public void startUpdate(ViewGroup container) {
startUpdate((View) container);
}
public Object instantiateItem(ViewGroup container, int position) {
return instantiateItem((View) container, position);
}
public void destroyItem(ViewGroup container, int position, Object object) {
destroyItem((View) container, position, object);
}
<pre name="code" class="java"> public abstract boolean isViewFromObject(View view, Object object);
public void finishUpdate(View container) { }
我们通过startUpdate和finishUpdate两个成员方法可以看出,对于ViewPager和Fragment框架之间确实存在着某种模式上的联系。一定是采用一种事务的方式来管理。当然你可能会问,我如果不采用事务,是否可以?当然可以,就像是ListView本身提供了复用的机制,你照样可以不用一个道理。由于Fragment一样采用事务管理,因此有一个叫做FragmentPagerAdapter也不稀奇。可以这么说,V4整个包,不论是提供了像Fragment这样的app管理,还是提供了ViewPager这样的控件管理,都是希望开发者用复用和事务的方式来管理自己的代码。
PagerAdapter的事务调用是通过ViewPager来调用的,ViewPager在管理控件并没有什么秘诀,主要分成两步。一步是填充,一步时动画。这样的管理实际上要比ListView的管理要简单的多。对于PagerAdapter的事务调用,主要发生在填充这一步。具体填充逻辑我们在分析ViewPager这个控件的时候细细去分析一下。但是通过我们对事务的了解,整个逻辑模型一定是:
startUpdate->(destroyItem)*<->(initItem)*->finishUpdate
我们按照这个逻辑来看下作为实现类的FragmentPagerAdapter是如何实现这些步骤的:
@Override
public void startUpdate(ViewGroup container) {
}
在事务启动的时候,FragmentPagerAdapter并没有做任何的处理。而在初始化的过程中,Fragment才开始介入。
@Override
public Object instantiateItem(ViewGroup container, int position) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
final long itemId = getItemId(position);
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position);
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
}
if (fragment != mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
}
return fragment;
}
这段代码还是非常好理解的,就是看在Fm里面的Active列表中是否有Fragment,如果有就attach。这样可以避免我们非法的创建对象。顺便提一下,之前我在写Fragment源码分析的时候有人问过为什么在Fragment加入的时候要引入一个控件id,直接引入控件不是更好么?这个主要是因为fragment支持持久化,如果你持久化以后再你restore的时候如果没有控件就会有问题,因此你记录一个id值可以保证你随时可以调用加入到container控件中。在这段init代码中,特别要注意的一点是它引入了Fragment的一个事务,实际上按照我们上面的事务模型,我们不难得出结论,在destoryItem里面肯定也有一个事务。
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
mCurTransaction.detach((Fragment)object);
}
果然如此!是不是觉得你离作者的真正意图更近了呢?~同时我们还可以得到一个结论就是在finishUpdate的时候数据模型才进行提交事务的操作:
@Override
public void finishUpdate(ViewGroup container) {
if (mCurTransaction != null) {
mCurTransaction.commitAllowingStateLoss();
mCurTransaction = null;
mFragmentManager.executePendingTransactions();
}
}
又想我们所预想的那样。也就是说FragmentPagerAdapter基于PagerAdapter的事务模型实现了往控件里面增加Fragment控件的操作。
这个时候你们会想,我知道了这个模型又能怎样。其实你知道了PagerAdapter和ViewPager的设计理念你就可以做很多事情。首先,你完全可以舍弃掉Fragment那一套东西,不需要什么东西都继承于Fragment的那一套接口。假如说你现在要实现一套自己的连续播放的广告位。如果你采用FragmentPagerAdapter的方式实现当然也可以。不过广告这个关Fragment什么事,就是一堆图片连续播放而已,如果有控件你根本不会主动用到Fragment.如果采用PagerAdapter的话我们无非就是实现上面的四个模型函数。我们首先分析一下这个轮播的控件,它无非有两个功能实现:
1.ViewPager的切换
2.定时切换
我们先用PagerAdapter的方式来实现ViewPager的切换功能,根据上面的理论我们可以简单的写出来:
public class CircleAdapter extends PagerAdapter {
private int[] mImageIds = null;
private List<ImageView> mImageViews = new ArrayList<ImageView>();
public CircleAdapter(Context context,int[] imgs) {
this.mImageIds = imgs;
for (int i = 0; i < imgs.length ; i ++) {
ImageView iv = new ImageView(context);
iv.setImageResource(imgs[i]);
mImageViews.add(iv);
}
}
@Override
public int getCount() {
return mImageIds.length;
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public void startUpdate(ViewGroup container) {
super.startUpdate(container);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
ImageView iv = mImageViews.get(position);
container.addView(iv);
return iv;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeViewInLayout(mImageViews.get(position));
}
@Override
public void finishUpdate(ViewGroup container) {
super.finishUpdate(container);
}
}
这个时候不要去纠结代码的结构如何,那是以后的事情。我们通过上面的逻辑实现了ViewPager里面显示图片目的,现在我们要实现连续播放的问题,为了方便我们继承了ViewPager。并提供一个接口方便定时器调用,我们这边定义成为smoothToNext。这个接口的目的是为了线性的切换到下一个界面,然后无限循环下去,无限循环这个操作我们可以采用定时器或者handler的方式来实现,不做为我们的重点。
public class CircleViewPager extends ViewPager {
private int[] mImageResources ;
public CircleViewPager(Context context,int[] imageResouces) {
super(context);
this.setAdapter(new CircleAdapter(context, imageResouces));
mImageResources = imageResouces;
this.setOnPageChangeListener(new ListenerImpl());
}
public void smoothToNext() {
this.setCurrentItem(this.getCurrentItem()+1,true);
}
private class ListenerImpl implements ViewPager.OnPageChangeListener {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
}
@Override
public void onPageScrollStateChanged(int state) {
if (state == 0) {
if (getCurrentItem() == mImageResources.length) {
setCurrentItem(0,false);
}
}
}
}
}
这里我们实际上做了一个取巧,并对上面的Adapter进行了改造。我们在最后一个控件后面又补了第一个控件,然后在动画状态结束以后,把位置移动到第一,就可以无缝的连续播放。
改造后的Adapter代码是:
public class CircleAdapter extends PagerAdapter {
private int[] mImageIds = null;
private List<ImageView> mImageViews = new ArrayList<ImageView>();
public CircleAdapter(Context context,int[] imgs) {
this.mImageIds = imgs;
for (int i = 0; i < imgs.length ; i ++) {
ImageView iv = new ImageView(context);
iv.setImageResource(imgs[i]);
mImageViews.add(iv);
}
}
@Override
public int getCount() {
return mImageIds.length + 1;
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public void startUpdate(ViewGroup container) {
super.startUpdate(container);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
ImageView iv = mImageViews.get(position%mImageViews.size());
if (iv.getParent() == null) {
container.addView(iv);
}
return iv;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (position >= 0 && position < mImageViews.size()) {
container.removeViewInLayout(mImageViews.get(position));
}
}
@Override
public void finishUpdate(ViewGroup container) {
super.finishUpdate(container);
}
}
代码很简单,还是控件复用的问题,你要对一些已经用到的控件,避免重复添加。好的~PagerAdapter我们就分析到这里。下一篇我会告诉大家ViewPager怎么写~