Flutter Stackを使った重ね合わせレイアウトの基本と実践例

モバイル開発

はじめに

FlutterではUIを自由にカスタマイズしやすい反面、レイアウト方法の種類が多くて少し戸惑ってしまうかもしれません。

とくに複数のウィジェットを重ね合わせるレイアウトは、直感的に実装しにくい印象を持つ方もいるのではないでしょうか。

そこで登場するのがStackです。

Widgetを文字どおり「スタック(積み重ね)」させ、座標や上下関係を管理しながら配置するのに適しています。

この仕組みを理解すると、透過な背景の上にテキストを浮かせたり、画像の一部にボタンを重ねるといったことが簡単にできるようになります。

今回は、このFlutter Stackの基本的な考え方と実務での活用シーンについて、できるだけわかりやすく説明していきます。

また、具体的なコード例を挙げながら、初心者でもステップを踏んで使い方を学べるように進めていきます。

初めてFlutterに触れる方や、Stackの活用に自信のない方もぜひ読み進めてみてください。

この記事を読むとわかること

  • Flutter Stackの基本的な役割とレイアウトの考え方
  • 実務での活用シーンと注意点
  • 具体的なコード例を使ったUI要素の重ね合わせ方法

FlutterにおけるStackの位置づけ

Flutterには、多種多様なレイアウトウィジェットがあります。

代表的なものとして、ColumnRowのように子ウィジェットを縦・横に並べるウィジェットが挙げられます。

これに対してStackは、「同じ画面上でウィジェットを重ね合わせる」ことを目的としたウィジェットです。

たとえば、背景全体に画像を敷き、その上にテキストを配置する場合にStackが役立ちます。

縦や横の配置ではなく「Z軸方向」の重なりを管理したいときに便利だと言えるでしょう。

ただし、Z軸方向にWidgetを配置できるがゆえに、Widget同士の重なり順や座標指定のルールを理解する必要があります。

そうしないと意図したレイアウトにならなかったり、ユーザーの操作対象が誤って別のWidgetに吸収されることがあるかもしれません。

一方で、Stackの特性を生かすと、複雑なビジュアル表現やダイナミックなUIの演出を実装しやすくなります。

次のセクションでは、Stackの特徴と基本的な使い方を詳しく見ていきます。

Stackの基本構造とプロパティ

Stackウィジェットは、その名のとおり「子ウィジェットを積み重ねる」ためのコンテナです。

いくつかの主なプロパティがありますが、ここでは代表的なものに絞って説明してみます。

alignment

子ウィジェット全体をどの位置に揃えるかを指定します。 例として Alignment.centerAlignment.topLeft などを使うと、Stackの領域内で子ウィジェットを一括で揃えることができます。

clipBehavior

子ウィジェットがStackの領域からはみ出した場合に、どのように表示をクリップするかを指定します。 たとえば Clip.none にすればはみ出した部分も表示されますし、 Clip.hardEdge にすれば領域外は表示されません。

fit

子ウィジェットがStackの領域に対してどのようにフィットするかを指定します。 StackFit.expand を使うと、子ウィジェットがStackをいっぱいに埋め尽くすように配置されます。

これらのプロパティを適宜組み合わせると、思い通りにウィジェットを重ね合わせることができるでしょう。

ただし、単純にStackの子要素を並べただけでは「重なった中でどの位置に表示されるか」という制御が難しいと感じるかもしれません。

そこで重要になってくるのが、Positionedウィジェットです。

Positionedウィジェットを使った座標指定

Stackの子ウィジェットを直接並べると、Stackで指定したalignmentに従って配置が決まります。

しかし、それだけでは「背景となる画像を全面に表示して、その右下にボタンを重ねる」というような細かな座標指定がやりにくい場合があります。

そこでよく使われるのがPositionedです。

PositionedはStackの子ウィジェットを明示的に上下左右からの距離で配置できます。

具体的には、次のようなプロパティを用います。

left, top, right, bottom

Stackの左端、上端、右端、下端からの相対位置を指定します。

以下にシンプルなコード例を示します。

import 'package:flutter/material.dart';

class StackSample extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: [
          // 背景となる画像
          Container(
            width: double.infinity,
            height: double.infinity,
            decoration: BoxDecoration(
              image: DecorationImage(
                image: AssetImage('assets/background.jpg'),
                fit: BoxFit.cover,
              ),
            ),
          ),
          // 右下に表示するボタンをPositionedで配置
          Positioned(
            right: 16,
            bottom: 16,
            child: ElevatedButton(
              onPressed: () {
                // ボタンの動作
              },
              child: Text('Tap Me'),
            ),
          ),
        ],
      ),
    );
  }
}

この例では、Stackの中に背景画像とボタンを重ねています。

背景画像は画面全体に広がるように設定し、その上にボタンを右下に固定するイメージです。

Positionedウィジェットを使うことで、「Stackの右端から何ピクセル離れた位置に置く」といった明示的な座標指定が可能になります。

この指定方法は、直感的に「ここから何ピクセル」といった形で理解できるため、デザイン案に合わせて配置を微調整しやすいです。

ただし、デバイスの画面サイズが異なると、同じ指定でも見え方が変わることがあります。

相対的な位置やレスポンシブ対応を考慮する場合は、レイアウト全体の設計も踏まえたうえで指定方法を調整するといいでしょう。

実務での活用シーン

Stackを使う機会は多岐にわたりますが、特に以下のようなシーンで役立ちます。

1. 背景画像や背景色の上に複数の要素を重ねる

例えば、カードのデザインをする際に、ユーザーのアバターを左上に配置しつつ、右下に小さなアイコンを配置するといったケースがあります。Stackを使えば、一つのコンテナ内で自由にレイアウトを組み合わせられます。

2. バナーやオーバーレイの作成

背景にぼかし(Blur)や半透明のオーバーレイをかけて、その上にテキスト情報を表示するといったデザインで使われることが多いです。まるで「レイヤー」を扱うように画面を設計できるため、情報を強調したり、アニメーションをつける際にも便利です。

3. アプリ内のガイドやチュートリアル画面

ユーザーに対して「ここをタップしてください」というガイドを表示するケースでは、一部を半透明にしながら注目してほしいボタンの上にアニメーションやポインターを重ねることがあります。そのようなUIでもStackがよく活用されます。

4. 画像やイラストを部分的にアニメーションさせる

背景全体を占めるイラストに対して、キャラクターやアイコンを独立して重ねるときにもStackが向いています。たとえば移動アニメーションや回転アニメーションをかける際に、それぞれの要素を別々に扱いやすくなるでしょう。

これらのシーンでは「ただ単にWidgetを並べるだけ」ではなく、「視覚的にどのように重ねるか」といった表現力も求められます。

Stackを使ったレイアウトをうまく活かすことで、デザイナーが考えたイメージを忠実に再現しやすくなる点が大きなメリットと言えます。

Stackを使う際の注意点とベストプラクティス

Stackは柔軟で便利な反面、使い方を誤ると予期しないレイアウト崩れやユーザビリティの低下を招く可能性があります。

ここでは実務でよく遭遇する注意点と、その対策を簡単にまとめてみます。

不要な重なりを避ける

無計画にStackの上に複数の要素を積み重ねすぎると、後からコードを見返したときに「どのWidgetがどこで描画されるのか」がわかりにくくなります。

可読性やメンテナンス性を維持するためにも、必要最小限のWidgetだけをStackで重ねるようにしましょう。

レイアウトの優先順位を意識する

Stackは先に宣言したWidgetが下、後に宣言したWidgetが上に来ます。

もし意図しないWidgetが手前に表示されていたら、宣言順を確認してみると良いでしょう。

あるいは、Positionedを使ったときに複数のWidgetが同じ座標にかぶっている場合、最終的に描画される順番によってレイアウトの見え方が変わることがあります。

タップ領域とヒット判定

StackでWidgetを重ねると、タップ領域が重なる場合があります。

たとえば、下のWidgetをタップしたいのに、上にある透明のWidgetがタップを先に受けてしまうことがあるかもしれません。

その場合、GestureDetectorなどを使う際は、ヒットテストの挙動やWidgetのオパシティに注意する必要があります。

画面サイズの違いへの対応

Positionedでピクセルベースで座標を指定すると、デバイスによっては表示バランスが崩れる可能性があります。

相対的な寸法指定や MediaQuery.of(context).size などを組み合わせるなど、柔軟な方法を検討してみてください。

StackとPositionedを多用しすぎるとレイアウトが複雑化しやすいです。 必要に応じて、他のレイアウトウィジェットやレスポンシブデザインの仕組みも組み合わせることをおすすめします。

こうした注意点を踏まえて、Stackを使えばUI表現の幅が大きく広がります。

次はさらに使いこなすためのアプローチを見ていきましょう。

さらに応用するためのヒント

Stackをただ使うだけでも十分に重ね合わせのUIは作れますが、より洗練された表現にするためにいくつかのアプローチを組み合わせることがあります。

AnimatedPositionedとの併用

StackとPositionedの組み合わせは便利ですが、ウィジェットをアニメーション付きで動かしたいときはAnimatedPositionedを使うとスムーズな演出が可能になります。

たとえば、ボタンをタップしたら要素が右から左に滑り込むような動きを実現したいときに便利です。

AlignやCenterを活用

Stack全体のalignmentを指定するだけでなく、個々のWidgetにAlignCenterウィジェットを組み合わせると、コードがシンプルにまとまることがあります。

Positionedでピクセル単位で調整しなくても、Alignを使って画面中央や任意の配置を柔軟に指定できます。

複数のStackを入れ子にする

一つのStackにWidgetを全て詰め込むのではなく、部分的にStackを使いたいエリアだけに分けて入れ子構造にする方法もあります。

そうすることで、各Stackの役割がより明確になり、複雑な重ね合わせにも対応しやすくなるでしょう。

カスタムウィジェットとして分割

Stackに配置するWidgetが複雑化してきたら、それぞれをカスタムウィジェットに切り出して管理するのがおすすめです。

画面のコード量が減り、可読性が大きく向上しますし、UIパーツを再利用しやすくなります。

個々の要素をしっかり分離しておくと、将来的にデザインを変更するときや、アニメーションを追加するときも楽になります。 Stackで重ねるUIは見た目の自由度が高いので、構造をきちんと分ける意識があると安心です。

サンプルコード:複数の要素を重ねるUI

最後に、Stackを使った少し応用的なUI例を示してみます。

画面の中央に大きめの画像を配置し、右上に小さなテキスト、左下にボタンを重ねるイメージで作ってみましょう。

import 'package:flutter/material.dart';

class MultiStackPage extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Stack Example'),
      ),
      body: Center(
        child: Stack(
          alignment: Alignment.center,
          children: [
            // メインとなる画像
            Container(
              width: 300,
              height: 200,
              decoration: BoxDecoration(
                color: Colors.grey[300],
                image: DecorationImage(
                  image: AssetImage('assets/sample_image.jpg'),
                  fit: BoxFit.cover,
                ),
              ),
            ),

            // 右上に表示するテキスト
            Positioned(
              top: 8,
              right: 16,
              child: Text(
                'Top Right',
                style: TextStyle(
                  color: Colors.white,
                  fontSize: 16,
                  shadows: [
                    Shadow(
                      blurRadius: 4,
                      color: Colors.black,
                      offset: Offset(1, 1),
                    ),
                  ],
                ),
              ),
            ),

            // 左下に配置するボタン
            Positioned(
              bottom: 8,
              left: 16,
              child: ElevatedButton(
                onPressed: () {
                  // ボタンの動作
                },
                child: Text('Click'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

ここで注目すべきポイントは、Stack全体のalignmentAlignment.centerを使いつつ、特定の要素だけPositionedで座標指定している点です。

このように、複数の配置手法をミックスすることで、画面中央に要素を配置しながら、その周辺に別のWidgetを重ねることが簡単にできます。

さらに、子ウィジェット同士の重なり具合を調整したい場合は、コードの宣言順序を変更すればOKです。

最初に書いたWidgetが奥、後に書いたWidgetが手前に来ます。

この仕組みに慣れると、比較的直感的にレイアウトを作れるでしょう。

まとめ

FlutterのStackは、UI要素を重ね合わせるうえで欠かせないウィジェットです。

背景画像や特殊なレイアウトを作る際に、縦・横の単純な配置だけでは表現しきれないケースをうまくカバーしてくれます。

ただし、重ね合わせるWidgetが増えるとレイアウトが複雑になるリスクもあります。

そのため、PositionedやAlignなどを適切に使い分けながら、コードの可読性や保守性を保つよう意識しましょう。

また、タップ領域が重なる場合や画面サイズごとの調整が必要な場合には、Stackの特性を十分に理解したうえで設計を行うことが大切です。

初心者の皆さんはまずシンプルなサンプルから試してみて、Stackでの重なり具合や座標指定の感覚をつかむと良いでしょう。

そして慣れてきたら、デザイナーのアイデアを積極的に取り入れたり、アニメーションを活かした動きのあるUIに発展させてみるのも面白いかもしれません。

Stackを使いこなすことで、Flutterならではの柔軟でリッチなUIを実現できるはずです。

Flutterをマスターしよう

この記事で学んだFlutterの知識をさらに伸ばしませんか?
Udemyには、現場ですぐ使えるスキルを身につけられる実践的な講座が揃っています。