Android.V4的ViewPager的源码和改造(一)

来源:互联网 发布:mysql 外键 编辑:程序博客网 时间:2024/05/29 18:32

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怎么写~


0 0
原创粉丝点击