책 검색 앱 제작 (2)
Jan 12, 2026

대원칙
- 실습은 최소 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',
),
);📍여기서 git push
Bottom sheet 내부 디자인 — 별도의 위젯으로 관리
home/ ├── widgets/ │ └── home_bottom_sheet.dart ← 새로 생성 ├── home_page.dart └── home_view_model.dart
home_bottom_sheet.dartimport 'package:flutter/material.dart';
class HomeBottomSheet extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(width: double.infinity, height: 200, child: Row());
}
}⇒
HomePage에 HomeBottomSheet 적용 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