Flutter: Stackで下層ウィジェットからはみ出た部分をタップできない

※当サイトは、アフィリエイト広告を利用しています

結論:下層ウィジェットを、Align、Center、Positionedいずれかでラップする 

2022/8/5  Flutter エラー・バグ日記

 

「Stack」を使い、ウィジェットの上に、「GestureDetector」でタップ処理を入れたウィジェットを重ねたところ、下層ウィジェットからはみ出た部分がタップできない(反応しない)事に気づいた。

 

本記事はライトな日記思考で書いているので、詳細説明はしておらず、基本、テキストのみで画像とかはあまり載せておりません。。m(_ _)m

解説記事ではないため、解決していない内容や、その時々の間違った解釈を述べてしまっている可能性が大いにありますので、何卒、ご了承ください。

 

発生した状況

 

下層のウィジェット(グレーのContainer)をはみ出した部分はタップできない

 

「Stack」部分のコードは以下のとおり。

 

body: Stack(
        // ↓はみ出た部分を表示させるには、これが必要
        clipBehavior: Clip.none,

        children: [

          /// 下層ウィジェット
          Container(
             width: 100,
             height: 100,
             color: Colors.blueGrey,
          ),

          /// 上層ウィジェット
          Positioned(
            top: 50,
            left: 50,
            child: GestureDetector(
              onTap: () {
                print("タップされた");
              },
              child: Container(
                width: 100,
                height: 100,
                color: Colors.lightBlueAccent.withOpacity(0.5),
              ),
            ),
          ),
        ],
      ),

 

そもそも当初、はみ出た部分が表示自体されなかったので、調べたところ、「Stack」の「clipBehavior:」属性に、「Clip.none」を設定すれば表示できる(※)と分かった。

 

※この部分が分かるまで苦労した。。「Stack」では、上層ウィジェットが、下層ウィジェットの範囲をはみ出すと、その部分は表示しないのがデフォルトらしい。

 

タップできない問題については、調べたところ、多くの情報があった。

 

 

 

1つ目の記事では、解決方法が提示されているものの、既存の「Stack」の拡張クラスを自作するという方法で、かなり難解な印象。

 

さらに調べると、GitHubにもイシューがあったが、オープン中の別のイシューに引き継がれ、まだ解決していない模様(若干論点が変わってしまっている気もするが、、)。

 

 

下層を「Center」でラップしたら解消

自分なりに試行錯誤したところ、下層のウィジェットを「Center」でラップすると、「clipBehavior: Clip.none」を設定しなくても、はみ出た部分が表示されることに気づいた。

 

そして、はみ出た部分のタップも普通に機能していた。

 

「Center」クラスは、「Align」クラスの拡張クラスなので、今度は下層のウィジェットを「Align」でラップして、「alignment: Alignment(-1, -1)」(画面左上の位置)を設定したところ、やはりはみ出た部分のタップが有効になった。

 

下層のウィジェット(グレーのContainer)をAlignでラップしたら、はみ出た部分もタップ有効になった

 

タップできるようになった理由

「Align」クラスの公式サイト説明

 

 

を見ると、

 

widthFactor と heightFactor が NULL の場合、可能な限り大きくなる

 

と書かれていた。

 

同じ内容が、「Center」クラスの公式説明にも書かれていた。 

 

 

「widthFactor」 と「heightFactor」は、子ウィジェットの幅を制限する係数らしい。

 

つまり、「widthFactor」 と「heightFactor」を設定しなければ、「Align」や「Center」のウィジェットは、画面全体に広がるため、その上にStackした(重ねた)ウィジェットは、下層に対して、はみ出た部分が無いことになり、全ての範囲がタップ可能になった、ということだと理解。

 

実際、「widthFactor」 と「heightFactor」を1以下の値で設定すると、下層ウィジェットの範囲外(はみ出た部分)は、タップできなくなった

 

タップできる場合・できない場合(その他の方法)

他の方法も調べてみたところ、結果は、以下のとおりだった。

 

■下層からはみ出た部分をタップできる場合

 

下層ウィジェットを

  • 「Align」(「Center」含む)でラップした場合
  • 「Positioned」でラップした場合

 

※「Positioned」の公式説明を確認したが、「Align」のように画面全体にウィジェットの範囲が広がるのか否か、よく分からなかった、、、。

 

■下層からはみ出た部分をタップできない場合

 

下層ウィジェットを

  • ラップしない場合
  • 「Transform.translate」でラップした場合

 

下層を「Align」か「Positioned」でラップする

とりあえず、「Stack」しているウィジェットに「タップ処理」を入れたいときは、下層ウィジェットを「Align」か「Positioned」でラップする、ことを意識しようと思う。

 

個人的には、「Positioned」の方が使いやすいので、こっちを使いたいと思った。

 

※以下に、今回検証したコードをご参考に掲載します(コメントアウトの箇所を変更すると、違いを検証いただけます)。

 

// クラス名、メソッド名、プロパティ名(変数名)について、筆者が作成したもの(名前変更可のもの)
// の名前の末尾には、大文字のオー「O」をつけています
// ※ライブラリ(パッケージ)で予め決められているもの(名前の変更不可のもの)と、
//  自分で作成したもの(名前の変更可のもの)の区別をしやすくするため

import 'package:flutter/material.dart';

void main() => runApp(MyApp());



class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "Test",
      theme: ThemeData.light(),
      home: SampleScreenO(),
    );
  }
}

class SampleScreenO extends StatefulWidget {
  @override
  _SampleScreenOState createState() => _SampleScreenOState();
}

class _SampleScreenOState extends State<SampleScreenO> {
  @override
  Widget build(BuildContext context) {
    return  Scaffold(
      appBar: AppBar(),
      body: Stack(
        clipBehavior: Clip.none,
        children: [

          /// 下層ウィジェット

          // はみ出た部分のタップが無効になるケース(1)
          // Container(
          //   width: 100,
          //   height: 100,
          //   color: Colors.blueGrey,
          // ),

          // はみ出た部分のタップが無効になるケース(2)
          // Transform.translate(
          //   offset: Offset(0, 0),
          //   child: Container(
          //     width: 100,
          //     height: 100,
          //     color: Colors.blueGrey,
          //   ),
          // ),

          // はみ出た部分のタップが有効になるケース(1)
          Positioned(
            left: 0,
            top: 0,
            child: Container(
              width: 100,
              height: 100,
              color: Colors.blueGrey,
            ),
          ),

          // はみ出た部分のタップが有効になるケース(2)
          // Align(
          //   alignment: Alignment(-1, -1),
          //   // ↓これを1以下で設定すると、はみ出た部分はタップできなくなる
          //   // widthFactor: 1,
          //   // heightFactor: 1,
          //   child: Container(
          //     width: 100,
          //     height: 100,
          //     color: Colors.blueGrey,
          //   ),
          // ),

          /// 上層ウィジェット
          Positioned(
            top: 50,
            left: 50,
            child: GestureDetector(
              onTap: () {
                print("タップされた");
              },
              child: Container(
                width: 100,
                height: 100,
                color: Colors.lightBlueAccent.withOpacity(0.5),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

 

 

\一般的なエラー対処法をまとめた記事はこちら/

 

リリースしたアプリ(全てFlutterで開発)

 

個人アプリ開発で役立ったもの

おすすめの学習教材

超初心者向けでオススメな元Udemyの講座/

 

 \キャンペーン時を狙えば安価で網羅的な内容が学べる(日本語訳あり)/

 

\Gitの基礎について無料で学べる/

 

おすすめの学習書籍

実用的image_pickerに関してかなり助けられた/

 

Dartの基礎文法を素早くインプットできる/


Dart入門 - Dartの要点をつかむためのクイックツアー

タイトルとURLをコピーしました