ViewPager とビュー
ViewPager は、ジェスチャ ナビゲーションを実装できるレイアウト マネージャーです。 ジェスチャ ナビゲーションを使うと、ユーザーは左右にスワイプしてデータのページを順繰りに移動できます。 このガイドでは、データ ページとしてビューを使って、ViewPager と PagerTabStrip でスワイプ可能な UI を実装する方法について説明します (この後のガイドでは、ページにフラグメントを使う方法を説明します)。
概要
このガイドは、ViewPager
を使って落葉樹と常緑樹の画像ギャラリーを実装する方法の手順を見ていくチュートリアルです。 このアプリでは、ユーザーは "樹木カタログ" を左右にスワイプして、樹木の画像を表示します。 カタログの各ページの先頭では、樹木の名前が PagerTabStrip
に一覧表示され、樹木の画像が ImageView
に表示されています。 ViewPager
と基になるデータ モデルの間のインターフェイスには、アダプターが使われています。 このアプリでは、PagerAdapter
から派生したアダプターを実装します。
ViewPager
ベースのアプリは Fragment
で実装されることがよくありますが、Fragment
の余分な複雑さを不要としない、比較的シンプルなユース ケースがいくつかあります。 たとえば、このチュートリアルで示す基本的な画像ギャラリー アプリでは、Fragment
を使う必要はありません。 コンテンツは静的であり、ユーザーは異なる画像間をスワイプして前後に移動するだけなので、Android の標準のビューとレイアウトを使うことで、実装をシンプルに保つことができます。
アプリ プロジェクトを開始する
TreePager という新しい Android プロジェクトを作成します (新しい Android プロジェクトの作成について詳しくは、Hello, Android に関する記事をご覧ください)。 次に、NuGet パッケージ マネージャーを起動します。 (NuGet パッケージのインストールについて詳しくは、プロジェクトに NuGet を含めるチュートリアルに関する記事をご覧ください)。 Android Support Library v4 を見つけてインストールします。
これにより、Android Support Library v4 で必要な追加パッケージもインストールされます。
データ ソースの例を追加する
この例では、樹木カタログ データ ソース (TreeCatalog
クラスで表されます) によって ViewPager
に項目の内容が提供されます。
TreeCatalog
には、アダプターが View
の作成に使用する樹木の画像と樹木のタイトルの既製のコレクションが含まれています。 TreeCatalog
コンストラクターには引数は必要ありません。
TreeCatalog treeCatalog = new TreeCatalog();
TreeCatalog
内の画像のコレクションは、インデクサーで各画像にアクセスできるように編成されています。 たとえば、次のコード行は、コレクション内の 3 番目の画像の画像リソース ID を取得します。
int imageId = treeCatalog[2].imageId;
TreeCatalog
の実装の詳細は ViewPager
の理解には関係ないため、TreeCatalog
のコードはここでは示しません。
TreeCatalog
のソース コードは、TreeCatalog.cs にあります。
このソース ファイルをダウンロードして (または、コードをコピーして新しい 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
が定義されています。 ViewPager
はサポート ライブラリにパッケージ化されているため、完全修飾名 android.support.v4.view.ViewPager を使う必要があることに注意してください。 ViewPager
は Android Support Library 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
をインスタンス化します。
このコードをビルドして実行すると、次のスクリーンショットのように表示されるはずです。
この時点では、TreeCatalog のコンテンツにアクセスするためのアダプターがないため、ViewPager
は空です。 次のセクションでは、ViewPager
を TreeCatalog に接続するための PagerAdapter を作成します。
アダプターを作成する
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
の実装は比較的簡単です。それは、樹木カタログ内の樹木の数を返します。 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
が 2 回呼び出されて、最初の 2 ページにビューが設定されます。 ユーザーがスクロールすると、それが再び呼び出されて、現在表示されている項目のすぐ後ろと前のビューが維持されます。
DestroyItem を実装する
DestroyItem
メソッドは、指定された位置からページを削除します。 任意の特定の位置にあるビューが変更される可能性があるアプリでは、ViewPager
に、新しいビューに置き換える前に、その位置にある古いビューを削除する何らかの方法が必要です。 TreeCatalog
の例では、各位置のビューは変化しないため、DestroyItem
によって削除されたビューは、その位置に対して InstantiateItem
が呼び出されると、単純にもう一度追加されます。
(効率を高めるため、プールを実装して、同じ位置に再表示される 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
が、その同じ位置に対するアダプターのオブジェクトと関連付けられていることを確認します (そのため、アダプターのオブジェクトは "オブジェクト キー" と呼ばれます)。 比較的単純なアプリの場合、関連付けは ID の 1 つです。そのインスタンスでのアダプターのオブジェクト キーは、以前に InstantiateItem
から ViewPager
に返されたビューです。 一方、他のアプリの場合は、オブジェクト キーは、ViewPager
がその位置に表示する子ビューに関連付けられている (ただし、子ビューと同じではない) 他のアダプター固有のクラス インスタンスである場合があります。 渡されたビューとオブジェクト キーが関連付けられているかどうかは、アダプターだけが知っています。
PagerAdapter
が正常に機能するためには、IsViewFromObject
を実装する必要があります。 指定された位置に対して IsViewFromObject
が false
を返す場合、ViewPager
はその位置にビューを表示しません。 TreePager
アプリでは、InstantiateItem
によって返されるオブジェクト キーツリーのページ View
であるため、コードで ID を確認するだけで済みます (つまり、オブジェクト キーとビューは 1 つであり、同じです)。 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);
このコードは、コンテキストとして MainActivity
を渡して (this
)、TreePagerAdapter
をインスタンス化します。 インスタンス化された TreeCatalog
は、コンストラクターの 2 番目の引数に渡されます。 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
を追加する方法を説明する手順を含めました。