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怎么写~
- Android.V4的ViewPager的源码和改造(一)
- Android v4包下的PagerTitleStrip,ViewPager的页面标题
- Android关联源码support-v4的问题解决
- Android关联源码support-v4的问题解决
- Android关联源码support-v4的问题解决
- Android关联源码support-v4的问题解决
- Android关联源码support-v4的问题解决
- 找不到android.support.v4.view.ViewPager里面的包
- 4.8 Android Fragment<v4>, ViewPager, RadioGroup的联动
- android.support.v4.view.ViewPager控件的位置
- Android --------------------ActionBar 与 ViewPager 和 ActionTab 切换 的源码实现
- 【Android API】3.ViewPager的实现原理和源码分析
- ViewPager改造其中的预加载
- viewpager导航栏(源码用别人的,稍作改造,留自己以后用)
- eclypse关联android源码和v4源码
- android的ViewPager和Animation的一些使用(一)
- Android查看android-support-v4.jar源码的方法
- 使用viewpager或者fragmentActivity等一些v4包下的类,当我们按F3时无法查看到源码,这个时候就需要我们关联该源码,该源码的关联与android源码的关联不一样。
- swift 中的数组
- 如何连接免费VPN
- 解决git提交时提示“git did not exit cleanly (exit code 1)” 问题
- Kubernetes基础概念
- 不同VLAN之间相互通信的两种方式 (单臂路由、三层交换)
- Android.V4的ViewPager的源码和改造(一)
- HttpClient Cookies设置
- 正方形的button变圆形
- MFC搭建OpenGL框架示例
- 青岛大虾吃不起!还不如勒紧腰带买它最划算~
- Android /system/lib底层库的调用
- 5G毫米波和超宽带信号的验证和测试
- yii命令行下运行控制器【没有验证】
- jQuery ajax - serialize() 方法