带视图的 ViewPager

ViewPager 是一个布局管理器,可用于实现手势导航。 通过手势导航,用户可以向左和向右轻扫浏览数据页。 本指南介绍如何将视图用作数据页面,使用 ViewPager 和 PagerTabStrip 实现可轻扫 UI(后续指南将介绍如何对页面使用片段)。

概述

本指南是一个演练,提供了分步演示来说明如何使用 ViewPager 实现落叶树木和常绿树木的图像库。 在此应用中,用户通过“树目录”向左和向右轻扫以查看树图像。 在目录的每一页顶部,树的名称列在 PagerTabStrip 中,树的图像显示在 ImageView 中。 适配器用于将 ViewPager 连接到基础数据模型。 此应用实现一个派生自 PagerAdapter 的适配器。

虽然基于 ViewPager 的应用通常使用 Fragment 来实现,但在一些相对简单的用例中,Fragment 的额外复杂性是不必要的。 例如,本演练中演示的基本图像库应用不需要使用 Fragment。 由于内容是静态的,并且用户仅在不同图像之间来回轻扫,因此可使用标准 Android 视图和布局来保持实现更加简单。

启动应用项目

新建一个名为 TreePager 的 Android 项目(请参阅 Hello, Android 详细了解如何创建新的 Android 项目)。 接下来,启动 NuGet 包管理器。 (若要详细了解如何安装 NuGet 包,请参阅演练:在项目中包括 NuGet)。 查找并安装 Android 支持库 v4:

NuGet 包管理器中选择的支持 v4 NuGet 的屏幕截图

这还将安装 Android 支持库 v4 要求的任何其他包。

添加示例数据源

在此示例中,树目录数据源(由 TreeCatalog 类表示)提供包含项内容的 ViewPagerTreeCatalog 包含一个现成集合,其中具有适配器将用于创建 View 的树图像和树标题。 TreeCatalog 构造函数不需要任何参数:

TreeCatalog treeCatalog = new TreeCatalog();

TreeCatalog 中图像集合的组织方式使每张图像都能由索引器访问。 例如,以下代码行会检索集合中第三张图像的图像资源 ID:

int imageId = treeCatalog[2].imageId;

由于 TreeCatalog 的实现详细信息与理解 ViewPager 无关,因此此处未列出 TreeCatalog 代码。 可在 TreeCatalog.cs 找到用于 TreeCatalog 的源代码。 下载此源文件(或将代码复制粘贴到新的TreeCatalog.cs 文件中),并将其添加到项目中。 此外,将图像文件下载并解压缩到 Resources/drawable 文件夹中,并将其包含在项目中。

创建 ViewPager 布局

打开 Resources/layout/Main.axml,并将其内容替换为以下 XML:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.view.ViewPager
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/viewpager"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

</android.support.v4.view.ViewPager>

此 XML 定义占用整个屏幕的 ViewPager。 请注意,必须使用完全限定名称 android.support.v4.view.ViewPager,因为 ViewPager 打包在支持库中。 ViewPager 仅适用于 Android 支持库 v4;它在 Android SDK 中不可用。

设置 ViewPager

编辑 MainActivity.cs 并添加以下 using 语句:

using Android.Support.V4.View;

OnCreate 方法替换为以下代码:

protected override void OnCreate(Bundle bundle)
{
    base.OnCreate(bundle);
    SetContentView(Resource.Layout.Main);
    ViewPager viewPager = FindViewById<ViewPager>(Resource.Id.viewpager);
    TreeCatalog treeCatalog = new TreeCatalog();
}

此代码执行以下操作:

  1. 设置 Main.axml 布局资源的视图。

  2. 从布局中检索对 ViewPager 的引用。

  3. 将新的 TreeCatalog 实例化为数据源。

生成并运行此代码时,应会看到类似于以下屏幕截图的显示:

显示空 ViewPager 的应用屏幕截图

此时,ViewPager 为空,因为它缺少用于访问 TreeCatalog 中的内容的适配器。 在下一部分中,会创建一个 PagerAdapter 来将 ViewPager 连接到 TreeCatalog。

创建适配器

ViewPager 使用位于 ViewPager 和数据源之间的适配器控制器对象(请参阅适配器中的插图)。 若要访问此数据,ViewPager 需要提供派生自 PagerAdapter 的自定义适配器。 此适配器使用数据源中的内容填充每个 ViewPager 页面。 由于此数据源特定于应用,因此自定义适配器是了解如何访问数据的代码。 当用户轻扫 ViewPager 的页面时,适配器会从数据源中提取信息,并将它加载到页面中,以便 ViewPager 显示。

实现 PagerAdapter 时,必须替换以下内容:

  • InstantiateItem - 为给定位置创建页面 (View),并将其添加到 ViewPager 的视图集合中。

  • DestroyItem - 从给定位置删除页面。

  • Count – 只读属性,该属性返回可用的视图数(页面)。

  • IsViewFromObject - 确定页面是否与特定键对象相关联。 (此对象由 InstantiateItem 方法创建。)在此示例中,键对象是 TreeCatalog 数据对象。

添加一个名为 TreePagerAdapter.cs 的新文件,并将其内容替换为以下代码:

using System;
using Android.App;
using Android.Runtime;
using Android.Content;
using Android.Views;
using Android.Widget;
using Android.Support.V4.View;
using Java.Lang;

namespace TreePager
{
    class TreePagerAdapter : PagerAdapter
    {
        public override int Count
        {
            get { throw new NotImplementedException(); }
        }

        public override bool IsViewFromObject(View view, Java.Lang.Object obj)
        {
            throw new NotImplementedException();
        }

        public override Java.Lang.Object InstantiateItem (View container, int position)
        {
            throw new NotImplementedException();
        }

        public override void DestroyItem(View container, int position, Java.Lang.Object view)
        {
            throw new NotImplementedException();
        }
    }
}

此代码会存根基本 PagerAdapter 实现。 在以下部分中,每个方法都替换为工作代码。

实现构造函数

当应用实例化 TreePagerAdapter 时,它提供上下文 (MainActivity) 和实例化的 TreeCatalog。 将以下成员变量和构造函数添加到 TreePagerAdapter.cs 中 TreePagerAdapter 类的顶部:

Context context;
TreeCatalog treeCatalog;

public TreePagerAdapter (Context context, TreeCatalog treeCatalog)
{
    this.context = context;
    this.treeCatalog = treeCatalog;
}

此构造函数的目的是存储 TreePagerAdapter 要使用的上下文和 TreeCatalog 实例。

实现计数

Count 实现相对简单:它返回树目录中的树数。 将 Count 替换为以下代码:

public override int Count
{
    get { return treeCatalog.NumTrees; }
}

TreeCatalogNumTrees 属性返回数据集中的树数(页数)。

实现 InstantiateItem

InstantiateItem 方法为给定位置创建页面。 它还必须将新创建的视图添加到 ViewPager 的视图集合。 为了实现这一点,ViewPager 将自身作为容器参数传递。

InstantiateItem 方法替换为以下代码:

public override Java.Lang.Object InstantiateItem (View container, int position)
{
    var imageView = new ImageView (context);
    imageView.SetImageResource (treeCatalog[position].imageId);
    var viewPager = container.JavaCast<ViewPager>();
    viewPager.AddView (imageView);
    return imageView;
}

此代码执行以下操作:

  1. 实例化新的 ImageView,以在指定位置显示树图像。 应用的 MainActivity 是将传递给 ImageView 构造函数的上下文。

  2. ImageView 资源设置为位于指定位置的 TreeCatalog 图像资源 ID。

  3. 将传递的容器 View 强制转换为 ViewPager 引用。 请注意,必须使用 JavaCast<ViewPager>() 来正确执行此强制转换(这是 Android 执行运行时检查类型转换所必需的)。

  4. 将实例化的 ImageView 添加到 ViewPager 并将 ImageView 返回给调用方。

ViewPagerposition 处显示图像时,它将显示此 ImageView。 最初,会调用两次 InstantiateItem,使用视图填充前两个页面。 当用户滚动时,将再次调用它来维护当前显示的项前面和后面的视图。

实现 DestroyItem

DestroyItem 方法从给定位置删除页面。 在任何给定位置的视图都可更改的应用中,ViewPager 必须先通过某种方式删除该位置的过时视图,然后再将其替换为新视图。 在 TreeCatalog 示例中,每个位置的视图不会更改,因此只需在为该位置调用InstantiateItem 时重新添加 DestroyItem 删除的视图。 (为了提高效率,可实现一个池来回收 View,它将在同一位置重新显示。)

DestroyItem 方法替换为以下代码:

public override void DestroyItem(View container, int position, Java.Lang.Object view)
{
    var viewPager = container.JavaCast<ViewPager>();
    viewPager.RemoveView(view as View);
}

此代码执行以下操作:

  1. 将传递的容器 View 强制转换为 ViewPager 引用。

  2. 将传递的 Java 对象 (view) 强制转换为 C# View (view as View);

  3. ViewPager 中删除视图。

实现 IsViewFromObject

当用户向左和向右滑动浏览内容页面时,ViewPager 会调用 IsViewFromObject 来验证给定位置的子级 View 是否与适配器在该相同位置的对象关联(因此,适配器的对象称为对象键)。 对于相对简单的应用,关联是标识之一 - 适配器在该实例处的对象键是以前通过 InstantiateItem 返回到 ViewPager 的视图。 但是对于其他应用,对象键可能是与 ViewPager 在该位置显示的子视图关联(但与该视图不同)的其他一些特定于适配器的类实例。 只有适配器知道传递的视图和对象键是否关联。

要使 PagerAdapter 正常运行,必须实现 IsViewFromObject。 如果 IsViewFromObject 为给定位置返回 false,则 ViewPager 不显示该位置的视图。 在TreePager应用中,返回InstantiateItem的对象键是树的页View,因此代码只需要检查标识(即对象键和视图是一个和相同的)。 将 IsViewFromObject 替换为以下代码:

public override bool IsViewFromObject(View view, Java.Lang.Object obj)
{
    return view == obj;
}

将适配器添加到 ViewPager

实现 TreePagerAdapter 后,即可将其添加到 ViewPager。 在 MainActivity.cs 中,将以下代码行添加到 OnCreate 方法的末尾:

viewPager.Adapter = new TreePagerAdapter(this, treeCatalog);

此代码会实例化 TreePagerAdapter,将 MainActivity 作为上下文 (this) 传入。 实例化的 TreeCatalog 将传递到构造函数的第二个参数中。 ViewPagerAdapter 属性设置为实例化的 TreePagerAdapter 对象;这会将 TreePagerAdapter 插入到 ViewPager 中。

核心实现现已完成 – 生成并运行应用。 你应该会看到屏幕上显示树目录的第一张图像,如下一个屏幕截图左侧所示。 向左轻扫可查看更多树视图,然后向右轻扫以在树目录中移动:

TreePager 应用的屏幕截图,轻扫树的图像

添加寻呼指示器

这种最小的 ViewPager 实现显树目录的图像,但它没有说明用户在目录中的位置。 下一步是添加 PagerTabStripPagerTabStrip 通过显示上一个和下一个页面的提示,通知用户显示的具体页面并提供导航上下文。 PagerTabStrip 旨在用作 ViewPager 的当前页面的指示器;当用户轻扫每页时,它会滚动和更新。

打开 Resources/layout/Main.axml,并向布局添加 PagerTabStrip

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.view.ViewPager
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/viewpager"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <android.support.v4.view.PagerTabStrip
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:layout_gravity="top"
          android:paddingBottom="10dp"
          android:paddingTop="10dp"
          android:textColor="#fff" />

</android.support.v4.view.ViewPager>

ViewPagerPagerTabStrip 旨在配合使用。 在 ViewPager 布局内声明 PagerTabStrip 时,ViewPager 将自动查找 PagerTabStrip 并将其连接到适配器。 构建并运行应用时,应该会看到每个屏幕顶部显示的空的 PagerTabStrip

空 PagerTabStrip 的特写屏幕截图

显示标题

若要向每个页面选项卡添加标题,请在 PagerAdapter 派生的类中实现 GetPageTitleFormatted 方法。 ViewPager 调用 GetPageTitleFormatted(如果已实现),以获取描述位于指定位置的页面的标题字符串。 将以下方法添加到 TreePagerAdapter.cs 中的 TreePagerAdapter 类:

public override Java.Lang.ICharSequence GetPageTitleFormatted(int position)
{
    return new Java.Lang.String(treeCatalog[position].caption);
}

此代码从树目录中的指定页面(位置)检索树描述文字字符串,将其转换为 Java String,并将其返回到 ViewPager。 使用这个新方法运行应用时,每个页面都会在 PagerTabStrip 中显示树描述文字。 应该会在屏幕顶部看到没有下划线的树名称:

包含文本填充的 PagerTabStrip 选项卡的页面屏幕截图

可来回轻扫以查看目录中每个带描述文字的树图像。

PagerTitleStrip 变体

PagerTitleStripPagerTabStrip 非常相似,但 PagerTabStrip 为当前选定的选项卡添加下划线。可以将 PagerTabStrip 替换为上述布局中的 PagerTitleStrip,然后再次运行应用来查看其在 PagerTitleStrip 中的呈现效果:

从文本中移除下划线的 PagerTitleStrip

请注意,转换为 PagerTitleStrip 时会删除下划线。

总结

本演练提供分步示例来说明如何在没有使用 Fragment 的情况下构建基本的基于 ViewPager 的应用。 它演示了一个示例数据源,其中包含图像和描述文字字符串、用于显示图像的 ViewPager布局,以及将 ViewPager 连接到数据源的 PagerAdapter 子类。 为了帮助用户浏览数据集,提供了说明来介绍如何添加 PagerTabStripPagerTitleStrip 以在每个页面顶部显示图像描述文字。