ViewPager とフラグメント

ViewPager は、ジェスチャ ナビゲーションを実装できるレイアウト マネージャーです。 ジェスチャ ナビゲーションを使うと、ユーザーは左右にスワイプしてデータのページを順繰りに移動できます。 このガイドでは、フラグメントをデータ ページとして使用し、ViewPager でスワイプ可能な UI を実装する方法について説明します。

概要

ViewPager は、多くの場合、フラグメントと組み合わせて使用されるため、ViewPager 内の各ページのライフサイクルを管理しやすくなります。 このチュートリアルでは、ViewPager を使用して、フラッシュ カードに一連の数学の問題を表示する FlashCardPager というアプリを作成します。 各フラッシュ カードはフラグメントとして実装されます。 ユーザーはフラッシュカードを左右にスワイプし、数学の問題をタップして解答を表示します。 このアプリは、フラッシュ カードごとに Fragment インスタンスを作成し、FragmentPagerAdapter から派生したアダプターを実装します。 Viewpager と Views では、ほとんどの作業は MainActivity ライフサイクル メソッドで実行されました。 FlashCardPager では、作業のほとんどは、Fragment によってライフサイクル メソッドの 1 つで実行されます。

このガイドではフラグメントの基本については説明しません。Xamarin.Android のフラグメントにまだ慣れていない場合は、フラグメントの使用を開始するために「フラグメント」を参照してください。

アプリ プロジェクトを開始する

FlashCardPager という新しい Android プロジェクトを作成します。 次に、NuGet パッケージ マネージャーを起動します (NuGet パッケージのインストールについて詳しくは、「チュートリアル: NuGet をプロジェクトに含める」を参照してください)。 Viewpager と Views の説明に従って、Xamarin.Android.Support.v4 パッケージを見つけてインストールします。

データ ソースの例を追加する

FlashCardPager において、データ ソースは FlashCardDeck クラスで表されるフラッシュ カードのデッキです。このデータ ソースは ViewPager に項目の内容を提供します。 FlashCardDeck には、数学の問題と解答の既製のコレクションが含まれています。 FlashCardDeck コンストラクターには引数は必要ありません。

FlashCardDeck flashCards = new FlashCardDeck();

FlashCardDeck 内のフラッシュ カードのコレクションは、インデクサーで各フラッシュ カードにアクセスできるように編成されています。 たとえば、次のコード行は、デッキ内の 4 番目のフラッシュ カード問題を取得します。

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 が定義されています。 ViewPager はサポート ライブラリにパッケージ化されているため、完全修飾名 android.support.v4.view.ViewPager を使う必要があることに注意してください。 ViewPagerAndroid Support Library v4 からのみ使用でき、Android SDK では使用できません。

ViewPager を設定する

MainActivity.cs を編集し、次の using ステートメントを追加します。

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

MainActivity クラスの宣言を、FragmentActivity から派生するように変更します。

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 を使用した FlashCardPager アプリのスクリーンショット

この時点では、ViewPager は空です。これは、ViewPager を設定するために使用されるフラグメントが不足しており、FlashCardDeck のデータからこれらのフラグメントを作成するためのアダプターも不足しているためです。

以降のセクションでは、各フラッシュ カードの機能を実装するために FlashCardFragment を作成し、FragmentPagerAdapter を作成して ViewPagerFlashCardDeck のデータから作成されたフラグメントに接続します。

フラグメントを作成する

各フラッシュ カードは、FlashCardFragment という UI フラグメントによって管理されます。 FlashCardFragment のビューには、1 つのフラッシュ カードに含まれる情報が表示されます。 FlashCardFragment の各インスタンスは ViewPager によってホストされます。 FlashCardFragment のビューは、フラッシュ カードの問題テキストを表示する TextView で構成されます。 このビューは、ユーザーがフラッシュ カードの問題をタップしたときに Toast を使用して解答を表示するイベント ハンドラーを実装します。

FlashCardFragment レイアウトを作成する

FlashCardFragment を実装する前に、そのレイアウトを定義する必要があります。 このレイアウトは、単一のフラグメント用のフラグメント コンテナー レイアウトです。 Resources/layoutflashcard_layout.axml という新しい Android レイアウトを追加します。 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 を呼び出し元に返します。 LayoutInflaterViewGroupOnCreateView に渡され、レイアウトをインフレートできるようになります。 savedInstanceState バンドルには、保存された状態から TextView を再作成するために OnCreateView が使用するデータが含まれています。

フラグメントのビューは、inflater.Inflate の呼び出しによって明示的にインフレートされます。 container 引数はビューの親であり、false フラグはインフレーターに、インフレートしたビューをビューの親に追加しないように指示します (このチュートリアルの後半で ViewPager がアダプターの GetItem メソッドを呼び出すときに追加されます)。

FlashCardFragment に状態コードを追加する

アクティビティと同様に、フラグメントには状態を保存および取得するために使用する Bundle があります。 FlashCardPager では、この Bundle は、関連付けられたフラッシュ カードの問題と解答のテキストを保存するために使用されます。 FlashCardFragment.cs で、FlashCardFragment クラス定義の先頭に次の Bundle キーを追加します。

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 の「アダプター」の記事の図を参照)。 ViewPager でこのデータにアクセスするには、PagerAdapter から派生したカスタム アダプターを指定する必要があります。 この例ではフラグメントを使用しているため、FragmentPagerAdapter を使用します。FragmentPagerAdapterPagerAdapter から派生しています。 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.csFlashCardDeckAdapter クラスの先頭に次のメンバー変数を追加します。

public FlashCardDeck flashCardDeck;

FlashCardDeckAdapter コンストラクターに次のコード行を追加します。

this.flashCardDeck = flashCards;

このコード行は、FlashCardDeckAdapter が使用する FlashCardDeck インスタンスを保存します。

Count を実装する

Count の実装は比較的単純です。フラッシュ カード デッキ内のフラッシュ カードの数を返します。 Count を次のコードに置き換えます。

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

FlashCardDeckNumCards プロパティは、データ セット内のフラッシュ カードの数 (フラグメントの数) を返します。

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 を作成して返します。

ViewPagerpositionFragment をレンダリングすると、フラッシュ カード デッキの position にある数学の問題の文字列を含む TextBox が表示されます。

アダプターを ViewPager に追加する

FlashCardDeckAdapter を実装したので、次にそれを ViewPager に追加します。 MainActivity.cs で、OnCreate メソッドの末尾に次のコード行を追加します。

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

このコードは、最初の引数に SupportFragmentManager を渡して、FlashCardDeckAdapter のインスタンスを作成します。 (FragmentActivity の SupportFragmentManager プロパティは、FragmentManager への参照を取得するために使用されます。FragmentManager の詳細については、「フラグメントの管理」を参照してください)。

これでコア実装は完了しました。アプリをビルドして実行します。 次のスクリーンショットの左側で示されているように、フラッシュ カード デッキの最初の画像が画面に表示されるはずです。 左にスワイプするとフラッシュ カードがさらに表示され、右にスワイプするとフラッシュ カード デッキ内を戻ります。

ポケットベル インジケーターのない FlashCardPager アプリのスクリーンショットの例

ページャー インジケーターを追加する

この最小限の 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/pager"
    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 が表示されます。

テキストなしの PagerTabStrip のクローズアップ

タイトルを表示する

各ページ タブにタイトルを追加するには、アダプターに GetPageTitleFormatted メソッドを実装します。 ViewPagerGetPageTitleFormatted (実装されている場合) を呼び出して、指定した位置にあるページを説明するタイトル文字列を取得します。 次のメソッドを、FlashCardDeckAdapter.csFlashCardDeckAdapter クラスに追加します。

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

このコードは、フラッシュ カード デッキ内の位置を問題番号に変換します。 結果の文字列は Java String に変換され、ViewPager に返されます。 この新しいメソッドを含むアプリを実行すると、各ページの PagerTabStrip に問題番号が表示されます。

各ページの上に問題番号が表示されている FlashCardPager のスクリーンショット

前後にスワイプすると、各フラッシュ カードの上部に表示されているフラッシュ カード デッキの問題番号を確認できます。

ユーザーによる入力を処理する

FlashCardPagerViewPager でフラグメントベースの一連のフラッシュ カードを表示しますが、まだ各問題の解答を表示する方法がありません。 このセクションでは、ユーザーがフラッシュ カードの問題テキストをタップしたときに解答を表示するためのイベント ハンドラーを FlashCardFragment に追加します。

FlashCardFragment.cs を開き、ビューが呼び出し元に返される直前の OnCreateView メソッドの末尾に次のコードを追加します。

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

この Click イベント ハンドラーは、ユーザーが TextBox をタップしたときに表示されるトーストに解答を表示します。 answer 変数は、OnCreateView に渡された Bundle から状態情報が読み取られたときに初期化されています。 アプリをビルドして実行し、各フラッシュ カードの問題テキストをタップして解答を確認します。

数学の問題がタップされたときの FlashCardPager アプリ トーストのスクリーンショット

このチュートリアルで紹介する FlashCardPager は、FragmentActivity から派生した MainActivity を使用しますが、AppCompatActivity (これもフラグメントの管理をサポートしています) から MainActivity を派生させることもできます。

まとめ

このチュートリアルでは、Fragment を使用した、基本的な ViewPager ベースのアプリを構築する方法の手順の例を示しました。 フラッシュ カードの問題と解答を含むサンプル データ ソース、フラッシュ カードを表示するための ViewPager レイアウト、および ViewPager をデータ ソースに接続する FragmentPagerAdapter サブクラスを紹介しました。 ユーザーがフラッシュ カード内を移動できるように、各ページの上部に問題番号を表示する PagerTabStrip を追加する方法を説明する手順を含めました。 最後に、ユーザーがフラッシュ カードの問題をタップしたときに解答を表示するイベント処理コードを追加しました。