ViewPager2学习

引入
1
implementation "androidx.viewpager2:viewpager2:1.0.0"
前言

ViewPager2与ViewPager同是继承自ViewGrop,但是ViewPager2被声明成了final。意味着我们不可能再像ViewPager一样通过继承来修改ViewPager2的代码。

简单使用Demo
基本思路

1、一个子项布局

2、一个adapter适配器

3.、在主布局引入viewpager2

4、viewpager2对象添加adapter

activity_main.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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="wrap_content"
tools:context=".MainActivity">

<androidx.viewpager2.widget.ViewPager2
android:id="@+id/page"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>

</RelativeLayout>
item_text.xml

和recyclerview一样,该布局是适配器(即子项页面的布局)要用到的布局。

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<TextView
android:id="@+id/item_text"
android:layout_width="match_parent"
android:layout_height="300dp"
android:gravity="center" />
</LinearLayout>
ViewAdapter.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class ViewAdapter extends RecyclerView.Adapter<ViewAdapter.ViewPagerHolder> {

private List<Integer> datas; //这个demo传入一组颜色数据

public ViewAdapter(List<Integer> datas) {
this.datas = datas;
}

@NonNull
@Override
public ViewPagerHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_text, parent, false); //根据上面的子布局生成view
return new ViewPagerHolder(view);
}

@Override
public void onBindViewHolder(@NonNull ViewPagerHolder holder, int position) {
holder.textView.setText("页面: " + position);
holder.textView.setBackgroundColor(datas.get(position));
}

@Override
public int getItemCount() {
return datas.size();
}

class ViewPagerHolder extends RecyclerView.ViewHolder {
TextView textView;

public ViewPagerHolder(@NonNull View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.item_text);
}
}
}
MainActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

ViewPager2 vp2 = findViewById(R.id.page);

List<Integer> colors = new ArrayList<>();
colors.add(Color.BLUE);
colors.add(Color.GREEN);
colors.add(Color.RED);

//vp2.setOrientation(ViewPager2.ORIENTATION_VERTICAL); //竖直方向 默认水平方向
vp2.setAdapter(new ViewAdapter(colors));
}
}
效果

与 Fragment 结合

在使用 ViewPager 的时候,可以使用 FragmentPagerAdapter 实现页数小的情况,用 FragmentStatePagerAdapter 实现页面比较多的情况。ViewPager2 则没有那么复杂,直接使用 FragmentStateAdapter 即可。

基本思路

1、fragment的布局

2、一个Adapter`承于FragmentStateAdapter

3、在主布局引入viewpager2

4、viewpager2 添加adapter

fm_demo.xml

fragment的布局

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
android:id="@+id/tvDemo"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="28sp" />

</LinearLayout>
DemoFragment.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class DemoFragment extends Fragment {

private TextView tvDemo;
private String title;

public DemoFragment(String title) {
this.title = title;
}

public DemoFragment() {
}

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fm_demo, container, false); //使用前面的 fm_demo 布局来构造fragment
tvDemo = view.findViewById(R.id.tvDemo);
tvDemo.setBackgroundColor(Color.YELLOW);
tvDemo.setText(title);
return view;
}

}
FragmentAdapter.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class FragmentAdapter extends FragmentStateAdapter {

private List<String> titles;

public FragmentAdapter(@NonNull FragmentActivity fragmentActivity, List<String> titles) {
super(fragmentActivity);
this.titles = titles;
}

public FragmentAdapter(@NonNull FragmentActivity fragmentActivity) {
super(fragmentActivity);
}

@NonNull
@Override
public Fragment createFragment(int position) {
return new DemoFragment(titles.get(position)); //构造fragment
}

@Override
public int getItemCount() {
return titles.size();
}
}
MainActivity.java

主布局和前面简单demo一样,只有一个viewpager2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

ViewPager2 vp2 = findViewById(R.id.page);

//简单demo
// List<Integer> colors = new ArrayList<>();
// colors.add(Color.BLUE);
// colors.add(Color.GREEN);
// colors.add(Color.RED);
//
// //vp2.setOrientation(ViewPager2.ORIENTATION_VERTICAL); //竖直方向 默认水平方向
// vp2.setAdapter(new ViewAdapter(colors));

// 和fragment结合
List<String> titles = new ArrayList<>();
titles.add("页面1");
titles.add("页面2");
titles.add("页面3");
FragmentAdapter adapter = new FragmentAdapter(MainActivity.this,titles);
vp2.setAdapter(adapter);
}
}
效果

配合 TabLayout 使用
基本思路

1、需要的布局中引入 tabLayout 和 viewPage2

2、一个Tab项的布局页面

3、一个页面适配器 Adapter 继承自 FragmentStateAdapter

4、创建Tab ,并添加到TabLayout中

5、viewpager2对象添加前面的适配器 Adapter

6、ViewPager2 配合 TabLayout 的联动 方式一:TabLayoutMediator进行绑定 attach() 方式二:原始的监听事件中操作

7、选择性的注册 TabLayout 的选择事件监听 和 viewpage2的事件监听

布局中引入

activity_main.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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:id="@+id/linearLayout"
android:orientation="vertical"
tools:context=".MainActivity">

<com.google.android.material.tabs.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>

<androidx.viewpager2.widget.ViewPager2
android:layout_marginTop="100dp"
android:id="@+id/page"
android:layout_width="match_parent"
android:layout_height="300dp"/>

</LinearLayout>
Tab项的布局页面

tab_item.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center">

<TextView
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="5dp"
tools:text="标签" />

<ImageView
android:layout_width="15dp"
android:layout_height="15dp"
android:background="@mipmap/ic_launcher" />

</LinearLayout>
页面适配器 TabAdapter

TabAdapter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class TabAdapter extends FragmentStateAdapter {
private List<String> titles;

public TabAdapter(@NonNull FragmentActivity fragmentActivity, List<String> titles) {
super(fragmentActivity);
this.titles = titles;
}

@NonNull
@Override
public Fragment createFragment(int position) {
return new DemoFragment(titles.get(position));
}

@Override
public int getItemCount() {
return titles.size();
}
}
MainActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
public class MainActivity extends AppCompatActivity {

private TabLayout tabLayout;
private List<String> titles;
private TabLayoutMediator tabLayoutMediator;
private ViewPager2 vp2;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

vp2 = findViewById(R.id.page);
tabLayout = findViewById(R.id.tabLayout);
titles = new ArrayList<>();

titles.add("title0");
titles.add("title1");
titles.add("title2");

TabLayout.Tab tab1 = tabLayout.newTab();
tab1.setCustomView(R.layout.tab_item);

//创建并添加 Tab
TextView tvTitle = tab1.getCustomView().findViewById(R.id.tvTitle);
tvTitle.setText("带图标");
tabLayout.addTab(tab1);
TabLayout.Tab tab2 = tabLayout.newTab().setText("自定义标题1");
tabLayout.addTab(tab2);
TabLayout.Tab tab3 = tabLayout.newTab().setText("自定义标题2");
tabLayout.addTab(tab3);

//创建 Adapter
TabAdapter tabAdapter = new TabAdapter(MainActivity.this, titles);
vp2.setAdapter(tabAdapter);

//tabLayout 和 vp2 联动 使用 tabLayoutMediator方式
tabLayoutMediator = new TabLayoutMediator(tabLayout, vp2, new TabLayoutMediator.TabConfigurationStrategy() {
@Override
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position) {
tab.setText(titles.get(position));
}
});
//绑定
tabLayoutMediator.attach();

//tabLayout的事件监听
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {

}

@Override
public void onTabUnselected(TabLayout.Tab tab) {

}

@Override
public void onTabReselected(TabLayout.Tab tab) {

}
});

//vp2的事件监听
vp2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {

}
});


}

//解绑
@Override
protected void onDestroy() {
if (tabLayoutMediator != null) {
tabLayoutMediator.detach();
}
super.onDestroy();
}
}
说明

TabLayout使用自定义布局和ViewPager2联动时会出现问题,TabLayout的自定义效果会无效,所以要想使用自定义的tablayout布局 ,就采用最原始的联动

原始联动

即在上面监听事件的基础上进行修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
vpTablayout.setCurrentItem(tab.getPosition());
}

@Override
public void onTabUnselected(TabLayout.Tab tab) {

}

@Override
public void onTabReselected(TabLayout.Tab tab) {

}
});

vp2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
tabLayout.selectTab(tabLayout.getTabAt(position));
}
});
效果

常用法
页面滑动事件监听
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//滑动监听
vp2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels);
}

@Override
public void onPageSelected(int position) {
Toast.makeText(MainActivity.this,"select: "+titles.get(position),Toast.LENGTH_SHORT).show();
}

@Override
public void onPageScrollStateChanged(int state) {
super.onPageScrollStateChanged(state);
}
});
禁止滑动
1
2
//禁止用户滑动  false为禁止滑动  true为可以滑动
vp2.setUserInputEnabled(false);
设置页面间距
1
vp2.setPageTransformer(new MarginPageTransformer(10))  //页面间距为10dp
组合页面效果
1
2
3
4
CompositePageTransformer compositePageTransformer = new CompositePageTransformer();
compositePageTransformer.addTransformer(new MarginPageTransformer(25));
compositePageTransformer.addTransformer(new ScaleInTransformer());
vp2.setPageTransformer(compositePageTransformer);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
private class ScaleInTransformer implements ViewPager2.PageTransformer {
private static final float DEFAULT_MIN_SCALE = 0.85f;
private static final float DEFAULT_CENTER = 0.5f;
private float mMinScale = DEFAULT_MIN_SCALE;
@Override
public void transformPage(@NonNull View view, float position) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
view.setElevation(-Math.abs(position));
}

int pageWidth = view.getWidth();
int pageHeight = view.getHeight();

view.setPivotY(pageHeight / 2);
view.setPivotX(pageWidth / 2);
if (position < -1) {
view.setScaleX(mMinScale);
view.setScaleY(mMinScale);
view.setPivotX(pageWidth);
} else if (position <= 1) {
if (position < 0) {
float scaleFactor = (1 + position) * (1 - mMinScale) + mMinScale;
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
view.setPivotX(pageWidth * (DEFAULT_CENTER + DEFAULT_CENTER * -position));
} else {
float scaleFactor = (1 - position) * (1 - mMinScale) + mMinScale;
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
view.setPivotX(pageWidth * ((1 - position) * DEFAULT_CENTER));
}
} else {
view.setPivotX(0);
view.setScaleX(mMinScale);
view.setScaleY(mMinScale);
}

}
}

大概类似于下面这种滑动效果

transformPage(@NonNull View view, float position)
  • 第一个参数,就是你当前想要进行transform的view。
  • 第二参数position,我们通常会第一反应理解为是这个view在整个ViewPager中的第几个,但是在这里我们要注意,它并不是一个int型数据,而是一个浮点型,所以它代表的是第一个参数view的左上角x轴坐标参数。
    我们都知道,对于手机屏幕来说,屏幕左上角为坐标原点(0,0),那么在ViewPager显示第一个View时,View1的position就是0,但是当我们往左滑动的时候,它就会慢慢发生变化。当滑动结束的时候,View1的位置就从原来手机屏幕的位置,到了屏幕左边的位置,它的position就从0.0变成了-1.0。而对于第二个View,当屏幕还在展示View1时,它是在屏幕的右边,所以它的position就是1.0。当左滑结束,View2显示在屏幕上时,它的position就从1.0变成了0.0。依次可以类推。

ViewPager2的一屏多页效果

ViewPager2的一屏多页实现跟ViewPager并无多大差别。需要把ViewPager2父容器的clipChildren设置为false,同时ViewPager2的clipChildren也设置为false并且将ViewPager2的offscreenPageLimit 设置为2以及为ViewPager2设置marge即可。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
LinearLayout linearLayout = findViewById(R.id.linearLayout);
linearLayout.setClipChildren(false);
vp2.setClipChildren(false);
vp2.setOffscreenPageLimit(2);
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams)vp2.getLayoutParams();
params.setMarginStart(80);
params.setMarginEnd(80);

CompositePageTransformer compositePageTransformer = new CompositePageTransformer();
compositePageTransformer.addTransformer(new MarginPageTransformer(25));
compositePageTransformer.addTransformer(new ScaleInTransformer());
vp2.setPageTransformer(compositePageTransformer);

Demo地址

https://github.com/Commandercc/DemoEX/blob/master/ViewPager2Demo.zip

0%