1. 程式人生 > >ViewPager 全面總結

ViewPager 全面總結

一、簡介

Viewpager,檢視翻頁工具,提供了多頁面切換的效果。Android 3.0後引入的一個UI控制元件,位於v4包中。低版本使用需要匯入v4包,但是現在我們開發的APP一般不再相容3.0及以下的系統版本,另外現在大多數使用Android studio進行開發,預設匯入v7包,v7包含了v4,所以不用導包,越來越方便了。

Viewpager使用起來就是我們通過建立adapter給它填充多個view,左右滑動時,切換不同的view。Google官方是建議我們使用Fragment來填充ViewPager的,這樣 可以更加方便的生成每個Page,以及管理每個Page的生命週期

Viewpager在Android開發中使用頻率還是比較高的,下面開始一起學習吧

二、基本使用

1. xml引用

<android.support.v4.view.ViewPager
    android:id="@+id/vp"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
</android.support.v4.view.ViewPager>

2. page佈局

<?xml version="1.0" encoding="utf-8"?>
<TextView
    android:id="@+id/tv"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#FAE8DA"
    android:gravity="center"
    android:text="Hello"
    android:textSize="22sp">
</TextView>

3. 建立介面卡

可直接建立PagerAdapter,亦可建立它的子類

public class MyPagerAdapter extends PagerAdapter {
    private Context mContext;
    private List<String> mData;

    public MyPagerAdapter(Context context ,List<String> list) {
        mContext = context;
        mData = list;
    }

    @Override
    public int getCount() {
        return mData.size();
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        View view = View.inflate(mContext, R.layout.item_base,null);
        TextView tv = (TextView) view.findViewById(R.id.tv);
        tv.setText(mData.get(position));
        container.addView(view);
        return view;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        // super.destroyItem(container,position,object); 這一句要刪除,否則報錯
        container.removeView((View)object);
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view == object;
    }
}

4. 設定介面卡

private void setVp() {
    List<String> list = new ArrayList<>();
    for (int i = 0; i < 3; i++) {
       list.add("第"+i+"個View");
    }

    ViewPager vp = (ViewPager) findViewById(R.id.vp);
    vp.setAdapter(new MyPagerAdapter(this,list));
}

效果: 

5. 標題欄

給Viewpager設定標題欄有一下幾種方式:

  • PagerTabStrip: 帶有下劃線
  • PagerTitleStrip: 不帶下劃線
  • TabLayout:5.0後推出

TabLayout的詳細使用,可以看我的另一篇文章TabLayout

下面介紹另外兩個的使用方法,沒什麼區別:

1. xml引用

<android.support.v4.view.ViewPager
    android:id="@+id/vp"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v4.view.PagerTitleStrip
        android:id="@+id/pager_title"
        android:layout_width="match_parent"
        android:layout_height="45dp"
        android:background="@android:color/white"
        android:layout_gravity="top"
        android:textColor="#ff0000"
        android:textSize="18sp">

    </android.support.v4.view.PagerTitleStrip>

</android.support.v4.view.ViewPager>
 <android.support.v4.view.ViewPager
    android:id="@+id/vp"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v4.view.PagerTabStrip
        android:id="@+id/pager_tab"
        android:layout_width="match_parent"
        android:layout_height="45dp"
        android:layout_gravity="top"
        android:background="@android:color/white"
        android:textColor="#ff0000">
    </android.support.v4.view.PagerTabStrip>

</android.support.v4.view.ViewPager>

2. 重寫PagerAdapter的getTitle()方法

 @Override
public CharSequence getPageTitle(int position) {
    return mTitles[position];
}

這兩種方法作為了解,不常用,專案中還沒用到過

效果:  

6. 翻頁動畫

ViewPager有個方法叫做:

setPageTransformer(boolean reverseDrawingOrder, PageTransformer transformer) 用於設定ViewPager切換時的動畫效果,並且google官方還給出了兩個示例(因為使用的是屬性動畫,所以不相容3.0以下)。

1. DepthPageTransformer

public class DepthPageTransformer implements ViewPager.PageTransformer {
    private static final float MIN_SCALE = 0.75f;
    public void transformPage(View view, float position) {
        int pageWidth = view.getWidth();
        if (position < -1) { // [-Infinity,-1)
            // This page is way off-screen to the left.
            view.setAlpha(0);
        } else if (position <= 0) { // [-1,0]
            // Use the default slide transition when moving to the left page
            view.setAlpha(1);
            view.setTranslationX(0);
            view.setScaleX(1);
            view.setScaleY(1);
        } else if (position <= 1) { // (0,1]
            // Fade the page out.
            view.setAlpha(1 - position);
            // Counteract the default slide transition
            view.setTranslationX(pageWidth * -position);
            // Scale the page down (between MIN_SCALE and 1)
            float scaleFactor = MIN_SCALE
                    + (1 - MIN_SCALE) * (1 - Math.abs(position));
            view.setScaleX(scaleFactor);
            view.setScaleY(scaleFactor);
        } else { // (1,+Infinity]
            // This page is way off-screen to the right.
            view.setAlpha(0);
        }
    }
}

呼叫:

vp.setPageTransformer(false,new DepthPageTransformer());
  • 1

效果: 

2. ZoomOutPageTransformer

public class ZoomOutPageTransformer implements ViewPager.PageTransformer
{
    private static final float MIN_SCALE = 0.85f;
    private static final float MIN_ALPHA = 0.5f;
    @SuppressLint("NewApi")
    public void transformPage(View view, float position)
    {
        int pageWidth = view.getWidth();
        int pageHeight = view.getHeight();
        Log.e("TAG", view + " , " + position + "");
        if (position < -1)
        { // [-Infinity,-1)
            // This page is way off-screen to the left.
            view.setAlpha(0);
        } else if (position <= 1) //a頁滑動至b頁 ; a頁從 0.0 -1 ;b頁從1 ~ 0.0
        { // [-1,1]
            // Modify the default slide transition to shrink the page as well
            float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position));
            float vertMargin = pageHeight * (1 - scaleFactor) / 2;
            float horzMargin = pageWidth * (1 - scaleFactor) / 2;
            if (position < 0)
            {
                view.setTranslationX(horzMargin - vertMargin / 2);
            } else
            {
                view.setTranslationX(-horzMargin + vertMargin / 2);
            }
            // Scale the page down (between MIN_SCALE and 1)
            view.setScaleX(scaleFactor);
            view.setScaleY(scaleFactor);
            // Fade the page relative to its size.
            view.setAlpha(MIN_ALPHA + (scaleFactor - MIN_SCALE)
                    / (1 - MIN_SCALE) * (1 - MIN_ALPHA));
        } else
        { // (1,+Infinity]
            // This page is way off-screen to the right.
            view.setAlpha(0);
        }
    }
}

呼叫:

vp.setPageTransformer(false,new ZoomOutPageTransformer());
  • 1

效果: 

3. 自定義動畫

網上看到鴻洋大神寫的

public class RotateDownPageTransformer implements ViewPager.PageTransformer {
    private static final float ROT_MAX = 20.0f;
    private float mRot;


    public void transformPage(View view, float position)
    {
        Log.e("TAG", view + " , " + position + "");
        if (position < -1)
        { // [-Infinity,-1)
            // This page is way off-screen to the left.
            view.setRotation(0);
        } else if (position <= 1) // a頁滑動至b頁 ; a頁從 0.0 ~ -1 ;b頁從1 ~ 0.0
        { // [-1,1]
            // Modify the default slide transition to shrink the page as well
            if (position < 0)
            {
                mRot = (ROT_MAX * position);
                view.setPivotX(view.getMeasuredWidth() * 0.5f);
                view.setPivotY(view.getMeasuredHeight());
                view.setRotation( mRot);
            } else
            {
                mRot = (ROT_MAX * position);
                view.setPivotX(view.getMeasuredWidth() * 0.5f);
                view.setPivotY(view.getMeasuredHeight());
                view.setRotation( mRot);
            }
            // Scale the page down (between MIN_SCALE and 1)
            // Fade the page relative to its size.
        } else
        { // (1,+Infinity]
            // This page is way off-screen to the right.
            view.setRotation( 0);
        }
    }
}

效果: 

position說明:  當前顯示頁為0,前一頁為-1,後一頁為1,滑動過程中數值不斷變大或變小,所以為float型別

4. 開源框架ViewPagerTransforms

7. 翻頁監聽

1. 設定方法

addOnPageChangeListener()

2. 翻頁監聽介面

ViewPager.OnPageChangeListener

3. 重寫方法

  1. onPageScrolled(int position, float positionOffset, int positionOffsetPixels)

    頁面滑動狀態停止前一直呼叫

    position:當前點選滑動頁面的位置  positionOffset:當前頁面偏移的百分比  positionOffsetPixels:當前頁面偏移的畫素位置

  2. onPageSelected(int position)

    滑動後顯示的頁面和滑動前不同,呼叫

    position:選中顯示頁面的位置

  3. onPageScrollStateChanged(int state)

    頁面狀態改變時呼叫

    state:當前頁面的狀態

    SCROLL_STATE_IDLE:空閒狀態  SCROLL_STATE_DRAGGING:滑動狀態  SCROLL_STATE_SETTLING:滑動後滑翔的狀態

4. 使用

vp.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        Log.e("vp","滑動中=====position:"+ position + "   positionOffset:"+ positionOffset + "   positionOffsetPixels:"+positionOffsetPixels);
    }

    @Override
    public void onPageSelected(int position) {
        Log.e("vp","顯示頁改變=====postion:"+ position);
    }

    @Override
    public void onPageScrollStateChanged(int state) {
        switch (state) {
            case ViewPager.SCROLL_STATE_IDLE:
                Log.e("vp","狀態改變=====SCROLL_STATE_IDLE====靜止狀態");
                break;
            case ViewPager.SCROLL_STATE_DRAGGING:
                Log.e("vp","狀態改變=====SCROLL_STATE_DRAGGING==滑動狀態");
                break;
            case ViewPager.SCROLL_STATE_SETTLING:
                Log.e("vp","狀態改變=====SCROLL_STATE_SETTLING==滑翔狀態");
                break;
        }
    }
});

Log: 

三、與Fragment結合使用

與Fragment結合使用其實也一樣,只是用Fragment代替原先的View,填充Viewpager;然後就是Adapter不一樣,配合Fragment使用的有兩個Adapter:FragmentPagerAdapterFragmentStatePagerAdapter

相同點:  FragmentPagerAdapter和FragmentStatePagerAdapter都繼承自PagerAdapter

不同點:  解除安裝不再需fragment時,各自採用的處理方法有所不同

FragmentStatePagerAdapter會銷燬不需要的fragment。事務提交後, activity的FragmentManager中的fragment會被徹底移除。 FragmentStatePagerAdapter類名中的“state”表明:在銷燬fragment時,可在onSaveInstanceState(Bundle)方法中儲存fragment的Bundle資訊。使用者切換回來時,儲存的例項狀態可用來恢復生成新的fragment

FragmentPagerAdapter有不同的做法。對於不再需要的fragment, FragmentPagerAdapter會選擇呼叫事務的detach(Fragment)方法來處理它,而非remove(Fragment)方法。也就是說, FragmentPagerAdapter只是銷燬了fragment的檢視, fragment例項還保留在FragmentManager中。因此,FragmentPagerAdapter建立的fragment永遠不會被銷燬

也就是:在destroyItem()方法中,FragmentStatePagerAdapter呼叫的是remove()方法,適用於頁面較多的情況;FragmentPagerAdapter呼叫的是detach()方法,適用於頁面較少的情況。但是有頁面資料需要重新整理的情況,不管是頁面少還是多,還是要用FragmentStatePagerAdapter,否則頁面會因為沒有重建得不到重新整理

使用如下:

1. 建立Fragment及相應的xml佈局

public class PagerFragment extends Fragment {
    String mContent;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        mContent = (String) getArguments().get("content");
        View view = inflater.inflate(R.layout.fragment_pager, container, false) ;
        TextView textView = (TextView) view.findViewById(R.id.tv);
        textView.setText(mContent);
        return view;
    }

}
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.strivestay.viewpagerdemo.PagerFragment">

    <TextView
        android:id="@+id/tv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:textSize="18sp"
        android:text="@string/hello_blank_fragment"/>

</FrameLayout>

2. 給Viewpager設定資料和介面卡

private void setVp() {
    final List<PagerFragment> list = new ArrayList<>();
    for (int i = 0; i < 5; i++) {
        PagerFragment fragment = new PagerFragment();
        Bundle bundle = new Bundle();
        bundle.putString("content","第"+i+"個Fragment");
        fragment.setArguments(bundle);

        list.add(fragment);
    }

    ViewPager vp = (ViewPager) findViewById(R.id.vp);
//        vp.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) {
//            @Override
//            public Fragment getItem(int position) {
//                return list.get(position);
//            }
//
//            @Override
//            public int getCount() {
//                return list.size();
//            }
//        });

    vp.setAdapter(new FragmentStatePagerAdapter(getSupportFragmentManager()) {
        @Override
        public Fragment getItem(int position) {
            return list.get(position);
        }

        @Override
        public int getCount() {
            return list.size();
        }
    });

}

效果: 

四、實現輪播圖效果

1. 特點

  1. 支援無限輪播,可設定自動翻頁和時間
  2. 可以配合你的圖片框架載入網路圖片
  3. 不只是圖片輪播

2. 使用介紹

1. 導包

// 輪播圖 convenientbanner
compile 'com.bigkoo:convenientbanner:2.0.5'
// 翻頁特效 ViewPagerTransforms
compile 'com.ToxicBakery.viewpager.transforms:view-pager-transforms:[email protected]'

2. xml引用

<com.bigkoo.convenientbanner.ConvenientBanner
        android:id="@+id/convenientBanner"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        app:canLoop="true"/>

3. 建立Holder

public class LocalImageHolderView implements Holder<Integer> {
    private ImageView imageView;
    @Override
    public View createView(Context context) {
        imageView = new ImageView(context);
        imageView.setScaleType(ImageView.ScaleType.FIT_XY);
        return imageView;
    }

    @Override
    public void UpdateUI(Context context, int position, Integer data) {
        imageView.setImageResource(data);
    }
}

4. ConvenientBanner設定資料

convenientBanner.setPages(
    new CBViewHolderCreator<LocalImageHolderView>() {
        @Override
        public LocalImageHolderView createHolder() {
            return new LocalImageHolderView();
        }
    }, localImages)
    // 設定兩個點圖片作為翻頁指示器,不設定則沒有指示器,可以根據自己需求自行配合自己的指示器,不需要圓點指示器可用不設
    .setPageIndicator(new int[]{R.drawable.ic_page_indicator, R.drawable.ic_page_indicator_focused})
    // 設定指示器的方向
    // .setPageIndicatorAlign(ConvenientBanner.PageIndicatorAlign.ALIGN_PARENT_RIGHT)
    // .setOnPageChangeListener(this)//監聽翻頁事件
    .setOnItemClickListener(this);

效果: 

五、實現畫廊效果

效果如下:  

實現步驟:

1. viewpager佈局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    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"
    android:orientation="vertical"
    android:clipChildren="false"
    tools:context="com.strivestay.viewpagerdemo.FourthActivity">

    <include layout="@layout/layout_toolbar"/>

    <android.support.v4.view.ViewPager
        android:id="@+id/vp"
        android:layout_width="match_parent"
        android:layout_height="180dp"
        android:layout_marginTop="100dp"
        android:layout_marginLeft="30dp"
        android:layout_marginRight="30dp"
        android:clipChildren="false">
    </android.support.v4.view.ViewPager>

</LinearLayout>

要點:  給viewpager和它的父佈局都設定屬性android:clipChildren=”false”

2. pager佈局

item_banner_samll.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="200dp"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/iv_banner"
        android:layout_width="200dp"
        android:layout_height="180dp"
        android:background="#009999"
        android:scaleType="centerCrop"/>
</LinearLayout>

item_banner.xml

<?xml version="1.0" encoding="utf-8"?>
<ImageView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/iv_banner"
    android:layout_width="match_parent"
    android:layout_height="180dp"
    android:background="#009999"
    android:scaleType="centerCrop"/>

區別:  寬度,一個佔滿viewpager的寬度,一個小於viewpager的寬度

3. Adapter

/**
 * 畫廊效果,page寬度佔滿vp
 * @author StriveStay
 * @date 2018/2/24
 */
public class FourthPageAdapter extends PagerAdapter {
    private Context mContext;

    public FourthPageAdapter(Context context) {
        mContext = context;
    }

    @Override
    public int getCount() {
        return Integer.MAX_VALUE;
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view == object;
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        // 就4張圖片
        position %= 4;

        View view = View.inflate(mContext,R.layout.item_banner,null);
        ImageView iv = (ImageView) view.findViewById(R.id.iv_banner);
        int resourceId = mContext.getResources().getIdentifier("img" + (position + 1), "drawable", mContext.getPackageName());
        Glide.with(mContext).load(resourceId).into(iv);
        //            iv.setImageResource(resourceId);
        container.addView(view);
        return view;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        container.removeView((View)object);
    }

}

如果是page寬度 < vp寬度,需要重寫getPageWidth()方法,用於計算page佔據vp的百分比

@Override
public float getPageWidth(int position) {
    float itemWidth =  (mContext.getResources().getDisplayMetrics().density * 200);
    float vpWidth = (mContext.getResources().getDisplayMetrics().widthPixels - mContext.getResources().getDisplayMetrics().density * 60);
    return  itemWidth / vpWidth;
}

3. vp設定adapter

private void setVp() {
    ViewPager vp = (ViewPager) findViewById(R.id.vp);

    // 設定介面卡
//        vp.setAdapter(new FourthPageAdapter(this));
    vp.setAdapter(new FourthSmallPageAdapter(this));

    // page 邊距
    vp.setPageMargin((int)(getResources().getDisplayMetrics().density * 15));

    // 為了左右無限滑動,顯示在中間,且顯示第一張
    int i = Integer.MAX_VALUE/2%4;
    vp.setCurrentItem(Integer.MAX_VALUE/2 + (4-i));

}

4. 問題

當page寬度 < vp寬度,且page的數量較少,沒有佔滿vp,這時滑動vp,會出現閃屏,如下: 

解決辦法:

  1. 當明確知道vp放不下2個page時,可以如下處理

    @Override
    public float getPageWidth(int position) {
        // 加上這句
        if(getCount() < 2){
            return super.getPageWidth(position);
        }
    
        float itemWidth =  (mContext.getResources().getDisplayMetrics().density * 200);
        float vpWidth = (mContext.getResources().getDisplayMetrics().widthPixels - mContext.getResources().getDisplayMetrics().density * 60);
    
        Log.e("比例",vpWidth+"==="+itemWidth+"==="+(int)(vpWidth/itemWidth));
    
        return  itemWidth / vpWidth;
    
    }
  2. 當vp可以放置兩個以上的page時,也是個通用的方法

vp.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        // vp中最多能放下2個page,則讓vp中page個數 < 3時,讓vp不能滑動
        if(vp.getChildCount() < 3 && event.getAction() == MotionEvent.ACTION_MOVE){
            return true;
        }
        return false;
    }
});