이코노딩

[Flutter Project] 애미티 앱 Getx 적용하기 - Custom 본문

프로젝트/애미티

[Flutter Project] 애미티 앱 Getx 적용하기 - Custom

떼이로 2023. 2. 4. 17:26

😶기존의 Drawer와 DropDown 기능을 Custom하기 힘들어 원하는 디자인을 직접 구현해보기로 하였다.

라이브러리를 가져와 쓰려고 했다가, 간단하게 만들 수 있을 것 같아서 그대로 사용하기로했다.

 

✅CustomDrawer

기존 앱들에서 좌측에 숨겨져있다가 어떠한 액션 이벤트에 반응하여 나오는 서랍 느낌이다.

 버튼을 눌렀을 나오는 더보기 버튼 처럼 만들었다.

 

☑️동작 구상

동작은 간단하게 버튼을 눌렀을 때, 페이지를 이동하는데 애니메이션을 주고 또 이동 옵션 중, opaque를 false로 주어서 이전 화면이 그대로 유지 될 수 있도록하였다.

그리고 Drawer페이지에서는 Stack과 Position위젯으로 위치를 조정해 주었다. 

☑️라우팅 코드

페이지 이동시 애니메이션은 fade가 가장 자연스러웠다.

InkWell(
         onTap: (){
                Get.to(()=>const More(), opaque: false, fullscreenDialog: true, transition: Transition.fade, curve: Curves.linear);
                },
         child: Icon(Icons.more_vert, color: const Color(0xffaecdff), size: 28.r,),
         )

☑️More() - Code

 클래스 이름은 간단하게 More로 만들었다. 상태 변화가 없기 때문에, 변수나 리로드를 해줄 필요가 없다.

GestureDetector( //버튼 외 터치시 페이지를 닫을 수 있도록 전체를 감싸 줌
      onTap: ()=> Get.back(), 
      child: Material( //Material 하위 위젯을 사용해야함
        color: Colors.transparent, // 뒷배경은 투명하게
        child: Stack(
          children: [
            SizedBox( //화면 전영역을 사용하기 위한 공간확복
              width: Get.width,
              height: Get.height,
            ),
            Positioned( //원하는 위치에 버튼 추가하기
                top: 50.h, right: 10.w,
                child: Column(
                  children: [
                    //벌칙 설정 버튼
                    DelayedWidget( 
                      animation: DelayedAnimations.SLIDE_FROM_RIGHT,
                      animationDuration: const Duration(milliseconds: 300),
                      delayDuration: const Duration(milliseconds: 100),
                      child: InkWell(
                        onTap: (){}, //터치시 벌칙 설정 페이지로이동
                        child: Container(
                          alignment: Alignment.center,
                          padding: EdgeInsets.all(20.r),
                          decoration: const BoxDecoration(
                            shape: BoxShape.circle,
                            color: Color(0xffaecdbb)
                          ),
                          child: Column(
                            children: [
                              Icon(Icons.hardware, size: 24.r, color: const Color(0xffffffff),),
                              SizedBox(height: 5.h,),
                              Text('벌칙 설정', style: TextStyle(fontSize: 12.sp, fontWeight: FontWeight.bold, color: const Color(0xffffffff)),)
                            ],
                          ),
                        ),
                      ),
                    ),
                    SizedBox(height: 20.h,),
                    ///커스텀설정 버튼
                    DelayedWidget(
                      animation: DelayedAnimations.SLIDE_FROM_RIGHT,
                      animationDuration: const Duration(milliseconds: 300),
                      delayDuration: const Duration(milliseconds: 200),
                      child: InkWell(
                        onTap: (){},//터치시 커스텀 설정 페이지 이동
                        child: Container(
                          alignment: Alignment.center,
                          padding: EdgeInsets.all(20.r),
                          decoration: const BoxDecoration(
                              shape: BoxShape.circle,
                              color: Color(0xffaecdcc)
                          ),
                          child: Column(
                            children: [
                              Icon(Icons.person, size: 24.r, color: const Color(0xffffffff),),
                              SizedBox(height: 5.h,),
                              Text('커스텀 설정', style: TextStyle(fontSize: 12.sp, fontWeight: FontWeight.bold, color: const Color(0xffffffff)),)
                            ],
                          ),
                        ),
                      ),
                    ),
                    SizedBox(height: 20.h,),
                    ///앱정보 버튼
                    DelayedWidget(
                      animation: DelayedAnimations.SLIDE_FROM_RIGHT,
                      animationDuration: const Duration(milliseconds: 300),
                      delayDuration: const Duration(milliseconds: 300),
                      child: InkWell(
                        onTap: (){},//터치시 앱정보 페이지 이동
                        child: Container(
                          alignment: Alignment.center,
                          padding: EdgeInsets.all(20.r),
                          decoration: BoxDecoration(
                              shape: BoxShape.circle,
                              color: const Color(0xffffffff),
                            border: Border.all(width: 2,color: const Color(0xffaecdff))
                          ),
                          child: Column(
                            children: [
                              Image.asset('assets/images/sub_icon.png', width: 30.r, height: 30.r,),
                              SizedBox(height: 5.h,),
                              Text('앱 정보', style: TextStyle(fontSize: 12.sp, fontWeight: FontWeight.bold, color: const Color(0xffaecdff)),)
                            ],
                          ),
                        ),
                      ),
                    )
                  ],
                )
            ),
          ],
        ),
      ),
    );

☑️결과

✅ CustomDropDown

앱 제작 중에 DropDown위젯을 많이 사용해야 하는데,

기존 위젯은 너무 안 어울려서 이것도 새롭게 커스텀 해보기로 하였다.

 

☑️동작 구상

위 Drawer처럼 클릭 시 새로운 페이지로 이동하여 선택할 수 있게 구현, 하지만 Drawer보다 DropDown리스트의 변경과 사용이 잦아서 라이브러리 처럼 그때 그때 쓸 수있는 형태로 제작

 

☑️CustomDropDown() - 변수

  const CustomDropDown({Key? key, required this.list, required this.posX, required this.posY, required this.width, required this.height, required this.itemheight}) : super(key: key);
  final List<String> list; //드롭다운리스트를 List<String>형으로 담을 변수
  final double posX; //드롭다운 위치는 터치 된 컨테이너에서 나와야함으로 그에 따른 X좌표 받을 변수
  final double posY; //위와 같은 이유의 Y좌표
  final double width; //드롭다운의 전체 넓이
  final double height; //드롭다운의 전체 높이
  final double itemheight; //드로다운 아이템의 높이

☑️CustomDropDown() - Code

Material(
        color: const Color(0xffffffff).withOpacity(0.4), //뒷배경을 살짝 불투명하게 만들어 드롭다운에 포커스가 됨
        child: Stack(children: [
          Positioned(
            top: 0,
            bottom: 0,
            right: 0,
            left: 0,
            child: GestureDetector(
                onTap: ()=> Get.back(),
                child: Container()),
          ),
          Positioned(
              left: posX,
              top: posY,
              child: Container(
                decoration: BoxDecoration(
                  border: Border.all(color: const Color(0xffaecdff)),
                  borderRadius: BorderRadius.circular(10),
                  color: Colors.white
                ),
                height: height,
                width: width,
                child: CustomScrollView(
                  slivers: [
                    SliverFixedExtentList(
                        itemExtent: itemheight,
                        delegate: SliverChildBuilderDelegate((ctx, index) {
                          return DelayedWidget(
                              delayDuration: Duration(milliseconds: index * 10),
                              animationDuration:
                                  const Duration(milliseconds: 100),
                              animation: DelayedAnimations.SLIDE_FROM_BOTTOM,
                              child: InkWell(
                                onTap: () {
                                  Get.back(result: list[index]);
                                },
                                child: Container(
                                  alignment: Alignment.center,
                                  color: Colors.transparent,
                                  child: Text(
                                    list[index],
                                    style: TextStyle(fontSize: 13.sp),
                                  ),
                                ),
                              ));
                        }, childCount: list.length))
                  ],
                ),
              ))
        ]));

 

☑️ 라우팅 코드

GestureDetector(
          onTap: () async{
            final result = await Get.to(()=>CustomDropDown(list: controller.numberList,
                posX: controller.getPosX(),
                posY: controller.getPosY(),
                width: 130.h,
                height: 120.w,
                itemheight: 30.h), opaque: false, fullscreenDialog: true, transition: Transition.fadeIn);
            if(result != ''){
              controller.numberPlayer.value = int.parse(result);
            }
          }

오류 코드를 찾기 편하게 하기위해 위젯들을 클래스 단위로 쪼게 구현 하였다.

PlayerWidget을 만들어 GetxController를 선언해준뒤 상태변화 변수들과 함수들을 정의해 주었다.

그리고 리스트의 결과를 비동기로 처리하여 결과를 불러올 수 있게 만들었다.

 

☑️필요 함수

//드롭다운될 Container Key
final dropdownKey = GlobalKey();

  ///드롭다운될 Container 좌표받기
  double getPosY(){
    var box = dropdownKey.currentContext!.findRenderObject();
    var translation = box!.getTransformTo(null).getTranslation();
    return box.paintBounds.shift(Offset(translation.x,translation.y)).bottomCenter.dy;
  }
  
  double getPosX(){
    var box = dropdownKey.currentContext!.findRenderObject();
    var translation = box!.getTransformTo(null).getTranslation();
    return box.paintBounds.shift(Offset(translation.x,translation.y)).bottomLeft.dx;
  }

☑️결과

 

❌오류 및 개선

플레이어 선택 시 테두리가 변화를 안한다. Obx안 감싸줘서 그런 듯,

hint text의 스타일을 바꿔 줘야할거 같다.

개선 후 게임 제작 해야징