본문 바로가기

일::개발

Flutter: 버튼이 튀어나온 ModalBottomSheet 만들기

ModalBottomSheet 의 경계 위로 튀어나온 버튼

위와 같은 UI를 만들어야 하게 되었다.

 

StackOverflow 검색해보면 Stack 안에 Positioned를 써서 만드는 방법이 나오는데, 대략 다음과 같다.

  Widget getModalBottomSheet(context, Article event) {
    return Stack(
      clipBehavior: Clip.none,
      alignment: Alignment.topCenter,
      children: [
        Container(
          decoration: BoxDecoration(
            color: Colors.white,
            boxShadow: const [
              BoxShadow(
                color: Color(0xff888888),
                blurRadius: 8.0,
                spreadRadius: 2.0,
              ),
            ],
          ),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              SizedBox(height: 42.0),
              Padding(
                padding: EdgeInsets.symmetric(horizontal: 41.5),
                child: Text('테스트 이벤트'),
              ),
              SizedBox(height: 20),
              Padding(
                padding: EdgeInsets.symmetric(horizontal: 41.5),
                child: Text('이것은 테스트 이벤트 입니다. 신나죠?'),
              ),
              
              // 하단 검은 줄은 코드 생략
              //.........
              
            ],
          ),
        ),

        // center X
        Positioned(
          top: -20,
          child: Container(
            width: 40,
            height: 40,
            padding: EdgeInsets.all(10),
            decoration: BoxDecoration(
              color: Colors.black,
              borderRadius: BorderRadius.circular(19.75),
            ),
            child: GestureDetector(
              onTap: () => Get.back(),
              child: SvgPicture.asset(
                ImageAssets.iconX,
                color: Colors.white,
              ),
            ),
          ),
        )
      ],
    );
  }
  
  ......
  
  showModalBottomSheet(context: context, builder: (BuildContext context) {
  	return getModalBottomSheet(context);
  });

중요한 부분은 showModalBottomSheet()의 builder 에 필요한 위젯을 Stack으로 만들어주는 것이고,

이 Stack의 clipBehavior 프로퍼티를 Clip.none 으로 설정하고, 버튼을 Positioned(top: -20) (버튼 높이의 1/2) 로 감싸주는 것이다.

 

이렇게 하면 모양은 원하는대로 나오지만, 약간 문제가 있는데, X 버튼의 하단(Stack에 포함되어 있는 부분)을 터치하면 제대로 동작하는데, Stack 범위에 포함되어 있지 않은 상단 반을 터치했을 때 이벤트가 발생하지 않는다.

 

X 버튼의 상단은 터치 이벤트 발생하지 않음

이것은 아래와 같이 Stack 에는 top margin을 버튼 높이의 1/2만큼 준 Container와 버튼을 넣어주어서 해결한다.

 Widget getModalBottomSheet(context, Article event) {
    return Container(
      alignment: Alignment.bottomCenter,
=
      child: Stack(
        alignment: Alignment.topCenter,
        children: [
        
          // top margin을 가진 Container
          Container(
            margin: EdgeInsets.only(top: 20),
            decoration: BoxDecoration(
              color: Colors.white,
              boxShadow: const [
                BoxShadow(
                  color: Color(0xff888888),
                  blurRadius: 8.0,
                  spreadRadius: 2.0,
                ),
              ],
            ),
            width: MySizerUtil.screenWidth,
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                SizedBox(height: 42.0),
                Padding(
                  padding: EdgeInsets.symmetric(horizontal: 41.5),
                  child: Text('테스트 이벤트'),
                ),
                SizedBox(height: 20),
                Padding(
                  padding: EdgeInsets.symmetric(horizontal: 41.5),
                  child: Text('이것은 테스트 이벤트 입니다. 신나죠?'),
                ),
             	
              ],
            ),
          ),
          
          // Positioned를 제거해도 된다.
          Container(
            width: 39.5,
            height: 39.5,
            padding: EdgeInsets.all(10),
            decoration: BoxDecoration(
              color: Colors.black,
              borderRadius: BorderRadius.circular(19.75),
            ),
            child: GestureDetector(
              onTap: () => closeBottomNotice(event.id.toString()),
              child: SvgPicture.asset(
                ImageAssets.iconX,
                color: Colors.white,
              ),
            ),
          ),
        ],
      ),
    );
  }
}

이렇게 하면 아래 그림처럼 상단에 흰 여백이 생기는데, showModalBottomSheet() 호출할 때 backgroundColor 파라미터를 주면 해결된다.

showModalBottomSheet(context: context, 
    backgroundColor: Colors.transparent,	// 이게 중요하다.
  	builder: (BuildContext context) {
  	  return getModalBottomSheet(context);
  });

원하는 모양, 동작이 나왔다.