상세 컨텐츠

본문 제목

[Flutter] 검색바의 이전 기록 화면 구현하기 (검색엔진 하단의 다른 팝업 화면)

IT/응용

by SINAFLA 2021. 6. 3. 19:35

본문

반응형

웹 페이지의 검색 입력 창을 클릭하면 아래와 같은 화면이 표출된다. 표출되는 걸 검색 바(이전 검색 창) 등 다양한 명칭으로 불려진다. 사용자 입장에서 이전 검색 목록을 표출하면 좋다. 이걸 Flutter로 구현할 일이 있어서 관련 자료를 찾았다.

 

 

조건은 아래와 같았다.

  1. TextField(입력 위젯)의 하단에 표출되어야 한다.
  2. 특정 이벤트가 발생되었을 때 표출되어야 한다.

 

먼저 Flutter의 플러그인을 조회할 수 있는 pub.dev 에서 TextField와 관련된 플러그인 조회했다.

  1. searchField
  2. advanced_search
  3. Search_Widget
  4. suggestion_textfield

 

4가지가 검색이 됐다. 위의 플러그인은 모두 잘 만들어졌다. suggestion_textfield는 제대로 작동이 안되는 단점이 있다. 그래도 플러그인의 기능을 파악해서 커스텀하여 개발하면 사용할 수 있어서 플러그인을 설치 후 사용했다.

위의 플러그인은 TextField가 한 줄인 경우 표시가 잘 됐다. TextField가 한 줄이 아닌 TextField 속성 중 maxLines에 값을 지정해줘서 웹 페이지의 <textarea> 태그처럼 TextField의 height 조정이 쉽지 않았다. 플러그인의 구현된 로직을 정리하다가 Overlay 위젯이 자주 보였다.

Overlay라는 단어가 '위에 까는 것'이란 의미를 지녔기 때문에 이 이름으로 된 위젯이 Dialog 화면 처럼 작동되는 지 공식 문서를 찾았다.

Overlay 위젯을 사용하면 독립적인 하위 위젯을 오버레이의 Stack에 삽입하여 다른 위젯 위에 시각적 요소를 '부동'할 수 있다고 적혀 있었다.

 

main.dart

import 'package:flutter/material.dart';

void main() => runApp(MyHome());

class MyHome extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Container(
            width:1000,
            height: 1000,
            child: Column(
              children: [
                CountriesField(),
                SizedBox(height: 16.0,),
                TextFormField(
                  decoration: InputDecoration(
                      labelText: 'City'
                  ),
                ),
                SizedBox(height: 16.0,),
                TextFormField(
                  decoration: InputDecoration(
                      labelText: 'Address'
                  ),
                ),
                SizedBox(height: 16.0,),
                RaisedButton(
                  child: Text('SUBMIT'),
                  onPressed: () {
                    // submit the form
                  },
                )
              ],
            ),
          ),
        ),
      ),
    );
  }
}
class CountriesField extends StatefulWidget {
  @override
  _CountriesFieldState createState() => _CountriesFieldState();
}

class _CountriesFieldState extends State<CountriesField> {

  final FocusNode _focusNode = FocusNode();

  OverlayEntry _overlayEntry;

  @override
  void initState() {
    _focusNode.addListener(() {
      if (_focusNode.hasFocus) {

        this._overlayEntry = this._createOverlayEntry();
        Overlay.of(context).insert(this._overlayEntry);

      } else {
        this._overlayEntry.remove();
      }
    });
  }

  OverlayEntry _createOverlayEntry() {

    RenderBox renderBox = context.findRenderObject();
    var size = renderBox.size;
    var offset = renderBox.localToGlobal(Offset.zero);

    return OverlayEntry(
        builder: (context) => Positioned(
          left: offset.dx,
          top: offset.dy + size.height + 5.0,
          width: size.width,
          child: Material(
            elevation: 4.0,
            child: ListView(
              padding: EdgeInsets.zero,
              shrinkWrap: true,
              children: <Widget>[
                ListTile(
                  title: Text('Syria'),
                ),
                ListTile(
                  title: Text('Lebanon'),
                )
              ],
            ),
          ),
        )
    );
  }

  @override
  Widget build(BuildContext context) {
    return TextFormField(
      focusNode: this._focusNode,
      decoration: InputDecoration(
          labelText: 'Country'
      ),
    );
  }
}

실행 화면

 

 


기능

1. OverlayEntry에 그리는 위젯 함수를 초기화 한다.

2. TextFormField에 초기화 된 _focusNode의 이벤트가 작동되면 오버레이할 함수를 그린다.

3. _focusNode.hasFocus 는 TextFormField 포커스가 (TextFormField 터치되었을 때) 잡혔을 때 값을 true로 뱉는다.

 

 

 

반응형

관련글 더보기

댓글 영역