引言
最近在工作中由于需要客制化系统的关系,接触到了很多ViewPager相关的UI,发现很多底层原生的界面也还是依然采用ViewPager+Fragment的布局方式,事实上这依然还是主流,微信5.0到6.0等还是采用这种布局方法,所以还是有必要总结下ViewPager的相关知识,增强下记忆和理解,毕竟伴随着经验的提升即使再去阅读同一本书也会有不一样的体会。
一、ViewPager
1、概述
ViewPager继承自ViewGroup,是左右两个屏幕平滑地切换的一个容器,容器里呈现的视图由对应的Adapter决定,和其他标准的AdapterView类似。简而言之就是我们通过Adapter把View放到ViewPager里,动动手指我们就可以实现左右滑动互相切换View了。另外ViewPager的更新不是直接由ViewPager本身去完成的,而是通过观察者去调用PagerAdapter的notifyDataSetChanged等相关方法去完成界面更新工作。
2、重要的成员
成员 | 说明 |
---|---|
public ViewPager (Context context) | |
public ViewPager (Context context, AttributeSet attrs) | |
interface ViewPager.OnPageChangeListener | 内部接口当切换不同Page时触发对应回调方法 |
void addView(View child, int index, ViewGroup.LayoutParams params) | 添加View到ViewPager里 |
boolean dispatchKeyEvent(KeyEvent event) | 派发键盘事件到下一个视图 |
ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) | |
PagerAdapter getAdapter() | 获取对应的适配器 |
int getCurrentItem() | 返回当前page的index |
int getOffscreenPageLimit() | Returns the number of pages that will be retained to either side of the current page in the view hierarchy in an idle state. |
int getPageMargin() | 返回Page之间的外间距 |
boolean onTouchEvent(MotionEvent ev) | 重写这个方法可以处理Touch事件 |
void removeView(View view) | 注意不能在 draw(android.graphics.Canvas), onDraw(android.graphics.Canvas), dispatchDraw(android.graphics.Canvas)或其他相关方法调用 |
void setPageTransformer(boolean reverseDrawingOrder, ViewPager.PageTransformer transformer) | 当the scroll position改变时触发,可以用于设置自定义的动画效果,即只要实现PageTransformer接口和其唯一的方法transformPage(View view, float position) |
void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) | 设置Page切换事件监听 |
void setPageMarginDrawable(int resId) | |
void setAdapter(PagerAdapter adapter) | 设置Adapter |
二、重要的PagerAdapter
PagerAdapter其实是一个抽象类封装了一些接口、回调和功能方法,在实际开发过程中我们往往是使用它及其子类——FragmentPagerAdapter和FragmentStatePagerAdapter的子类来填充到ViewPager中,通俗点来说,ViewPager只是个空皮囊,只有填充了PagerAdapter才能发挥其作用,正所谓巧妇也难为无米之炊,PagerAdapter就是这里的米,有了米才能各显神通做出各种各样的饭。
1、实现PagerAdapter时必须要重写的四个成员方法
ViewPager不是直接与View关联的,而是通过一个key对象,通过这个key对象我们可以追踪并且唯一识别指定的page在PagerAdapter的位置。
Object instantiateItem(ViewGroup container, int position)——实例化item,PagerAdapter将选择将这个对象填充到在当前ViewPager里。
void destroyItem(ViewGroup container, int position, Object object)——将item从指定的位置移出容器
int getCount()——获取item的总数,一般都是获取集合的size
isViewFromObject(View,Object)——判断容器里的View是否与一个key值相关联,比如说例如Fragment+ViewPager的处理,每个页面都由它是对应的Fragment来呈现,但ViewPager并不是直接与View关联,而是关联一个key。我们实现这个方法也很简单谷歌推荐我们只需一句——return view == object;。
public class ViewPagerAdapter extends PagerAdapter {
private List mList;
public ViewPagerAdapter(List list) {
this.mList = list;
}
@Override
public int getCount() {
if (mList != null && mList.size() > 0) {
return mList.size();
} else {
return 0;
}
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
container.addView(mList.get(position));
return mList.get(position);
}
@Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
}
2、PagerAdapter重要的成员方法
PagerAdapter的工作过程其实很简单,当ViewPager里的内容要改变的时候,就调用startUpdate方法来完成,随即也会多次调用instantiateItem或者destroyItem方法,finishUpdate最后被调用来完成更新工作。finishUpdate最终会返回需要添加到容器里的View和对应的 key对象(通过instantiateItem完成);而一些传递到destroyItem的则被移除。
成员方法 | 说明 |
---|---|
void finishUpdate(ViewGroup container) | 当完成page的更新工作时被调用 |
int getItemPosition(Object object) | 但需要确定item位置是否改变时被调用,默认实现永远返回-1 |
void notifyDataSetChanged() | 但需要去更新的时候我们可以去调用这个方法完成 |
void registerDataSetObserver(DataSetObserver observer) | 注册观察者 |
void unregisterDataSetObserver(DataSetObserver observer) | 取消注册 |
void restoreState(Parcelable state, ClassLoader loader) | |
Parcelable saveState() |
3、FragmentPagerAdapter
FragmentPagerAdapter继承自PagerAdapter,主要的成员方法功能都是大同小异的,这个适配器主要就是用于快速实现Fragment在ViewPager里面进行滑动切换的,所以,如果我们想实现Fragment的左右滑动,优先选择ViewPager+FragmentPagerAdapter模式,因为FragmentPagerAdapter拥有自己的缓存策略,当配合ViewPager配合使用时,会缓存当前Fragment以及左边一个、右边一个(一共三个Fragment对象)假设有三个Fragment,那么在ViewPager初始化之后,3个fragment都会加载完成,中间的Fragment在整个生命周期里面只会加载一次,当最左边的Fragment处于显示状态,最右边的Fragment由于超出缓存范围,会被销毁,当再次滑到中间的Fragment的时候,最右边的Fragment会被再次初始化。所以比较适合用户制作较少页面切换的tab界面(最多3个),FragmentPagerAdapter会对我们浏览过Fragment进行缓存,保存这些界面的临时状态,这样当我们左右滑动的时候,界面切换更加的流畅。但是,这样也会增加程序占用的内存。那么3个以上的话,谷歌推荐移步到FragmentStatePagerAdapter。
3.1、当我们使用FragmentPagerAdapter的时候,它的宿主ViewPager必须设置对应的id,同时必须实现两个方法和构造方法。
getCount()——返回的是ViewPager页面的数量,即Fragement的数量。
getItem(int position)——返回的是要显示的fragment对象。
//实现一个FragmentPagerAdapter最少只需要实现getCount和getItem方法即可
FragmentPagerAdapter fragmentadapter = new FragmentPagerAdapter(
getSupportFragmentManager()) {
@Override
public int getCount() {
return fragments.size(); //getCount()返回的是ViewPager页面的数量,多少个Fragement对应多少个页面
}
@Override
public Fragment getItem(int position) {
return fragments.get(position);//返回的是要显示的fragment对象。
}
}
};
4、FragmentStatePagerAdapter
当然除了FragmentPagerAdapter之外,还有一个类也是专门用于快速实现多个(3个以上)Fragment在ViewPager里面进行适配并滑动切换的,即FragmentStatePagerAdapter。FragmentStatePagerAdapter也直接继承自PagerAdapter的,其工作方式和ListView十分相似的。当Fragment对用户不可见的时候,整个Fragment会被销毁并且保存Fragment的保存状态。基于这样的特性,FragmentStatePagerAdapter比FragmentPagerAdapter更适合用于很多界面之间的转换,而且消耗更少的内存资源。
3.1、当我们使用FragmentStatePagerAdapter的时候,它的宿主ViewPager必须设置对应的id,同时必须实现两个方法。
getCount()——返回的是ViewPager页面的数量,即Fragement的数量。
getItem(int position)——返回的是要显示的fragment对象。
5、关于使用中发现,在删除或者修改数据的时候,PagerAdapter无法像BaseAdapter那样仅通过notifyDataSetChanged方法通知刷新View。
通常情况下调用notifyDataSetChanged方法会让ViewPager通过Adapter的getItemPosition方法查询一遍所有child view,如果所有child view位置均为POSITION_NONE,表示所有的child view都不存在,ViewPager会调用destroyItem方法销毁,并且重新生成,从而加大系统开销,并在一些复杂情况下导致逻辑问题。特别是对于只是希望更新child view内容的时候,但造成了完全不必要的开销,
- 列表内容如果是针对于child view比较简单的情况(例如仅有TextView、ImageView等,没有ListView等展示数据的情况),可以重写PagerAdapter的方法getItemPosition
@Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
- 但复杂的情况则需要根据自己的需求来实现notifyDataSetChanged的功能,比如,在仅需要对某个View内容进行更新时,在instantiateItem()时,用View.setTag方法加入标志,在需要更新信息时,通过findViewWithTag的方法找到对应的View进行局部更新。
6、FragmentPagerAdapter和FragmentStatePagerAdapter
FragmentPagerAdapter适合在较少Fragment滑动切换的界面使用的,划过的fragment会保存在内存中,尽管已经划过。而FragmentStatePagerAdapter和ListView有点类似,只会保存当前界面,以及下一个界面和上一个界面(如果有),最多保存3个,其他会被销毁掉。最后要注意的是FragmentStatePagerAdapter可能不经意间会造成内存未正常回收,严重导致内存溢出,比如图片资源没有释放,资源引用问题。(之前在网上看到过EditText由于保存焦点导致Fragment未被释放而导致OOM,设置edtText.saveEanble(false)就可以解决此问题)。最后PagerAdapter都只是数据集,数据集的改变只能发生在主线程里并且以必须调用notifyDataSetChanged() 来通知完成更新。
小结
这篇主要是简单描述了下ViewPager的主要功能以及PagerAdapter的角色和一些简略的原理,由于篇幅问题,下一篇再结合实例来总结下。