책 검색 앱 제작 (2)

Eungae's avatar
Jan 12, 2026
책 검색 앱 제작 (2)
 
 
 
💡
대원칙
  • 실습은 최소 3번씩 해보기.
  • 안 되면 더 하기.
  • 안 하고 불안해 하지 않기.
 

검색 함수 세팅

위치: class HomePage 아래 → TextEditingController와 같은 위상.
void onSearch(String text) { }
→ TextField 하위에
onSubmitted: onSearch, // onSubmitted: 키보드에서 done을 눌렀을 때 작동될 작업 설정 -> onSearch
 
 

검색 아이콘

@override Widget build(BuildContext context) { return GestureDetector( onTap: () { FocusScope.of(context).unfocus(); }, child: Scaffold( appBar: AppBar( actions: [ GestureDetector( onTap: () { onSearch(textEditingController.text); }, // 버튼의 터치영역은 44 디바이스 픽셀 이상으로 설정할 것 -> 이하일 경우 오류 발생 child: Container( width: 50, height: 50, // 컨테이너에 배경색이 없으면 자녀위젯에만 터치 이벤트가 적용됨. color: Colors.transparent, child: Icon(Icons.search), ), ), ],
 
 

그리드 뷰

그리드 뷰 사용법

GridView : 격자 형태의 뷰
  • 기본 사용법
    • GridView( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, // 한 행에 들어갈 위젯 갯수 childAspectRatio: 3 / 4, // 각 위젯 비율 crossAxisSpacing: 10, // 위젯간 가로 간격 mainAxisSpacing: 10, // 위젯간 세로 간격 ), children: [ Image.network('https://picsum.photos/300/400'), Image.network('https://picsum.photos/300/400'), ], )
  • builder 생성자 이용
    • itemCount 속성 갯수만큼 itemBuilder 속성에 들어가는 함수 호출
    • itemBuilder에서는 return되는 위젯이 GridView children에 하나씩 들어간다고 생각! ⇒ 동일한 위젯인데 안에 내용만 바뀔 때 주로 사용
    • GridView.builder( itemCount: 20, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, childAspectRatio: 3 / 4, crossAxisSpacing: 10, mainAxisSpacing: 10, ), itemBuilder: (context, index) { return Image.network( 'https://picsum.photos/300/400', fit: BoxFit.cover, ); }, )
  • gridDelegate
    • 그리드 레이아웃을 제어하는 클래스
    • SliverGridDelegateWithFixedCrossAxisCount
      • 한 행에 들어갈 위젯 갯수를 고정하고 싶을때 사용
        • GridView.builder( padding: EdgeInsets.all(20), itemCount: 10, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, childAspectRatio: 3 / 4, crossAxisSpacing: 10, mainAxisSpacing: 10, ), itemBuilder: (context, index) { return Image.network( 'https://picsum.photos/300/400', fit: BoxFit.cover, ); }, )
    • SliverGridDelegateWithMaxCrossAxisExtent
      • 위젯의 가로 크기를 정해서 사용하고 싶을 때
        • GridView.builder( padding: EdgeInsets.all(20), itemCount: 10, gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 50, childAspectRatio: 3 / 4, crossAxisSpacing: 10, mainAxisSpacing: 10, ), itemBuilder: (context, index) { return Image.network( 'https://picsum.photos/300/400', fit: BoxFit.cover, ); }, )
 
 
body: GridView.builder( padding: EdgeInsets.all(20), itemCount: 10, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, childAspectRatio: 3 / 4, crossAxisSpacing: 10, mainAxisSpacing: 10, ), itemBuilder: (context, index) { return Image.network( 'https://fastly.picsum.photos/id/705/300/400.jpg?hmac=fS4rfBAi01bPlJVcEOYfDlIGjxgtg21XNhBAgAThAOQ', ); }, ),
📍여기서 git push
 

bottom sheet

HomePage 내에 bottm sheet 구현

itemBuilder: (context, index) { return GestureDetector( onTap: () { showModalBottomSheet( context: context, builder: (context) { return Container( width: double.infinity, height: 300, child: Text("data"), ); }, ); }, child: Image.network( 'https://fastly.picsum.photos/id/705/300/400.jpg?hmac=fS4rfBAi01bPlJVcEOYfDlIGjxgtg21XNhBAgAThAOQ', ), );
💡

트러블 슈팅

처음에 Container 없이 Text로 바텀시트 만들었을 때는 바텀시트가 너무 작게 나오는 문제가 있었다. 이를 조정하기 위해 어디에 Container를 입혀야 할까? → 정답은 Text에 입히는 것이었다.
이 감각을 어떻게 가질 수 있을까.

point
  • 부모 위젯은 자식의 위젯의 크기를 존중한다.
  • → 무엇이 자식 위젯인가.
📍여기서 git push
 

Bottom sheet 내부 디자인 — 별도의 위젯으로 관리

home/ ├── widgets/ │ └── home_bottom_sheet.dart ← 새로 생성 ├── home_page.dart └── home_view_model.dart
 
home_bottom_sheet.dart
import 'package:flutter/material.dart'; class HomeBottomSheet extends StatelessWidget { @override Widget build(BuildContext context) { return Container(width: double.infinity, height: 200, child: Row()); } }
HomePageHomeBottomSheet 적용
itemBuilder: (context, index) { return GestureDetector( onTap: () { showModalBottomSheet( context: context, builder: (context) { return HomeBottomSheet(); }, );
 
home_bottom_sheet.dart 에서 Row()
import 'package:book_search/presentation/detail/detail_page.dart'; import 'package:flutter/material.dart'; class HomeBottomSheet extends StatelessWidget { @override Widget build(BuildContext context) { return Container( width: double.infinity, height: 300, padding: EdgeInsets.only(top: 20, right: 20, left: 20, bottom: 60), child: Row( children: [ Image.network( 'https://fastly.picsum.photos/id/705/300/400.jpg?hmac=fS4rfBAi01bPlJVcEOYfDlIGjxgtg21XNhBAgAThAOQ', fit: BoxFit.cover, ), SizedBox(width: 20), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "해리포터", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18), ), Text( "J.K.롤링", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14), ), Text("해리포터와 마법사의 돌을 찾아가는 여정", style: TextStyle(fontSize: 14)), Spacer(), GestureDetector( // "자세히 보기" 클릭 시 작업되도록 onTap: () { Navigator.push( // DetailPage로 넘어가도록 context, MaterialPageRoute( builder: (context) { return DetailPage(); }, ), ); }, child: Container( // 글씨 부분 주변을 클릭해도 넘어가도록 Container 씌움 width: double.infinity, height: 50, color: Colors.transparent, alignment: Alignment.center, child: Text( "자세히 보기", style: TextStyle( color: Colors.blueAccent, fontWeight: FontWeight.bold, ), ), ), ), ], ), ), ], ), ); } }
📍 git push
 
 
Share article

나새끼메이커