ViewPager系列文章(一)- ViewPager源码分析及加载页面原理图
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ViewPager系列文章(一)- ViewPager源码分析及加载页面原理图相关的知识,希望对你有一定的参考价值。
参考技术A 1>:点击 viewPager.setAdapter进入下边源码,会调用 populate() 方法,这个方法作用是创建和销毁子条目(子item):在populate()方法中:
创建ItemView:mAdapter.instantiateItem(this, position);
销毁ItemView:mAdapter.destroyItem(this, pos, ii.object);
所以由ViewPager的源码可以看出,ViewPager里边无论放多少个页面都不会内存溢出,它会不断的去创建和销毁view;
和 ListView、RecyclerView不一样,ListView、RecyclerView是会不断的复用view,而viewpager是不断的创建和销毁view
轮播图刚打开默认显示当前页,是第一页,默认会缓存左右两个页面,如果左边没有,只有右边有,那么右边是第0页,当前页是第一页;
如果你滑动到第1页,ViewPager会默认把 左边第0页 和 右边第2页 创建出来;
如果你滑动到第2页,ViewPager会默认把第1页和第3页创建出来,而原来的第0页就会变成需要销毁的页面;
如果想要缓存多页,可以调用setOffscreenPageLimit()方法:
setOffscreenPageLimit(1):ViewPager机制默认就是缓存1,表示左边、右边各缓存1页,加上自己,总共是3页,其余页面全部销毁;
setOffscreenPageLimit(2):表示默认给左右各缓存2页,共4页,加上自己,总共缓存5页,其余页面全部销毁;
setOffscreenPageLimit(3):表示默认给左右各缓存3页,共6页,加上自己,总共缓存7页,其余页面全部销毁;
因为 smoothScrollTo()滑动方法也调用populate(),而populate()方法维护了当前显示页面和 左右缓存的页面,就能做到无限滑动而不出问题;
A:从populate()源码中可知:先判断页面是否在缓存范围内:如果在,则addNewItem添加进来,否则在destroyItem掉;
B:ViewPager会缓存左右两边页面+1(当前显示页面),默认认为当前页面的 左右两边各有1个,用户可以手动调用setOffscreenPageLimit()方法设置数量,如果传的值小于1,就默认设置为1;
ViewPager实际示意图如下:
ViewPager 系列之 打造一个通用的 ViewPager
背景
CommonViewPager.pngViewPager是Android开发者比较常用的一个控件了,由于它允许数据页从左到右或者从右到左翻页,因此这种交互也备受设计师的青睐。在APP中的很多场景都用得到,比如第一次安装APP时的用户引导页、图片浏览时左右翻页、广告Banner页等等都会用到ViewPager。ViewPager 的使用和RecyclerView的使用方式很相似,熟悉RecyclerView的朋友都知道,我们要使用RecyclerView,就得给RecyclerView提供一个Adapter来提供布局和装载数据。但是有一个比较麻烦的事情是,我们每次使用RecyclerView都要给他提供一个Adapter,并且这些Adapter中的一些方法和代码都是相同的,这使得我们写了很多重复的代码,降低了我们的开发效率,因此github有各种个样的对RecyclerView 的再度封装,目的就是减少这些重复的代码,尽量代码复用,使开发更简单。那么ViewPager的使用和RecyclerView 是非常相似的,我们同样也是给ViewPager提供一个Adapter来提供布局和装载数据。写Adapter的时候同样会写很多重复代码,那么我们是否能像RecyclerView
一样,也对Viewpager来做一个再次封装,达到复用和简单的效果呢?答案是肯定的,因此这篇文章就一起来封装一个通用的ViewPager。
现状
看过一些技术博客,对于普通的ViewPager使用封装的比较少,大多数的封装只是在用作Banner 的时候,也就是ViewPager 每页只显示一张图片。对外提供一个接口,传递一个imageUrl 数组就直接展示,不用再写其他的Adapter之类的。但是这样封装其实还是有一些局限性的。
-
每个项目用的图片加载框架是不一样的,Picasso、Glide、ImageLoader等等各不相同,那么我们还需要在显示图片的时候换成自己用的图片加载框架才行。
-
并不是所有的Banner 都只是显示一张图片,还有各种个样的文案展示等等,因此不能个性化定制,这是比较致命的。
看看上面的局限性,是什么造成了这些局限性呢?答案是我们没有主动权,主动权在Adapter手中,他控制了布局,控制了数据绑定,所以它说怎样展示就怎样展示,它说展示什么就展示什么。那么现在问题的关键来了,我们又不想写Adapter,又想按照我们的指示展示布局和数据,怎么办呢?那就要从Adapter中夺回主动权,我们想ViewPager展示成什么样子我们自己说了算。Adapter只需要把我们提供给他的东西按照我们的指示展示就行了。具体的布局和数据绑定都我们自己控制。因此,有了主动权,展示什么布局我们能控制,用什么框架加载图片我们同样能控制。用什么方式来告诉Adapter 做页面展示呢?就用万能的接口啦。
封装通用的ViewPager
通过上面现状的分析,我们知道了,要封装一个比较通用的ViewPager,首先就是要从Adapter那里夺回主动权,因为它控制了布局和数据绑定。有了主动权之后,我们提供布局给Adapter,然后我们自己控制数据绑定。其中有2个关键的点:1,提供布局 。 2,数据绑定。 看到这两个点是不是觉得很熟悉?当然很熟悉,这不就是RecyclerView
的ViewHolder
干的事情嘛。既然是这样我们就借鉴一下 RecyclerView
的ViewHolder
呗。
第一步:定义一个ViewHolder接口来提供布局和绑定数据:ViewPagerHolder
代码如下:
/**
* Created by zhouwei on 17/5/28.
*/
public interface ViewPagerHolder<T>
/**
* 创建View
* @param context
* @return
*/
View createView(Context context);
/**
* 绑定数据
* @param context
* @param position
* @param data
*/
void onBind(Context context,int position,T data);
ViewPagerHolder
接收一个泛型T,这是绑定数据要用的实体类。其中有2个方法,一个提供给Adapter布局,另一个则用于绑定数据。
第二步: 创建一个ViewHolder生成器,用来生成各种ViewHolder:
ViewPagerHolderCreator
代码如下:
/**
* Created by zhouwei on 17/5/28.
*/
public interface ViewPagerHolderCreator<VH extends ViewPagerHolder>
/**
* 创建ViewHolder
* @return
*/
public VH createViewHolder();
该类接受一个 泛型,但是必须得是ViewPagerHolder 的子类,一个方法createViewHolder,返回ViewHolder实例。
第三步: 重写 ViewPager 的Adapter:
/**
* Created by zhouwei on 17/5/28.
*/
public class CommonViewPagerAdapter<T> extends PagerAdapter
private List<T> mDatas;
private ViewPagerHolderCreator mCreator;//ViewHolder生成器
public CommonViewPagerAdapter(List<T> datas, ViewPagerHolderCreator creator)
mDatas = datas;
mCreator = creator;
@Override
public int getCount()
return mDatas == null ? 0:mDatas.size();
@Override
public boolean isViewFromObject(View view, Object object)
return view == object;
@Override
public Object instantiateItem(ViewGroup container, int position)
//重点就在这儿了,不再是把布局写死,而是用接口提供的布局
// 也不在这里绑定数据,数据绑定交给Api调用者。
View view = getView(position,null,container);
container.addView(view);
return view;
@Override
public void destroyItem(ViewGroup container, int position, Object object)
container.removeView((View) object);
/**
* 获取viewPager 页面展示View
* @param position
* @param view
* @param container
* @return
*/
private View getView(int position,View view ,ViewGroup container)
ViewPagerHolder holder =null;
if(view == null)
//创建Holder
holder = mCreator.createViewHolder();
view = holder.createView(container.getContext());
view.setTag(R.id.common_view_pager_item_tag,holder);
else
holder = (ViewPagerHolder) view.getTag(R.id.common_view_pager_item_tag);
if(holder!=null && mDatas!=null && mDatas.size()>0)
// 数据绑定
holder.onBind(container.getContext(),position,mDatas.get(position));
return view;
这个类比较重要,因为以前我们的布局提供和数据绑定都是在Adapter中的,因此现在我们就将这两项工作交给我们的ViewHolder。
CommonViewPagerAdapter
的构造方法需要展示的数据集合和ViewPagerHolderCreator 生成器。其他代码都有注释一看便明白。
第四部:包装ViewPager
Adapter和ViewHolder都有了,现在我们只需要一个ViewPager 就大功告成了。我们采用自定义View 组合的方式来写这个ViewPager.
1 . 提供ViewPager 布局:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- ViewPager-->
<android.support.v4.view.ViewPager
android:id="@+id/common_view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<!-- 指示器 indicatorView-->
<com.zhouwei.indicatorview.CircleIndicatorView
android:id="@+id/common_view_pager_indicator_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="10dp"
app:indicatorSelectColor="@android:color/white"
app:indicatorColor="@android:color/darker_gray"
app:fill_mode="none"
app:indicatorSpace="5dp"
android:layout_centerHorizontal="true"
/>
</RelativeLayout>
布局中一个ViewPager 和一个指示器View, IndicatorView 用的是前面分享的CircleIndicatorView 。详情请看https://github.com/pinguo-zhouwei/CircleIndicatorView,博客地址:Android自定义View之 实现一个多功能的IndicatorView。
2 . CommonViewPager ,代码如下:
/**
* Created by zhouwei on 17/5/28.
*/
public class CommonViewPager<T> extends RelativeLayout
private ViewPager mViewPager;
private CommonViewPagerAdapter mAdapter;
private CircleIndicatorView mCircleIndicatorView;
public CommonViewPager(@NonNull Context context)
super(context);
init();
public CommonViewPager(@NonNull Context context, @Nullable AttributeSet attrs)
super(context, attrs);
init();
public CommonViewPager(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr)
super(context, attrs, defStyleAttr);
init();
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public CommonViewPager(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes)
super(context, attrs, defStyleAttr, defStyleRes);
init();
private void init()
View view = LayoutInflater.from(getContext()).inflate(R.layout.common_view_pager_layout,this,true);
mViewPager = (ViewPager) view.findViewById(R.id.common_view_pager);
mCircleIndicatorView = (CircleIndicatorView) view.findViewById(R.id.common_view_pager_indicator_view);
/**
* 设置数据
* @param data
* @param creator
*/
public void setPages(List<T> data, ViewPagerHolderCreator creator)
mAdapter = new CommonViewPagerAdapter(data,creator);
mViewPager.setAdapter(mAdapter);
mAdapter.notifyDataSetChanged();
mCircleIndicatorView.setUpWithViewPager(mViewPager);
public void setCurrentItem(int currentItem)
mViewPager.setCurrentItem(currentItem);
public int getCurrentItem()
return mViewPager.getCurrentItem();
public void setOffscreenPageLimit(int limit)
mViewPager.setOffscreenPageLimit(limit);
/**
* 设置切换动画,see @link ViewPager#setPageTransformer(boolean, ViewPager.PageTransformer)
* @param reverseDrawingOrder
* @param transformer
*/
public void setPageTransformer(boolean reverseDrawingOrder, ViewPager.PageTransformer transformer)
mViewPager.setPageTransformer(reverseDrawingOrder,transformer);
/**
* see @link ViewPager#setPageTransformer(boolean, ViewPager.PageTransformer)
* @param reverseDrawingOrder
* @param transformer
* @param pageLayerType
*/
public void setPageTransformer(boolean reverseDrawingOrder, ViewPager.PageTransformer transformer,
int pageLayerType)
mViewPager.setPageTransformer(reverseDrawingOrder,transformer,pageLayerType);
/**
* see @link ViewPager#addOnPageChangeListener(ViewPager.OnPageChangeListener)
* @param listener
*/
public void addOnPageChangeListener(ViewPager.OnPageChangeListener listener)
mViewPager.addOnPageChangeListener(listener);
/**
* 设置是否显示Indicator
* @param visible
*/
private void setIndicatorVisible(boolean visible)
if(visible)
mCircleIndicatorView.setVisibility(VISIBLE);
else
mCircleIndicatorView.setVisibility(GONE);
public ViewPager getViewPager()
return mViewPager;
CommonViewPager
是对ViewPager的包装,提供了一些ViewPager的常用方法。 其中有一个非常重要的方法public void setPages(List<T> data, ViewPagerHolderCreator creator)
,提供数据和ViewHolder。其他的基本上都是ViewPager的方法。也可以通过getViewPager 获取到ViewPager 再调用ViewPager的方法。
到此封装也就全部完成了。
CommonViewPager 简便使用
啰嗦了这么久的封装,那么用起来方便不呢?看一下就知道。
1 , activity 布局文件:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.zhouwei.commonviewpager.MainActivity">
<com.zhouwei.viewpagerlib.CommonViewPager
android:id="@+id/activity_common_view_pager"
android:layout_width="match_parent"
android:layout_height="200dp"
/>
</RelativeLayout>
ViewPager Item 的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/viewPager_item_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
/>
<TextView
android:id="@+id/item_desc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="15sp"
android:gravity="center"
android:layout_centerInParent="true"
android:textColor="@android:color/white"
/>
</RelativeLayout>
Activity 代码:
private void initView()
mCommonViewPager = (CommonViewPager) findViewById(R.id.activity_common_view_pager);
// 设置数据
mCommonViewPager.setPages(mockData(), new ViewPagerHolderCreator<ViewImageHolder>()
@Override
public ViewImageHolder createViewHolder()
// 返回ViewPagerHolder
return new ViewImageHolder();
);
/**
* 提供ViewPager展示的ViewHolder
* <P>用于提供布局和绑定数据</P>
*/
public static class ViewImageHolder implements ViewPagerHolder<DataEntry>
private ImageView mImageView;
private TextView mTextView;
@Override
public View createView(Context context)
// 返回ViewPager 页面展示的布局
View view = LayoutInflater.from(context).inflate(R.layout.view_pager_item,null);
mImageView = (ImageView) view.findViewById(R.id.viewPager_item_image);
mTextView = (TextView) view.findViewById(R.id.item_desc);
return view;
@Override
public void onBind(Context context, int position, DataEntry data)
// 数据绑定
// 自己绑定数据,灵活度很大
mImageView.setImageResource(data.imageResId);
mTextView.setText(data.desc);
代码逻辑很清晰,也很简单,只需要提供一个ViewHolder,ViewHolder 自己实现,然后调用setPages
方法绑定数据就好了。最后上一张效果图:
总结
本篇文章的这种封装思想不仅仅对于ViewPager,对于其他的展示集合数据的控件同样实用。其实整个封装还是蛮简单的,但是我觉得这种方法值得推广,以后像我们自己写一个扩展性比较强的控件时,就可以用这种方式。如果把这些一个个控件做成独立的通用的组件,那么我们开发的效率要提高很多。
以上是关于ViewPager系列文章(一)- ViewPager源码分析及加载页面原理图的主要内容,如果未能解决你的问题,请参考以下文章
(转)ViewPager,ScrollView 嵌套ViewPager滑动冲突解决
带有 Recyclerview 的 ViewPager 上的 NullPointerException