Xamarin.Android ListView를 데이터로 채우기

행을 ListView 추가하려면 레이아웃에 행을 추가하고 자체 채우기 위해 호출하는 IListAdapterListView 메서드를 구현해야 합니다. Android에는 사용자 지정 레이아웃 XML 또는 코드를 정의하지 않고도 사용할 수 있는 기본 제공 ListActivityArrayAdapter 클래스와 클래스가 포함되어 있습니다. 이 클래스는 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 터치하여 일부 작업(예: 노래 재생, 연락처 호출 또는 다른 화면 표시)을 수행할 수 있습니다. 사용자 터치에 응답하려면 다음과 같이 하나 이상의 메서드를 ListActivityOnListItemClick 구현해야 합니다.

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 표시됩니다.

행을 터치할 때 나타나는 알림의 스크린샷

ListAdapter 구현

ArrayAdapter<string> 은 단순성 때문에 훌륭하지만 매우 제한적입니다. 그러나 바인딩하려는 문자열이 아닌 비즈니스 엔터티 컬렉션이 있는 경우가 많습니다. 예를 들어 데이터가 Employee 클래스 컬렉션으로 구성된 경우 목록에 각 직원의 이름만 표시하도록 할 수 있습니다. 표시되는 데이터를 제어하는 동작을 ListView 사용자 지정하려면 다음 네 항목을 재정의 BaseAdapter 하는 하위 클래스를 구현해야 합니다.

  • 개수 – 데이터에 있는 행 수를 컨트롤에 알릴 수 있습니다.

  • 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 기본 제공 ArrayAdapter어댑터와 string[] 유사합니다.

ListAdapter = new HomeScreenAdapter(this, items);

이 예제에서는 동일한 행 레이아웃(SimpleListItem1)을 사용하므로 결과 애플리케이션은 이전 예제와 동일하게 표시됩니다.

행 보기 다시 사용

이 예제에는 6개의 항목만 있습니다. 화면이 8개에 맞을 수 있으므로 행 재사용이 필요하지 않습니다. 그러나 수백 또는 수천 개의 행을 표시할 때 한 번에 8개만 화면에 맞을 때 수백 또는 수천 View 개의 개체를 만드는 것은 메모리 낭비입니다. 이 상황을 방지하기 위해 화면에서 행이 사라지면 다시 사용하기 위해 보기가 큐에 배치됩니다. 사용자가 스크롤 ListView 할 때 새 보기를 표시하도록 요청하는 호출 GetView 이 표시되며, 사용 가능한 경우 매개 변수에서 사용되지 않는 뷰를 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 없으므로 두 가지 메서드가 필요하며 두 메서드 NewViewBindViewGetView 책임을 구분하여 행 재사용을 적용합니다. 문서의 뒷부분에 예제 CursorAdapter 가 있습니다.

빠른 스크롤 사용

빠른 스크롤을 사용하면 스크롤 막대 역할을 하는 추가 '핸들'을 제공하여 긴 목록을 스크롤하여 목록의 일부에 직접 액세스할 수 있습니다. 이 스크린샷은 빠른 스크롤 핸들을 보여줍니다.

스크롤 핸들이 있는 빠른 스크롤 스크린샷

빠른 스크롤 핸들이 표시되도록 하는 것은 속성을 true다음과 같이 설정하는 FastScrollEnabled 것만큼 간단합니다.

ListView.FastScrollEnabled = true;

섹션 인덱스 추가

섹션 인덱스가 긴 목록을 빠르게 스크롤할 때 사용자에게 추가 피드백을 제공합니다. 이는 사용자가 스크롤한 '섹션'을 보여 줍니다. 섹션 인덱스가 표시되도록 하려면 어댑터 하위 클래스는 표시되는 행에 따라 인덱스 텍스트를 제공하는 인터페이스를 구현 ISectionIndexer 해야 합니다.

H로 시작하는 섹션 위에 표시된 H의 스크린샷

구현 ISectionIndexer 하려면 어댑터에 다음 세 가지 메서드를 추가해야 합니다.

  • GetSections – 표시할 수 있는 섹션 인덱스 제목의 전체 목록을 제공합니다. 이 메서드는 코드가 .NET 컬렉션에서 만들어야 하므로 Java 개체의 배열이 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" 섹션 인덱스가 매핑되어야 하는 섹션 인덱스가 있을 수 있습니다.