Xamarin.Android ListView へのデータの入力

ListView に行を追加するには、行をレイアウトに追加し、ListView 呼び出し自体を設定するメソッドを使用して、IListAdapter を実装する必要があります。 Android には、カスタム レイアウト XML またはコードを定義せずに使用できる組み込みの ListActivity クラスと ArrayAdapter クラスがあります。 ListActivity クラスは、ListView を自動的に作成し、アダプターを介して表示する行ビューを指定する ListAdapter プロパティを公開します。

組み込みアダプターは、各行に使用されるパラメーターとしてビュー リソース ID を受け取ります。 Android.Resource.Layout にあるような組み込みのリソースを使うことができるため、独自のリソースを記述する必要はありません。

ListActivity および ArrayAdapter <文字列>の使用

BasicTable/HomeScreen.cs の例では、これらのクラスを使用して、わずか数行のコードのみで ListView を表示する方法について説明します。

[Activity(Label = "BasicTable", MainLauncher = true, Icon = "@drawable/icon")]
public class HomeScreen : ListActivity {
   string[] items;
   protected override void OnCreate(Bundle bundle)
   {
       base.OnCreate(bundle);
       items = new string[] { "Vegetables","Fruits","Flower Buds","Legumes","Bulbs","Tubers" };
       ListAdapter = new ArrayAdapter<String>(this, Android.Resource.Layout.SimpleListItem1, items);
   }
}

行クリックの処理

通常、ListView では、ユーザーが行をタッチして何らかのアクション (曲の再生、連絡先の呼び出し、別の画面の表示など) を実行することもできます。 ユーザーのタッチに応答するには、次に示すように、もう 1 つのメソッドが ListActivityOnListItemClick の間に実装されている必要があります。

Screenshot of a SimpleListItem

protected override void OnListItemClick(ListView l, View v, int position, long id)
{
   var t = items[position];
   Android.Widget.Toast.MakeText(this, t, Android.Widget.ToastLength.Short).Show();
}

これで、ユーザーが行にタッチすると Toast アラートが表示されます。

Screenshot of Toast that appears when a row is touched

ListAdapter の実装

ArrayAdapter<string> は、そのシンプルさゆえに素晴らしいものですが、非常に限定的です。 ただし、多くの場合、バインドするのは文字列だけでなく、ビジネス エンティティのコレクションもあります。 たとえば、データが Employee クラスのコレクションで構成されている場合に、リストに各従業員の名前のみ表示する必要があるとします。 表示されるデータを制御する ListView の動作をカスタマイズするには、次の 4 つの項目をオーバーライドする BaseAdapter のサブクラスを実装する必要があります。

  • Count – データ内の行数をコントロールに伝達します。

  • GetView – 各行のビューを返すために、データが設定されます。 このメソッドには ListView のパラメーターがあり、再利用のために既存の未使用の行を渡すことができます。

  • GetItemId – 行の識別子を返します (通常は行番号ですが、任意の長い値を指定できます)。

  • this[int] インデクサー – 特定の行番号に関連付けられているデータを返します。

BasicTableAdapter/HomeScreenAdapter.cs のコード例は、BaseAdapter をサブクラス化する方法を示しています。

public class HomeScreenAdapter : BaseAdapter<string> {
   string[] items;
   Activity context;
   public HomeScreenAdapter(Activity context, string[] items) : base() {
       this.context = context;
       this.items = items;
   }
   public override long GetItemId(int position)
  {
       return position;
   }
   public override string this[int position] {  
       get { return items[position]; }
   }
   public override int Count {
       get { return items.Length; }
   }
   public override View GetView(int position, View convertView, ViewGroup parent)
   {
       View view = convertView; // re-use an existing view, if one is available
      if (view == null) // otherwise create a new one
           view = context.LayoutInflater.Inflate(Android.Resource.Layout.SimpleListItem1, null);
       view.FindViewById<TextView>(Android.Resource.Id.Text1).Text = items[position];
       return view;
   }
}

カスタム アダプターの使用

カスタム アダプターの使用は、表示する値の context および string[] を渡す、組み込みの ArrayAdapter と似ています。

ListAdapter = new HomeScreenAdapter(this, items);

この例では同じ行レイアウト (SimpleListItem1) を使用するため、結果のアプリケーションは前の例と同じように見えます。

行表示の再利用

この例では、6 つの項目しかありません。 画面には 8 行が収まるため、行の再利用は必要ありません。 ただし、数百行や数千行を表示する場合は、一度に 8 行しか画面に収まらない場合に、数百または数千の View オブジェクトを作成するメモリの無駄になります。 この状況を回避するために、行が画面から消えたときに、そのビューは再利用のためにキューに配置されます。 ユーザーがスクロールすると、ListViewGetView を呼び出して、表示する新しいビューを要求します。使用可能な場合は、convertView パラメーターに未使用のビューを渡します。 この値が null の場合、コードは新しいビュー インスタンスを作成する必要があります。それ以外の場合は、そのオブジェクトのプロパティを再設定して再利用できます。

GetView メソッドは、このパターンに従って次の行ビューを再利用する必要があります。

public override View GetView(int position, View convertView, ViewGroup parent)
{
   View view = convertView; // re-use an existing view, if one is supplied
   if (view == null) // otherwise create a new one
       view = context.LayoutInflater.Inflate(Android.Resource.Layout.SimpleListItem1, null);
   // set view properties to reflect data for the given row
   view.FindViewById<TextView>(Android.Resource.Id.Text1).Text = items[position];
   // return the view, populated with data, for display
   return view;
}

カスタム アダプターの実装では、長いリストを表示するときにメモリが不足しないように、新しいビューを作成する前に、常にconvertView オブジェクトを再利用する必要があります。

一部のアダプター実装 (CursorAdapter など) には GetView メソッドがないため、NewViewBindView の 2 つの異なるメソッドが必要であり、GetView の役割を 2 つのメソッドに分離することで強制的に行が再利用されます。 ドキュメントの後半部分に CursorAdapter の例があります。

高速スクロールの有効化

高速スクロールは、リストの一部に直接アクセスするためのスクロール バーとして機能する追加の "ハンドル" を提供することで、ユーザーが長いリストをスクロールするのに役立ちます。 このスクリーンショットは、高速スクロール ハンドルを示しています。

Screenshot of fast-scrolling with a scroll handle

高速スクロール ハンドルの表示は、FastScrollEnabled プロパティを true に設定するのと同じくらい簡単です。

ListView.FastScrollEnabled = true;

セクション インデックスの追加

セクション インデックスは、ユーザーが長いリストを高速スクロールしているときに、どの 'セクション' までスクロールしたかを示します。 セクション インデックスを表示するには、アダプター サブクラスで ISectionIndexer インターフェイスを実装し、表示される行に応じてインデックス テキストを提供する必要があります。

Screenshot of H appearing above section that starts with H

ISectionIndexer を実装するには、アダプターに次の 3 つのメソッドを追加する必要があります。

  • GetSections – 表示できるセクション インデックス タイトルの完全な一覧が提供されます。 このメソッドには Java オブジェクトの配列が必要であるため、コードは .NET コレクションから Java.Lang.Object[] を作成する必要があります。 この例では、リスト内の最初の文字のリストを Java.Lang.String として返します。

  • GetPositionForSection – 特定のセクション インデックスの最初の行の位置を返します。

  • GetSectionForPosition – 特定の行に表示されるセクション インデックスを返します。

例の SectionIndex/HomeScreenAdapter.cs ファイルでは、これらのメソッドと、コンストラクター内のいくつかの追加コードが実装されています。 コンストラクターは、すべての行をループし、タイトルの最初の文字を抽出することによってセクション インデックスを構築します (これを機能させるには、項目が既に並べ替えられている必要があります)。

alphaIndex = new Dictionary<string, int>();
for (int i = 0; i < items.Length; i++) { // loop through items
   var key = items[i][0].ToString();
   if (!alphaIndex.ContainsKey(key))
       alphaIndex.Add(key, i); // add each 'new' letter to the index
}
sections = new string[alphaIndex.Keys.Count];
alphaIndex.Keys.CopyTo(sections, 0); // convert letters list to string[]

// Interface requires a Java.Lang.Object[], so we create one here
sectionsObjects = new Java.Lang.Object[sections.Length];
for (int i = 0; i < sections.Length; i++) {
   sectionsObjects[i] = new Java.Lang.String(sections[i]);
}

データ構造を作成すると、ISectionIndexer メソッドは非常に単純になります。

public Java.Lang.Object[] GetSections()
{
   return sectionsObjects;
}
public int GetPositionForSection(int section)
{
   return alphaIndexer[sections[section]];
}
public int GetSectionForPosition(int position)
{   // this method isn't called in this example, but code is provided for completeness
    int prevSection = 0;
    for (int i = 0; i < sections.Length; i++)
    {
        if (GetPositionForSection(i) > position)
        {
            break;
        }
        prevSection = i;
    }
    return prevSection;
}

セクション インデックスのタイトルは、実際のセクションに 1:1 でマップする必要はありません。 これが、GetPositionForSection メソッドが存在する理由です。 GetPositionForSection では、インデックス リストにあるインデックスを、リスト ビューにあるセクションにマップできます。 たとえば、インデックスに "z" があっても、すべての文字にテーブル セクションがあるわけではないため、"z" を 26 にマップするのではなく、25 または 24 にマップするか、セクション インデックス "z" をマップする必要があります。