2014年12月24日

Xamarin.FormsでListViewを使う

この投稿はもしかしたらXamarinのAdvent Calendar 2014の記事になると思います。
また、かなり初心者向きです。
(社内の人の洗脳用も兼ねてますw)

ListViewを使う

前提の詳しい説明は省きますね。
ListViewってのは項目を縦に並べて選択するようなもの。
要するに、こんな感じのものです。

image

Xamarin.FormsでListView

もう、XamarinやXamarin.Formsが何であるかの説明は省いて。
簡単な使い方。

ListViewはViewなので、PageやLayoutに貼付けて使います。
iOSなら、AppDelegateにこんな感じで書くと使えます。

        ObservableCollection<string> items = new ObservableCollection<string>();
        public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions)
        {
            Forms.Init ();

            ListView lv = new ListView() {
                ItemsSource = items,
            };
            ContentPage cp = new ContentPage () {
                Title = "ListView Test",
                Content = lv,
            };
            cp.Appearing += (object sender, EventArgs e) => {
                if (items.Count == 0) {
                    items.Add ("Test 1");
                    items.Add ("Test 2");
                    items.Add ("Test 3");
                    items.Add ("Test 4");
                    items.Add ("Test 5");
                    items.Add ("Test 6");
                }
            };
            Window = new UIWindow (UIScreen.MainScreen.Bounds);
            Window.RootViewController = new NavigationPage(cp).CreateViewController();
            Window.MakeKeyAndVisible ();

            return true;
        }

これで、こんな感じの画面に。

image

超簡単。
やってることは、

  1. Xamarin.Formsを初期化して
  2. ListViewを作って
  3. 土台になるページを作ってListViewを中に入れて
  4. 土台のページが表示されたときにListViewに表示するアイテムを追加して
  5. ウインドウを作って
  6. Navigationで土台ページを表示する

ってこと。
ObjectiveCでやってたdelegateの山はどこ行ったんだ、と。
思ってしまいます。

アイテムを選択したときに次のページへ

ListViewでアイテムを選択した際に、次のページへ遷移するようなコードは以下のようになります。
(先のソースからの変更点のみ)

            ListView lv = new ListView() {
                ItemsSource = items,
            };
            lv.ItemTapped += async (object sender, ItemTappedEventArgs e) => {
                await lv.Navigation.PushAsync(new ContentPage() {
                    Title = e.Item.ToString(),
                    Content = new ListView() {
                        ItemsSource = items,
                    },
                });
            };

アイテムをタップしたときに次のページを作って表示してます。
簡単簡単。
ここではasync~awaitを使用して、スレッド処理が行われます。

セクション表示を行う

仕切り線みたいなのです。
あれを実現するには、ListView.IsGroupingEnabledをtrueに設定し、ListView.GroupDisplayBindingにセクションラベルのプロパティをBindingし、リストのリストをListView.ItemsSourceに設定することで表示されます。

        public class GroupingItem : ObservableCollection<string>
        {
            public string SectionLabel { get; set; }
        }
        ObservableCollection<GroupingItem> items = new ObservableCollection<GroupingItem>();
        public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions)
        {
            Forms.Init ();
            ListView lv = new ListView () {
                IsGroupingEnabled = true,
                ItemsSource = items,
                GroupDisplayBinding = new Binding("SectionLabel"),
            };
            ContentPage cp = new ContentPage () {
                Title = "ListView Test",
                Content = lv,
            };
            cp.Appearing += (object sender, EventArgs e) => {
                if (items.Count == 0) {
                    GroupingItem itm = new GroupingItem() { SectionLabel = "Section 1" };
                    itm.Add ("Test 1");
                    itm.Add ("Test 2");
                    itm.Add ("Test 3");
                    items.Add(itm);
                    itm = new GroupingItem() { SectionLabel = "Section 2" };
                    itm.Add ("Test 4");
                    itm.Add ("Test 5");
                    itm.Add ("Test 6");
                    items.Add(itm);
                }
            };
            Window = new UIWindow (UIScreen.MainScreen.Bounds);
            Window.RootViewController = new NavigationPage(cp).CreateViewController();
            Window.MakeKeyAndVisible ();
            return true;
        }

Bindingとか面倒くさくなりますが、そのBindingを適切に設定することでデータをいじくるだけで表示されるアイテムが置き換わるわけです。
(MVCとかMVVMとかいう考え方の様です。その辺は調べてください)

リストのアイテムをいじる

リスト内のアイテムは、Cellというものでできています。
一番最初のスクリーンショットは自分がQiitaのアイテム一覧を出せるようにしたアプリの画面になります。

では、最初のスクリーンショットのようなアプリを作ってみましょう。
Qiitaのアイテム一覧のJSONを取得し、ListViewで表示するものです。

        [DataContract]
        public class QiitaUser
        {
            [DataMember]
            public string name { get; set; }
        }
        [DataContract]
        public class QiitaItem
        {
            [DataMember]
            public string title { get; set; }
            [DataMember]
            public QiitaUser user { get; set; }
        }
        [CollectionDataContract]
        public class QiitaItems : ObservableCollection<QiitaItem> {};
        QiitaItems items = new QiitaItems();
        public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions)
        {
            Forms.Init ();
            ContentPage cp = new ContentPage () {
                Title = "Qiita Items",
                Content = new ListView () {
                    HorizontalOptions = LayoutOptions.FillAndExpand,
                    VerticalOptions = LayoutOptions.FillAndExpand,
                    HasUnevenRows = true,
                    RowHeight = 52,
                    ItemsSource = items,
                    ItemTemplate = new DataTemplate (() => {
                        Label titleLabel = new Label () {
                            HorizontalOptions = LayoutOptions.FillAndExpand,
                            VerticalOptions = LayoutOptions.Start,
                            LineBreakMode = LineBreakMode.TailTruncation,
                            FontSize = 18,
                        };
                        titleLabel.SetBinding<QiitaItem> (Label.TextProperty, i => i.title);
                        Label usernameLabel = new Label () {
                            HorizontalOptions = LayoutOptions.Start,
                            VerticalOptions = LayoutOptions.End,
                            FontSize = 12,
                        };
                        usernameLabel.SetBinding<QiitaItem> (Label.TextProperty, i => i.user.name);
                        return new ViewCell () {
                            View = new StackLayout () {
                                Padding = new Thickness(10,0,0,0),
                                Orientation = StackOrientation.Vertical,
                                Children = {
                                    titleLabel,
                                    new StackLayout() {
                                        Orientation = StackOrientation.Horizontal,
                                        Children = { usernameLabel }
                                    },
                                },
                            },
                        };
                    }),
                },
            };
            cp.Appearing += (object sender, EventArgs e) => {
#if __IOS__
                UIApplication.SharedApplication.NetworkActivityIndicatorVisible = true;
#endif
                HttpClient client = new HttpClient ();
                client.GetStreamAsync ("http://qiita.com/api/v2/items").ContinueWith ((res) => {
                    Device.BeginInvokeOnMainThread (() => {
                        if (res.Exception == null) {
                            DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(QiitaItems));
                            QiitaItems il = (QiitaItems)serializer.ReadObject(res.Result);
                            foreach(QiitaItem i in il){
                                items.Add(i);
                            }
                        }
#if __IOS__
                        UIApplication.SharedApplication.NetworkActivityIndicatorVisible = false;
#endif
                    });
                });
            };
            Window = new UIWindow (UIScreen.MainScreen.Bounds);
            Window.RootViewController = new NavigationPage(cp).CreateViewController();
            Window.MakeKeyAndVisible ();
            return true;
        }

ちょっと、というか、だいぶんと長くなってしまいました。
Cellの中身を自由にレイアウトするには、ViewCellを使ってStackLayoutとかと組み合わせると比較的楽にレイアウトできます。

また、中ではネットワークの処理も書いていますが、ContinueWithを使うよりもasyncawaitを使う方がスマートになります。
Exceptionの判定も面倒くさくなりますし。

JSONのパースはSystem.Runtime.SerializationにあるDataContractJsonSerializerを使用しました。
Windows8でもWindowsPhoneでも使えますので、比較的楽にマルチデバイスで作れる様になります。

あと、Xaml使うと楽なのですが、個人的に好きなShared Projectでうまく読み込んでくれなかった経験もあり、個人的にはソースでガリガリ書いています。

最後に

本当ならアイテムの長押しとかジェスチャー的なところまで書きたかったのですが
、それをするにはListViewRendererを使わないとできないということなんですね。
で、ListViewRendererはiOSでは見れるんですが、Androidはプライベートなクラスになっています。
なので、Androidで実現する方法が今のところわかっていなかったりするので…。
断念しました。

Xamarin.Formsを使うと、ネットワークからデータを読んで一覧で表示するだけならかなり簡単にできてしまいます。
一度使ってみるといいですよ♪

0 件のコメント:

コメントを投稿