えむにわリソース

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

.NET Standardプロジェクトに対応した Xamarin.FormsのCarouselView(CollectionView)を使ってみた

f:id:m2wasabi:20190319144458p:plain

世間ではXamarin.Formsの 4.0プレビュー版が入手可能になっています。

https://blog.xamarin.com/xamarin-forms-4-0-feature-preview-an-entirely-new-point-of-collectionview/

その中で CollectionView の機能更新があります。 中でも注目なのが CarouselView の実装です。

Xamarin.Forms.CarouselView というライブラリがあったのですが、 .NET Standardに対応しておらず、 PCLプロジェクト非推奨な現在においては空前のカルーセルロストだったのですが、ようやく公式にお目見えです。

なお、若干のToDoコメントが見られるものの、既にコードが Xamarin.Forms 3.6 でも実装されているため、 安定版でもプレビュー機能を有効にすることにより動かすことが出来てしまいます。

それでは使い方を見ていきましょう。

CollectionView機能の有効化

MainActivity.csAppDelegate.cs の Xamarin.Forms 初期化のところに、以下の1行を挿入することで、 CollectionView が有効になります。

global::Xamarin.Forms.Forms.SetFlags("CollectionView_Experimental");      // ←これを追加
global::Xamarin.Forms.Forms.Init(this, bundle);

どんなViewにするか

簡単に以下の様な仕様のようなViewを作りたいとします。

  • キャラクター一覧をスワイプして見れる
  • 遷移元によってビューに表示させる初期値が変化する
  • INavigationAware のためにPrismを使う

Xamlの書き方

基本的に昔の Xamarin.Forms.CarouselView と書き方は同じです。 記述例を以下に示します。

<?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"
              prism:ViewModelLocator.AutowireViewModel="True"
              x:Class="MyApp.Views.CharacterDetailPage"
              Title="プロフィール詳細"
              InitialIndex="{Binding SelectedCharacterIndex}"
              >

  <ContentPage.Content>
    <CarouselView x:Name="CV" ItemsSource="{Binding Characters}">
      <CarouselView.ItemsLayout>
        <GridItemsLayout Orientation="Horizontal" SnapPointsType="MandatorySingle" SnapPointsAlignment="Center"/>
      </CarouselView.ItemsLayout>
      <CarouselView.ItemTemplate>
        <DataTemplate>
          <StackLayout>
            <Image Source="{Binding Image}" HorizontalOptions="FillAndExpand" HeightRequest="{Binding ImageHeight}" />
            <Label Text="{Binding Name}" FontSize="Medium"/>
            <Label Text="{Binding Description}" FontSize="Medium" HorizontalOptions="Center"/>
          </StackLayout>
        </DataTemplate>
      </CarouselView.ItemTemplate>
    </CarouselView>
  </ContentPage.Content>
</ContentPage>

ContentPageの InitialIndex は、詳しくはViewのコードビハインド参照ですが、独自に追加したBindablePropertyです。

ここの設定のミソは、 GridItemsLayout の各種設定です

属性 説明
Orientation Xamarin.Forms.Itemslayout.Orientation スクロール方向を決める
SnapPointsType Xamarin.Forms.SnapPointsType スクロールの張り付き方を決める。 MandatorySingle で単一ページにびしっと張り付き、 None だとスムーズにスクロールする
SnapPointsAlignment Xamarin.Forms.SnapPointsAlignment スクロール張り付きの基準座標を決める。 Start , Center , End の中から決める

コードビハインド(xaml.cs)

ここで書くコードは少ないですが、前述のBindablePropertyは追加したからには定義する必要があります。 CarouselViewをスクロールさせるメソッドは ScrollTo です。

    public partial class CharacterDetailPage : ContentPage
    {
        // BindablePropertyを定義する
        public static readonly BindableProperty InitialIndexProperty =
            BindableProperty.Create("InitialIndex", typeof(int), typeof(CharacterDetailPage), 0 , BindingMode.OneWay, null, OnInitialIndexPropertyChanged, null, null);

        private static void OnInitialIndexPropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
        {
            var view = bindable as CharacterDetailPage;
            if (view == null || !(newvalue is int))
            {
                return;
            }
            // CarouselViewをスクロールさせる
            view.CV.ScrollTo((int)newvalue,    // Scroll先のIndex
                                        -1,                     // グループ(無い場合は-1)
                                        ScrollToPosition.Center,     // スクロール位置の基準
                                        false                                   // スクロールアニメーションを行うか
                                        );
        }

        public CharacterDetailPage()
        {
            InitializeComponent();
        }
    }

ViewModel

ここはあまり説明が要らないかもしれません。 OnNavigatedTo メソッドでデータを読み込んで初期値 _defaultIndex を設定しています。

捕捉すると データはここに無いモデルからロードします。 初期値はPrismのナビゲーションで受け取ったパラメータから取得します。 それらの値は Viewにバインドされています。

    public class CharacterDetailPageViewModel : BindableBase, INavigationAware
    {
        public static readonly string DefaultIndexKey = "SelectedId";

        private ObservableCollection<Character> _characters;
        public ObservableCollection<Character> Characters {
            get { return _characters; }
            set
            {
                SetProperty(ref _characters, value);
            }
        }

        private int _selectedCharacterIndex;

        public int SelectedCharacterIndex
        {
            get { return _selectedCharacterIndex; }
            set { SetProperty(ref _selectedCharacterIndex, value); }
        }

        public CharacterDetailPage()
        {
            CharacterItems = new ObservableCollection<CharacterItem>();
        }

        public void OnNavigatedFrom(INavigationParameters parameters)
        {
        }

        public void OnNavigatedTo(INavigationParameters parameters)
        {
            var _defaultIndex = parameters[DefaultIndexKey] as int?;
            if (_defaultIndex != null)
            {
                var _characters = TeamModel.loadCharacters();  // キャラクターの読み込み
                foreach (var _character in _characters)
                {
                    CharacterItems.Add(_character);
                }
                SelectedCharacterItemIndex = _defaultIndex;
            }
        }

        public void OnNavigatingTo(INavigationParameters parameters)
        {
        }
    }

    public class Character
    {
        public string Name { get; set; }
        public string Description { get; set; }
        public ImageSource Image { get; set; }
    }

まとめ

  • CollectionViewを使うにはネイティブのXamarin.Forms初期化部分に global::Xamarin.Forms.Forms.SetFlags("CollectionView_Experimental"); を書く
  • スクロールの挙動は CarouselView.ItemsLayout.GridItemsLayout の属性で設定する
  • 移動したい場合は CarouselView.ScrollTo() メソッドを使う

これでカルーセルマスターだ!

参考

Xamarin.Forms CollectionView - Xamarin | Microsoft Docs

Xamarin.Forms/ItemsView.cs at 3.6.0 · xamarin/Xamarin.Forms · GitHub

Xamarin.Forms/CarouselView.cs at 3.6.0 · xamarin/Xamarin.Forms · GitHub