Xamarin.Forms の TextCell の Detail に複数行を表示したい


動機

Xamarin.Forms に TextCell という UI があります。大きなフォントで1行の Text ラベルと、小さなフォントで1行の Detail ラベルとからなるセルです。ListViewTableView の要素になります。

Detail に複数行の文字列を表示したいことがありました。試しに Detail に複数行の文字列を入れると、1行目だけが表示されました。これではダメですね。(あとで分かったのですが、1行目だけが表示されるのは iOS の場合で、Android では改行されずに続けて表示されます。)

こういうとき、まずは ViewCell を使うのだと思います。しかし、ViewCell の中に Label を並べただけだと、マージンやフォントサイズなどのスタイルが周囲の TextCell と合いません。合わせるにしても、iOS と Android とではスタイルが微妙に違うこともあって、スタイルを作りこむのは少々面倒です。

そこで、Detail に複数行の文字列を表示できる TextCellカスタム レンダラーで作ろうと思い立ちました。そちらの方が面倒ではないかという意見もあるやもしれませんが、あるいは、既にどこかに似たようなものがあるかもしれませんが、ご容赦ください。

準備

Xamarin.Forms のソースTextCellRenderer の実装などを見て作戦を練ります。

  • iOS
  • Android
    • BaseCellView に実装の中に _DetailText.SetSingleLine(true) とあるので、この値を false に変更すれば良さそう。
    • TextView SetSingleLine - Android Developers

Android の方ですが、 _DetailText が private であるため外から触ることができません。ググったところ、GetChildAt() を使ってアクセスできることが分かりました。次の記事を参考にしました。

成果物

共通部分は空っぽです。

/App1/App1/MultilineTextCell.cs
using Xamarin.Forms;
namespace App1
{
    public class MultilineTextCell : TextCell
    {
    }
}

iOS のカスタムレンダラー:

/App1/App1.iOS/MultilineTextCellRenderer.cs
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(App1.MultilineTextCell), typeof(App1.iOS.MultilineTextCellRenderer))]
namespace App1.iOS
{
    public class MultilineTextCellRenderer : TextCellRenderer
    {
        public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
        {
            var cell = base.GetCell(item, reusableCell, tv);
            cell.DetailTextLabel.Lines = 0;
            cell.DetailTextLabel.LineBreakMode = UILineBreakMode.WordWrap;
            return cell;
        }
    }
}

Android のカスタムレンダラー:

/App1/App1.Android/MultilineTextCellRenderer.cs
using Android.Content;
using Android.Views;
using Android.Widget;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(App1.MultilineTextCell), typeof(App1.Droid.MultilineTextCellRenderer))]
namespace App1.Droid
{
    public class MultilineTextCellRenderer : TextCellRenderer
    {
        protected override Android.Views.View GetCellCore(Cell item, Android.Views.View convertView, ViewGroup parent, Context context)
        {
            var cell = (LinearLayout)base.GetCellCore(item, convertView, parent, context);
            var detailText = (TextView)(cell.GetChildAt(1) as LinearLayout).GetChildAt(1);
            detailText.SetSingleLine(false);
            return cell;
        }
    }
}

XAML で利用します。

/App1/App1/Views/Page1.xaml
xmlns:local="clr-namespace:App1"
...
<TextCell Text="通常のTextCell" Detail="{Binding MultilineText}"/>
<local:MultilineTextCell Text="複数行テキスト" Detail="{Binding MultilineText}" />

iOS のスクリーンショット:

Android のスクリーンショット:

以上、ツイートしながら コーディングしていました。