Xamarin.Forms CarouselViewで縦スワイプする方法|ItemsLayout VerticalListとSnapPoints設定

Xamarin.FormsのCarouselViewは横スワイプが基本ですが、縦に並べて縦スワイプで1件ずつ切り替えたい場面も多いです。結論はItemsLayoutを縦向きに設定するだけ。VerticalListを使う方法と、LinearItemsLayout+SnapPointsで「止まる挙動」を維持するコツ、縦スワイプが効かないときの切り分けまでまとめます。

目次

CarouselViewで「縦スワイプ」をやりたい理由と、つまずきやすいポイント

Xamarin.FormsのCarouselViewは、写真ギャラリーやオンボーディング画面のように「ページ送り(1項目ずつ切り替え)」を実現するためのコントロールです。初期状態では横方向(Horizontal)のスワイプが前提になっているため、縦に並べて縦スワイプにしたい場合は、レイアウトの設定を明示してあげる必要があります。

ところが、単に縦方向に並べられたとしても、次のような“体験の違い”が出ることがあります。

  • 縦にできたが、「1件ずつ止まる」挙動にならず、連続スクロール(リストのような動き)に見える
  • 縦スワイプが効かない(親のScrollViewなどがジェスチャーを奪っている)
  • アイテムの高さが揃わず、スナップ位置がずれて気持ち悪い

この記事では、まず最短の正解(VerticalList)を押さえたうえで、ハマりやすい設定(LinearItemsLayout)とその対策、さらに「縦スワイプが効かない」ケースの切り分けまで、実務向けに整理します。

結論:ItemsLayoutを縦向きにする

CarouselViewで縦方向に並べて縦スワイプしたい場合、ポイントはItemsLayoutです。XAMLでは次のように指定できます。

推奨:ItemsLayout="VerticalList"

CarouselViewらしい「1項目ずつ切り替わる」挙動を保ちやすいのがVerticalListです。まずはこれを基準にすると失敗しにくくなります。

<CarouselView ItemsSource="{Binding Monkeys}"
              ItemsLayout="VerticalList" />

VerticalListは、XAMLでの指定が簡潔で読みやすく、意図(縦方向のカルーセル)も伝わりやすいのが利点です。チーム開発でもレビューしやすくなります。

注意点:LinearItemsLayout Orientation="Vertical"だけだと“リストっぽく”なることがある

同じく縦方向にする方法として、LinearItemsLayoutを明示的に書くパターンがあります。ただし、指定の仕方やアイテムサイズ次第では、CarouselViewというよりCollectionView/ListViewのような連続スクロールに見えることがあります。

そこで、LinearItemsLayoutを使う場合は、スナップ関連(SnapPointsType/SnapPointsAlignment)もセットで指定して、ページ送り感を維持するのが安全です。

<CarouselView ItemsSource="{Binding Monkeys}">
  <CarouselView.ItemsLayout>
    <LinearItemsLayout Orientation="Vertical"
                       SnapPointsType="MandatorySingle"
                       SnapPointsAlignment="Center" />
  </CarouselView.ItemsLayout>
</CarouselView>

VerticalListとLinearItemsLayoutの違いを整理する

縦スワイプ自体はどちらでも実現できますが、実務で差が出るのは「挙動」「調整のしやすさ」「ハマりどころ」です。まずは違いを表で押さえておくと、設計の判断が早くなります。

選択肢縦スワイプ1件ずつ止まる(スナップ)調整のしやすさ向いているケース
VerticalList〇〇(維持しやすい)〇(シンプル)まず縦カルーセルを作りたい/デフォルト挙動を保ちたい
LinearItemsLayout + SnapPoints〇〇(指定が必要)◎(間隔やスナップを細かく制御)アイテム間隔やスナップ位置をカスタムしたい/挙動を厳密に揃えたい
LinearItemsLayoutのみ〇△(条件次第で崩れる)〇連続スクロールでも良い/カルーセルにこだわらない

迷ったら、まずはVerticalListで成立させるのがおすすめです。そこから「間隔を詰めたい」「中央スナップにしたい」「上寄せにしたい」など要件が出たタイミングで、LinearItemsLayoutへ移行するとスムーズです。

SnapPointsTypeとSnapPointsAlignmentの使い分け

LinearItemsLayoutで“カルーセル感”を出す鍵がスナップ設定です。縦カルーセルでは特に、アイテムの高さとスナップ位置が体験を左右します。

プロパティ主な値意味縦カルーセルでのおすすめ
SnapPointsTypeNoneスナップしない(連続スクロール)カルーセル目的なら基本は避ける
SnapPointsTypeMandatoryスナップする(複数アイテム単位で止まることも)「ページ」より「区切り」重視の場合に検討
SnapPointsTypeMandatorySingle常に1アイテム単位でスナップ縦スワイプで1件ずつ切り替えたいなら最有力
SnapPointsAlignmentStart / Center / Endスナップ時の寄せ位置カードUIならCenter、上から読ませるならStartが定番

実務では、まずMandatorySingle+Centerで気持ち良い挙動を作り、画面デザインに合わせてStartへ寄せる、という流れが多いです。特にカードを縦にスワイプして切り替えるUIでは、中央スナップの方が「切り替わった感」が出やすく、見た目も安定します。

VerticalListで足りないとき:ItemSpacingやPeekAreaInsetsを調整する

VerticalListは手軽ですが、実装が進むと「カード間隔を一定にしたい」「次のカードが少し見えるようにしてスワイプ可能だと伝えたい」といった要望が出がちです。そういうときは、次の2つの方向で調整します。

アイテム間隔をプロパティで揃えたいなら、LinearItemsLayoutへ

カードのMarginでも間隔は作れますが、デザイントークンとして「間隔=12」のように統一したい場合は、ItemSpacingで制御できるLinearItemsLayoutが便利です。もちろんスナップも忘れずに指定します。

<CarouselView ItemsSource="{Binding Items}">
  <CarouselView.ItemsLayout>
    <LinearItemsLayout Orientation="Vertical"
                       ItemSpacing="12"
                       SnapPointsType="MandatorySingle"
                       SnapPointsAlignment="Center" />
  </CarouselView.ItemsLayout>
</CarouselView>

「次のカードが見える」UIにしたいなら、PeekAreaInsetsを活用

スワイプ可能なUIでも、初見ではユーザーが気づかないことがあります。そこで、次(または前)のアイテムが少しだけ見えるようにして、操作を誘導する方法が有効です。CarouselViewにはPeekAreaInsetsが用意されているため、縦方向なら上下に余白を持たせます。

<CarouselView ItemsSource="{Binding Items}"
              ItemsLayout="VerticalList"
              PeekAreaInsets="0,24,0,24" />

PeekAreaInsetsは見た目の「誘導」に効く一方、カードの高さが画面に対して大きすぎると効果が出にくいです。実機で「次が少し見える」状態になっているかを必ず確認しましょう。

縦スワイプが効かない/止まらないときの原因別チェック

設定自体は合っているのに挙動が崩れる場合、原因は「上位レイアウト」「アイテムサイズ」「ジェスチャー競合」にあることがほとんどです。よくある症状を表でまとめます。

症状ありがちな原因対処
縦スワイプが反応しない親のScrollView/CollectionViewが縦ジェスチャーを奪っている親のスクロールをなくす/レイアウトをGridにしてCarouselViewを固定領域に置く
縦には動くが、止まらず連続スクロールになるLinearItemsLayoutのみでスナップ指定がない/アイテム高さが揃っていないSnapPointsType="MandatorySingle"を追加/アイテムの高さを揃える
スナップするが位置がずれる各アイテムの高さがバラバラ/画像ロードで高さが後から変わる固定の高さを持つコンテナで包む/画像のアスペクトを固定/必要ならサイズ計測戦略を見直す
スワイプがカクつくテンプレートが重い(大きい画像、影、複雑なレイアウト)表示要素を絞る/画像を縮小・キャッシュ/セル内で重い処理をしない
意図せず上下に引っ張られて“戻る”PeekAreaInsetsやMarginが大きすぎて、実質的に複数アイテムが見えているPeekAreaInsetsや余白を調整/カードの高さを画面に対して適正化する

親にScrollViewがいる場合の典型パターン

「画面全体を縦にスクロールしたい」意図でScrollViewに包むと、CarouselViewの縦スワイプが吸収されやすくなります。縦カルーセルを実現したい画面では、まずはScrollViewを外して、Gridの行にCarouselViewを配置する構成を試すのが近道です。

<Grid RowDefinitions="Auto,*">
  <StackLayout Grid.Row="0" Padding="16">
    <Label Text="縦スワイプで切り替え" FontSize="18" />
    <Label Text="CarouselViewはScrollViewで包まない" FontSize="12" />
  </StackLayout>

  <CarouselView Grid.Row="1"
                ItemsSource="{Binding Items}"
                ItemsLayout="VerticalList" />
</Grid>

どうしても画面全体のスクロールが必要な場合は、縦方向のスクロール領域を分ける(ヘッダーは固定、本文だけスクロールなど)設計が有効です。CarouselViewを“スクロールの中に入れる”より、スクロールしたい部分をCarouselViewの外へ出す方が安定します。

アイテムサイズがバラつくと「1件ずつ」が崩れやすい

縦カルーセルで気持ち良くスナップさせるには、各アイテムの高さを揃えるのが基本です。カードUIなら、FrameやGridなど高さを持つコンテナで包み、内側はLabelの行数や画像サイズが変わっても外枠は一定になるように設計すると安定します。

実践サンプル:縦カルーセルのカードを作る(XAML+ViewModel)

ここでは「カードを縦にスワイプして切り替える」よくあるUIを、最小限のコードで組み立てます。ポイントは次の3つです。

  • ItemsLayoutはVerticalList(まずは簡潔に成立させる)
  • カードの外枠で高さを安定させる
  • 必要なら後からLinearItemsLayout+SnapPointsへ移行できる形にする
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Sample.Views.VerticalCarouselPage">

  <Grid RowDefinitions="Auto,*" Padding="16" RowSpacing="12">

    <StackLayout Grid.Row="0" Spacing="4">
      <Label Text="縦CarouselViewサンプル" FontAttributes="Bold" FontSize="20" />
      <Label Text="縦スワイプで1件ずつ切り替え" FontSize="12" />
    </StackLayout>

    <CarouselView Grid.Row="1"
                  ItemsSource="{Binding Items}"
                  ItemsLayout="VerticalList"
                  PeekAreaInsets="0,24,0,24"
                  Loop="False">

      <CarouselView.ItemTemplate>
        <DataTemplate>
          <Frame Padding="16"
                 CornerRadius="16"
                 HasShadow="True"
                 Margin="0,8">
            <Grid RowDefinitions="Auto,Auto,Auto" RowSpacing="8">

              <Label Text="{Binding Title}"
                     FontSize="18"
                     FontAttributes="Bold" />

              <Label Grid.Row="1"
                     Text="{Binding Description}"
                     LineBreakMode="TailTruncation"
                     MaxLines="3" />

              <StackLayout Grid.Row="2" Orientation="Horizontal" Spacing="12">
                <Label Text="{Binding Category}" FontSize="12" />
                <Label Text="{Binding UpdatedAt, StringFormat='{0:yyyy/MM/dd}'}" FontSize="12" />
              </StackLayout>

            </Grid>
          </Frame>
        </DataTemplate>
      </CarouselView.ItemTemplate>

    </CarouselView>

  </Grid>
</ContentPage>

ViewModel側は、ObservableCollectionでデータを持たせればOKです。縦カルーセルは「現在表示している要素」をトリガーにデータをロードしたいケースも多いので、後述の位置連動も見据えて、まずはMVVMで組んでおくのがおすすめです。

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace Sample.ViewModels
{
    public class VerticalCarouselViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        void OnPropertyChanged([CallerMemberName] string propertyName = null)
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

        public ObservableCollection<CardItem> Items { get; } = new ObservableCollection<CardItem>

        int _currentIndex;
        // PositionをTwoWayバインドするなら、変更通知を出すのが安全
        public int CurrentIndex
        {
            get => _currentIndex;
            set
            {
                if (_currentIndex == value) return;
                _currentIndex = value;
                OnPropertyChanged();
            }
        }

        public VerticalCarouselViewModel()
        {
            Items.Add(new CardItem
            {
                Title = "VerticalListで縦スワイプ",
                Description = "ItemsLayoutをVerticalListにすると、CarouselViewを縦方向に並べてスワイプ切り替えできます。",
                Category = "Layout",
                UpdatedAt = DateTime.Today
            });

            Items.Add(new CardItem
            {
                Title = "SnapPointsで1件ずつ止める",
                Description = "LinearItemsLayoutを使う場合はSnapPointsTypeをMandatorySingleにして、カルーセルらしい挙動を維持します。",
                Category = "Gesture",
                UpdatedAt = DateTime.Today.AddDays(-1)
            });

            Items.Add(new CardItem
            {
                Title = "親ScrollViewとの競合に注意",
                Description = "縦ジェスチャーが親に吸われると、縦スワイプが効かないことがあります。Gridで領域を分けると安定します。",
                Category = "Troubleshoot",
                UpdatedAt = DateTime.Today.AddDays(-2)
            });
        }
    }

    public class CardItem
    {
        public string Title { get; set; }
        public string Description { get; set; }
        public string Category { get; set; }
        public DateTime UpdatedAt { get; set; }
    }
}

Position/CurrentItemを使って「任意のカードへジャンプ」する

縦スワイプを実装すると、次に出やすい要件が「特定のカードを初期表示したい」「ボタンで次へ進めたい」「入力エラーがあるカードへ戻したい」といった制御です。CarouselViewはPosition(インデックス)やCurrentItem(要素)で現在位置を管理できます。

例えば、インデックスをバインドしておけば、ViewModel側でCurrentIndexを書き換えるだけでページ移動ができます。

<CarouselView ItemsSource="{Binding Items}"
              ItemsLayout="VerticalList"
              Position="{Binding CurrentIndex, Mode=TwoWay}" />

スワイプ操作とバインドが相互に影響するため、次の点だけは意識しておくと安全です。

  • ItemsSourceが空のときにPositionを進めない(範囲外になりやすい)
  • ItemsSourceの差し替え時は、必要なら先にPositionを0へ戻すなど「位置の整合」を取る
  • 無限ループ(Loop=true)と組み合わせる場合は、分析・ログ用途の「実質ページ番号」を別で持つ

縦カルーセルを“気持ちよく”する調整ポイント

縦スワイプが動いたら終わりではなく、実際のアプリでは「読ませ方」「誤操作のしにくさ」「パフォーマンス」が大切になります。縦カルーセルを運用に耐える品質にするための観点をまとめます。

カードの高さは「揃える」か「揃えて見える」ようにする

スナップが気持ち良いUIは、基本的に“高さが一定”です。本文が長い場合でも、カード内はMaxLinesで切り、詳細は別画面で見せるなど、カルーセル上の情報量をコントロールすると操作感が安定します。

余白(Margin/Padding)は、スワイプ体験に直結する

縦方向のカルーセルは、上下の余白が小さすぎると「切り替わった感」が弱くなり、逆に余白が大きすぎると情報密度が落ちます。まずはカードにMargin="0,8"程度の余白を入れて、実機でスワイプ感を確認するのがおすすめです。余白をLinearItemsLayoutのItemSpacingで制御したい場合は、VerticalListから移行して調整するとよいでしょう。

画像や重い描画は、テンプレートに入れすぎない

CarouselViewはスワイプ中に複数アイテムが描画されるため、画像を多用するとフレーム落ちしやすくなります。サムネイルは事前に縮小したものを使う、必要なら表示時に遅延ロードする、影や角丸は最小限にするなど、セルを軽く保つ工夫が効きます。

現在位置(Position/CurrentItem)と連動させる

「現在表示中のカードに合わせて説明文を変える」「特定のページにジャンプする」「最後まで読んだら次へ促す」といった要件はよくあります。CarouselViewには位置変更イベントが用意されているため、必要になったら連動処理を追加できます。

void OnPositionChanged(object sender, PositionChangedEventArgs e)
{
    // e.CurrentPosition が現在のインデックス
    // ログ、分析、ページングロードなどの起点に使える
}

イベントを使う場合は、スワイプのたびに重い処理をしないよう注意してください。例えばAPI呼び出しを毎回走らせるのではなく、一定範囲を先読みする、連続イベントを間引くなど、UXと通信のバランスを取ると安定します。

縦カルーセルが向くケース/向かないケース

縦スワイプは直感的ですが、すべての画面に向くわけではありません。導入前に「ユーザーが縦スクロールを期待する画面か」「1枚ずつ見せたい画面か」を整理すると、後戻りが減ります。

目的縦CarouselViewが向く別案が向く
カードを1枚ずつ読ませたい〇(スナップで集中して読める)—
長文を自然にスクロールさせたい△(ページ切り替えがストレスになることがある)ScrollView / CollectionViewの通常スクロール
大量の項目を高速に一覧したい△(1件ずつだと遅い)CollectionView(検索・フィルタと相性が良い)
「次へ」を強制して手順を踏ませたい〇(オンボーディング、チュートリアル)ボタン操作+ステップUI

それでもうまくいかないときの追加チェック

最後に、縦スワイプが効かない・挙動が不安定なときに見落としがちなポイントを補足します。

  • ネストしたスクロール:カルーセルのアイテム内にScrollViewや縦スクロール可能な一覧を入れると競合しやすい
  • レイアウトの測定:StackLayoutに入れて高さが確定しないと、期待したスナップになりにくいことがある
  • データ更新のタイミング:ItemsSourceを差し替えるタイミングで位置が跳ぶことがあるため、更新はUIスレッドで行い、必要なら現在位置を保持する

縦カルーセルは、横カルーセルよりも「画面全体の縦スクロール」と競合しやすいのが難点です。その分、画面設計(どこをスクロールさせるか)と高さの設計を先に決めておくと、実装が一気に楽になります。

まとめ:最短はVerticalList、こだわるならSnapPointsまでセット

  • 縦スワイプしたいだけなら、ItemsLayout="VerticalList"が最短
  • 細かい調整が必要なら、LinearItemsLayout+SnapPointsでカルーセル感を維持する
  • 効かない場合は、親のScrollViewなどジェスチャー競合を疑い、Gridで領域を分ける

この3点を押さえるだけで、Xamarin.FormsのCarouselViewでも縦方向のカルーセルUIを安定して実装できます。要件が増えても、VerticalListを起点にして段階的に調整していくと、安全に“気持ち良い縦スワイプ”に仕上げられます。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次