ViewPager 是一种可以让用户通过左右滑动来切换页面的控件,通过它我们可以展示超过屏幕尺寸大小的内容,在某种程度上它可以说是实现多页面的最佳方式,同时 ViewPager 还支持任意动态的添加/删除页面。比如我们可以将不同的类别的内容分别放在不同页面当中,然后通过滑动切换不同的类别从而给用户展示不同的页面,这个在类似百度App等新闻类App中非常适用。在 ViewPager 中插入“娱乐”、“国际”、“体育”、“星座”等等新闻类别,然后在不同的 View 中展示不同的新闻内容,还可以根据用户的喜好动态增加/删除某些页面,接下来就一起来看看如何完成多页视图。
1. ViewPager 的特性
大家在使用 Android 手机的时候一定都见过下图的效果:
没错,这个就是今天的主角——ViewPager 了。在实际开发过程中,我们大多数时候会采用 Fragment 来展示一个页面而不会直接采用 View,在后面的章节学完 Fragment 之后就会知道,Fragment 可以封装 UI 和逻辑,并且会维护自己的生命周期,所以通过 Fragment 我们可以实现更丰富生动的效果,当然对于 ViewPager 的使用而言其实二者几乎没什么差别,我们现在还是把重点放在 ViewPager 上,在后面学完 Fragment 之后只需要做一些简单的改动即可将 View 替换成 Fragment。ViewPager 和前面所学的 ListView/GridView 类似,也需要一个适配器来完成数据的适配,不同的是 ViewPager 有一个专门的适配器——PagerAdapter,所以我们很多的工作也是围绕着 PagerAdapter 展开。
2. PagerAdapter 的使用方法
类似前面所讲的 BaseAdapter 的四个回调接口(不记得的同学可以返回前面章节回顾一下),PagerAdapter 同样也有类似的回调接口,如下:
- public Object instantiateItem(ViewGroup container, int position):
根据传入的 position 创建一个 Page,适配器需要在这个回调里网 container 里面添加 View,如下:
public Object instantiateItem(ViewGroup container, int position) { View itemView = mLayoutInflater.inflate(R.layout.view_pager, container, false); TextView textView = itemView.findViewById(R.id.TextView); textView.setText("Mybj Android"); container.addView(itemView); return itemView; }
- public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object):
根据传入的 position 移除一个 Page
1. pupublic void destroyItem(ViewGroup container, int position, Object object) { container.removeView((View)object); }
- gpublic int getCount():
返回当前 ViewPager 中可用 View 的数量
1. pupublic int getCount() { return mList.length; }
public boolean isViewFromObject(@NonNull View view, @NonNull Object object):
通过instantiateItem()返回的对象可以看做是一个 key,这个方法用来判断传入的 View 是否是之前创建的 key,如下:
public boolean isViewFromObject(View view, Object object) { return view == object; }
常用的一般就是以上四种回调方法,理解起来都比较简单,其中要注意的是getCount
和isViewFromObject
这两个是必须实现的,而instantiateItem()
和destroyItem()
是可选的,不过大多数场景还是推荐大家实现 4 个回调方法。
3. ViewPager 完整示例
本节将通过一个简单的例子学习 ViewPager 的使用,每一个 Page 将会通过一个 View 来实现(在学习了 Fragment 之后,可以尝试将 View 替换成 Fragment)。例子中的每一个 Page 表示一种类别,在切换过程中我们会接收切换的状态回调,同步更新类别标题。
3.1 整体布局
由于每个页面都在 ViewPager 中,所以整体的布局非常简单,我们只需要放置一个 ViewPager 以及一个 TextView 用来显示当前 Page 的标题即可。
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout 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:showIn="@layout/activity_main"> <androidx.viewpager.widget.ViewPager android:id="@+id/view_pager" android:layout_width="match_parent" android:layout_height="match_parent"/> <TextView android:text="Num" android:textSize="100sp" android:id="@+id/text" android:layout_marginTop="50dp" android:layout_centerHorizontal="true" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </RelativeLayout>
3.2 Page 页面的布局编写
对于整体布局而言,主要的页面都在 ViewPager 当中,所以我们需要为不同结构的 Page 编写不同的页面,由于本例中每个 Page 的页面结构都一样,所以可以直接复用一套,直接在里面放置一个 ImageView 用于展示类别图片。
<?xml version="1.0" encoding="utf-8"?> <ImageView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" />
3.3 适配器的编写
适配这一块主要就是对四个回调接口的实现,其实在第 2 小节的描述中已经展示了各个方法的实现方式。
package com.emercy.myapplication; import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.viewpager.widget.PagerAdapter; import java.util.List; public class MyAdapter extends PagerAdapter { private final List<View> mView; public MyAdapter(List<View> view) { mView = view; } @NonNull @Override public Object instantiateItem(@NonNull ViewGroup container, int position) { container.addView(mView.get(position)); return mView.get(position); } @Override public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) { container.removeView(mView.get(position)); } @Override public int getCount() { return mView.size(); } @Override public boolean isViewFromObject(@NonNull View view, @NonNull Object object) { return view == object; } }
3.4 MainActivity 主逻辑编写
主逻辑主要的任务就是将前面的布局都用上,并通过 Adapter 将数据和布局串联起来,所以我们需要获取到 ViewPager 中每个 View 的实例,设置类别之后传递给 Adpater,绑定的任务就交由Adpater完成。接下来再监听 ViewPager 的滑动状态从而判断当前切换的位置,从而实现同步更新类别标题,整体代码如下:
package com.emercy.myapplication; import android.app.Activity; import android.content.Context; import android.graphics.Color; import android.os.Bundle; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import android.widget.ViewFlipper; import androidx.viewpager.widget.ViewPager; import org.w3c.dom.Text; import java.util.ArrayList; import java.util.List; public class MainActivity extends Activity { private ViewPager mViewPager; private String[] mTitle = new String[]{"苹果", "香蕉", "荔枝"}; private List<View> mView = new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final TextView tv = findViewById(R.id.text); mViewPager = findViewById(R.id.view_pager); mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int position) { tv.setText(mTitle[position]); } @Override public void onPageScrollStateChanged(int state) { } }); ImageView view1 = (ImageView) LayoutInflater.from(this).inflate(R.layout.list_item, null); view1.setBackgroundColor(Color.RED); view1.setImageResource(R.drawable.apple); mView.add(view1); ImageView view2 = (ImageView) LayoutInflater.from(this).inflate(R.layout.list_item, null); view2.setBackgroundColor(Color.GREEN); view2.setImageResource(R.drawable.banana); mView.add(view2); ImageView view3 = (ImageView) LayoutInflater.from(this).inflate(R.layout.list_item, null); view3.setBackgroundColor(Color.BLUE); view3.setImageResource(R.drawable.lychee); mView.add(view3); mViewPager.setAdapter(new MyAdapter(mView)); tv.setText(mTitle[0]); } }
编译之后效果如下:
通过左右滑动可以切换不同的页面,每个页面对应的一种水果类别,这样就通过 ViewPager 实现了一个简单的页面切换效果。
4. 小节
本节介绍了 ViewPager 的特点及使用场景,并讲解了 ViewPager 的专属适配器——PagerAdapter 的几个回调函数的使用方法,最后采用 ViewPager 实现了一个简单的例子用于切换不同的页面从而展示不同的类别。这一节中是直接采用 View 来承载每一个 Page,而在实际开发中大多数场景会采用 Fragemnt 来承载 Page,不过对于 ViewPager 的使用到同小异,针对 Fragment 系统提供了两种 Adapter:FragmentPageAdapter和FragmentStatePagerAdapter,在学完 Fragement之后大家可以自行修改本节的例子,通过 Fragment 来实现本例的效果。