본문 바로가기
flutter

Flutter Autocomplete 커스텀

by Rogan_Kim 2023. 11. 21.
728x90

flutter Autocomplete optionsView와 fieldView 커스텀 방법입니다.

Autocomplete 관련 검색하는데 자료가 별로 없어서 소스코드를 뜯어보고 핵심 부분을 기록하였습니다.

 

 

Autocomplete

	 Autocomplete<SchoolInfo>(
                initialValue: TextEditingValue(
                  text: //학교에서 잘라서 주소 빼고 보여주기
                      "${_textControllers[FieldKey.schoolName]!.text.split('학교')[0]}학교",
                ),
                optionsMaxHeight: 200.0,
                displayStringForOption: (option) => option.schoolName,
                onSelected: (option) async {
                  // 옵션이 선택될 때의 동작
                  _isAutocompleteOptionsVisible = false;
                  _textControllers[FieldKey.schoolName]!.text =
                      option.schoolName + option.adres;
                  setState(() {});
                },
                optionsViewBuilder: _optionsViewBuilder, // >> 이부분만 보면 됩니다.
                fieldViewBuilder: _fieldViewBuilder, // >> 이부분만 보면 됩니다.
                optionsBuilder: (TextEditingValue textEditingValue) async {
                  /// '중'이 입력되면 중학교 검색
                  return await ref.read(careerController).getSchoolList(
                        gubun: textEditingValue.text.contains('중')
                            ? Gubun.midd_list
                            : Gubun.elem_list,
                        searchSchulNm: textEditingValue.text,
                      );
                },
              ),

 

>> 이부분만 보면 됩니다

 

OptionViewBuilder  코드 및 구현 방법

직접 구현했던 코드

  /// autoCompleteOptionsView
  Widget _optionsViewBuilder(BuildContext context,
      void Function(SchoolInfo) onSelected, Iterable<SchoolInfo> options) {
    if (!_isAutocompleteOptionsVisible) {
      _isAutocompleteOptionsVisible = true;
      Future.delayed(
        const Duration(milliseconds: 1),
        () {
          if (mounted) {
            setState(() {});
          }
        },
      );
    }
    return Align(
      alignment: Alignment.topLeft,
      child: Container(
        clipBehavior: Clip.hardEdge,
        decoration: const BoxDecoration(
          borderRadius: BorderRadius.only(
            bottomLeft: Radius.circular(Sizes.size12),
            bottomRight: Radius.circular(Sizes.size12),
          ),
        ),
        child: Material(
          elevation: 4.0,
          child: ConstrainedBox(
            constraints: BoxConstraints(
                maxHeight: 200.0,
                maxWidth: MediaQuery.of(context).size.width - 32),
            child: ListView.builder(
              padding: EdgeInsets.zero,
              shrinkWrap: true, // 리스트 자식 높이 크기의 합 만큼으로 영역 고정
              itemCount: options.length,
              itemBuilder: (context, index) {
                return InkWell(
                  onTap: () {
                    onSelected(options.elementAt(index));
                  },
                  child: Builder(builder: (context) {
                    final bool highlight =
                        AutocompleteHighlightedOption.of(context) == index;
                    if (highlight) {
                      SchedulerBinding.instance
                          .addPostFrameCallback((Duration timeStamp) {
                        Scrollable.ensureVisible(context, alignment: 0.5);
                      });
                    }
                    return Container(
                      padding: const EdgeInsets.only(
                        top: Sizes.size6,
                        bottom: Sizes.size6,
                        left: Sizes.size16,
                      ),
                      color: highlight
                          ? AppColors.grayScale6
                          : AppColors.grayScale5,
                      child: Row(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Padding(
                            padding: const EdgeInsets.only(top: Sizes.size4),
                            child: SvgPicture.asset(
                              'assets/svg/search-icon.svg',
                              width: 20,
                              height: 20,
                            ),
                          ),
                          Gaps.h10,
                          Expanded(
                            child: Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                Text(
                                  '학교: ${options.elementAt(index).schoolName}',
                                  style: const TextStyle(
                                    overflow: TextOverflow.ellipsis,
                                    fontSize: Sizes.size14,
                                  ),
                                ),
                                Text(
                                  '주소: ${options.elementAt(index).adres}',
                                  style: const TextStyle(
                                    overflow: TextOverflow.ellipsis,
                                    fontSize: Sizes.size14,
                                  ),
                                )
                              ],
                            ),
                          ),
                        ],
                      ),
                    );
                  }),
                );
              },
            ),
          ),
        ),
      ),
    );
  }

코드 다 읽어 볼 필요 없습니다. 

 

 

 

Autocomplete 내부 소스에서 _AutocompleteOptions를 따라가면 아래와 같은 코드를 볼 수 있습니다.

핵심은 Align > Meterial > ConstrainedBox > ListView.builder(혹은 원하는 위젯)으로 레이아웃을 잡는 것입니다.

또한 ConstrainedBox의 maxOptionsHeight 값을 AutoComplete의 optionsMaxHeight은 동일한 값을 주면 좋습니다.

select 이벤트는 onSelected(option) 부분과 똑같이 사용하거나 변형해서 사용하면 됩니다.

 

// The default Material-style Autocomplete options.
class _AutocompleteOptions<T extends Object> extends StatelessWidget {
  const _AutocompleteOptions({
    super.key,
    required this.displayStringForOption,
    required this.onSelected,
    required this.options,
    required this.maxOptionsHeight,
  });

  final AutocompleteOptionToString<T> displayStringForOption;

  final AutocompleteOnSelected<T> onSelected;

  final Iterable<T> options;
  final double maxOptionsHeight;

  @override
  Widget build(BuildContext context) {
    return Align(
      alignment: Alignment.topLeft,
      child: Material(
        elevation: 4.0,
        child: ConstrainedBox(
          constraints: BoxConstraints(maxHeight: maxOptionsHeight),
          child: ListView.builder(
            padding: EdgeInsets.zero,
            shrinkWrap: true,
            itemCount: options.length,
            itemBuilder: (BuildContext context, int index) {
              final T option = options.elementAt(index);
              return InkWell(
                onTap: () {
                  onSelected(option);
                },
                child: Builder(
                  builder: (BuildContext context) {
                    final bool highlight = AutocompleteHighlightedOption.of(context) == index;
                    if (highlight) {
                      SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) {
                        Scrollable.ensureVisible(context, alignment: 0.5);
                      });
                    }
                    return Container(
                      color: highlight ? Theme.of(context).focusColor : null,
                      padding: const EdgeInsets.all(16.0),
                      child: Text(displayStringForOption(option)),
                    );
                  }
                ),
              );
            },
          ),
        ),
      ),
    );
  }
}

 

 

FieldViewBuilder  코드 및 구현 방법

 

fieldViewBuilder에는 TextField 위젯을 렌더링하면 됩니다.

핵심은 focusNode와 controller를 꼭 일치 시켜야 합니다.

둘을 일치시켜야 optionView가 제대로 렌더 됩니다.

  Widget _fieldViewBuilder(
    BuildContext buildContext,
    TextEditingController textEditingController,
    FocusNode focusNode,
    void Function() onFieldSubmitted,
  ) {
    return TextField(
          focusNode: focusNode,
          cursorColor: AppColors.primary,
          controller: textEditingController,
          keyboardType: TextInputType.text,
          decoration: InputDecoration(
            contentPadding: const EdgeInsets.symmetric(
              vertical: Sizes.size18,
              horizontal: Sizes.size16,
            ),
            prefixIcon: prefixIcon,
            filled: true,
            fillColor: const Color.fromRGBO(244, 244, 245, 1),
            hintText: hintText,
            hintStyle: const TextStyle(color: AppColors.grayScale3),
            border: inputBorder,
            enabledBorder: inputBorder,
            focusedBorder: inputBorder,
          ),
        );
  }

 

 

커스텀 시연

 

 

관련 자료

- https://api.flutter.dev/flutter/material/Autocomplete-class.html

728x90

댓글