Xamarin.Forms ListViewでTwitter風のレイアウトを作成してみました(機種依存コードなし)
Xamarin.FormsのListViewでカスタマイズセル(CellView)を使用することで、Twitter風の画面を作成してみました。(「Xamarin」というキーワード検索を表示しただけのもです)
機種依存のコードはなく、共有プロジェクトだけで書きました。
最新のXamarin.Formsにアップデート(1.2.2.6243)することで、メッセージの長さに応じて行ごとの高さを変更することもできています。
なお、サンプルコードは、GitHubにも置きました。
https://github.com/furuya02/Xamarin.Forms.ListView.Sample
1 Xamarin.Formsのアップデート
2014.08.07現在、Xamarin.Formsのバージョンは、1.1.0.6201ですが、iOSでListViewの高さ変更ができなかったり、Uri指定でのイメージ表示が一部で動作しいなど、いくつかの問題があるため最新のものにアップデートしました。アップデートは、パッケージマネージャコンソールから簡単に行うことができます。(すべてのプロジェクトにインストールするのを忘れないでください)
PM> Install-Package Xamarin.Forms -ProjectName App1 PM> Install-Package Xamarin.Forms -ProjectName App1.iOS PM> Install-Package Xamarin.Forms -ProjectName App1.Android PM> Install-Package Xamarin.Forms -ProjectName App1.WinPhone PM> Get-Package Id Version Description/Release Notes -- ------- ------------------------- WPtoolkit 4.2013.08.16 Windows Phone toolkit provides a collection of controls, extension methods a... Xamarin.Android.Support.v4 19.1.0 C# bindings for android support library v4. Xamarin.Forms 1.2.2.6243 Build native UIs for iOS, Android, and Windows Phone from a single, shared C..
2 Linq To Twitter

Twitterのデータ取得には、LinqToTwitterを使用させて頂きました。
LINQ to Twitter – Code Plex
http://linqtotwitter.codeplex.com/
.NETでTwitterを扱うには、非常に便利にできています。
NuGetで公開されているので、こちらもパッケージマネージャコンソールで簡単にインストールが可能です。
PM> Install-Package LinqToTwitter -ProjectName App1 PM> Install-Package LinqToTwitter -ProjectName App1.iOS PM> Install-Package LinqToTwitter -ProjectName App1.Android PM> Install-Package LinqToTwitter -ProjectName App1.WinPhone PM> Get-Package Id Version Description/Release Notes -- ------- ------------------------- LinqToTwitter 3.0.4 Supporting Twitter API v1.1, async, and PCL! It uses standard LINQ syntax fo... Microsoft.Bcl 1.1.9 This packages enables projects targeting down-level platforms to use some of... Microsoft.Bcl.Build 1.0.14 This package provides build infrastructure components so that projects refer... Microsoft.Bcl.Compression 3.9.83 This package allows projects targeting Windows Phone Silverlight 8 directly ... Microsoft.Net.Http 2.2.22 This package includes HttpClient for sending requests over HTTP, as well as ... WPtoolkit 4.2013.08.16 Windows Phone toolkit provides a collection of controls, extension methods a... Xamarin.Android.Support.v4 19.1.0 C# bindings for android support library v4. Xamarin.Forms 1.2.2.6243 Build native UIs for iOS, Android, and Windows Phone from a single, shared C..
なお、LinqToTwitterを使用するためには、Twitter開発者のページで、アプリを作成し、「AppKey」及び「AppSecret」を取得する必要があります。
サンプルコードは、この部分を上書きすることで動作させることが可能になります。
3 レイアウト

図は、ListViewのセルを構成する要素を表したものです。簡単に言ってしまば、ImageとLabelをStackLayoutで組み合わせただけです。
Imageは、サイズを指定して上段に寄せ、ラベルは、要素ごとにフォントサイズと色を指定しています。
4 各行の高さの変更
各行は、メッセージの行数に応じて高さが変化するようになっています。ListViewで各行の高さを指定するには、セルのテンプレートクラス(MyCell)でOnBindingContextChangedをオーバーライドします。
サンプルでは、メッセージから改行コードと文字数で行数を算出し、そこから高さを指定しています。(日本語で27文字ぐらいが1行の収まっていたので、1行を27文字で固定しましたが、正確には、もう少しちゃんと作りこむ必要があるでしょう)
5 サンプルコード
以下が、記述したコードのすべてです。サンプルとして分かりやすいように、共有プロジェクトのApp.csだけを編集しました。
public class App { public static Page GetMainPage() { return new TweetPage(); } } //1つのTweetを表現するクラス internal class Tweet { public string Name {get;set;}//表示名 public string Text {get;set;}//メッセージ public string ScreenName {get;set;}//アカウント名 public string CreatedAt {get;set;}//作成日時 public string Icon {get;set;}//アイコン } internal class TweetPage : ContentPage { //データソース(class Tweetのコレクション) private readonly ObservableCollection<Tweet> _tweets = new ObservableCollection<Tweet>(); public TweetPage() { //パディング(iPhone用) Padding = new Thickness(0, Device.OnPlatform(20, 0, 0), 0, 0); //ListViewの生成 var listView = new ListView{ ItemTemplate = new DataTemplate(typeof (MyCell)),//セルの指定 ItemsSource = _tweets,//データソースの指定 HasUnevenRows = true,//行の高さを可変とする }; //このページのコンテンツとしてListView(のみ)を指定する Content = new StackLayout() { Children = {listView, } }; //更新 ("xamarin"という文字列を検索する) Refresh("xamarin"); } //セル用のテンプレート private class MyCell : ViewCell { public MyCell() { //アイコン var icon = new Image(); icon.WidthRequest = icon.HeightRequest = 50;//アイコンのサイズ icon.VerticalOptions = LayoutOptions.Start;//アイコンを行の上に詰めて表示 icon.SetBinding(Image.SourceProperty, "Icon"); //名前 var name = new Label{Font = Font.SystemFontOfSize(12)}; name.SetBinding(Label.TextProperty, "Name"); //アカウント名 var screenName = new Label{Font = Font.SystemFontOfSize(12)}; screenName.SetBinding(Label.TextProperty, "ScreenName"); //作成日時 var createAt = new Label{Font = Font.SystemFontOfSize(8),TextColor = Color.Gray}; createAt.SetBinding(Label.TextProperty, "CreatedAt"); //メッセージ本文 var text = new Label{Font = Font.SystemFontOfSize(10)}; text.SetBinding(Label.TextProperty, "Text"); //名前行 var layoutName = new StackLayout { Orientation = StackOrientation.Horizontal, //横に並べる Children = { name,screenName }//名前とアカウント名を横に並べる }; //サブレイアウト var layoutSub = new StackLayout{ Spacing = 0,//スペースなし Children ={layoutName,createAt, text}//名前行、作成日時、メッセージを縦に並べる }; View = new StackLayout{ Padding = new Thickness(5), Orientation = StackOrientation.Horizontal, //横に並べる Children ={icon,layoutSub} //アイコンとサブレイアウトを横に並べる }; } //テキストの長さに応じて行の高さを増やす protected override void OnBindingContextChanged() { base.OnBindingContextChanged(); //メッセージ var text = ((Tweet)BindingContext).Text; //メッセージを改行で区切って、各行の最大文字数を27として行数を計算する(27文字は、日本を基準にしました) var row = text.Split('\n').Select(l => l.Length / 27).Select(c => c + 1).Sum(); Height = 12 + 8 + row*10 + 20;//名前行、作成日時行、メッセージ行、パディングの合計値 if (Height <60){ Height = 60;//列の高さは、最低でも60とする } } } private async void Refresh(string searchString) { //認証 var auth = new ApplicationOnlyAuthorizer() { CredentialStore = new InMemoryCredentialStore { ConsumerKey = "{API Keyt}", ConsumerSecret = "{API secret}", }, }; await auth.AuthorizeAsync(); //コンテキストの作成 var context = new TwitterContext(auth); var response = await (from search in context.Search where search.Type == SearchType.Search && search.Query == searchString && search.Count == 30 select search).SingleOrDefaultAsync(); //取得データの解釈 foreach (var a in response.Statuses) { _tweets.Add(new Tweet { Text = a.Text, Name = a.User.Name, ScreenName = a.User.ScreenNameResponse, CreatedAt = a.CreatedAt.ToString("f"), Icon = a.User.ProfileImageUrl }); } } }
[2015.01.27追記]
@espresso3389 さんから、AOTで問題が発生するとの指摘を頂きました。
AOTの罠: Xamarin.Forms の ListView で System.MissingMethodException: Default constructor not found for type - espresso3389の日記
iOSにおけるAOTの最適化からコンストラクタの削除を避けるため、次の記述が必要とのことでした。
class PreserveAttribute : System.Attribute{ public bool Conditional { get; set; } } //セル用のテンプレート private class MyCell : ViewCell { [Preserve(Conditional=true)] public MyCell() { ...