ViewPager 系列之 打造一個通用的 ViewPager

背景

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。java

現狀

看過一些技術博客,對於普通的ViewPager使用封裝的比較少,大多數的封裝只是在用做Banner 的時候,也就是ViewPager 每頁只顯示一張圖片。對外提供一個接口,傳遞一個imageUrl 數組就直接展現,不用再寫其餘的Adapter之類的。可是這樣封裝其實仍是有一些侷限性的。android

  1. 每一個項目用的圖片加載框架是不同的,Picasso、Glide、ImageLoader等等各不相同,那麼咱們還須要在顯示圖片的時候換成本身用的圖片加載框架才行。git

  2. 並非全部的Banner 都只是顯示一張圖片,還有各類個樣的文案展現等等,所以不能個性化定製,這是比較致命的。github

看看上面的侷限性,是什麼形成了這些侷限性呢?答案是咱們沒有主動權,主動權在Adapter手中,他控制了佈局,控制了數據綁定,因此它說怎樣展現就怎樣展現,它說展現什麼就展現什麼。那麼如今問題的關鍵來了,咱們又不想寫Adapter,又想按照咱們的指示展現佈局和數據,怎麼辦呢?那就要從Adapter中奪回主動權,咱們想ViewPager展現成什麼樣子咱們本身說了算。Adapter只須要把咱們提供給他的東西按照咱們的指示展現就好了。具體的佈局和數據綁定都咱們本身控制。所以,有了主動權,展現什麼佈局咱們能控制,用什麼框架加載圖片咱們一樣能控制。用什麼方式來告訴Adapter 作頁面展現呢?就用萬能的接口啦。api

封裝通用的ViewPager

經過上面現狀的分析,咱們知道了,要封裝一個比較通用的ViewPager,首先就是要從Adapter那裏奪回主動權,由於它控制了佈局和數據綁定。有了主動權以後,咱們提供佈局給Adapter,而後咱們本身控制數據綁定。其中有2個關鍵的點:1,提供佈局 。 2,數據綁定。 看到這兩個點是否是以爲很熟悉?固然很熟悉,這不就是RecyclerViewViewHolder乾的事情嘛。既然是這樣咱們就借鑑一下 RecyclerViewViewHolder唄。數組

第一步:定義一個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的方法。

到此封裝也就所有完成了。

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效果.gif

總結

本篇文章的這種封裝思想不只僅對於ViewPager,對於其餘的展現集合數據的控件一樣實用。其實整個封裝仍是蠻簡單的,可是我以爲這種方法值得推廣,之後像咱們本身寫一個擴展性比較強的控件時,就能夠用這種方式。若是把這些一個個控件作成獨立的通用的組件,那麼咱們開發的效率要提升不少。

喜歡的同窗能夠看一下個人其餘幾個通用系列的文章:
PopupWindow 通用系列:
通用PopupWindow,幾行代碼搞定PopupWindow彈窗
通用PopupWindow,幾行代碼搞定PopupWindow彈窗(續)

RecylerView 通用系列:
RecyclerView 之Adapter的簡化過程淺析
RecyclerView Adapter 優雅封裝,一個Adapter搞定全部列表

相關文章
相關標籤/搜索