背景

CommonViewPager.png

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。

现状

看过一些技术博客,对于普通的ViewPager使用封装的比较少,大多数的封装只是在用作Banner 的时候,也就是ViewPager 每页只显示一张图片。对外提供一个接口,传递一个imageUrl 数组就直接展示,不用再写其他的Adapter之类的。但是这样封装其实还是有一些局限性的。

  1. 每个项目用的图片加载框架是不一样的,Picasso、Glide、ImageLoader等等各不相同,那么我们还需要在显示图片的时候换成自己用的图片加载框架才行。

  2. 并不是所有的Banner 都只是显示一张图片,还有各种个样的文案展示等等,因此不能个性化定制,这是比较致命的。

看看上面的局限性,是什么造成了这些局限性呢?答案是我们没有主动权,主动权在Adapter手中,他控制了布局,控制了数据绑定,所以它说怎样展示就怎样展示,它说展示什么就展示什么。那么现在问题的关键来了,我们又不想写Adapter,又想按照我们的指示展示布局和数据,怎么办呢?那就要从Adapter中夺回主动权,我们想ViewPager展示成什么样子我们自己说了算。Adapter只需要把我们提供给他的东西按照我们的指示展示就行了。具体的布局和数据绑定都我们自己控制。因此,有了主动权,展示什么布局我们能控制,用什么框架加载图片我们同样能控制。用什么方式来告诉Adapter 做页面展示呢?就用万能的接口啦。

封装通用的ViewPager

通过上面现状的分析,我们知道了,要封装一个比较通用的ViewPager,首先就是要从Adapter那里夺回主动权,因为它控制了布局和数据绑定。有了主动权之后,我们提供布局给Adapter,然后我们自己控制数据绑定。其中有2个关键的点:1,提供布局 。 2,数据绑定。 看到这两个点是不是觉得很熟悉?当然很熟悉,这不就是RecyclerViewViewHolder干的事情嘛。既然是这样我们就借鉴一下 RecyclerViewViewHolder呗。

第一步:定义一个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;}@Overridepublic int getCount() {return mDatas == null ? 0:mDatas.size();}@Overridepublic boolean isViewFromObject(View view, Object object) {return view == object;}@Overridepublic Object instantiateItem(ViewGroup container, int position) {//重点就在这儿了,不再是把布局写死,而是用接口提供的布局// 也不在这里绑定数据,数据绑定交给Api调用者。View view = getView(position,null,container);container.addView(view);return view;}@Overridepublic 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){//创建Holderholder = 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.ViewPagerandroid:id="@+id/common_view_pager"android:layout_width="match_parent"android:layout_height="match_parent"/><!-- 指示器 indicatorView--><com.zhouwei.indicatorview.CircleIndicatorViewandroid: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"?>
<RelativeLayoutxmlns: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.CommonViewPagerandroid: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"><ImageViewandroid:id="@+id/viewPager_item_image"android:layout_width="match_parent"android:layout_height="match_parent"android:scaleType="centerCrop"/><TextViewandroid: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>() {@Overridepublic ViewImageHolder createViewHolder() {// 返回ViewPagerHolderreturn new ViewImageHolder();}});}/*** 提供ViewPager展示的ViewHolder* <P>用于提供布局和绑定数据</P>*/public static class ViewImageHolder implements ViewPagerHolder<DataEntry>{private ImageView mImageView;private TextView mTextView;@Overridepublic 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;}@Overridepublic void onBind(Context context, int position, DataEntry data) {// 数据绑定// 自己绑定数据,灵活度很大 mImageView.setImageResource(data.imageResId);mTextView.setText(data.desc);}}

代码逻辑很清晰,也很简单,只需要提供一个ViewHolder,ViewHolder 自己实现,然后调用setPages 方法绑定数据就好了。最后上一张效果图:

ViewPager效果.gif

总结

本篇文章的这种封装思想不仅仅对于ViewPager,对于其他的展示集合数据的控件同样实用。其实整个封装还是蛮简单的,但是我觉得这种方法值得推广,以后像我们自己写一个扩展性比较强的控件时,就可以用这种方式。如果把这些一个个控件做成独立的通用的组件,那么我们开发的效率要提高很多。

ViewPager 系列之 打造一个通用的 ViewPager相关推荐

  1. 牵手思必驰,博泰车联网要打造一个通用的车载语音平台

    *上海博泰创始人兼董事长应宜伦&思必驰董事长兼 CEO 高始兴 语音交互已经成为车载交互方式革命的一项重要成果,如今只要是敢称自己为智能汽车的车型,其上必定配备了语音识别.语音控制的功能.不得 ...

  2. 牵手思必驰,博泰车联网要打造一个通用的车载语音平台...

    *上海博泰创始人兼董事长应宜伦&思必驰董事长兼 CEO 高始兴 语音交互已经成为车载交互方式革命的一项重要成果,如今只要是敢称自己为智能汽车的车型,其上必定配备了语音识别.语音控制的功能.不得 ...

  3. android 复用标题栏,Android基础---使用ToolBar教你打造一个通用的标题栏

    现在项目中一般都会使用标题栏,谷歌在2014年推出了新的app bar---ToolBar,代替了以前使用的ActionBar.在做项目中会经常用到这个ToolBar,虽然用的很多,但是自己对它如何用 ...

  4. 如何打造一个抗住千万级流量短信服务(续)

    前言 在之前写过一篇博文<短信服务设计>当时讲述了设计的思路,有很多读者朋友反馈说想了解具体的设计思路:今天又重新回顾下当时的具体实现细节发现当时实现的还是有一些巧妙的地方,值得大家参考, ...

  5. 打造炫酷通用的ViewPager指示器 Adapter模式适配所有 1

    ###1.概述 上一期我们已经写了一篇 打造炫酷通用的ViewPager指示器 - 玩转字体变色 可是这种效果虽然绚烂可以装装A和C之间,但是在实际的大多数效果中并不常见,只是在内涵段子中有这个效果而 ...

  6. 一个通用中间组件,简单通用的适配 ViewPager,以及 pager 中的 RecycleView 简化复杂的操作,简单直接。

    EasyTabPager 项目地址:ccj659/EasyTabPager  简介:一个通用中间组件,简单通用的适配 ViewPager,以及 pager 中的 RecycleView 简化复杂的操作 ...

  7. Android实战简易教程-第三十四枪(基于ViewPager和FragmentPagerAdapter实现滑动通用Tab)...

    上一段时间写过一篇文章<基于ViewPager实现微信页面切换效果> 里面实现了相似微信Tab的页面.可是这样的实现方法有个问题.就是以后全部的代码逻辑都必须在MainActivity中实 ...

  8. ViewPager系列之-仿掌上英雄联盟皮肤浏览效果

    封面图.png 能有一个双休的周末,对于程序员来说,也算是一件幸福的事情吧.苦逼的加了一周的班,终于可以休息放松放松了.作为一个LOL爱好者,周末最开心的事当然就是约上几个小伙伴一起开黑了.一起超神. ...

  9. 打造一个丝滑般自动轮播无限循环Android库

    作者:一包纯牛奶 链接: https://juejin.im/post/5d6bce24f265da03db0790d1 本文由作者授权发布. 这里我把作者两篇文章合体了,主要是为了在项目功能介绍的基 ...

最新文章

  1. HDUOJ------Worm
  2. 【译】为什么这样宏定义#define INT_MIN (-2147483647 - 1)?
  3. MySQL查询面试题
  4. 粒子群算法(PSO)Matlab实现(两种解法)
  5. 谈谈我对Promise的理解
  6. 贴片电容耐压值一般都是多少?
  7. WinForm窗体PropertyGrid控件的使用
  8. java 科学计数_Java和甜蜜的科学
  9. DelphiXE下的字符串变化
  10. 分享一下淘宝iData技术嘉年华的几点感触
  11. NeHe OpenGL教程 第四十五课:顶点缓存
  12. 47 - 算法 - Leetcode-160 -相交链表
  13. innovus停止当前命令_从命令行停止node.js程序
  14. 服务不支持chkconfig
  15. Centos7:dubbo监控中心安装,配置和使用
  16. [转]myeclipse 8.5最新注册码(过期时间到2016年)
  17. 详解python使用browsermobproxy获取当前网页xhr的get数据方法
  18. win10系统内置PDF虚拟打印机不能用了怎么办
  19. 在线教育项目(六)之讲师功能实现
  20. MySQL必知必会6

热门文章

  1. 【.NET】EF框架之三种模式
  2. java异常之-ClassNotFoundException: .......web.context.ContextLoaderServlet
  3. 功能测试---正交实验法
  4. xml文件解析的三种方式
  5. 计算机行业各种职业技能树
  6. 日本动漫作家和其部分作品
  7. pfx 证书导出公钥和私钥
  8. 防复制防破解小区门禁梯控升级非联网CPU卡脱机写卡门禁梯控一卡通系统92HID623CPU V5.00操作说明之用户卡加密发卡设置说明
  9. 视频网站服务器该怎么选择呢
  10. Linux--网络命令大全--使用