앱 개발 공부

플러터 학습기(7)-옵션 추가할 수 있는 드롭다운 만들기

polissage 2024. 2. 18. 02:01
728x90

개인 프로젝트를 하면서 chatGPT와 5시간 정도 씨름하여 선택지 추가 및 삭제를 할 수 있는 드롭다운을 만들었다.

 

드롭다운 + 텍스트필드에서 옵션을 입력하면 드롭다운이 열리면서 입력한 옵션을 추가하는 버튼이 나오고, 그 버튼을 누르면 옵션 생성이 완료된다. 생성한 옵션을 선택한 후 선택 해제 버튼을 클릭하여 다시 새로운 옵션을 입력하여 추가할 수 있다. 드롭다운을 클릭해 설정한 다른 옵션을 선택하는 것도 가능하고, 삭제 버튼을 클릭하여 옵션을 삭제할 수도 있다.

 

이 UX가 최선인지는 아직 고민이 되는데, 원래는 추가하기 버튼을 항상 노출하고 다이얼로그에서 옵션을 추가하게 하려고 했다. 이것보다는 위가 더 나은 방식인 것 같은데, 새로 생성한 옵션이 바로 선택되지 않는 것과 옵션 선택 상태에서 선택 해제와 드롭다운 화살표가 나란히 있는 것이 좀 맘에 걸린다. 첫번째는 chatGPT와 아무리 씨름해도 되지 않았고 두번째는 UI를 조금 손보면 나아지지 않을까 싶다.

원래 UX

 

드롭다운 하나에 기능이 꽤 많아서 구현에 시간이 꽤 오래 걸렸다.

 

우선 옵션 추가, 삭제 함수 적용이 필요했다. 그리고 옵션 추가하기 버튼을 기존에 존재하는 옵션과 다른 새로운 옵션을 입력할때만 입력값과 함께 나오게 하고 싶어서 이 부분을 적용하는데도 시간이 오래 걸렸다. 옵션 선택을 해제하면 옵션 추가하기 버튼이 다시 나와서 이 부분을 제어하기 위한 핸들러가 별도로 필요했다. 정리하고 나니 간단한데 직접 짠 것이 아니니 소화하는 시간이 필요할 것 같다.

 

일단 중간 과정으로 드롭다운 클릭했을때 옵션 추가하기 버튼이 항상 나오는게 하는 코드를 정리해보았다.

 

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: DropdownExample(),
    );
  }
}

class DropdownExample extends StatefulWidget {
  @override
  _DropdownExampleState createState() => _DropdownExampleState();
}

class _DropdownExampleState extends State<DropdownExample> {
  List<String> options = [];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Dropdown Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          //하단에 customDropdown을 정의하고 불러온다.
          children: [
            CustomDropdown(
              value: null,
              items: options.map<DropdownMenuItem<String>>((String value) {
                return DropdownMenuItem<String>(
                  value: value,
                  child: Text(value),
                );
              }).toList(),
              onChanged: (String? newValue) {
                setState(() {
                  // 나중에 설정한 옵션을 선택할 수 있도록 한다
                });
              },
            ),
          ],
        ),
      ),
    );
  }
}

class CustomDropdown extends StatefulWidget {
  final String? value;
  final List<DropdownMenuItem<String>> items;
  final ValueChanged<String?>? onChanged;

  CustomDropdown({
    required this.value,
    required this.items,
    required this.onChanged,
  });

  @override
  _CustomDropdownState createState() => _CustomDropdownState();
}

class _CustomDropdownState extends State<CustomDropdown> {
  bool _isOpen = false;

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [
        GestureDetector(
          onTap: () {
            setState(() {
              _isOpen = !_isOpen;
            });
          },
          //드롭다운 열기 전 필드 부분 정의
          child: Container(
            decoration: BoxDecoration(
              border: Border.all(),
              borderRadius: BorderRadius.circular(4),
            ),
            padding: EdgeInsets.all(8),
            child: Row(
              children: [
                Expanded(
                  child: Text(
                    widget.value ?? '카테고리 선택',
                    overflow: TextOverflow.ellipsis,
                  ),
                ),
                Icon(Icons.arrow_drop_down),
              ],
            ),
          ),
        ),
        //드롭다운 열었을때 옵션 추가 버튼과 생성된 옵션 확인
        if (_isOpen)
          Container(
              margin: EdgeInsets.only(top: 4),
              decoration: BoxDecoration(
              	 border: Border.all(),
                 borderRadius: BorderRadius.circular(4),
                  ),
              child: Column(
               children: [
                   ...widget.items.map((item) {
                       return ListTile(
                           title: item.child!,
                              onTap: () {
                               setState(() {
                                   widget.onChanged?.call(item.value);
                                      _isOpen = false;
                                      });
                              },
                          )
                      }).toList(),
                      ListTile(
                       title: Row(
                           children: [
                               Icon(Icons.add),
                               SizedBox(width: 8),
                               Text('카테고리 추가'),
                               ],
                             ),
                            onTap: () {
                              //옵션 생성 콜백을 호출해준다.
                              setState(() {
                                _isOpen = false;
                                  });
                              },
                          ),
                      ],
                  ),
              ),
          ],
      );
   }
}

 

이렇게 긴 코드로 간신히 요만큼을 구현할 수 있다. 흑 ㅠ 옵션 추가 함수가 없어서 카테고리 추가는 아직 동작하지 않는다. 이 부분은 다음 편에..

728x90