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を使うと、ネットワークからデータを読んで一覧で表示するだけならかなり簡単にできてしまいます。
一度使ってみるといいですよ♪

2014年8月19日

Visual Studio 2013の共有プロジェクトを使ってみる

そもそも共有プロジェクトって何ぞや?

普通なら、共有プロジェクトって何ぞ?
ってところから始まると思うんですが。
まあ、それはもう前提条件として。
もうすっ飛ばしてしまいます。

ソースファイルが増減しなければリンクでもよし

同じようなことは、「既存の項目」でファイルを追加する際に、「リンクとして追加」を選べばできます。
ソースファイルの増減がなければ、それでもいいかもしれません。

2014年8月18日

XamarinでAndroid用の動画プレイヤーを作ってみる

車移動の子供のお供に

週末はよく車で移動するんですが、子供用にはお古のiPhoneで動画を見せていました。
日本初代のiPhone。
病院行きます、と仮病で発売日に並んで買ったやつ。

さすがにもうバッテリーがへばりまして。
1時間近く充電して(車内充電なのでパワーが弱いのです)やっと目を覚ますという。
代替わりということで、一時代替えで正式に変わりが用意できるまで借り物のLG製のAndroid端末を据えてみました。

借り物なので無茶苦茶するわけにもいかず。
Android 2.3なのですが、Play Storeにつながらず、アプリをインストールすることもできず。
仕方なしに車内用連続再生動画プレイヤーを一晩で作ってみたのです。

…前置きが長くなってしまってすみません。