全部开发者教程

Android 入门教程

菜单类控件
菜单:Menu
并发编程
多线程

视频页面:ViewPager

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("Imooc Android");
    container.addView(itemView);

    return itemView;
 }
  • public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object):
    根据传入的 position 移除一个 Page
public void destroyItem(ViewGroup container, int position, Object object) {
     container.removeView((View)object);
}
  • gpublic int getCount():
    返回当前 ViewPager 中可用 View 的数量
public 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;
}

常用的一般就是以上四种回调方法,理解起来都比较简单,其中要注意的是getCountisViewFromObject这两个是必须实现的,而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:FragmentPageAdapterFragmentStatePagerAdapter,在学完 Fragement之后大家可以自行修改本节的例子,通过 Fragment 来实现本例的效果。