带视图的 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:
这还将安装 Android 支持库 v4 要求的任何其他包。
添加示例数据源
在此示例中,树目录数据源(由 TreeCatalog
类表示)提供包含项内容的 ViewPager
。
TreeCatalog
包含一个现成集合,其中具有适配器将用于创建 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();
}
此代码执行以下操作:
设置 Main.axml 布局资源的视图。
从布局中检索对
ViewPager
的引用。将新的
TreeCatalog
实例化为数据源。
生成并运行此代码时,应会看到类似于以下屏幕截图的显示:
此时,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; }
}
TreeCatalog
的 NumTrees
属性返回数据集中的树数(页数)。
实现 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;
}
此代码执行以下操作:
实例化新的
ImageView
,以在指定位置显示树图像。 应用的MainActivity
是将传递给ImageView
构造函数的上下文。将
ImageView
资源设置为位于指定位置的TreeCatalog
图像资源 ID。将传递的容器
View
强制转换为ViewPager
引用。 请注意,必须使用JavaCast<ViewPager>()
来正确执行此强制转换(这是 Android 执行运行时检查类型转换所必需的)。将实例化的
ImageView
添加到ViewPager
并将ImageView
返回给调用方。
ViewPager
在 position
处显示图像时,它将显示此 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);
}
此代码执行以下操作:
将传递的容器
View
强制转换为ViewPager
引用。将传递的 Java 对象 (
view
) 强制转换为 C#View
(view as View
);从
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
将传递到构造函数的第二个参数中。 ViewPager
的 Adapter
属性设置为实例化的 TreePagerAdapter
对象;这会将 TreePagerAdapter
插入到 ViewPager
中。
核心实现现已完成 – 生成并运行应用。 你应该会看到屏幕上显示树目录的第一张图像,如下一个屏幕截图左侧所示。 向左轻扫可查看更多树视图,然后向右轻扫以在树目录中移动:
添加寻呼指示器
这种最小的 ViewPager
实现显树目录的图像,但它没有说明用户在目录中的位置。 下一步是添加 PagerTabStrip
。 PagerTabStrip
通过显示上一个和下一个页面的提示,通知用户显示的具体页面并提供导航上下文。 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>
ViewPager
和 PagerTabStrip
旨在配合使用。 在 ViewPager
布局内声明 PagerTabStrip
时,ViewPager
将自动查找 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
中显示树描述文字。 应该会在屏幕顶部看到没有下划线的树名称:
可来回轻扫以查看目录中每个带描述文字的树图像。
PagerTitleStrip 变体
PagerTitleStrip
与 PagerTabStrip
非常相似,但 PagerTabStrip
为当前选定的选项卡添加下划线。可以将 PagerTabStrip
替换为上述布局中的 PagerTitleStrip
,然后再次运行应用来查看其在 PagerTitleStrip
中的呈现效果:
请注意,转换为 PagerTitleStrip
时会删除下划线。
总结
本演练提供分步示例来说明如何在没有使用 Fragment
的情况下构建基本的基于 ViewPager
的应用。 它演示了一个示例数据源,其中包含图像和描述文字字符串、用于显示图像的 ViewPager
布局,以及将 ViewPager
连接到数据源的 PagerAdapter
子类。 为了帮助用户浏览数据集,提供了说明来介绍如何添加 PagerTabStrip
或 PagerTitleStrip
以在每个页面顶部显示图像描述文字。