えむにわリソース

ITのスキマ的なあれこれを書きます。

Xamarin.Forms の ListView内で可変数個の画像オブジェクトを表示する

はじめに

TwitterなどのSNSのタイムラインをXamarin.Formsを使って表示したいとき、
ListViewの中で可変数個の画像ファイルを表示する必要があります。
方法は色々ありそうですが、
今回は画像表示用のカスタムビューを定義することによりシンプルに実装しました。

本記事の価値はCustomViewのソースに集約されているので、
「説明不要、ソースくれ!」という方は以下のリンクからお持ち帰りください。

ImageListView for Xamarin.Forms · GitHub

目次

実装

モデルの定義

Modelを以下のように定義します。
AwitterのAPIから最低限の情報を取ってくるモデルです。

using System.Collections.Generic;
using Newtonsoft.Json;

namespace MyTwitterApp.Models
{
    public class Article
    {
        [JsonProperty("text")]
        public string Text { get; set; }
        [JsonProperty("id_str")]
        public string MessageId { get; set; }
        [JsonProperty("created_at")]
        public string Date { get; set; }
        [JsonProperty("user")]
        public ArticleUser User { get; set; }
        [JsonProperty("extended_entities")]
        public ArticleEntities Entities { get; set; }

        public string ScreenName
        {
            get { return User.ScreenName; }
        }
    }

    public class ArticleUser
    {
        [JsonProperty("id")]
        public string Id { get; set; }
        [JsonProperty("name")]
        public string Name { get; set; }
        [JsonProperty("screen_name")]
        public string ScreenName { get; set; }
        [JsonProperty("profile_image_url")]
        public string ProfileImageUrl { get; set; }
    }

    public class ArticleEntities
    {
        [JsonProperty("media")]
        public List<ArticleMedia> Media { get; set; }
    }

    public class ArticleMedia
    {
        [JsonProperty("media_url")]
        public string Url { get; set; }
    }
}

カスタムビューの実装

ContentViewを継承して中のStackLayoutにImageを設置するビューです。
幅90%を画像の枚数で等分割しています。
Gridにして公式アプリっぽく見せたり、 Imageをタップしたアクションや Imageのトリミングなど、 改修の余地は多くありますが、今回はサンプルなのでできるだけ質素に実装します。

ビューの実装

以下ではPrismを使っていますが、使わなくても基本的な記述は変わりません。
local:TweetImages を置いてItemsSourceList<Media>をバインドします。

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
             xmlns:local="clr-namespace:MyTwitterApp.Views;assembly=MyTwitterApp"
             prism:ViewModelLocator.AutowireViewModel="True"
             x:Class="MyTwitterApp.Views.TimelinePage"
             Title="タイムライン"
             x:Name="rootElement">
  <ListView ItemsSource="{Binding Articles}" HasUnevenRows="True" IsRefreshing="{Binding IsRefreshing}" IsPullToRefreshEnabled="True" RefreshCommand="{Binding RefreshCommand}">
    <ListView.ItemTemplate>
      <DataTemplate>
        <ViewCell>
          <StackLayout>
            <Label Text="{Binding Text}" FontSize="Medium" HorizontalOptions="Start"/>
            <local:TweetImages ItemsSource="{Binding Entities.Media}"></local:TweetImages>
          </StackLayout>
        </ViewCell>
      </DataTemplate>
    </ListView.ItemTemplate>
  </ListView>
</ContentPage>

ビューモデルの実装

ViewModelは、Binding先の ObservableCollection の定義と、 Modelが取ってきたAPIデータ(Jsonからモデルオブジェクトに変換済み)を取得するイベントの発火のみを定義します。

using Prism.Mvvm;
using System;
using System.Collections.ObjectModel;
using MoveNavi.Models;
using Prism.Navigation;
using Xamarin.Forms;
using System.Windows.Input;

namespace MyTwitterApp.ViewModels
{
    public class TimelinePageViewModel : BindableBase, INavigationAware
    {
        private readonly INavigationService _navigationService;
        public ObservableCollection<Article> Articles { get; set; }
        private ArticleJson _articleJson;

        public TimelinePageViewModel(INavigationService navigationService)
        {
            _navigationService = navigationService;
            _articleJson = new ArticleJson();
            Articles = new ObservableCollection<Article>();
        }

        public void OnNavigatedTo(NavigationParameters parameters)
        {
            LoadData();
        }

        private async void LoadData()
        {
            var articles = await _articleJson.LoadData();
            Articles.Clear();
            foreach (var article in articles)
            {
                Articles.Add(article);
            }
        }
        // ~以下略~
    }
}

完成図(API周りの実装は省略するけど)

あとはTwitterAPIから JSON.NETでオブジェクトに変換して流し込めば出来上がりです。
※サンプルで @wankotkt さんのタイムラインをお借りしています。

まとめ

カスタムビューは簡単に定義できるので、バシバシ使いましょう。
自分の実装モデルに合ったものを定義するとかっこいいかもしれないですね。

記事のListViewの中に画像のListViewを入れ子にするような血迷った実装をせずに済みました。

バインディングに関して深く理解してないので、Viewの更新まわりが少し改善の余地がありそうです。

参考記事

Xamarin.Forms で独自コントロールを定義する - Qiita

Xamarin.Forms 描画で考慮すべき2つのこと - SIN@SAPPOROWORKSの覚書

Xamarin Formsでカスタムレイアウトを作ってみる - ryuichi111stdの技術日記

StackLayout Class - Xamarin

Deserialize with CustomCreationConverter