ListView
ListView는 Item을 담아 목록을 보여주는 View다.
주요 속성
- SeparatorVisibility: 구분선 표시 여부
- SeperatorColor: 구분선 컬러
XAML
<ListView x:Name="listView" SeparatorVisibility="None" SeparatorColor="Black"></ListView>
C#
using System.Collections.Generic; using Xamarin.Forms; namespace MyFirstXamarinApp { public partial class ListPage : ContentPage { public ListPage() { InitializeComponent(); var list = new List<string> { "라이언", "어피치", "프로도" }; listView.ItemsSource = list; } } }
ListView Template 사용하기
이전 예제는 문자열만을 List<T>에 등록하여 보여주는 예제다. ListView가 기본적으로 제공하는 템플릿을 사용하면 간단히 텍스트 내용, 상세 내용, 이미지 등을 보여줄 수 있다.
ListView Item 클래스 생성
ListView에 어떤 내용(Item)이 들어갈 지를 정의할 클래스를 만든다.
여기서는 간단한 프로필 정보(이름, 상태메시지, 프로필사진)를 정의하는 Contact 클래스를 만든다.
namespace MyFirstXamarinApp.Models { public class Contact { public string Name { get; set; } public string Status { get; set; } public string ImageUri { get; set; } } }
ListView에서 제공하는 템플릿 사용: TextCell, ImageCell
ListView에 대한 속성 요소 구문을 사용하여 ListView의 내용을 어떻게 보여줄 것인지를 정의한다.
아래는 텍스트만을 사용하는 TextCell을 사용한 ListView 예제.
<?xml version="1.0" encoding="UTF-8"?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="MyFirstXamarinApp.ListPage"> <ListView x:Name="listView"> <ListView.ItemTemplate> <DataTemplate> <TextCell Text="{Binding Name}" Detail="{Binding Status}"></TextCell> </DataTemplate> </ListView.ItemTemplate> </ListView> </ContentPage>
이미지도 보여주려면 ImageCell을 사용한다.
<?xml version="1.0" encoding="UTF-8"?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="MyFirstXamarinApp.ListPage"> <ListView x:Name="listView"> <ListView.ItemTemplate> <DataTemplate> <ImageCell Text="{Binding Name}" Detail="{Binding Status}" ImageSource="{Binding ImageUri}"></ImageCell> </DataTemplate> </ListView.ItemTemplate> </ListView> </ContentPage>
ListView에 어떤 Item을 포함할지 정의
using System.Collections.Generic; using MyFirstXamarinApp.Models; using Xamarin.Forms; namespace MyFirstXamarinApp { public partial class ListPage : ContentPage { public ListPage() { InitializeComponent(); listView.ItemsSource = new List<Contact>{ new Contact{Name="라이언", Status="안녕요?ㅎ", ImageUri="http://lorempixel.com/100/100/people/1"}, new Contact{Name="어피치", Status="하이요?ㅎ", ImageUri="http://lorempixel.com/100/100/people/2"}, new Contact{Name="프로도", Status="헬로우?ㅎ", ImageUri="http://lorempixel.com/100/100/people/3"} }; } } }
실행 화면
ListView Item Custom: ViewCell
이전에 실습한 ListView의 Item 오른쪽에 Follow 버튼을 추가한다고 해보자. 이 경우 기본적으로 제공되는 TextCell 및 ImageCell로 구현하기에 한계가 있다.
ViewCell을 통해 Item을 어떻게 보여줄지 세부적으로 디자인할 수 있다.
<?xml version="1.0" encoding="UTF-8"?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="MyFirstXamarinApp.ListPage"> <!-- HasUnevenRows: List Cell의 열 크기를 균등하게 할 것인지를 정의 --> <!-- 이 값이 false면 열의 크기가 충분하지 않을 때 레이아웃이 틀어질 수 있음 --> <ListView x:Name="listView" HasUnevenRows="true"> <ListView.ItemTemplate> <DataTemplate> <!-- ViewCell 안에 여러가지 View를 정의 --> <ViewCell> <StackLayout Orientation="Horizontal" Padding="5"> <Image Source="{Binding ImageUri}"></Image> <StackLayout Orientation="Vertical" HorizontalOptions="StartAndExpand"> <Label Text="{Binding Name}"></Label> <Label Text="{Binding Status}" TextColor="Gray"></Label> </StackLayout> <Button x:Name="btnFollow" Text="Follow"></Button> </StackLayout> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> </ContentPage>
ListView를 Group별로 보여주기
ListView Group 정의 클래스 생성
ListView에 들어갈 Group에 어떤 정보를 담을지 나타내는 클래스를 만든다.
여기서는 제목과 소제목을 나타내는 ContactGroup 클래스를 만든다.
namespace MyFirstXamarinApp.Models { /* List Group을 정의하는 클래스는 어떤 List를 상속받을지 결정해야 함 */ public class ContactGroup : List<Contact> { public string Title { get; set; } public string ShortTitle { get; set; } public ContactGroup(string title, string shortTitle) { Title = title; ShortTitle = shortTitle; } } }
ListPage.xaml
이전에 실습한 ListView Item Custom의 XAML 파일 내용을 그대로 사용한다.
ListView의 Group 관련 속성
- IsGroupEnabled: 그룹 사용 여부
- GroupDisplayBinding: 그룹 제목에 보여줄 값
- GroupShortNameBinding: 그룹 소제목에 보여줄 값
<?xml version="1.0" encoding="UTF-8"?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="MyFirstXamarinApp.ListPage"> <ListView x:Name="listView" HasUnevenRows="true" IsGroupingEnabled="true" GroupDisplayBinding="{Binding Title}" GroupShortNameBinding="ShortTitle"> <ListView.ItemTemplate> <DataTemplate> <!-- ViewCell 안에 여러가지 View를 정의 --> <ViewCell> <StackLayout Orientation="Horizontal" Padding="5"> <Image Source="{Binding ImageUri}"></Image> <StackLayout Orientation="Vertical" HorizontalOptions="StartAndExpand"> <Label Text="{Binding Name}"></Label> <Label Text="{Binding Status}" TextColor="Gray"></Label> </StackLayout> <Button x:Name="btnFollow" Text="Follow"></Button> </StackLayout> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> </ContentPage>
ListPage.xaml.cs
각 ListView Group에 어떤 Item을 포함할지를 정의한다.
using System.Collections.Generic; using MyFirstXamarinApp.Models; using Xamarin.Forms; namespace MyFirstXamarinApp { public partial class ListPage : ContentPage { public ListPage() { InitializeComponent(); listView.ItemsSource = new List<ContactGroup>{ new ContactGroup("카카오 프렌즈", "Kakao Friends"){ new Contact{Name="라이언", Status="안녕요?ㅎ", ImageUri="http://lorempixel.com/100/100/people/1"}, new Contact{Name="어피치", Status="하이요?ㅎ", ImageUri="http://lorempixel.com/100/100/people/2"}, new Contact{Name="프로도", Status="헬로우?ㅎ", ImageUri="http://lorempixel.com/100/100/people/3"} }, new ContactGroup("라인 프렌즈", "Line Friends"){ new Contact{Name="브라운", Status="안녕요?ㅎ", ImageUri="http://lorempixel.com/100/100/people/4"}, new Contact{Name="제임스", Status="하이요?ㅎ", ImageUri="http://lorempixel.com/100/100/people/5"}, new Contact{Name="코니", Status="헬로우?ㅎ", ImageUri="http://lorempixel.com/100/100/people/6"} } }; } } }
실행 화면
ListView 이벤트 처리
ListView의 주요 이벤트
- ItemTapped: Item을 터치(탭)했을 때 발생하는 이벤트
- ItemSelected: Item을 선택했을 때 발생하는 이벤트
ListPage.xaml
<ListView x:Name="listView" ItemTapped="Handle_ItemTapped" ItemSelected="Handle_ItemSelected"></ListView>
ListPage.xaml.cs
아이템을 처음 터치하면 ItemTapped 및 ItemSelected 이벤트 2개가 발생한다. 선택된 아이템을 다시 터치하면 ItemTapped 이벤트만 발생한다.
namespace MyFirstXamarinApp { public partial class ListPage : ContentPage { public ListPage() { InitializeComponent(); // 생략... } /* e: 이벤트 정보를 담고 있는 매개변수 */ void Handle_ItemSelected(object sender, Xamarin.Forms.SelectedItemChangedEventArgs e) { // e.SelectedItem: object 타입. 선택된 List Item의 타입(여기서는 Contact 타입) var contact = e.SelectedItem as Contact; DisplayAlert("Selected", contact.Name, "OK"); /* List Item이 선택되지 않도록 하려면 List 객체의 SelectedItem 객체를 null로 지정 */ //listView.SelectedItem = null; } void Handle_ItemTapped(object sender, Xamarin.Forms.ItemTappedEventArgs e) { // e.Item: object 타입. 탭한 List Item의 타입(여기서는 Contact 타입) var contact = e.Item as Contact; DisplayAlert("Tapped", contact.Name, "OK"); } } }
실행 결과
ListView의 Context Action
Context Action은 ListView의 Item을 좌우로 스와이프 할 때 버튼이 나타나는 행동이다.
ListPage.xaml
ContextAction의 CommandParameter 속성을 사용하여 MenuItem에 대응하는 ListView의 Item을 사용할 수 있도록 한다.
<?xml version="1.0" encoding="UTF-8"?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="MyFirstXamarinApp.ListPage"> <ListView x:Name="listView"> <ListView.ItemTemplate> <DataTemplate> <TextCell Text="{Binding Name}" Detail="{Binding Status}"> <TextCell.ContextActions> <!-- CommandParameter는 MenuItem과 이벤트 처리기의 연결을 위한 속성 --> <!-- MenuItem에 대응하는 ListView Item의 모든 속성을 가져오기 위해 바인딩 값을 .으로 지정 --> <MenuItem Text="Call" Clicked="Call_Clicked" CommandParameter="{Binding .}"></MenuItem> <!-- isDestruction 속성: Context Action을 빨간색으로 표시 여부 설정 --> <MenuItem Text="Delete" Clicked="Delete_Clicked" IsDestructive="true" CommandParameter="{Binding .}"></MenuItem> </TextCell.ContextActions> </TextCell> </DataTemplate> </ListView.ItemTemplate> </ListView> </ContentPage>
ListPage.xaml.cs
ListView에 List<T> 대신 ObservableCollection<T>를 사용하면 Item을 추가하거나 제거해도 ListView가 자동 갱신된다.
MenuItem Click 이벤트 객체는 ListView의 Item에 대한 정보를 갖고있지 않다. 이를 사용하려면 이벤트 발생 객체의 정보가 담긴 sender 객체를 사용해야 한다.
- object sender -> MenuItem.CommandParameter -> ListView Item
using System.Collections.Generic; using System.Collections.ObjectModel; // ObservableCollection using MyFirstXamarinApp.Models; using Xamarin.Forms; namespace MyFirstXamarinApp { public partial class ListPage : ContentPage { // ListView에 List<T>를 사용하면 List<T>의 내용이 바뀌어도 ListView가 이를 인지하지 못함 // List<T> 대신 ObservableCollection<T>을 사용하면 내용이 바뀔 때 이벤트가 발생함 // Xamarin.Forms의 ListView는 ObservableCollection의 작동을 인지하여 ListView를 자동 갱신함 // 따라서 ListView의 내용을 갱신하기 위한 코드를 작성할 필요가 없음 private ObservableCollection<Contact> _contacts; public ListPage() { InitializeComponent(); _contacts = new ObservableCollection<Contact>{ new Contact{Name="라이언", Status="안녕요?ㅎ", ImageUri="http://lorempixel.com/100/100/people/1"}, new Contact{Name="어피치", Status="하이요?ㅎ", ImageUri="http://lorempixel.com/100/100/people/2"}, new Contact{Name="프로도", Status="헬로우?ㅎ", ImageUri="http://lorempixel.com/100/100/people/3"} }; // ListView의 ItemSource에 ObservableCollection<T> 객체 사용 listView.ItemsSource = _contacts; } void Call_Clicked(object sender, System.EventArgs e) { // MenuItem.Click 이벤트 객체는 ListView의 Item에 대한 정보를 갖고있지 않음 // sender 객체를 통해 object 타입 형태로 전달받은 후 MenuItem 타입으로 캐스팅하면 ListView의 Item 정보를 사용할 수 있음 var menuItem = sender as MenuItem; // MenuItem.CommandParameter 속성을 사용하여 ListView Item 객체를 가져올 수 있음 // 정리: object sender -> MenuItem menuItem.CommandParameter -> Contact contact var contact = menuItem.CommandParameter as Contact; DisplayAlert("Call", contact.Name, "OK"); } void Delete_Clicked(object sender, System.EventArgs e) { var contact = (sender as MenuItem).CommandParameter as Contact; // ListView.ItemSource 속성은 IEnumerable 타입 // 순차 접근 방식이므로 직접 원하는 위치에 요소를 추가하거나 제거할 수 없음 // ObservableCollection<T>를 사용하여 요소를 추가하거나 제거해야 함 _contacts.Remove(contact); } } }
실행 화면
ListView를 아래로 당겨 갱신하기
Pull To Refresh를 위한 ListView의 주요 속성
- IsPullToRefreshEnabled: Pull To Refresh 사용 여부 설정
Pull To Refresh를 위한 ListView의 주요 이벤트
- Refreshing: Pull To Refresh가 발동될 때 발생하는 이벤트
ListPage.xaml
<?xml version="1.0" encoding="UTF-8"?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="MyFirstXamarinApp.ListPage"> <ListView x:Name="listView" IsPullToRefreshEnabled="true" Refreshing="Handle_Refreshing"> <ListView.ItemTemplate> <DataTemplate> <TextCell Text="{Binding Name}" Detail="{Binding Status}"></TextCell> </DataTemplate> </ListView.ItemTemplate> </ListView> </ContentPage>
ListPage.xaml.cs
using System.Collections.Generic; using System.Collections.ObjectModel; // ObservableCollection using MyFirstXamarinApp.Models; using Xamarin.Forms; namespace MyFirstXamarinApp { public partial class ListPage : ContentPage { /* ListView에 추가할 List<T> 리턴. ObservableCollection<T>도 가능 */ List<Contact> GetContacts(){ return new List<Contact> { new Contact{Name="라이언", Status="안녕요?ㅎ", ImageUri="http://lorempixel.com/100/100/people/1"}, new Contact{Name="어피치", Status="하이요?ㅎ", ImageUri="http://lorempixel.com/100/100/people/2"}, new Contact{Name="프로도", Status="헬로우?ㅎ", ImageUri="http://lorempixel.com/100/100/people/3"} }; } public ListPage() { InitializeComponent(); } /* ListView Refreshing 이벤트 처리 메소드 */ void Handle_Refreshing(object sender, System.EventArgs e) { /* ListView가 리프레시되면 새로운 List<T>를 리턴받음 */ listView.ItemsSource = GetContacts(); /* ListView 리프레시가 끝나면 리프레시 중단을 알려야 함 */ /* 아래 둘 중 하나를 쓰면 됨. 취향의 차이(?) */ listView.IsRefreshing = false; listView.EndRefresh(); } } }
실행 결과
Search Bar 만들기
SearchBar View를 사용하면 ListView의 내용을 검색할 수 있다.
SearchBar의 주요 속성
- Placeholder: Search Bar의 값이 비어있을 때 기본적으로 보여줄 텍스트
SearchBar의 주요 이벤트
- TextChanged: Search Bar의 값이 바꼈을 때 발생하는 이벤트
ListView.xaml
<?xml version="1.0" encoding="UTF-8"?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="MyFirstXamarinApp.ListPage"> <StackLayout> <SearchBar Placeholder="Search..." TextChanged="Handle_TextChanged"></SearchBar> <ListView x:Name="listView"> <ListView.ItemTemplate> <DataTemplate> <TextCell Text="{Binding Name}" Detail="{Binding Status}"></TextCell> </DataTemplate> </ListView.ItemTemplate> </ListView> </StackLayout> </ContentPage>
ListView.xaml.cs
using System.Collections.Generic; using System.Linq; using Xamarin.Forms; using MyFirstXamarinApp.Models; namespace MyFirstXamarinApp { public partial class ListPage : ContentPage { public ListPage() { InitializeComponent(); listView.ItemsSource = new List<Contact> { new Contact{Name="라이언", Status="안녕요?ㅎ", ImageUri="http://lorempixel.com/100/100/people/1"}, new Contact{Name="어피치", Status="하이요?ㅎ", ImageUri="http://lorempixel.com/100/100/people/2"}, new Contact{Name="프로도", Status="헬로우?ㅎ", ImageUri="http://lorempixel.com/100/100/people/3"} }; } /* Search Bar 검색에 사용할 메소드 */ IEnumerable<Contact> GetContacts(string searchText = null) { // 이 부분은 Hard Coding(데이터를 코드 내부에 직접 입력하는 것) // 보통 이렇게 사용하지는 않지만 일단 실습을 위해 사용... var contacts = new List<Contact> { new Contact{Name="라이언", Status="안녕요?ㅎ", ImageUri="http://lorempixel.com/100/100/people/1"}, new Contact{Name="어피치", Status="하이요?ㅎ", ImageUri="http://lorempixel.com/100/100/people/2"}, new Contact{Name="프로도", Status="헬로우?ㅎ", ImageUri="http://lorempixel.com/100/100/people/3"} }; // 입력값이 없거나 공백인 경우 모든 값을 반환 if (string.IsNullOrWhiteSpace(searchText)) return contacts; // LINQ의 Where() 메소드는 IEnumerable 타입을 반환 // 메소드의 리턴 형식을 List<T>로 선언한 후 ToList() 메소드를 사용하여 List<T>로 반환하거나 // - 예: return contacts.Where(c => c.Name.StartsWith(searchText)).ToList(); // 순차 처리 방식만 사용한다면 IEnumerable 타입을 사용 return contacts.Where(c => c.Name.StartsWith(searchText)); } /* Search Bar TextChanged 이벤트 처리 메소드 */ void Handle_TextChanged(object sender, Xamarin.Forms.TextChangedEventArgs e) { listView.ItemsSource = GetContacts(e.NewTextValue); } } }
실행 화면