Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 160|回复: 0

[默认分类] ViewPager 系列之 打造一个通用的 ViewPager

[复制链接]
  • TA的每日心情
    开心
    2021-12-13 21:45
  • 签到天数: 15 天

    [LV.4]偶尔看看III

    发表于 2018-7-6 10:37:37 | 显示全部楼层 |阅读模式

    背景
    CommonViewPager.png
    ViewPager是Android开发者比较常用的一个控件了,由于它允许数据页从左到右或者从右到左翻页,因此这种交互也备受设计师的青睐。在APP中的很多场景都用得到,比如第一次安装APP时的用户引导页、图片浏览时左右翻页、广告Banner页等等都会用到ViewPager。ViewPager 的使用和RecyclerView的使用方式很相似,熟悉RecyclerView的朋友都知道,我们要使用RecyclerView,就得给RecyclerView提供一个Adapter来提供布局和装载数据。但是有一个比较麻烦的事情是,我们每次使用RecyclerView都要给他提供一个Adapter,并且这些Adapter中的一些方法和代码都是相同的,这使得我们写了很多重复的代码,降低了我们的开发效率,因此github有各种个样的对RecyclerView 的再度封装,目的就是减少这些重复的代码,尽量代码复用,使开发更简单。那么ViewPager的使用和RecyclerView 是非常相似的,我们同样也是给ViewPager提供一个Adapter来提供布局和装载数据。写Adapter的时候同样会写很多重复代码,那么我们是否能像
    1. 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,数据绑定。 看到这两个点是不是觉得很熟悉?当然很熟悉,这不就是
    1. RecyclerView
    复制代码
    1. ViewHolder
    复制代码
    干的事情嘛。既然是这样我们就借鉴一下
    1. RecyclerView
    复制代码
    1. ViewHolder
    复制代码
    呗。
    第一步:定义一个ViewHolder接口来提供布局和绑定数据:
    1. ViewPagerHolder
    复制代码
    代码如下:
    1. [code]/**
    2. * Created by zhouwei on 17/5/28.
    3. */
    4. public interface ViewPagerHolder<T> {
    5.     /**
    6. * 创建View
    7. * @param context
    8. * @return
    9. */
    10.     View createView(Context context);
    11.     /**
    12. * 绑定数据
    13. * @param context
    14. * @param position
    15. * @param data
    16. */
    17.     void onBind(Context context,int position,T data);
    18. }
    复制代码
    [/code]
    1. ViewPagerHolder
    复制代码
    接收一个泛型T,这是绑定数据要用的实体类。其中有2个方法,一个提供给Adapter布局,另一个则用于绑定数据。
    第二步: 创建一个ViewHolder生成器,用来生成各种ViewHolder:
    1. ViewPagerHolderCreator
    复制代码
    代码如下:
    1. [code]/**
    2. * Created by zhouwei on 17/5/28.
    3. */
    4. public interface ViewPagerHolderCreator<VH extends ViewPagerHolder> {
    5.     /**
    6. * 创建ViewHolder
    7. * @return
    8. */
    9.     public VH createViewHolder();
    10. }
    复制代码
    [/code]
    该类接受一个 泛型,但是必须得是ViewPagerHolder 的子类,一个方法createViewHolder,返回ViewHolder实例。
    第三步: 重写 ViewPager 的Adapter:
    1. [code]/**
    2. * Created by zhouwei on 17/5/28.
    3. */
    4. public class CommonViewPagerAdapter<T> extends PagerAdapter {
    5.     private List<T> mDatas;
    6.     private ViewPagerHolderCreator mCreator;//ViewHolder生成器
    7.     public CommonViewPagerAdapter(List<T> datas, ViewPagerHolderCreator creator) {
    8.         mDatas = datas;
    9.         mCreator = creator;
    10.     }
    11.     @Override
    12.     public int getCount() {
    13.         return mDatas == null ? 0:mDatas.size();
    14.     }
    15.     @Override
    16.     public boolean isViewFromObject(View view, Object object) {
    17.         return view == object;
    18.     }
    19.     @Override
    20.     public Object instantiateItem(ViewGroup container, int position) {
    21.         //重点就在这儿了,不再是把布局写死,而是用接口提供的布局
    22.         // 也不在这里绑定数据,数据绑定交给Api调用者。
    23.         View view = getView(position,null,container);
    24.         container.addView(view);
    25.         return view;
    26.     }
    27.     @Override
    28.     public void destroyItem(ViewGroup container, int position, Object object) {
    29.         container.removeView((View) object);
    30.     }
    31.     /**
    32. * 获取viewPager 页面展示View
    33. * @param position
    34. * @param view
    35. * @param container
    36. * @return
    37. */
    38.     private View getView(int position,View view ,ViewGroup container){
    39.         ViewPagerHolder holder =null;
    40.         if(view == null){
    41.             //创建Holder
    42.             holder = mCreator.createViewHolder();
    43.             view = holder.createView(container.getContext());
    44.             view.setTag(R.id.common_view_pager_item_tag,holder);
    45.         }else{
    46.             holder = (ViewPagerHolder) view.getTag(R.id.common_view_pager_item_tag);
    47.         }
    48.         if(holder!=null && mDatas!=null && mDatas.size()>0){
    49.             // 数据绑定
    50.             holder.onBind(container.getContext(),position,mDatas.get(position));
    51.         }
    52.         return view;
    53.     }
    54. }
    复制代码
    [/code]
      这个类比较重要,因为以前我们的布局提供和数据绑定都是在Adapter中的,因此现在我们就将这两项工作交给我们的ViewHolder。
    1. CommonViewPagerAdapter
    复制代码
    的构造方法需要展示的数据集合和ViewPagerHolderCreator 生成器。其他代码都有注释一看便明白。
    第四部:包装ViewPager
    Adapter和ViewHolder都有了,现在我们只需要一个ViewPager 就大功告成了。我们采用自定义View 组合的方式来写这个ViewPager.
    1 . 提供ViewPager 布局:
    1. [code]<?xml version="1.0" encoding="utf-8"?>
    2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    3.                 xmlns:app="http://schemas.android.com/apk/res-auto"
    4.                 android:orientation="vertical"
    5.                 android:layout_width="match_parent"
    6.                 android:layout_height="match_parent">
    7.      <!-- ViewPager-->
    8.      <android.support.v4.view.ViewPager
    9.          android:id="@+id/common_view_pager"
    10.          android:layout_width="match_parent"
    11.          android:layout_height="match_parent"/>
    12.      <!-- 指示器 indicatorView-->
    13.      <com.zhouwei.indicatorview.CircleIndicatorView
    14.          android:id="@+id/common_view_pager_indicator_view"
    15.          android:layout_width="wrap_content"
    16.          android:layout_height="wrap_content"
    17.          android:layout_alignParentBottom="true"
    18.          android:layout_marginBottom="10dp"
    19.          app:indicatorSelectColor="@android:color/white"
    20.          app:indicatorColor="@android:color/darker_gray"
    21.          app:fill_mode="none"
    22.          app:indicatorSpace="5dp"
    23.          android:layout_centerHorizontal="true"
    24.          />
    25. </RelativeLayout>
    复制代码
    [/code]
    布局中一个ViewPager 和一个指示器View, IndicatorView 用的是前面分享的CircleIndicatorView 。详情请看https://github.com/pinguo-zhouwei/CircleIndicatorView,博客地址:Android自定义View之 实现一个多功能的IndicatorView
    2 . CommonViewPager ,代码如下:
    1. [code]/**
    2. * Created by zhouwei on 17/5/28.
    3. */
    4. public class CommonViewPager<T> extends RelativeLayout {
    5.     private ViewPager mViewPager;
    6.     private CommonViewPagerAdapter mAdapter;
    7.     private CircleIndicatorView mCircleIndicatorView;
    8.     public CommonViewPager(@NonNull Context context) {
    9.         super(context);
    10.         init();
    11.     }
    12.     public CommonViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
    13.         super(context, attrs);
    14.         init();
    15.     }
    16.     public CommonViewPager(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
    17.         super(context, attrs, defStyleAttr);
    18.         init();
    19.     }
    20.     @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    21.     public CommonViewPager(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
    22.         super(context, attrs, defStyleAttr, defStyleRes);
    23.         init();
    24.     }
    25.     private void init(){
    26.         View view = LayoutInflater.from(getContext()).inflate(R.layout.common_view_pager_layout,this,true);
    27.         mViewPager = (ViewPager) view.findViewById(R.id.common_view_pager);
    28.         mCircleIndicatorView = (CircleIndicatorView) view.findViewById(R.id.common_view_pager_indicator_view);
    29.     }
    30.     /**
    31. * 设置数据
    32. * @param data
    33. * @param creator
    34. */
    35.     public void setPages(List<T> data, ViewPagerHolderCreator creator){
    36.         mAdapter = new CommonViewPagerAdapter(data,creator);
    37.         mViewPager.setAdapter(mAdapter);
    38.         mAdapter.notifyDataSetChanged();
    39.         mCircleIndicatorView.setUpWithViewPager(mViewPager);
    40.     }
    41.     public void setCurrentItem(int currentItem){
    42.         mViewPager.setCurrentItem(currentItem);
    43.     }
    44.     public int getCurrentItem(){
    45.         return mViewPager.getCurrentItem();
    46.     }
    47.     public void setOffscreenPageLimit(int limit){
    48.         mViewPager.setOffscreenPageLimit(limit);
    49.     }
    50.     /**
    51. * 设置切换动画,see {@link ViewPager#setPageTransformer(boolean, ViewPager.PageTransformer)}
    52. * @param reverseDrawingOrder
    53. * @param transformer
    54. */
    55.     public void setPageTransformer(boolean reverseDrawingOrder, ViewPager.PageTransformer transformer){
    56.         mViewPager.setPageTransformer(reverseDrawingOrder,transformer);
    57.     }
    58.     /**
    59. * see {@link ViewPager#setPageTransformer(boolean, ViewPager.PageTransformer)}
    60. * @param reverseDrawingOrder
    61. * @param transformer
    62. * @param pageLayerType
    63. */
    64.     public void setPageTransformer(boolean reverseDrawingOrder, ViewPager.PageTransformer transformer,
    65. int pageLayerType) {
    66.         mViewPager.setPageTransformer(reverseDrawingOrder,transformer,pageLayerType);
    67.     }
    68.     /**
    69. * see {@link ViewPager#addOnPageChangeListener(ViewPager.OnPageChangeListener)}
    70. * @param listener
    71. */
    72.     public void addOnPageChangeListener(ViewPager.OnPageChangeListener listener){
    73.         mViewPager.addOnPageChangeListener(listener);
    74.     }
    75.     /**
    76. * 设置是否显示Indicator
    77. * @param visible
    78. */
    79.     private void setIndicatorVisible(boolean visible){
    80.         if(visible){
    81.             mCircleIndicatorView.setVisibility(VISIBLE);
    82.         }else{
    83.             mCircleIndicatorView.setVisibility(GONE);
    84.         }
    85.     }
    86.     public ViewPager getViewPager() {
    87.         return mViewPager;
    88.     }
    89. }
    复制代码
    [/code]
    1. CommonViewPager
    复制代码
    是对ViewPager的包装,提供了一些ViewPager的常用方法。 其中有一个非常重要的方法
    1. public void setPages(List<T> data, ViewPagerHolderCreator creator)
    复制代码
    ,提供数据和ViewHolder。其他的基本上都是ViewPager的方法。也可以通过getViewPager 获取到ViewPager 再调用ViewPager的方法。
    到此封装也就全部完成了。
    CommonViewPager 简便使用
    啰嗦了这么久的封装,那么用起来方便不呢?看一下就知道。
    1 , activity 布局文件:
    1. [code]<?xml version="1.0" encoding="utf-8"?>
    2. <RelativeLayout
    3.     xmlns:android="http://schemas.android.com/apk/res/android"
    4.     xmlns:app="http://schemas.android.com/apk/res-auto"
    5.     xmlns:tools="http://schemas.android.com/tools"
    6.     android:layout_width="match_parent"
    7.     android:layout_height="match_parent"
    8.     tools:context="com.zhouwei.commonviewpager.MainActivity">
    9.     <com.zhouwei.viewpagerlib.CommonViewPager
    10.         android:id="@+id/activity_common_view_pager"
    11.         android:layout_width="match_parent"
    12.         android:layout_height="200dp"
    13.         />
    14. </RelativeLayout>
    复制代码
    [/code]
    ViewPager Item 的布局文件:
    1. [code]<?xml version="1.0" encoding="utf-8"?>
    2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    3.               android:orientation="vertical"
    4.               android:layout_width="match_parent"
    5.               android:layout_height="match_parent">
    6.    <ImageView
    7.        android:id="@+id/viewPager_item_image"
    8.        android:layout_width="match_parent"
    9.        android:layout_height="match_parent"
    10.        android:scaleType="centerCrop"
    11.        />
    12.    <TextView
    13.        android:id="@+id/item_desc"
    14.        android:layout_width="match_parent"
    15.        android:layout_height="wrap_content"
    16.        android:textSize="15sp"
    17.        android:gravity="center"
    18.        android:layout_centerInParent="true"
    19.        android:textColor="@android:color/white"
    20.        />
    21. </RelativeLayout>
    复制代码
    [/code]
    Activity 代码:
    1. [code]  private void initView() {
    2.         mCommonViewPager = (CommonViewPager) findViewById(R.id.activity_common_view_pager);
    3.         // 设置数据
    4.         mCommonViewPager.setPages(mockData(), new ViewPagerHolderCreator<ViewImageHolder>() {
    5.             @Override
    6.             public ViewImageHolder createViewHolder() {
    7.                 // 返回ViewPagerHolder
    8.                 return new ViewImageHolder();
    9.             }
    10.         });
    11.     }
    12.     /**
    13. * 提供ViewPager展示的ViewHolder
    14. * <P>用于提供布局和绑定数据</P>
    15. */
    16.     public static class ViewImageHolder implements ViewPagerHolder<DataEntry>{
    17.         private ImageView mImageView;
    18.         private TextView mTextView;
    19.         @Override
    20.         public View createView(Context context) {
    21.             // 返回ViewPager 页面展示的布局
    22.             View view = LayoutInflater.from(context).inflate(R.layout.view_pager_item,null);
    23.             mImageView = (ImageView) view.findViewById(R.id.viewPager_item_image);
    24.             mTextView = (TextView) view.findViewById(R.id.item_desc);
    25.             return view;
    26.         }
    27.         @Override
    28.         public void onBind(Context context, int position, DataEntry data) {
    29.            // 数据绑定
    30.            // 自己绑定数据,灵活度很大
    31.            mImageView.setImageResource(data.imageResId);
    32.            mTextView.setText(data.desc);
    33.         }
    34.     }
    复制代码
    [/code]
    代码逻辑很清晰,也很简单,只需要提供一个ViewHolder,ViewHolder 自己实现,然后调用
    1. setPages
    复制代码
    方法绑定数据就好了。最后上一张效果图:
    ViewPager效果.gif
    总结
    本篇文章的这种封装思想不仅仅对于ViewPager,对于其他的展示集合数据的控件同样实用。其实整个封装还是蛮简单的,但是我觉得这种方法值得推广,以后像我们自己写一个扩展性比较强的控件时,就可以用这种方式。如果把这些一个个控件做成独立的通用的组件,那么我们开发的效率要提高很多。
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2025-2-24 05:19 , Processed in 0.323350 second(s), 35 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表