Xamarin.AndroidでListViewを使用する

最近、AndroidのListViewで最も一般的に使用される基本的な機能をすべて収集し、それらを便宜上1つのプロジェクトに結合するというアイデアを思いつきました。 いつものように、私はインターネットに行き、そこに素晴らしい記事とその翻訳がハブにあるのを見つけました( translation 1translation 2 )。 私の意見では、すべてがこの記事で必要であり有用ではなかったので、最終的なプロジェクトには自分にとって重要だと思われるものだけを含めました。 これが将来他の誰かに役立つことを願っています。



ところで、私はXamarin開発者なので、 プロジェクト (およびサンプル)はXamarin.AndroidのC#で記述されます。



それでは始めましょう:



データ入力(TODO 1)



ご存知のように、AndroidのListViewはリストの形式でデータを提供する要素であり、各要素はそのビューで表されます。 アダプターは、セルの表示を制御するために使用されます。 ListViewに最も控えめな形式のデータを入力するには、ArrayAdapterを使用できます。 これは2行で行われます。



var animals = GetAnimals (); var adapter = new ArrayAdapter<Animal> (this, Android.Resource.Layout.SimpleListItem1, animals);
      
      





ご覧のとおり、結果は適切です-高速で安価であまり美しくありませんが、これに戻ります。







セル選択処理(TODO 2)



ユーザーは、表示されたリストと何らかの形で通信する必要があります-アイテムの選択、詳細情報の表示など。 Xamarin.Androidでセルの選択を処理するには、ListViewのItemClickイベントにサブスクライブするだけです。 次のコードを使用して、選択したアイテムを削除します。



 bool inDeletion = false; list.ItemClick += (sender, e) => { if (!inDeletion) { inDeletion = true; e.View.Animate () .SetDuration (500) .Alpha (0.0f) .WithEndAction (new Runnable (() => { #region TODO5 //TODO 05 var item = adapter.GetRawItem (e.Position); #endregion //var item = list.Adapter.GetItem(e.Position); adapter.Remove (item); adapter.NotifyDataSetChanged (); e.View.Alpha = 1; #region TODO9 //TODO 09 ShowSnackBar (1); #endregion inDeletion = false; })); } };
      
      





空のリストの処理(TODO 3)



ユーザーにはアイテムのないリストが表示される可能性があります。 迷子にならずにアプリケーションを閉じないようにするには、EmptyViewを使用するのが最善です。 レイアウトアクティビティで宣言し、ListViewに要素がない場合にこのビューを表示することを1行で理解させる必要があります。



 list.EmptyView = FindViewById<TextView> (Resource.Id.empty);
      
      





セルの外観を変更する(TODO 4.5)



リストの外観をわずかに改善するには、ArrayAdapterコンストラクターのオーバーロードを使用し、リストアイテムを表示するために使用されるレイアウトをそれに渡すことができます。 また、オブジェクトの文字列アナログが表示されるテキストフィールドのIDを渡す必要があります。



 var adapter = new ArrayAdapter<Animal> (this, Resource.Layout.row_custom, Resource.Id.row_custom_text, animals);
      
      





リストが少し良くなりました。







外観をさらに近代化するために、GetViewメソッドを再定義する独自のアダプターを作成し、セルの外観を自分で制御します。 アダプタの実装は最も基本的なものなので、ここにすべてのコードを投稿する必要はありません。 変更を確認する場合は、 TODO5リージョンのコードのコメントを解除します。 また、一部の古いコードは変更が必要です-GetItemメソッドは、Java.Lang.Object型のオブジェクトのみを返すことができます。 Java.Lang.Objectのリストのすべての要素をラップすることは不必要な贅沢なので、目的のタイプを返す独自のメソッドGetRawItemを作成しましょう。 ここで、変更に関して、セルを削除するときにプロジェクトをコンパイルするには、GetItem呼び出しをGetRawItemに置き換える必要があります。







これが結果として見られるものです。 デザインコンペティションには適していませんが、テストプロジェクトには必要なものです。



コンテキストアクションバー(CAB)の追加(TODO 6)



次のステップは、CAB機能をListViewに追加することです。 参照用に、説明と詳細な実装ガイドがこちらにあります 。 CABを追加するには、最初にカスタムロングタップで表示されるメニューが必要です。 メニューファイル:



 <?xml version="1.0" encoding="UTF-8" ?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/ActionModeDeleteItem" android:title="Delete" android:showAsAction="always"/> </menu>
      
      





次に、次のようにActivityMode.ICallbackインターフェイスをActivityに実装する必要があります。



 public bool OnActionItemClicked (ActionMode mode, IMenuItem item) { switch(item.ItemId){ case Resource.Id.ActionModeDeleteItem: SparseBooleanArray selected = _adapter.SelectedIds; List<Animal> itemList = new List<Animal> (); var keys = new List<int> (); for (int i = (selected.Size () - 1); i >= 0; i--) { //checkisvaluecheckedby user if (selected.ValueAt (i)) { keys.Add (selected.KeyAt (i)); var selectedItem = _adapter.GetRawItem (selected.KeyAt (i)); itemList.Add (selectedItem); } } _adapter.Remove (itemList); _mode.Finish(); return true; default: return false; } } public bool OnCreateActionMode (ActionMode mode, IMenu menu) { mode.MenuInflater.Inflate (Resource.Menu.menu, menu); inActionMode = true; return true; } public void OnDestroyActionMode (ActionMode mode) { _adapter.RemoveSelection (); _adapter.NotifyDataSetChanged (); _mode = null; inActionMode = false; } public bool OnPrepareActionMode (ActionMode mode, IMenu menu) { return false; }
      
      





第三に、ユーザークリック処理メソッドでは、ユーザーがActionModeにいる可能性があることを考慮する必要があります。



 if(inActionMode){ adapter.ToggleSelection(e.Position); _mode.Title=(adapter.SelectedCount.ToString()+" selected"); return; }
      
      





第4に、アダプターに、アイテムの選択を担当するメソッドを追加する必要があります。



 protected virtual void SelectView(int position, bool value){ if(value) _selectedItemsIds.Put(position,value); else _selectedItemsIds.Delete(position); NotifyDataSetChanged(); } public void ToggleSelection(int position){ SelectView(position, !_selectedItemsIds.Get(position)); } public int SelectedCount { get{ return _selectedItemsIds.Size(); } } public void RemoveSelection(){ _selectedItemsIds=new SparseBooleanArray(); NotifyDataSetChanged(); } public SparseBooleanArray SelectedIds{ get { return _selectedItemsIds; } }
      
      





次に、目的の結果を取得します。







ListViewでのパフォーマンス(TODO 7)



ListViewのアイテムをより速く表示するには、 ViewHolderパターンを実装します 。 これを行うには、ベースJava.Lang.Objectを継承するViewHolderクラスを作成します(これは、ViewHolderをview.Tagプロパティに割り当てるために必要です)。 知識のある人 、ViewHolderを使用したListViewが15%高速であると言います。 私のシミュレータでは、1つのビューで1〜3ミリ秒から0〜1ミリ秒にジャンプしました。 もちろん、これはFindViewByIDが呼び出される頻度に依存します。 それでは、GetViewメソッドがどのように変更されたかを見てみましょう。



 public override View GetView(int position, View convertView, ViewGroup parent) { var item = this [position]; var view = convertView; if (view == null) { view = LayoutInflater.From (parent.Context).Inflate (Resource.Layout.row_custom_adapter, parent, false); var viewHolder = new ViewHolder (); viewHolder.Text = view.FindViewById<TextView> (Resource.Id.row_custom_name); viewHolder.Weight = view.FindViewById<TextView> (Resource.Id.row_custom_weight); viewHolder.Icon = view.FindViewById<ImageView> (Resource.Id.row_custom_icon); view.Tag = viewHolder; } var holder = (ViewHolder)view.Tag; holder.Text.Text = item.Name; holder.Weight.Text = item.Weight.ToString ("F"); if (_selectedItemsIds.Size () > 0 && _selectedItemsIds.Get (position)) { view.SetBackgroundColor (Android.Graphics.Color.CadetBlue); } else { view.Background = GetBackground (item.Color); } holder.Icon.SetImageResource (GetImage (item.Name)); return view; }
      
      





セクションの追加(TODO 8)



動物がより一貫して見えるようにするには、色でグループに分ける必要があります。 これを行うには、AnimalAdapterを継承する新しいSectionAnimalAdapterアダプターを作成します。 このアダプタの主な違いは、オブジェクトをディクショナリ辞書<string、List>として保存し、要素が削除されるたびにBuildSectionsメソッドを呼び出してディクショナリを再作成することです。 新しいViewtypeも追加されます-ヘッダーとレイアウト。 コードはファイルで表示できます



リストは次のようになります。







最後のアクションを元に戻す(TODO 9)



非常に多くの場合、ユーザーは最後のアクション、特に削除に関しては元に戻すことを望んでいます。 Snackbarを使用してこの機能を実装します 。 削除後、ユーザーSnackbarに最後のアクションをキャンセルする提案が表示されます。 もちろん、これにはアダプターで少し操作する必要があります-ユーザーがロールバックできる最後の状態が保存される追加リストと、Rollbackメソッドを作成する必要があります。



 public virtual void RollBack () { _animals = _rollbackAnimals; _rollbackAnimals = null; }
      
      









ドロップダウンリスト(TODO 10)



必要に応じて、ドロップダウンリストを実装できます。 リストをドロップダウンするには、ExpandableListView型である必要があり、アダプターはBaseExpandableListAdapter型を継承する必要があります。 通常のアダプターとの主な違いは、グループと子要素の2つのメソッドです。



 public override View GetGroupView (int position, bool isExpandable, View convertView, ViewGroup parent) { GroupViewHolder holder = null; var view = convertView; if (view != null) holder = view.Tag as GroupViewHolder; if (holder == null) { view = LayoutInflater.From (parent.Context).Inflate (Resource.Layout.row_expandable_header, null); holder = new GroupViewHolder (); holder.Text = view as CheckedTextView; } var sect = _sections.Keys.ToList () [position]; var name = _sections [sect].First ().Name; holder.Text.Text = name; holder.Text.Checked = isExpandable; return view; } public override View GetChildView (int groupPosition, int childPosition, bool isLastChild, View convertView, ViewGroup parent) { var view = convertView; var sect = _sections.Keys.ToList () [groupPosition]; var item = _sections [sect] [childPosition]; if (view == null) { view = LayoutInflater.From (parent.Context).Inflate (Resource.Layout.row_custom_adapter, parent, false); var viewHolder = new ChildViewHolder (); viewHolder.Text = view.FindViewById<TextView> (Resource.Id.row_custom_name); viewHolder.Weight = view.FindViewById<TextView> (Resource.Id.row_custom_weight); viewHolder.Icon = view.FindViewById<ImageView> (Resource.Id.row_custom_icon); view.Tag = viewHolder; } var holder = (ChildViewHolder)view.Tag; holder.Text.Text = item.Name; holder.Weight.Text = item.Weight.ToString ("F"); holder.Icon.SetImageResource (GetImage (item.Name)); return view; }
      
      









ドラッグアンドドロップリスト(TODO 11)



ドラッグアンドドロップがサポートするリストを実装するには、ListViewクラスを継承する新しいクラスを作成する必要があります。 元の例はこのビデオから取られています 。 一言で言えば、ユーザーがセルをロングタップすると、そのスナップショットが作成され、ユーザーの指の動きで動きます。 指が隣接するセルに到達すると、アダプター内のオブジェクトが交換され、再描画されます。 DynamicListViewコードはこちらです。



ドラッグアンドドロップリストでアプリケーションを起動するには、開始のSecondaryActivityを作成する必要があります。 行MainLauncher = trueのコメントを外します。







ListView for Androidについて私が言いたかったのはそれだけです。 しかし、おそらくプロジェクトにはまだいくつかの重要な機能が欠けていますか? ListViewを最も頻繁に使用する機能をコメントで聞いてうれしいです:)



All Articles