22、Android--ViewPager ViewPager ViewPager2
ViewPager是android扩展包v4包中的类,这个类可以让用户左右切换当前的view。我们首先来看看API对于这个类的表述:
ViewPager类直接继承了ViewGroup类,所有它是一个容器类,可以在其中添加其他的view类。
ViewPager类需要一个PagerAdapter适配器类给它提供数据。
ViewPager经常和Fragment一起使用,并且提供了专门的FragmentPagerAdapter、FragmentStatePagerAdapter类来供Fragment中的ViewPager使用。
基本使用
1、在布局文件中定义ViewPager,由于早期是在V4兼容包中提供的(过时),现在推荐使用Androix包下的ViewPager。
<android.support.v4.view.ViewPager
android:
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
修改为
<androidx.viewpager.widget.ViewPager
android:
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
2、创建三个Layout文件,用于滑动切换的视图显示:
layout01.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:andro
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
android:orientation="vertical">
</LinearLayout>
layout02.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:andro
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffff00"
android:orientation="vertical">
</LinearLayout>
layout03.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:andro
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ff00ff"
android:orientation="vertical">
</LinearLayout>
3、编写自定义的MyPageAdapter继承自PageAdapter
class MyPageAdapter extends PagerAdapter{
private List<View> viewList;
public MyPageAdapter(List<View> viewList) {
this.viewList = viewList;
}
// 返回要滑动的VIew的个数
@Override
public int getCount() {
return viewList.size();
}
// 判断pager的一个view是否和instantiateItem方法返回的object有关联
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
// 将当前视图添加到container中,然后返回当前View
@Override
public Object instantiateItem(ViewGroup container, int position) {
container.addView(viewList.get(position));
return viewList.get(position);
}
// 从当前container中删除指定位置(position)的View
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView(viewList.get(position));
}
}
4、在Activity中构建数据,并设置适配器即可
public class MainActivity extends AppCompatActivity {
private ViewPager mViewPager;
private View mView1, mView2, mView3;
private List<View> mViewList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mViewPager = findViewById(R.id.viewpager);
// 构建布局
mView1 = View.inflate(this, R.layout.layout01, null);
mView2 = View.inflate(this,R.layout.layout02,null);
mView3 = View.inflate(this,R.layout.layout03, null);
// 将要分页显示的View装入数组中
mViewList = new ArrayList<>();
mViewList.add(mView1);
mViewList.add(mView2);
mViewList.add(mView3);
MyPageAdapter adapter = new MyPageAdapter(mViewList);
mViewPager.setAdapter(adapter);
}
}
PageAdapter
在使用PagerAdapter的时候,至少需要覆写如下几个方法:
instantiateItem(ViewGroup, int):将当前视图添加到container中,然后返回当前View
destroyItem(ViewGroup, int, Object):从当前container中删除指定位置的View
getCount():返回要滑动的View的个数
isViewFromObject(View, Object):判断pager的一个view是否和instantiateItem方法返回的object有关联
每个滑动页面都对应一个Key
,而且这个Key值是用来唯一追踪这个页面的,也就是说每个滑动页面都与一个唯一的Key对应。可以将当前页面本身的View作为Key,也可以自定义的方式来实现Key。
自定义Key
由于Key与View要一一对应,所以我把每个视图所处的位置Position作为Key来实现自定义Key。
class MyPageAdapter extends PagerAdapter{
private List<View> viewList;
public MyPageAdapter(List<View> viewList) {
this.viewList = viewList;
}
// 返回要滑动的VIew的个数
@Override
public int getCount() {
return viewList.size();
}
// 判断pager的一个view是否和instantiateItem方法返回的object有关联
@Override
public boolean isViewFromObject(View view, Object object) {
return view == viewList.get((Integer) object);
}
// 将当前视图添加到container中,然后返回当前View
@Override
public Object instantiateItem(ViewGroup container, int position) {
container.addView(viewList.get(position));
return position;
}
// 从当前container中删除指定位置(position)的View
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView(viewList.get(position));
}
}
由于instantiateItem()
方法返回的是position,所以在isViewFromObject()
方法中的object则是int类型的position,这样就保持一一对应的关系。
ViewPager标题
ViewPager提供两种标题栏的方式:PagerTitleStrip
与PagerTabStrip
PagerTitleStrip:标题附带的是普通的文字
PagerTabStrip:标题除了文字外,还带有下划线
两者的区别仅仅是布局不一样而已,其他的都一样,在开发中一般不使用,这里做简单介绍。
PagerTitleStrip
1、创建PagerTitleStrip所在的布局文件:activity_pager_title_strip
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:andro
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.viewpager.widget.ViewPager
android:
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center">
<androidx.viewpager.widget.PagerTitleStrip
android:
android:layout_width="wrap_content"
android:layout_gravity="top"
android:layout_height="40dp"
android:textColor="#FF0000"/>
</androidx.viewpager.widget.ViewPager>
</LinearLayout>
2、创建适配器,需要实现getPageTitle()
方法来返回标题的数据信息
class MyPageAdapter extends PagerAdapter{
private List<String> titleList;
private List<View> viewList;
public MyPageAdapter(List<String> titleList, List<View> viewList) {
this.titleList = titleList;
this.viewList = viewList;
}
@Override
public CharSequence getPageTitle(int position) {
return titleList.get(position);
}
@Override
public int getCount() {
return viewList.size();
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == viewList.get((Integer) object);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
container.addView(viewList.get(position));
return position;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView(viewList.get(position));
}
}
3、在Activity中使用的代码如下所示:
public class MainActivity extends AppCompatActivity {
private ViewPager mViewPager;
private View mView1, mView2, mView3;
private List<View> mViewList;
private List<String> mTitleList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pager_title_strip);
mViewPager = findViewById(R.id.viewpager);
// 构建标题数组
mTitleList = new ArrayList<>();
mTitleList.add("标题一");
mTitleList.add("标题二");
mTitleList.add("标题三");
// 构建布局
mView1 = View.inflate(this, R.layout.layout01, null);
mView2 = View.inflate(this,R.layout.layout02,null);
mView3 = View.inflate(this,R.layout.layout03, null);
// 将要分页显示的View装入数组中
mViewList = new ArrayList<>();
mViewList.add(mView1);
mViewList.add(mView2);
mViewList.add(mView3);
MyPageAdapter adapter = new MyPageAdapter(mTitleList, mViewList);
mViewPager.setAdapter(adapter);
}
}
注:PagerTabStrip
的用法和PagerTitleStrip
一样,只是显示效果不同,这里不再演示。
OnPageChangeListener
OnPageChangeListener
是ViewPager用来实现页面切换的监听器,它包含如下方法:
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
public void onPageSelected(int position)
public void onPageScrollStateChanged(int state)
对于该三个方法的详细叙述如下所示:
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
/**
* 当页面在滑动的时候会调用此方法,在滑动停止前会一直被调用
* @param position 当前页面及滑动页面的位置编号
* @param positionOffset 当前页面偏移的百分比
* @param positionOffsetPixels 当前页面偏移的像素位置
*/
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
/**
* 页面跳转完后调用
* @param position 当前选中的页面的的位置编号
*/
@Override
public void onPageSelected(int position) {
}
/**
* 状态改变的时候调用
* @param state 有三种状态:0:默认什么也不做;1:表示正在滑动;2:表示滑动完毕
*/
@Override
public void onPageScrollStateChanged(int state) {
}
});
注意:ViewPager早期使用的setOnPageChangeListener()
方法已经过时,并不推荐再使用。
结合Fragment
在Android开发中,我们可以使用ViewPager+Fragment的方式来实现界面的横向切换,这样就需要用到如下两个适配器,它们都继承自PageAdapter。
FragmentPagerAdapter:类中每一个生成的Fragment都将保存在内存中,内存开销大,适合页面较少的静态页面。
FragmentStatePagerAdapter:每次生成的Fragment只保留在当前页面,当页面离开时,就会被消除,释放其资源。
FragmentPagerAdapter
1、在Activity的布局文件中创建ViewPager
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:andro
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.viewpager.widget.ViewPager
android:
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
2、创建三个Fragment,并实现三个布局文件
fragment1的创建
public class Fragment1 extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View layout = View.inflate(getActivity(), R.layout.fragment_one, null);
return layout;
}
}
使用到的布局文件如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:andro
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textSize="20sp"
android:text="Fragment1"/>
</RelativeLayout>
fragment2的创建
public class Fragment2 extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View layout = View.inflate(getActivity(), R.layout.fragment_two, null);
return layout;
}
}
使用到的布局文件如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:andro
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textSize="20sp"
android:text="Fragment2"/>
</RelativeLayout>
fragment3的创建
public class Fragment3 extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View layout = View.inflate(getActivity(), R.layout.fragment_three, null);
return layout;
}
}
使用到的布局文件如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:andro
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textSize="20sp"
android:text="Fragment3"/>
</RelativeLayout>
3、创建三个Fragment后,编写一个类MyFragmentPageAdapter
继承自FragmentPageAdapter
:
class MyFragmentPageAdapter extends FragmentPagerAdapter{
private List<Fragment> mFragments;
public MyFragmentPageAdapter(FragmentManager fm, List<Fragment> mFragments) {
super(fm);
this.mFragments = mFragments;
}
@Override
public Fragment getItem(int position) {
return mFragments.get(position);
}
@Override
public int getCount() {
return mFragments.size();
}
}
这里需要注意,FragmentPageAdapter
有两个构造函数,它们分别如下:
public FragmentPagerAdapter( FragmentManager fm) :在API 27之后已经过时,其实也是调用第二个构造函数。
public FragmentPagerAdapter(FragmentManager fm, int behavior):推荐使用,增加了behavior参数,用来实现懒加载。
其中第二个构造函数的behavior
的取值如下:
FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT:解决ViewPager和Fragment生命周期的缺陷问题
4、最后是Activity中代码的实现如下:
public class MainActivity extends AppCompatActivity {
private ViewPager mViewPager;
private List<Fragment> mFragments;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mViewPager = findViewById(R.id.viewpager);
mFragments = new ArrayList<>();
mFragments.add(new Fragment1());
mFragments.add(new Fragment2());
mFragments.add(new Fragment3());
MyFragmentPageAdapter adapter = new MyFragmentPageAdapter(getSupportFragmentManager(), mFragments);
mViewPager.setAdapter(adapter);
}
}
注:FragmentStatePagerAdapter
的使用方式和FragmentPagerAdapter
完全一致,这里不再叙述。
ViewPager2
Google在androidX
组件包中新增了ViewPager2
组件,它是用来替换之前的ViewPager的。它包含如下特性:
1、能够关闭用户输入(
setUserInputEnabled
,isUserInputEnabled
)
2、支持RTL布局(视图从左到右显示),支持横向和纵向的滑动以及notifyDataSetChanged
刷新视图
其中API的变更如下所示:
1、
FragmentStateAdapter
替代FragmentStatePagerAdapter
;
2、RecyclerView.Adapter
替代PagerAdapter
;
3、registerOnPageChangeCallback
替代addPageChangeListener
。
初步了解ViewPager2后,我们要使用它需要添加相关的依赖:
implementation "androidx.viewpager2:viewpager2:1.0.0-alpha02"
横向和纵向滑动
上面以及介绍了ViewPager2支持横向和纵向的滑动,那么我们先来看看横向的滑动。
1、在Activity的布局中声明ViewPager2
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:andro
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">
<androidx.viewpager2.widget.ViewPager2
android:
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
2、建立简单的item的布局item_page.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:andro
android:
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textSize="20sp"
android:text="ViewPager2"/>
</RelativeLayout>
3、建立数据适配器ViewPagerAdapter.java
:
class ViewPagerAdapter extends RecyclerView.Adapter<ViewPagerAdapter.ViewPagerViewHolder{
private Context mContext;
private List<Integer> mColors;
public ViewPagerAdapter(Context context, List<Integer> colors) {
this.mContext = context;
this.mColors = colors;
}
@Override
public ViewPagerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View layout = LayoutInflater.from(mContext).inflate(R.layout.item_page, parent, false);
ViewPagerViewHolder viewHolder = new ViewPagerViewHolder(layout);
return viewHolder;
}
@Override
public void onBindViewHolder(ViewPagerViewHolder holder, int position) {
holder.tvContent.setText("item " + position);
holder.rlContainer.setBackgroundResource(mColors.get(position));
}
@Override
public int getItemCount() {
return mColors.size();
}
class ViewPagerViewHolder extends RecyclerView.ViewHolder {
RelativeLayout rlContainer;
TextView tvContent;
public ViewPagerViewHolder(View itemView) {
super(itemView);
rlContainer = itemView.findViewById(R.id.rl_container);
tvContent = itemView.findViewById(R.id.tv_content);
}
}
}
注:如果在使用过程中出现Pages must fill the whole ViewPager2 (use match_parent)
错误,可以使用如下代码来填充布局:
View layout = View.inflate(mContext, R.layout.item_page, null);
ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
layout.setLayoutParams(layoutParams);
4、Activity中的代码如下所示:
public class ViewPagerActivity extends AppCompatActivity {
private List<Integer> mColors;
private ViewPager2 mViewPager2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view_pager);
mViewPager2 = findViewById(R.id.viewpager2);
mColors = new ArrayList<>();
mColors.add(R.color.white);
mColors.add(R.color.black);
mColors.add(R.color.purple_200);
ViewPagerAdapter adapter = new ViewPagerAdapter(this, mColors);
mViewPager2.setAdapter(adapter);
}
}
运行后就可以横向滑动了,由于默认是横向滑动,如果想要实现纵向滑动只需要设置ViewPager2
的方向即可:
viewPager2.setOrientation(ViewPager2.ORIENTATION_VERTICAL);
registerOnPageChangeCallback
registerOnPageChangeCallback
是ViewPager2用来实现页面切换的监听器,它包含如下方法:
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
public void onPageSelected(int position)
public void onPageScrollStateChanged(int state)
对于该三个方法的详细叙述如下所示:
mViewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
/**
* 当页面在滑动的时候会调用此方法,在滑动停止前会一直被调用
* @param position 当前页面及滑动页面的位置编号
* @param positionOffset 当前页面偏移的百分比
* @param positionOffsetPixels 当前页面偏移的像素位置
*/
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
/**
* 页面跳转完后调用
* @param position 当前选中的页面的的位置编号
*/
@Override
public void onPageSelected(int position) {
}
/**
* 状态改变的时候调用
* @param state 有三种状态:0:默认什么也不做;1:表示正在滑动;2:表示滑动完毕
*/
@Override
public void onPageScrollStateChanged(int state) {
}
});
监听器除了方法名之外,基本用法和ViewPager一样。
FragmentStateAdapter
FragmentStateAdapter
有三个构造方法,它们分别是:
public FragmentStateAdapter(FragmentActivity fragmentActivity)
public FragmentStateAdapter(Fragment fragment)
public FragmentStateAdapter(FragmentManager fragmentManager, Lifecycle lifecycle)
FragmentStateAdapter
在使用上和FragmentStatePagerAdapter
一样,所以它们的特性也是一样的。
1、编写MyFragmentStateAdapter
适配器,代码如下:
class MyFragmentStateAdapter extends FragmentStateAdapter {
private List<Fragment> mFragments;
public MyFragmentStateAdapter(FragmentActivity fragmentActivity, List<Fragment> fragments) {
super(fragmentActivity);
this.mFragments = fragments;
}
@Override
public Fragment createFragment(int position) {
return mFragments.get(position);
}
@Override
public int getItemCount() {
return mFragments.size();
}
}
2、在Activity中使用的时候,当前Activity需要继承自FragmentActivity
,在构建适配器的时候需要用到:
public class ViewPagerActivity extends FragmentActivity {
private ViewPager2 mViewPager2;
private List<Fragment> mFragments;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view_pager);
mViewPager2 = findViewById(R.id.viewpager2);
mFragments = new ArrayList<>();
mFragments.add(new Fragment1());
mFragments.add(new Fragment2());
mFragments.add(new Fragment3());
MyFragmentStateAdapter adapter = new MyFragmentStateAdapter(this, mFragments);
mViewPager2.setAdapter(adapter);
}
}
其中Fragment1、Fragment2、Fragment3的代码非常简单,这里就不一一列举。