Published on

Stack 위젯의 onTap 인식 오류

Authors

구현

Stack 위젯의 자식으로 Container와 Positioned이 위치하는 일반적인 카드 형태의 UI를 구현했습니다.

Stack 위젯 으로 구현한 카드 UI

문제점

삭제 버튼을 클릭했을 때 이벤트가 정상적으로 실행될 때도 있지만 그렇지 않은 경우도 발생했습니다.

처음에는 Badges 패키지에서 발생한 오류로 생각해서 패키지를 사용하지 않고 구현해보기도 하고 Badge 패키지를 Gesturedetector 위젯으로 감싸기도 했지만 여전히 제대로 작동하지 않았습니다.

여러 시도를 해보았고 다행히 구글링 끝에 에러 원인을 찾았습니다.

Flutter Issue Fixed: onTap Gesture isn’t recognised in Stack if the child widget is outside of its parent’s bound

Stack 위젯의 자식 요소에서 onTap이 작동하려면 Stack 위젯 범위 내에 위치해야한다.

현재 구현한 UI에서는 삭제 버튼의 1/4 가량만이 Stack 위젯에 포함되어 있습니다. 그래서 해당 부분을 클릭했을 때는 이벤트가 정상적으로 작동하지만 이외의 범위를 클릭했을 때는 이벤트가 발생하지 않았던 것입니다.

inspector로 확인한 Stack 위젯의 범위
Stack 위젯의 클릭 이벤트 허용 범위

해결법

DeferPointer 패키지를 이용하여 부모 위젯의 범위와 상관 없이 자식 위젯의 클릭 이벤트를 받을 수 있도록 수정했습니다.

  • 부모 위젯을 DeferredPointerHandler로 감싼다.
  • onTap 이벤트를 실행할 자식 위젯을 DeferPointer로 감싼다.
정상적으로 출력되는 onTap event GIF

최종 코드

import 'package:flutter/material.dart';
import 'package:badges/badges.dart' as badges;
import 'package:defer_pointer/defer_pointer.dart'; // Import the package

class ConsentListWidget extends StatelessWidget {
  const ConsentListWidget({super.key});

  
  Widget build(BuildContext context) {
    return DeferredPointerHandler( // Wrap with DeferredPointerHandler
      child: Stack(
        clipBehavior: Clip.none,
        children: [
          Container(
            width: 160,
            height: 80,
            padding: const EdgeInsets.all(10),
            decoration: BoxDecoration(
                border: Border.all(color: Colors.grey, width: 1),
                borderRadius: BorderRadius.circular(10)),
            alignment: Alignment.center,
            child: const Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text(
                  'Hayden',
                  style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                ),
                Text(
                  '010-1234-5678',
                  style: TextStyle(
                    fontSize: 12,
                  ),
                ),
              ],
            ),
          ),
          Positioned(
            top: -8,
            right: -8,
            child: DeferPointer( // Wrap with DeferPointer
              child: badges.Badge(
                showBadge: true,
                ignorePointer: false,
                onTap: () {
                  debugPrint('Badge tapped');
                },
                badgeContent: const Icon(
                  Icons.close,
                  color: Colors.white,
                  size: 10,
                ),
                badgeStyle: badges.BadgeStyle(
                  shape: badges.BadgeShape.circle,
                  badgeColor: Colors.red,
                  padding: const EdgeInsets.all(5),
                  borderRadius: BorderRadius.circular(5),
                  elevation: 0,
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}