TEXTBOOK SECTION / AI LEARNING

【横スクロールUI】Popular・Trending・Top10風の作品一覧を作る

Flutterアプリケーション開発概論の「NETFLIX風動画アプリを作りながらUI・検索・動画再生・共有機能を学ぶFlutter(iOS・Android)アプリ開発」より、【横スクロールUI】Popular・Trending・Top10風の作品一覧を作るを解説。生成AI、AI活用、DX、業務改善を実践しながら学べるオンライン教材です。

15NETFLIX風動画アプリを作りながらUI・検索・動画再生・共有機能を学ぶFlutter(iOS・Android)アプリ開発Flutter / iOS / Android / MacOS / Windows / 基礎から学ぶ / 開発 / アプリ開発

OVERVIEW

この節で学べること

概要を表示する
項目内容
教材名Flutterアプリケーション開発概論
NETFLIX風動画アプリを作りながらUI・検索・動画再生・共有機能を学ぶFlutter(iOS・Android)アプリ開発
【横スクロールUI】Popular・Trending・Top10風の作品一覧を作る
カテゴリFlutter / iOS / Android / MacOS / Windows / 基礎から学ぶ / 開発 / アプリ開発
学習内容生成AI、AI活用、DX、業務改善を実践しながら理解するための教材です。

TABLE OF CONTENTS

目次

CONTENT

ここから

この節で学ぶこと

前の節では、メニューアイコンをタップしたときに、画面下からメニューを表示する方法を学びました。

今回の節では、Home画面に表示されている 横スクロールの作品一覧 を作る仕組みを見ていきます。

動画アプリでは、作品が横にずらっと並んでいて、指で左右にスクロールできるUIをよく見かけます。

今回のアプリでも、Home画面に次のような作品一覧があります。

Popular on NETAFLIX
Trending Neta
Top 10 TV Shows in Japan Today
Only on NETAFLIX

これらは、ContentRow という部品を使って作っています。

この節では、次の流れで学びます。

作品一覧のタイトルを表示する
↓
作品カードを横に並べる
↓
ListViewで横スクロールできるようにする
↓
MoviePosterCardでポスター画像を表示する
↓
Top10風にランキング番号を重ねる
↓
タップしたら作品詳細画面へ移動する

横スクロールUIの全体像

Home画面では、複数の作品一覧を縦に並べています。

コードでは、次のように ContentRow を何度も使っています。

SliverToBoxAdapter(
  child: ContentRow(
    title: selectedCategory == HomeCategory.all
        ? 'Popular on NETAFLIX'
        : 'Popular ${selectedCategory.label}',
    items: items,
    large: false,
  ),
),
SliverToBoxAdapter(
  child: ContentRow(
    title: selectedCategory == HomeCategory.all
        ? 'Trending Neta'
        : 'Trending ${selectedCategory.label}',
    items: [...items.reversed],
    large: false,
  ),
),
SliverToBoxAdapter(
  child: ContentRow(
    title: selectedCategory == HomeCategory.all
        ? 'Top 10 TV Shows in Japan Today'
        : 'Top ${selectedCategory.label} Today',
    items: items,
    large: true,
    showRank: true,
  ),
),

ここでは、同じ ContentRow を使いながら、タイトルや表示する作品、カードの大きさ、ランキング番号の有無を変えています。

つまり、ContentRow は横スクロール作品一覧を作るための共通部品です。


ContentRowとは?

ContentRow は、作品一覧の1行を作るWidgetです。

class ContentRow extends StatelessWidget {
  const ContentRow({
    super.key,
    required this.title,
    required this.items,
    this.large = false,
    this.showRank = false,
  });

  final String title;
  final List<MovieItem> items;
  final bool large;
  final bool showRank;

受け取っている値は、次の4つです。

役割
title作品一覧の見出し
items表示する作品リスト
largeカードを大きめにするかどうか
showRankランキング番号を表示するかどうか

この4つを変えることで、同じ部品からいろいろな作品一覧を作れます。


titleで見出しを変える

title は、作品一覧の見出しです。

たとえば、次のように渡しています。

ContentRow(
  title: 'Popular on NETAFLIX',
  items: items,
  large: false,
),

この場合、画面には次のような見出しが表示されます。

Popular on NETAFLIX

別の場所では、次のように使っています。

ContentRow(
  title: 'Trending Neta',
  items: [...items.reversed],
  large: false,
),

この場合は、見出しが Trending Neta になります。

このように、ContentRow は中身を変えながら何度も使えるように作られています。


itemsで表示する作品を渡す

items には、表示する作品リストを渡しています。

items: items,

この items は、Home画面で選ばれているカテゴリに合わせた作品リストです。

たとえば、Shows タブが選ばれているときは、Showsカテゴリの作品だけが入ります。

Movies タブが選ばれているときは、Moviesカテゴリの作品だけが入ります。

つまり、ContentRow は自分でカテゴリを判断しているわけではありません。

親から渡された作品リストを、そのまま横に並べています。


items.reversedとは?

Trendingの行では、次のように書かれています。

items: [...items.reversed],

items.reversed は、リストの順番を逆にする処理です。

たとえば、元のリストが次の順番だったとします。

Squid Game
Stranger Things
Money Heist

items.reversed を使うと、次の順番になります。

Money Heist
Stranger Things
Squid Game

[...] で囲んでいるのは、逆順にしたものを新しいリストとして扱うためです。

[...items.reversed]

こうすることで、PopularとTrendingで同じ作品を使いながら、表示順だけ変えています。


largeとshowRankの役割

ContentRow には、largeshowRank があります。

this.large = false,
this.showRank = false,

これは、指定しなければ false になるという意味です。

Top10風の一覧では、次のように指定しています。

ContentRow(
  title: 'Top 10 TV Shows in Japan Today',
  items: items,
  large: true,
  showRank: true,
),

ここでは、次のような意味になります。

large: true → カードを少し大きくする
showRank: true → ランキング番号を表示する

PopularやTrendingの行では、ランキング番号はいらないので、showRank は指定していません。


ContentRowのbuildを見てみよう

ContentRow の見た目は、次のように作られています。

@override
Widget build(BuildContext context) {
  final itemWidth = large ? 132.0 : 116.0;
  final itemHeight = large ? 190.0 : 164.0;

  return Padding(
    padding: const EdgeInsets.only(bottom: 24),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        RowTitle(title: title),
        const SizedBox(height: 10),
        SizedBox(
          height: itemHeight,
          child: ListView.separated(
            padding: const EdgeInsets.symmetric(horizontal: 12),
            scrollDirection: Axis.horizontal,
            itemCount: items.length,
            separatorBuilder: (context, index) => const SizedBox(width: 9),
            itemBuilder: (context, index) {
              final movie = items[index];

              return MoviePosterCard(
                movie: movie,
                width: itemWidth,
                height: itemHeight,
                rank: showRank ? index + 1 : null,
              );
            },
          ),
        ),
      ],
    ),
  );
}

少し長いですが、流れはシンプルです。

カードのサイズを決める
↓
見出しを表示する
↓
余白を入れる
↓
ListViewで作品カードを横に並べる

カードのサイズを切り替える

最初に、カードの横幅と高さを決めています。

final itemWidth = large ? 132.0 : 116.0;
final itemHeight = large ? 190.0 : 164.0;

これは、largetrue かどうかでサイズを変えています。

large高さ
false116.0164.0
true132.0190.0

つまり、Top10風の行ではカードが少し大きくなります。

large: true,

PopularやTrendingの行では、通常サイズです。

large: false,

三項演算子を確認しよう

この書き方をもう少し見てみましょう。

final itemWidth = large ? 132.0 : 116.0;

これは、次の意味です。

largeがtrueなら132.0
largeがfalseなら116.0

このような書き方を、三項演算子といいます。

短く条件分岐を書きたいときに便利です。

もし普通の if 文で書くなら、次のようなイメージです。

double itemWidth;

if (large) {
  itemWidth = 132.0;
} else {
  itemWidth = 116.0;
}

三項演算子を使うと、これを1行で書けます。


Paddingで行の下に余白を作る

ContentRow 全体は、Padding で包まれています。

return Padding(
  padding: const EdgeInsets.only(bottom: 24),
  child: Column(
    ...
  ),
);

ここでは、下に 24 の余白を入れています。

padding: const EdgeInsets.only(bottom: 24),

横スクロールの行がいくつも続くので、行同士がくっつきすぎないようにしています。

UIでは、要素そのものだけでなく、要素と要素の間の余白もとても大切です。


Columnで見出しと作品一覧を縦に並べる

ContentRow の中では、Column を使っています。

child: Column(
  crossAxisAlignment: CrossAxisAlignment.start,
  children: [
    RowTitle(title: title),
    const SizedBox(height: 10),
    SizedBox(
      height: itemHeight,
      child: ListView.separated(
        ...
      ),
    ),
  ],
),

並びはこうです。

見出し
↓
余白
↓
横スクロール作品一覧

Column は、縦に並べるときに使うWidgetです。

見出しと作品一覧を上から下に並べるために使っています。


crossAxisAlignment: CrossAxisAlignment.startとは?

Column には、次の指定があります。

crossAxisAlignment: CrossAxisAlignment.start,

これは、横方向の位置を左寄せにする指定です。

Column は縦に並べるWidgetですが、その中にある要素を横方向にどこへ寄せるかも決められます。

今回の場合、見出しを左寄せにしたいので、CrossAxisAlignment.start にしています。

Popular on NETAFLIX
[作品カード][作品カード][作品カード]

動画アプリの一覧では、見出しは左寄せにすることが多いです。


RowTitleで見出しを表示する

作品一覧の見出しは、RowTitle という部品で表示しています。

RowTitle(title: title),

RowTitle のコードはこちらです。

class RowTitle extends StatelessWidget {
  const RowTitle({
    super.key,
    required this.title,
  });

  final String title;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 14),
      child: Text(
        title,
        style: const TextStyle(
          color: NetflixColors.white,
          fontSize: 18,
          fontWeight: FontWeight.w900,
        ),
      ),
    );
  }
}

見出しの文字色は白、サイズは18、太さはかなり太めです。

color: NetflixColors.white,
fontSize: 18,
fontWeight: FontWeight.w900,

黒背景の上でしっかり見えるようにしています。


SizedBoxで見出しとカードの間に余白を作る

見出しと作品カードの間には、SizedBox で余白を入れています。

const SizedBox(height: 10),

これは、縦方向に10の余白を作る指定です。

もしこの余白がないと、見出しと作品カードが近すぎて窮屈に見えます。

Popular on NETAFLIX
[作品カード]

少し余白があることで、見出しと一覧の関係が分かりやすくなります。


ListViewで横スクロールを作る

横スクロールの中心になるのが、ListView.separated です。

SizedBox(
  height: itemHeight,
  child: ListView.separated(
    padding: const EdgeInsets.symmetric(horizontal: 12),
    scrollDirection: Axis.horizontal,
    itemCount: items.length,
    separatorBuilder: (context, index) => const SizedBox(width: 9),
    itemBuilder: (context, index) {
      final movie = items[index];

      return MoviePosterCard(
        movie: movie,
        width: itemWidth,
        height: itemHeight,
        rank: showRank ? index + 1 : null,
      );
    },
  ),
),

ListView は、リストをスクロール表示するためのWidgetです。

普通に使うと縦スクロールになります。

今回は横にスクロールしたいので、次の指定をしています。

scrollDirection: Axis.horizontal,

これで、作品カードが横に並び、左右にスクロールできるようになります。


なぜSizedBoxで高さを指定するの?

ListView は、表示する範囲の高さが必要です。

そのため、外側を SizedBox で包んでいます。

SizedBox(
  height: itemHeight,
  child: ListView.separated(
    ...
  ),
),

もし高さを指定しないと、Flutterが「このListViewはどれくらいの高さで表示すればいいの?」と迷ってエラーになることがあります。

横スクロールの ListView を作るときは、だいたい高さを指定します。


ListView.separatedとは?

ListView.separated は、リスト項目の間に余白や線を入れやすいListViewです。

通常の ListView.builder では、項目だけを作ります。

ListView.separated では、項目と項目の間のWidgetも作れます。

今回の場合は、カード同士の間に横幅9の余白を入れています。

separatorBuilder: (context, index) => const SizedBox(width: 9),

これにより、作品カード同士がくっつきすぎず、見やすくなります。


paddingで左右の余白を作る

ListView.separated には、左右の余白も入れています。

padding: const EdgeInsets.symmetric(horizontal: 12),

これは、左と右に12ずつ余白を作る指定です。

作品カードが画面の端にぴったりくっつくと、少し窮屈に見えます。

左右に余白を入れることで、見た目にゆとりが出ます。


itemCountで表示する数を決める

itemCount には、作品数を指定しています。

itemCount: items.length,

items.length は、作品リストの数です。

たとえば、items に6作品入っていれば、カードが6枚表示されます。

items.length = 6
↓
作品カードを6枚作る

このように、作品データの数に合わせて、表示されるカード数が自動で変わります。


itemBuilderで作品カードを作る

作品カードを作っているのが itemBuilder です。

itemBuilder: (context, index) {
  final movie = items[index];

  return MoviePosterCard(
    movie: movie,
    width: itemWidth,
    height: itemHeight,
    rank: showRank ? index + 1 : null,
  );
},

index は、今何番目の項目を作っているかを表します。

たとえば、最初のカードなら index0 です。

final movie = items[index];

これで、作品リストから該当する作品データを取り出しています。

その作品データを MoviePosterCard に渡して、カードとして表示しています。


MoviePosterCardとは?

MoviePosterCard は、作品ポスター画像を表示するカードです。

MoviePosterCard(
  movie: movie,
  width: itemWidth,
  height: itemHeight,
  rank: showRank ? index + 1 : null,
);

渡している値は次の通りです。

役割
movie表示する作品データ
widthカードの横幅
heightカードの高さ
rankランキング番号。不要なときはnull

Top10風の行では、showRanktrue なので、ランキング番号が入ります。

rank: showRank ? index + 1 : null,

PopularやTrendingの行では、showRankfalse なので、ranknull になります。


rank: showRank ? index + 1 : null の意味

このコードを見てみましょう。

rank: showRank ? index + 1 : null,

意味はこうです。

showRankがtrueなら、index + 1を渡す
showRankがfalseなら、nullを渡す

index は0から始まります。

でも、ランキング表示は1から始めたいです。

そのため、index + 1 にしています。

index表示したい順位
01
12
23

このように、プログラムの番号は0から始まることが多いですが、人間に見せる順位は1から始めることが多いです。


MoviePosterCardのコードを見てみよう

次に、作品カード本体を見ていきます。

class MoviePosterCard extends StatelessWidget {
  const MoviePosterCard({
    super.key,
    required this.movie,
    required this.width,
    required this.height,
    this.rank,
  });

  final MovieItem movie;
  final double width;
  final double height;
  final int? rank;

rank には int? が使われています。

int? は、整数が入ることもあれば、null になることもあるという意味です。

ランキング番号を表示する行では数字が入ります。

通常の作品一覧では null になります。


タップしたら詳細画面へ移動する

作品カード全体は、GestureDetector で包まれています。

return GestureDetector(
  onTap: () {
    Navigator.of(context).push(
      MaterialPageRoute<void>(
        builder: (context) => MovieDetailPage(movie: movie),
      ),
    );
  },
  child: SizedBox(
    ...
  ),
);

カードをタップすると、MovieDetailPage に移動します。

そのときに、タップした作品データを渡しています。

MovieDetailPage(movie: movie)

つまり、どのカードを押したかによって、詳細画面に表示される作品が変わります。

Squid Gameのカードを押す
↓
Squid Gameの詳細画面

Wednesdayのカードを押す
↓
Wednesdayの詳細画面

SizedBoxでカードの大きさを決める

カードの外側は、SizedBox で大きさを決めています。

child: SizedBox(
  width: rank == null ? width : width + 18,
  height: height,
  child: Stack(
    children: [
      ...
    ],
  ),
),

ここで、ランキング番号があるときだけ横幅を少し広くしています。

width: rank == null ? width : width + 18,

ランキング番号はポスターの左側に重ねて表示するため、少し余分な幅が必要です。

そのため、rank があるときだけ width + 18 にしています。


Stackで番号と画像を重ねる

作品カードの中でも Stack を使っています。

child: Stack(
  children: [
    Positioned(
      right: 0,
      top: 0,
      bottom: 0,
      child: ClipRRect(
        borderRadius: BorderRadius.circular(6),
        child: Image.network(
          movie.posterUrl,
          width: width,
          height: height,
          fit: BoxFit.cover,
        ),
      ),
    ),
    if (rank != null)
      Positioned(
        left: 0,
        bottom: -9,
        child: Text(
          '$rank',
          ...
        ),
      ),
  ],
),

ここでは、ポスター画像とランキング番号を重ねています。

ランキング番号
その右にポスター画像

Top10風のデザインでは、大きな数字とポスターを少し重ねることで、ランキングらしい見た目になります。


ポスター画像を表示する

ポスター画像は、Image.network で表示しています。

Image.network(
  movie.posterUrl,
  width: width,
  height: height,
  fit: BoxFit.cover,
)

movie.posterUrl は、作品データの中にある縦長画像URLです。

posterUrl:
    'https://image.tmdb.org/t/p/w500/dDlEmu3EZ0Pgg93K2SVNLCjCSvE.jpg',

横スクロールの作品カードでは、縦長のポスター画像を使うときれいに見えます。


ClipRRectでカードを角丸にする

ポスター画像は、ClipRRect で包まれています。

ClipRRect(
  borderRadius: BorderRadius.circular(6),
  child: Image.network(
    ...
  ),
),

ClipRRect は、画像を角丸に切り抜くためのWidgetです。

borderRadius: BorderRadius.circular(6),

角丸を少し入れることで、カードらしい見た目になります。

動画アプリ風のUIでは、角丸を控えめにすると落ち着いた印象になります。


ランキング番号を表示する

rank があるときだけ、ランキング番号を表示します。

if (rank != null)
  Positioned(
    left: 0,
    bottom: -9,
    child: Text(
      '$rank',
      style: const TextStyle(
        color: Colors.black,
        fontSize: 84,
        fontWeight: FontWeight.w900,
        height: 1,
        shadows: [
          Shadow(
            color: Colors.white,
            blurRadius: 1.6,
            offset: Offset(1.1, 1.1),
          ),
          Shadow(
            color: Colors.white,
            blurRadius: 1.6,
            offset: Offset(-1.1, -1.1),
          ),
        ],
      ),
    ),
  ),

if (rank != null) と書くことで、rank がある場合だけ表示されます。

通常の作品一覧では ranknull なので、番号は出ません。

Top10風の一覧だけ、番号が表示されます。


‘$rank’とは?

ランキング番号を表示するときに、次のように書いています。

'$rank'

これは、数字を文字として表示するための書き方です。

rankint、つまり数字です。

Text は文字を表示するWidgetなので、数字を文字列にして渡す必要があります。

'$rank' と書くと、rank の値を文字として使えます。

たとえば、rank1 なら、'1' として表示されます。


shadowsで数字に縁取りをつける

ランキング番号には、shadows が入っています。

shadows: [
  Shadow(
    color: Colors.white,
    blurRadius: 1.6,
    offset: Offset(1.1, 1.1),
  ),
  Shadow(
    color: Colors.white,
    blurRadius: 1.6,
    offset: Offset(-1.1, -1.1),
  ),
],

これは、数字のまわりに白い影をつけるための指定です。

黒い数字だけだと、黒背景の上では見えにくくなります。

そこで、白い影を少し入れて、数字の輪郭が見えるようにしています。

Top10風の大きな数字では、このような影や縁取りがあると読みやすくなります。


まずカスタマイズしてみよう

今回は、作品カードの大きさを変えてみましょう。

次のコードを探してください。

final itemWidth = large ? 132.0 : 116.0;
final itemHeight = large ? 190.0 : 164.0;

通常カードを少し大きくしたい場合は、次のように変更します。

final itemWidth = large ? 132.0 : 126.0;
final itemHeight = large ? 190.0 : 178.0;

保存して、Home画面を確認してください。

PopularやTrendingの作品カードが少し大きくなります。


作品カード同士の間隔を変えてみよう

カード同士の間隔は、次のコードで決まっています。

separatorBuilder: (context, index) => const SizedBox(width: 9),

間隔を広くしたい場合は、次のようにします。

separatorBuilder: (context, index) => const SizedBox(width: 14),

保存して、横スクロールの作品一覧を確認してください。

カード同士の余白が広がります。

余白が広いとゆったり見えますが、一度に見えるカード数は少なくなります。


Top10の数字を小さくしてみよう

ランキング番号の大きさは、次の部分で決まっています。

fontSize: 84,

少し小さくしたい場合は、次のようにします。

fontSize: 72,

数字を大きくすると迫力が出ますが、画面内で少し重く見えることもあります。

作品カードとのバランスを見ながら調整しましょう。


よくあるつまずきポイント

Q. 横スクロールになりません。

scrollDirection が横方向になっているか確認してください。

scrollDirection: Axis.horizontal,

これがない場合、ListViewは基本的に縦方向にスクロールしようとします。

Q. ListViewでエラーが出ます。

横スクロールの ListView は、高さを指定しておくと安定します。

SizedBox(
  height: itemHeight,
  child: ListView.separated(
    ...
  ),
),

SizedBox で高さを指定しているか確認してください。

Q. 作品カードが表示されません。

items に作品データが入っているか確認してください。

itemCount: items.length,

items.length0 だと、カードは表示されません。

また、作品画像が表示されない場合は、posterUrl が正しいか確認してください。

Q. Top10の番号が表示されません。

ContentRowshowRank: true が指定されているか確認してください。

ContentRow(
  title: 'Top 10 TV Shows in Japan Today',
  items: items,
  large: true,
  showRank: true,
),

また、MoviePosterCardrank が渡されているかも確認します。

rank: showRank ? index + 1 : null,

Q. カードを押しても詳細画面に移動しません。

MoviePosterCard の中で GestureDetectorNavigator.push があるか確認してください。

GestureDetector(
  onTap: () {
    Navigator.of(context).push(
      MaterialPageRoute<void>(
        builder: (context) => MovieDetailPage(movie: movie),
      ),
    );
  },
  child: ...
)

チャレンジ

チャレンジ1:通常カードを少し大きくしよう

次のコードを探してください。

final itemWidth = large ? 132.0 : 116.0;
final itemHeight = large ? 190.0 : 164.0;

これを次のように変更します。

final itemWidth = large ? 132.0 : 126.0;
final itemHeight = large ? 190.0 : 178.0;

PopularやTrendingのカードが大きくなるか確認してください。


チャレンジ2:カード同士の間隔を広げよう

次のコードを探してください。

separatorBuilder: (context, index) => const SizedBox(width: 9),

これを次のように変更します。

separatorBuilder: (context, index) => const SizedBox(width: 14),

カード同士の間隔が広くなるか確認しましょう。


チャレンジ3:Top10の数字を小さくしよう

次のコードを探してください。

fontSize: 84,

これを次のように変更します。

fontSize: 72,

ランキング番号の見え方がどう変わるか確認してください。


チャレンジ4:新しい作品一覧を追加しよう

Home画面の CustomScrollView の中に、次の ContentRow を追加してみましょう。

SliverToBoxAdapter(
  child: ContentRow(
    title: 'Because You Watched',
    items: items,
    large: false,
  ),
),

保存して、Home画面に新しい作品一覧が追加されるか確認してください。


チャレンジの答え

チャレンジ1の答え

変更前:

final itemWidth = large ? 132.0 : 116.0;
final itemHeight = large ? 190.0 : 164.0;

変更後:

final itemWidth = large ? 132.0 : 126.0;
final itemHeight = large ? 190.0 : 178.0;

通常の作品カードが少し大きくなります。


チャレンジ2の答え

変更前:

separatorBuilder: (context, index) => const SizedBox(width: 9),

変更後:

separatorBuilder: (context, index) => const SizedBox(width: 14),

カード同士の間隔が広くなります。


チャレンジ3の答え

変更前:

fontSize: 84,

変更後:

fontSize: 72,

Top10風のランキング番号が少し小さくなります。


チャレンジ4の答え

追加するコードはこちらです。

SliverToBoxAdapter(
  child: ContentRow(
    title: 'Because You Watched',
    items: items,
    large: false,
  ),
),

既存の ContentRow の下に追加すると、Home画面に新しい横スクロール一覧が増えます。


この節のまとめ

この節では、Home画面にPopular・Trending・Top10風の横スクロール作品一覧を作る方法を学びました。

大切なポイントは次の通りです。

  • 横スクロールの作品一覧は、ContentRow で作っている。
  • title を変えると、一覧の見出しを変えられる。
  • items に作品リストを渡すと、その作品が横に並ぶ。
  • large を使うと、カードサイズを切り替えられる。
  • showRank を使うと、Top10風のランキング番号を表示できる。
  • ListView.separated を使うと、横スクロールのリストと項目間の余白を作れる。
  • scrollDirection: Axis.horizontal で横スクロールになる。
  • 横スクロールListViewでは、SizedBox で高さを指定すると安定する。
  • MoviePosterCard は、作品ポスターを表示するカード。
  • GestureDetectorNavigator.push を使うと、カードタップで詳細画面に移動できる。
  • Stack を使うと、Top10風の大きな番号とポスター画像を重ねられる。
  • ClipRRect を使うと、ポスター画像を角丸にできる。

次のステップ

次の節では、作品カードをタップしたときに表示される 作品詳細画面 を見ていきます。

作品タイトル、説明文、メタ情報、Playボタン、Downloadボタン、Shareボタンなどをどう配置しているのかを学びます。

Home画面から詳細画面へ作品データを渡す流れも、もう少し詳しく確認していきましょう。

FAQ

よくある質問

【横スクロールUI】Popular・Trending・Top10風の作品一覧を作るは医療関係者向けだけの内容ですか。
医療分野の例が含まれる場合もありますが、医療関係者だけに限定した内容ではありません。生成AI、AI活用、DX、業務改善、プロトタイプ開発など、一般的なAI学習の事例として読める内容です。
AI初心者でも読めますか。
はい。AIをこれから学ぶ方、数学が苦手な方、仕事でAIを使いたい方にも読み進めやすいように、教材の章と節の流れに沿って整理しています。
サムネイル画像は必ず表示されますか。
はい。教材にcoverUrlが設定されている場合はその画像を表示し、未設定の場合は代替サムネイル画像を表示します。
Flutterアプリケーション開発概論のほかの章も読めますか。
はい。教材トップから章立てを確認でき、前後の節へもページ下部のナビゲーションから移動できます。