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. 重寫方法
-
onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
頁面滑動狀態停止前一直呼叫
position:當前點選滑動頁面的位置 positionOffset:當前頁面偏移的百分比 positionOffsetPixels:當前頁面偏移的畫素位置
-
onPageSelected(int position)
滑動後顯示的頁面和滑動前不同,呼叫
position:選中顯示頁面的位置
-
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:FragmentPagerAdapter和FragmentStatePagerAdapter。
相同點: 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. 特點
- 支援無限輪播,可設定自動翻頁和時間
- 可以配合你的圖片框架載入網路圖片
- 不只是圖片輪播
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,會出現閃屏,如下:
解決辦法:
-
當明確知道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; }
-
當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;
}
});