[译]:Xamarin.Android用户界面——ViewPager与Fragment组合使用

标签: Xamarin.Android, 官方教程, 中文翻译

博客分类: 官方教程

返回索引目录
原文链接:Part 2 - ViewPager with Fragments
译文链接:Xamarin.Android用户界面——ViewPager与Fragment组合使用

Part 2 - ViewPager with Fragments

ViewPager是一个布局管理器,以此实现手势导航。手势导航可以允许用户左右滑动来逐步浏览数据页面。本指南将介绍如何使用ViewPager来实现可滑动的UI —— 使用Fragment作为数据页。

概览

ViewPager通常和片段(fragment)组合使用,以便于更容易地管理ViewPager中的每个页面的生命周期。在本文中,我们创建一个名为FlashCardPager的应用来使用ViewPager,它将会在学习卡片上呈现一系列的数学问题。每个学习卡片都实现为片段。用户通过左右滑动学习卡片,然后点击数学问题来显示其答案。此应用将为每个学习卡片创建一个Fragment实例,并从FragmentPagerAdapter派生实现一个适配器。在第一部分(原文译文)中,大部分工作都是在MainActivity生命周期方法中完成的。在FlashCardPager示例中,大部分工作将由其生命周期方法中的一个Fragment来完成。

本指南将不包含片段的基础知识 —— 如果你还不熟悉Xamarin.Android中的片段,请参阅Fragments以此帮助你入门片段的使用。学习地址:原文:Fragments

创建应用项目

创建一个名为FlashCardPager的新的Android项目。然后,启动NuGet包管理器。关于安装NuGet软件包的更多信息,参阅:原文:Walkthrough: Including a NuGet in your project。最后,查找并安装Xamarin.Android.Support.v4包,如第一部分(原文译文)所述。

添加示例数据

在FlashCardPager示例中,数据源是由FlashCardDeck类表示的一叠学习卡片;此数据源为ViewPager提供项目内容。FlashCardDeck包含一个预制的数学问题和答案的集合。FlashCardDeck构造函数不需要参数:

FlashCardDeck flashCards = new FlashCardDeck();

FlashCardDeck中的学习卡片集合组织为可以由索引器访问每个学习卡片的集合。例如,如下代码行查找卡片中的第四个学习卡片里的问题:

string problem = flashCardDeck[3].Problem;

下面这行代码查找上面问题的相应答案:

string answer = flashCardDeck[3].Answer;

由于FlashCardDeck的具体实现与理解ViewPager无关,故此处将不列出FlashCardDeck代码。FlashCardDeck源代码见:FlashCardDeck.cs。下载此源文件,并将其添加到项目中(或者将 代码复制并粘贴到新的FlashCardDeck.cs文件中)。

创建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 Support Library v4中获取;它在Android SDK中不可用。

设置ViewPager

编辑MainActivity.cs并添加以下using声明:

using Android.Support.V4.View;
using Android.Support.V4.App;

更改MainActivity类的声明,使其从FragmentActivity派生:

原文为从AppCompatActivity派生,与下面代码不匹配,此处处理为从FragmentActivity派生,关于从AppCompatActivity派生内容,见文末示例。

public class MainActivity : FragmentActivity

MainActivity派生 自FragmentActivity(而不是Activity),因为FragmentActivity知道如何管理片段的支持。使用下面的代码替换OnCreate方法:

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

此代码执行了如下操作:

  1. 设置视图对应为Main.axml布局资源。
  2. 从布局中查找ViewPager引用。
  3. 实例化一个新的FlashCardDeck,以便作为数据源。

当你生成并运行此代码时,你可以看到如下图所示的界面:

此时,ViewPager是空的,因为它缺少用于填充ViewPager的片段,并且还缺少一个创建片段(从FlashCardDeck中的数据)的适配器。

在下面的部分中,将会创建一个FlashCardFragment来为每个学习卡片实现功能,并且还创建一个FragmentPagerAdapter来连接ViewPager和片段(以FlashCardDeck中的数据创建片段)。

创建片段(Fragment)

每个学习卡片都将由称为FlashCardFragment的UI片段来管理。FlashCardFragment的视图将显示单个学习卡片中包含的信息。每个FlashCardFragment的实例都将由ViewPager托管。FlashCardFragment的视图将会包含一个显示学习卡片问题文本的TextView。此视图将实现一个事件处理程序 —— 当用户点击学习卡片问题时,将会用Toast显示其答案。

创建FlashCardFragment布局

在实现FlashCardFragment之前,必须先定义布局。此布局是一个单独片段的片段容器布局。添加一个名为flashcard_layout.axml的新的Android布局到Resources/layout目录下。打开Resources/layout/flashcard_layout.axml文件,并使用以下代码替换其内容:

<?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">
    <TextView
        android:id="@+id/flash_card_question"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:textAppearance="@android:style/TextAppearance.Large"
        android:textSize="100sp"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:text="Question goes here" />
</RelativeLayout>

此布局定义了一个单独的学习卡片片段;每个片段是由一个利用大号(100sp)字体显示数学问题的TextView组成。学习卡片上的文本是垂直水平居中的。

初步创建FlashCardFragment类

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

using System;
using Android.OS;
using Android.Views;
using Android.Widget;
using Android.Support.V4.App;

namespace FlashCardPager
{
    public class FlashCardFragment : Android.Support.V4.App.Fragment
    {
        public FlashCardFragment() { }

        public static FlashCardFragment newInstance(String question, String answer)
        {
            FlashCardFragment fragment = new FlashCardFragment();
            return fragment;
        }
        public override View OnCreateView (
            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
        {
            View view = inflater.Inflate (Resource.Layout.flashcard_layout, container, false);
            TextView questionBox = (TextView)view.FindViewById (Resource.Id.flash_card_question);
            return view;
        }
    }
}

此代码作用是进行基本的Fragment定义,并以此显示学习卡片。注意,FlashCardFragment派生自Android.Support.V4.App.Fragment中定义的Fragment版本。此处构造 函数为空,因此newInstance工厂方法将用于创建一个新的FlashCardFragment,以此替换构造函数创建。

OnCreateView生命周期方法中创建并配置TextView。它用TextView填充了布局,并将填充的TextView返回给调用者。LayoutInflater和ViewGroup以参数形式传递到OnCreateView,以便于填充布局。savedInstanceState参数包含了OnCreateView中用于为保存状态重建TextView的数据。

片段的视图通过调用inflater.Inflate来显式填充。container参数是此视图的父视图,并且false标志通知inflater避免将填充的视图添加到视图的父级(在本文后面,当ViewPager调用适配器的GetItem方法时,它将被添加)。

将状态代码添加到FlashCardFragment

如一个Activity,一个片段有一个保存并检查状态的Bundle。在FlashCardPager中,Bundle是用于保存相关学习卡片上的问题和答案文本。在FlashCardFragment.cs中,将以下Bundle键添加到FlashCardFragment类定义的顶部:

private static string FLASH_CARD_QUESTION = "card_question";
private static string FLASH_CARD_ANSWER = "card_answer";

修改newInstance工厂方法,以便于创建一个Bundle对象,并在其实例化之后,使用上面的键来将传递进来的问题和答案文本存储到片段中:

public static FlashCardFragment newInstance(String question, String answer)
{
    FlashCardFragment fragment = new FlashCardFragment();

    Bundle args = new Bundle();
    args.PutString(FLASH_CARD_QUESTION, question);
    args.PutString(FLASH_CARD_ANSWER, answer);
    fragment.Arguments = args;

    return fragment;
}

修改片段生命周期方法OnCreateView,从传入的Bundle中查找信息,并将问题文本加载到TextBox中:

public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
    string question = Arguments.GetString(FLASH_CARD_QUESTION, "");
    string answer = Arguments.GetString(FLASH_CARD_ANSWER, "");

    View view = inflater.Inflate(Resource.Layout.flashcard_layout, container, false);
    TextView questionBox = (TextView)view.FindViewById(Resource.Id.flash_card_question);
    questionBox.Text = question;

    return view;
}

answer变量不在此使用,但在将事件处理程序代码添加到文件中时,将会使用它。

创建适配器

ViewPager使用位于ViewPager和数据源之间的适配器控制对象(相关内容见ViewPager适配器文章内插图:Adapter)。为了访问数据,ViewPager要求你提供派生自PagerAdapter的自定义适配器。由于此示例使用片段,将会使用FragmentPagerAdapter —— FragmentPagerAdapter是派生自PagerAdapter。FragmentPagerAdapter会将每一个页面表示为Fragment,只要用户可以返回到页面,对应片段就会一直保留在片段管理器中。当用户滑动ViewPager中的页面时,FragmentPagerAdapter会从数据源中提取信息,并为ViewPager创建Fragment来显示内容。

当实现FragmentPagerAdapter时,必须重写以下内容:

  • Count —— 只读属性,用于返回可用视图(页面)的数量
  • GetItem —— 返回指定页面待显示的片段。

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

using System;
using Android.Views;
using Android.Widget;
using Android.Support.V4.App;

namespace FlashCardPager
{
    class FlashCardDeckAdapter : FragmentPagerAdapter
    {
        public FlashCardDeckAdapter (Android.Support.V4.App.FragmentManager fm, FlashCardDeck flashCards)
            : base(fm)
        {
        }

        public override int Count
        {
            get { throw new NotImplementedException(); }
        }

        public override Android.Support.V4.App.Fragment GetItem(int position)
        {
            throw new NotImplementedException();
        }
    }
}

此代码是FragmentPagerAdapter的基本实现。在后面的小节中,将会用工作代码替换他们每一个方法。构造函数的目的是将片段管理器 传递给FlashCardDeckAdapter的基类构造函数。

实现适配器构造函数

当应用实例化FlashCardDeckAdapter时,它会提供一个片段管理器的引用和一个实例化的FlashCardDeck。将以下成员变量添加到FlashCardDeckAdapter.cs中的FlashCardDeckAdapter类的顶部:

public FlashCardDeck flashCardDeck;

将以下代码行添加到FlashCardDeckAdapter的构造函数中:

this.flashCardDeck = flashCards;

这行代码存储了FlashCardDeckAdapter要使用的FlashCardDeck实例。

实现Count属性

Count属性实现相对简单:它返回学习卡片中的学习卡片数量。使用以下代码替换Count:

public override int Count 
{ 
    get { return flashCardDeck.NumCards; } 
}

FlashCardDeck的NumCards属性返回数据集中学习卡片的数量(片段数量)。

实现GetItem方法

GetItem方法返回与给定位置相关的片段。当在学习卡片组中的位置上调用GetItem时,它返回一个FlashCardFragment来在该位置上配置显示学习卡片的问题。使用以下代码替换GetItem方法:

public override Android.Support.V4.App.Fragment GetItem(int position)
{
    return (Android.Support.V4.App.Fragment)
        FlashCardFragment.newInstance (
            flashCardDeck[position].Problem, flashCardDeck[position].Answer);
}

此代码进行了如下操作:

  1. 在FlashCardDeck卡片组中查找指定位置上的数学问题字符串。
  2. 在FlashCardDeck卡片组中查找指定位置上的答案。
  3. 调用FlashCardFragment的工厂方法newInstance,并传入学习卡片上的问题和答案字符串。
  4. 创建并返回一个新的学习卡片Fragment,其中包含该位置上的问题和答案文本。

当ViewPager在某位置上提供Fragment时,它会展示一个包含学习卡片组中相应位置数学问题字符串的TextBox。

将适配器添加到ViewPager

现在上面已经实现了FlashCardDeckAdapter,下面将它添加到ViewPager中。在MainActivity.cs中,将以下代码行添加到OnCreate方法的尾部:

FlashCardDeckAdapter adapter = 
    new FlashCardDeckAdapter(SupportFragmentManager, flashCards);
viewPager.Adapter = adapter;

此代码实例化FlashCardDeckAdapter,并将SupportFragmentManager作为第一个参数传入。(FragmentActivity的SupportFragmentManager属性是用于获取FragmentManager引用的 —— 关于FragmentManager的更多信息,见:原文:Managing Fragments

至此,核心实现已经完成,生成并运行应用。你可以看到学习卡片组中的第一个图像呈现在屏幕上,如左下图所示。向左滑动可以查看更多的学习卡片,然后向右滑动就可以通过学习卡片组返回到之前的内容:

添加页面指示器

ViewPager最简实现显示了卡片组中的每个学习卡片,但是它不提供用户在卡片组中的哪个位置的标志。下一步我们添加一个PagerTabStrip。此PagerTabStrip会告诉用户在显示哪个问题的编号,并且通过显示上一张和下一张学习卡片的提示信息来提供上下文导航。

打开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>

当你生成并运行程序时,你可以看到一个空的PagerTabStrip显示在每个学习卡片的顶部:

显示标题

要向每个选项卡上添加标题,需要在适配器中实现GetPageTitleFormatted方法。ViewPager会调用GetPageTitleFormatted(如果实现了)来获取指定位置页面描述的标题字符串。在FlashCardDeckAdapter.cs中的FlashCardDeckAdapter类里添加以下方法:

public override Java.Lang.ICharSequence GetPageTitleFormatted(int position)
{
    return new Java.Lang.String("Problem " + (position + 1));
}

此代码将学习卡片组中指定位置转换为问题编号。结果字符串会转化为Java的String来返回给ViewPager。当你运行含有此方法的应用时,每个页面将会在PagerTabStrip中显示问题编号:

你可以来回滑动来查看学习卡片组中每个学习卡片顶部的问题编号。

处理用户输入

FlashCardPager在ViewPager中提供了一系列的基础片段学习卡片,但它没有显示每个问题答案的方法。在本小节,将会为FlashCardFragment添加一个事件处理程序 —— 当用户点击学习卡片问题时,显示答案。

打开FlashCardFragment.cs,并将以下代码添加到OnCreateView方法的末尾 —— 在将视图返回给调用者之前:

questionBox.Click += delegate
{
    Toast.MakeText(Activity.ApplicationContext,
            "Answer: " + answer, ToastLength.Short).Show();
};

此Click事件处理程序会在用户点击TextBox时,通过Toast显示答案。当从传递给OnCreateView的Bundle中读取状态信息时,answer变量在此之前已经初始化。生成并运行应用,然后点击每一个学习卡片上的问题文本来查看答案:

在本文操作步骤中介绍的FlashCardPager使用派生自FragmentActivity的MainActivity,但是你也可以让MainActivity派生自AppCompatActivity(它同样提供片段管理的支持)。关于AppCompatActivity的示例,查看示例库中的官方示例:FlashCardPager个人练习示例:FlashCardPagerWithAppCompat

关于从AppCompatActivity派生,则需要额外添加NuGet包引用,即添加Xamarin.Android.Support.v7.AppCompat包引用,然后将FragmentActivity替换为AppCompatActivity。另外,还需要修改一下主题,即见AndroidManifest.xml中的主题设置,需来自AppCompat。

总结

本文演示提供了如何利用Fragment创建一个基本的基于ViewPager的应用的逐步操作示例。它提供一个包含了学习卡片问题及答案的示例数据源,一个显示学习卡片的ViewPager布局,以及一个连接ViewPager和数据源的FragmentPagerAdapter子类。为了帮助用户浏览学习卡片,本文包含了如何添加一个PagerTabStrip来在每个页面顶部显示问题编号。最后,当用户点击学习卡片上的问题时,添加了事件处理程序来显示答案。


译:奇葩史

没有评论