前書き
XamarinもAndroidも初めてで、両方の知識が必要なCustomRendererを作成するのに手こずったので書き残しました。
冗長な書き方や間違いがある可能性が高いです。
※CustomRendererはPCL側のXamlで実装できない、できたけど速度が遅い。みたいなときに各プラットフォームごとの固有の書き方をすることで改善することができる機能。(今回はAndroidなのでC#、xmlで実装していきます)
完成形
実装
※私のnamespaceやプロジェクト名は自分のものに置き換えてください
PCL側
PCLとXamarin.Androidを紐づけるためのクラスを用意します。
PCLCustomGridViewRenderer.cs
using System.Runtime.CompilerServices;
using Xamarin.Forms;
[assembly: InternalsVisibleTo("MyProject.Android")]
namespace MyProject.Views.Renderer
{
public class PCLCustomGridViewRenderer : ItemsView
{
}
}
↑
ItemsViewは配列を受け取るための抽象クラス。継承してやることでXamlからPCLCustomGridViewRendererに対して配列を渡すことができる。(配列はそのままAndroid側の実装で扱えます)
XamlからPCLCustomGridViewRendererを呼び出します。
<StackLayout xmlns:renderer="clr-namespace:MyProject.Views.Renderer">
<renderer:PCLCustomGridViewRenderer ItemsSource="{Binding Items}"/>
</StackLayout>
※PCLCustomGridViewRendererが見つからないエラーが出る場合はプロジェクトをリビルドしてみてください。
ちなみにItems内のデータ構成はObservableCollectionになっています。
ObservableCollection<Item> Items;
class Item
{
public string Text { get; set; }
}
PCL側の実装は以上です。
Xamarin.Android側
3つほどクラスファイルを用意します。
1.Android側で配列を受け取りGridViewを生成するクラス。
2.Androidの世界で出てくるAdpterクラス。データとxmlを紐づけるためのクラス。
3.GridView内に配置する要素のxmlファイル
1.AndroidCustomGridViewRenderer.cs
エラー線が出る箇所がありますが、進めていく際に治るので、一旦無視してください。
using Android.Content;
using Android.Support.V7.View;
using Android.Widget;
using MyProject.Droid.Renderer;
using MyProject.Droid.Views.Adapter;
using MyProject.Views.Renderer;
using System.ComponentModel;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
//互いに参照できるよう定義
[assembly: ExportRenderer(typeof(PCLCustomGridViewRenderer), typeof(AndroidCustomGridViewRenderer))]
namespace MyProject.Droid.Renderer
{
public class AndroidCustomGridViewRenderer : ViewRenderer<PCLCustomGridViewRenderer, GridView>
{
private GridViewAdapter adapter;
public AndroidCustomGridViewRenderer(Context context) : base(context) { }
// 生成時一度だけ呼ばれるイベント
protected override void OnElementChanged(ElementChangedEventArgs<PCLCustomGridViewRenderer> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
if (adapter != null)
adapter.Element = null;
}
if (e.NewElement != null)
{
if (Control == null)
{
// Adpterの生成
adapter = new GridViewAdapter(Context);
// GridViewの生成
var gridView = new CustomGridView(new ContextThemeWrapper(Context, Resource.Style.VerticalScrollbarRecyclerView));
// GridViewのパラメータを設定(親サイズに追従する設定)
gridView.LayoutParameters = new LayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent);
// カラム数
gridView.NumColumns = 3;
// Viewに対しAdpterを設定
gridView.Adapter = adapter;
//多分コントロールを実際に生成
SetNativeControl(gridView);
}
if (adapter != null)
// NewElementにはIList<Person>が入ってきます
// ElementはAdpterクラスが私が定義しました。
adapter.Element = e.NewElement;
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == ItemsView.ItemsSourceProperty.PropertyName)
{
adapter?.UpdateItems();
}
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
adapter.Dispose();
}
}
}
2.AndroidCustomGridViewAdapter.cs
using System.Collections.ObjectModel;
using System.Linq;
using Android.Content;
using Android.Views;
using Android.Widget;
using MyProject.Models;
using MyProject.Views.Renderer;
namespace MyProject.Droid.Views.Adapter
{
class AndroidCustomGridViewAdapter : BaseAdapter
{
private PCLCustomGridViewRenderer element;
public PCLCustomGridViewRenderer Element
{
get => element;
set
{
element = value;
UpdateItems();
}
}
Context context;
private ObservableCollection<Item> viewModels = new ObservableCollection<Item>();
public AndroidCustomGridViewAdapter(Context c)
{
context = c;
}
public override int Count
{
get { return viewModels.Count(); }
}
public override Java.Lang.Object GetItem(int position)
{
return null;
}
public override long GetItemId(int position)
{
return 0;
}
public void UpdateItems()
{
viewModels.Clear();
if (Element?.ItemsSource != null)
{
foreach (var item in Element.ItemsSource)
{
if (item is Item model)
{
viewModels.Add(model);
}
}
}
NotifyDataSetChanged();
}
// Countプロパティの数だけ呼ばれるViewを生成する関数
public override Android.Views.View GetView(int position, Android.Views.View convertView, ViewGroup parent)
{
var view = LayoutInflater.From(parent.Context).Inflate(Resource.Layout.custom_gridview_item, parent, false);
// 要素の作成
SetUp(view, viewModels[position]);
return view;
}
private void SetUp(Android.Views.View view, Item model)
{
var text = view.FindViewById<TextView>(Resource.Id.text);
text.Text = model.Text;
}
}
}
3.custom_gridview_item.xml
Resources/layoutフォルダに配置して下さい。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text"
android:text="ああああああああああああああああ"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
まだエラー線が出ている箇所があると思うので定義を追加します。
Resources/values/styles.xmlのresourcesタグの中に以下を追加します
<style name="VerticalScrollbarRecyclerView" parent="android:Widget">
<item name="android:scrollbars">vertical</item>
</style>
完成
ビルドしてみてください。
もしプロジェクトの実行の際に以下のエラーが出た場合
System.InvalidOperationException: 'The class, property, or method you are attempting to use ('VerifyCollectionViewFlagEnabled') is part of CollectionView; to use it, you must opt-in by calling Forms.SetFlags("CollectionView_Experimental") before calling Forms.Init()
MyProject.AndroidプロジェクトのMainActivity.csに1行追加してください
global::Xamarin.Forms.Forms.SetFlags("CollectionView_Experimental"); ←下の行より前にこの行を追加
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
あとがき
今回はただテキストを表示するだけでしたが、Itemクラスを拡張したり、xmlを変えたりすることで自由な表現ができると思います。