본문 바로가기
공부기록/Flutter

[Flutter] 포커스에 따른 input 박스 색상 변화 구현

by 책읽는 개발자 ami 2023. 7. 9.
728x90
반응형

플러터를 활용한 검색창 UI 개선: 포커스에 따른 컬러 변화 구현

TextField와 InputDecoration 위젯을 사용한 검색창 UI 구현

이번 글에선 플러터를 활용해 검색창의 사용자 경험을 개선하는 방법에 대해 이야기하려고 합니다. 이번 포스팅의 핵심은 바로 "검색창의 포커스에 따라 아이콘과 테두리 색이 변화하는 기능"입니다. 이 기능을 통해 사용자는 검색창이 현재 활성화되어 있는지 쉽게 파악할 수 있게 됩니다.

첫 번째로 보실 부분은 `_SearchPageState` 클래스 내부에 선언된 `_focusNode`라는 인스턴스입니다. 이는 `FocusNode` 클래스의 인스턴스로, 해당 위젯이 현재 포커스를 가지고 있는지 아닌지를 판단하는 역할을 합니다. 초기에는 `initState()` 메소드에서 `_focusNode`를 생성하고, `_onFocusChange` 메소드를 통해 포커스가 변화할 때마다 화면을 다시 그릴 수 있도록 설정하였습니다.

다음으로 `_onFocusChange` 메소드입니다. 이 메소드는 `_focusNode`의 포커스 상태가 변경될 때마다 호출되며, `setState()`를 호출해 화면을 다시 그립니다. 이를 통해 검색창이 포커스를 얻거나 잃을 때마다 아이콘과 테두리의 색상을 실시간으로 업데이트할 수 있습니다.

그럼 실제로 어떻게 색상이 변화하는지 살펴봅시다. `build()` 메소드 내에서는 `_focusNode.hasFocus`의 값에 따라 아이콘의 색상을 결정합니다. 만약 포커스가 있으면 아이콘 색상은 파란색, 없으면 회색으로 설정하였습니다. 이렇게 설정된 색상은 `TextField`의 `decoration` 프로퍼티에서 `Icon(Icons.clear, color: iconColor)`와 같은 형태로 사용되어 아이콘 색상이 실제로 반영됩니다.

마지막으로, 테두리 색상 역시 비슷한 방법으로 변경할 수 있습니다. `TextField`의 `decoration` 프로퍼티에서 `focusedBorder` 옵션을 사용하여 포커스가 있을 때 테두리의 스타일을 정의할 수 있습니다. 여기서는 파란색으로 설정하였습니다.

이렇게 간단한 코드 변경만으로도 사용자의 경험을 크게 향상시킬 수 있습니다. 포커스에 따라 색상이 변경되는 검색창은 사용자에게 직관적인 인터페이스를 제공하며, 이를 통해 사용자는 검색창의 상태를 쉽게 이해할 수 있습니다. 

코드는 아래를 참조해주세요.

import 'package:flutter/material.dart';

class SearchApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Search Page',
      home: SearchPage(),
    );
  }
}

class SearchPage extends StatefulWidget {
  @override
  _SearchPageState createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage> {
  String searchQuery = '';
  List<String> searchResults = [];
  late FocusNode _focusNode;

  void search(String searchInput) {
    // 여기에 검색 로직
    setState(() {
      searchResults = List<String>.generate(5, (int index) {
        return '$searchInput result $index';
      });
    });
  }

  void clearSearch() {
    setState(() {
      searchQuery = '';
      searchResults = [];
    });
  }

  @override
  void initState() {
    super.initState();
    _focusNode = FocusNode();
    _focusNode.addListener(_onFocusChange);
  }

  void _onFocusChange() {
    setState(() {}); // Focus 변화 시 화면을 다시 그립니다.
  }

  @override
  void dispose() {
    _focusNode.removeListener(_onFocusChange);
    _focusNode.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    Color iconColor = _focusNode.hasFocus ? Colors.blue : Colors.grey;

    return Scaffold(
      appBar: AppBar(
        title: Text(
          '검색',
          style: TextStyle(
            color: Colors.black,
          ),
        ),
        centerTitle: true,
        leading: IconButton(
          icon: Icon(Icons.arrow_back),
          color: Colors.grey,
          onPressed: () {
            Navigator.pop(context);
          },
        ),
      ),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: Column(children: [
          TextField(
            focusNode: _focusNode,
            onChanged: (value) {
              searchQuery = value;
              search(value);
            },
            decoration: InputDecoration(
              hintText: '검색할 공연이름을 입력해주세요!',
              suffixIcon: IconButton(
                onPressed: clearSearch,
                icon: Icon(Icons.clear, color: iconColor),
              ),
              prefixIcon: IconButton(
                onPressed: () {
                  search(searchQuery);
                },
                icon: Icon(Icons.search, color: iconColor),
              ),
              border: OutlineInputBorder(
                borderRadius: BorderRadius.circular(32.0),
              ),
              // 포커스가 있을 때 테두리 색상
              focusedBorder: OutlineInputBorder(
                borderRadius: BorderRadius.circular(32.0),
                borderSide: BorderSide(
                  color: Colors.blue, // 원하는 색상으로 변경
                ),
              ),
            ),
          ),
          Expanded(
            child: ListView.builder(
              itemCount: searchResults.length,
              itemBuilder: (BuildContext context, int index) {
                return ListTile(
                  title: Text(searchResults[index]),
                );
              },
            ),
          ),
        ]),
      ),
    );
  }
}
728x90
반응형