ViewPager是Android开发者比较经常使用的一个控件了,因为它容许数据页从左到右或者从右到左翻页,所以这种交互也备受设计师的青睐。在APP中的不少场景都用获得,好比第一次安装APP时的用户引导页、图片浏览时左右翻页、广告Banner页等等都会用到ViewPager。ViewPager 的使用和RecyclerView的使用方式很类似,熟悉RecyclerView的朋友都知道,咱们要使用RecyclerView,就得给RecyclerView提供一个Adapter来提供布局和装载数据。可是有一个比较麻烦的事情是,咱们每次使用RecyclerView都要给他提供一个Adapter,而且这些Adapter中的一些方法和代码都是相同的,这使得咱们写了不少重复的代码,下降了咱们的开发效率,所以github有各类个样的对RecyclerView 的再度封装,目的就是减小这些重复的代码,尽可能代码复用,使开发更简单。那么ViewPager的使用和RecyclerView 是很是类似的,咱们一样也是给ViewPager提供一个Adapter来提供布局和装载数据。写Adapter的时候一样会写不少重复代码,那么咱们是否能像RecyclerView
同样,也对Viewpager来作一个再次封装,达到复用和简单的效果呢?答案是确定的,所以这篇文章就一块儿来封装一个通用的ViewPager。java
看过一些技术博客,对于普通的ViewPager使用封装的比较少,大多数的封装只是在用做Banner 的时候,也就是ViewPager 每页只显示一张图片。对外提供一个接口,传递一个imageUrl 数组就直接展现,不用再写其余的Adapter之类的。可是这样封装其实仍是有一些局限性的。android
每一个项目用的图片加载框架是不同的,Picasso、Glide、ImageLoader等等各不相同,那么咱们还须要在显示图片的时候换成本身用的图片加载框架才行。git
并非全部的Banner 都只是显示一张图片,还有各类个样的文案展现等等,所以不能个性化定制,这是比较致命的。github
看看上面的局限性,是什么形成了这些局限性呢?答案是咱们没有主动权,主动权在Adapter手中,他控制了布局,控制了数据绑定,因此它说怎样展现就怎样展现,它说展现什么就展现什么。那么如今问题的关键来了,咱们又不想写Adapter,又想按照咱们的指示展现布局和数据,怎么办呢?那就要从Adapter中夺回主动权,咱们想ViewPager展现成什么样子咱们本身说了算。Adapter只须要把咱们提供给他的东西按照咱们的指示展现就好了。具体的布局和数据绑定都咱们本身控制。所以,有了主动权,展现什么布局咱们能控制,用什么框架加载图片咱们一样能控制。用什么方式来告诉Adapter 作页面展现呢?就用万能的接口啦。api
经过上面现状的分析,咱们知道了,要封装一个比较通用的ViewPager,首先就是要从Adapter那里夺回主动权,由于它控制了布局和数据绑定。有了主动权以后,咱们提供布局给Adapter,而后咱们本身控制数据绑定。其中有2个关键的点:1,提供布局 。 2,数据绑定。 看到这两个点是否是以为很熟悉?固然很熟悉,这不就是RecyclerView
的ViewHolder
干的事情嘛。既然是这样咱们就借鉴一下 RecyclerView
的ViewHolder
呗。数组
第一步:定义一个ViewHolder接口来提供布局和绑定数据:ViewPagerHolder
代码以下:app
/** * 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
代码以下:ide
/** * 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 。详情请看github.com/pinguo-zhou…,博客地址: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的方法。
到此封装也就所有完成了。
啰嗦了这么久的封装,那么用起来方便不呢?看一下就知道。
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,对于其余的展现集合数据的控件一样实用。其实整个封装仍是蛮简单的,可是我以为这种方法值得推广,之后像咱们本身写一个扩展性比较强的控件时,就能够用这种方式。若是把这些一个个控件作成独立的通用的组件,那么咱们开发的效率要提升不少。
喜欢的同窗能够看一下个人其余几个通用系列的文章:
PopupWindow 通用系列:
通用PopupWindow,几行代码搞定PopupWindow弹窗
通用PopupWindow,几行代码搞定PopupWindow弹窗(续)
RecylerView 通用系列:
RecyclerView 之Adapter的简化过程浅析
RecyclerView Adapter 优雅封装,一个Adapter搞定全部列表