22、Android--ViewPager

ViewPager

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:id="@+id/viewpager"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center" />
修改为
<androidx.viewpager.widget.ViewPager
    android:id="@+id/viewpager"
    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:android="http://schemas.android.com/apk/res/android"
    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:android="http://schemas.android.com/apk/res/android"
    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:android="http://schemas.android.com/apk/res/android"
    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提供两种标题栏的方式:PagerTitleStripPagerTabStrip

PagerTitleStrip:标题附带的是普通的文字

PagerTabStrip:标题除了文字外,还带有下划线

两者的区别仅仅是布局不一样而已,其他的都一样,在开发中一般不使用,这里做简单介绍。

PagerTitleStrip

1、创建PagerTitleStrip所在的布局文件:activity_pager_title_strip

<?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">

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center">
        <androidx.viewpager.widget.PagerTitleStrip
            android:id="@+id/pager_title"
            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:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <androidx.viewpager.widget.ViewPager
        android:id="@+id/viewpager"
        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:android="http://schemas.android.com/apk/res/android"
    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:android="http://schemas.android.com/apk/res/android"
    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:android="http://schemas.android.com/apk/res/android"
    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: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">
    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/viewpager2"
        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:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/rl_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <TextView
        android:id="@+id/tv_content"
        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的代码非常简单,这里就不一一列举。

原文地址:https://www.cnblogs.com/pengjingya/p/5509964.html