[Flutter] Flutter 예제- Airbnb App 만들기

류재성's avatar
Jun 04, 2024
[Flutter] Flutter 예제- Airbnb App 만들기
 

1. 기본 세팅

 
notion image
 
assets 폴더를 만든 후 사진을 넣는다.
 
notion image
 
pubspec.yaml 설정 후 pub get을 누른다.
 

2. 기본 테마 설정하기

 
constants.dart
import 'package:flutter/material.dart'; const kAccentColor = Color(0xFFFF385C);
 
size.dart
import 'package:flutter/cupertino.dart'; const double gap_xl = 40 ; const double gap_l = 30 ; const double gap_m = 20 ; const double gap_s = 10 ; const double gap_xs = 5 ; const double header_heigh = 620 ; double getBodyWidth(BuildContext context){ return MediaQuery.of(context).size.width*0.7; }
 
style.dart
import 'package:flutter/material.dart'; TextStyle h4({Color mColor = Colors.black}){ return TextStyle(fontSize :34 , fontWeight : FontWeight.bold,color :mColor); } TextStyle h5({Color mColor = Colors.black}){ return TextStyle(fontSize :24 , fontWeight : FontWeight.bold,color :mColor); } TextStyle subTitle1({Color mColor = Colors.black}){ return TextStyle(fontSize :16 , fontWeight : FontWeight.bold,color :mColor); } TextStyle subTitle2({Color mColor = Colors.black}){ return TextStyle(fontSize :14 , fontWeight : FontWeight.bold,color :mColor); } TextStyle overLine({Color mColor = Colors.black}){ return TextStyle(fontSize :10 , fontWeight : FontWeight.bold,color :mColor); } TextStyle body1({Color mColor = Colors.black}){ return TextStyle(fontSize :16 , fontWeight : FontWeight.bold,color :mColor); }
 

3. HomePage 만들기

 
main.dart
import 'package:airbnb_app/size.dart'; import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: Scaffold( body: ListView( children: [ SizedBox( width: double.infinity, height: header_heigh, child: Container( decoration: BoxDecoration( image: DecorationImage( image: AssetImage("assets/background.jpeg"), fit: BoxFit.cover, ), ), ), ), ], ), ), ); } }
 
notion image
 
 
가독성을 위해 컴포넌트를 분리한다.
 
pages/home_page.dart
import 'package:flutter/material.dart'; import '../components/home_header.dart'; class HomePage extends StatelessWidget { const HomePage({ super.key, }); @override Widget build(BuildContext context) { return Scaffold( body: ListView( children: [ HomeHeader(), ], ), ); } }
 
components/home_header.dart
import 'package:flutter/material.dart'; import '../size.dart'; class HomeHeader extends StatelessWidget { const HomeHeader({ super.key, }); @override Widget build(BuildContext context) { return SizedBox( width: double.infinity, height: header_heigh, child: Container( decoration: BoxDecoration( image: DecorationImage( image: AssetImage("assets/background.jpeg"), fit: BoxFit.cover, ), ), ), ); } }
 
components/form_title.dart
import 'package:flutter/material.dart'; import '../size.dart'; import '../style.dart'; class FormTitle extends StatelessWidget { const FormTitle({ super.key, }); @override Widget build(BuildContext context) { return Column( children: [ Text( "모두의 숙소에서 숙소를 검색하세요.", style: h4(), ), SizedBox(height: gap_xs), Text( "혼자하는 여행에 적합한 개인실부터 여럿이 함께 하는 여행에 좋은 '공간전체' 숙소까지, 모두의 숙소에 다 있습니다.", style: body1(), ), SizedBox(height: gap_m), ], ); } }
 
home_header.dart
import 'package:flutter/material.dart'; import '../size.dart'; import '../style.dart'; import 'form_title.dart'; class HomeHeader extends StatelessWidget { const HomeHeader({ super.key, }); @override Widget build(BuildContext context) { double screenWidth = MediaQuery.of(context).size.width; return SizedBox( width: double.infinity, height: header_heigh, child: Container( decoration: BoxDecoration( image: DecorationImage( image: AssetImage("assets/background.jpeg"), fit: BoxFit.cover, ), ), child: Column( children: [ Align( alignment: screenWidth < 520 ? Alignment(0, 0) : Alignment(-0.6, 0), child: Container( width: 420, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), ), child: Form( child: Column( children: [ FormTitle(), ], ), ), ), ), ], ), ), ); } }
notion image
 
 

4. CommonFormField 만들기

 
common/common_form_field.dart
import 'package:flutter/material.dart'; import '../style.dart'; class CommonFormField extends StatelessWidget { final prefixText; final hintText; CommonFormField({required this.prefixText, required this.hintText}); @override Widget build(BuildContext context) { return Stack( children: [ TextFormField( textAlignVertical: TextAlignVertical.bottom, decoration: InputDecoration( contentPadding: EdgeInsets.only(top: 30, left: 20, bottom: 10), hintText: hintText, border: OutlineInputBorder( borderRadius: BorderRadius.circular(10), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), borderSide: BorderSide( color: Colors.black, width: 2, ), ), ), ), Positioned( top: 8, left: 20, child: Text( prefixText, style: overLine(), ), ) ], ); } }
 
components/form_field_detail.dart
import 'package:flutter/material.dart'; import '../common/common_form_field.dart'; import '../size.dart'; import 'form_title.dart'; class FormFieldDetial extends StatelessWidget { @override Widget build(BuildContext context) { return Column( children: [ CommonFormField( prefixText: "위치", hintText: "근처 추천 장소", ), SizedBox(height: gap_s), Row( children: [ Expanded(child: CommonFormField( prefixText: "체크인", hintText: "날짜 입력", )), Expanded(child: CommonFormField( prefixText: "체크 아웃", hintText: "날짜 입력", )), ], ), SizedBox(height: gap_s), Row(children: [ Expanded(child: CommonFormField( prefixText: "성인", hintText: "2", )), Expanded(child: CommonFormField( prefixText: "어린이", hintText: "0", )), ]), ], ); } }
 
components/form_submit.dart
import 'package:flutter/material.dart'; import '../constants.dart'; import '../style.dart'; class FormSubmit extends StatelessWidget { const FormSubmit({ super.key, }); @override Widget build(BuildContext context) { return SizedBox( width: double.infinity, height: 50, child: TextButton( style: TextButton.styleFrom( backgroundColor: kAccentColor, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10)), ), onPressed: () { print("버튼 클릭됨"); }, child: Text( "검색", style: subTitle1(mColor: Colors.white), ), ), ); } }
 
home_header_form.dart
import 'package:airbnb_app/size.dart'; import 'package:flutter/material.dart'; import 'form_field_detail.dart'; import 'form_submit.dart'; import 'form_title.dart'; class HomeHeaderForm extends StatelessWidget { const HomeHeaderForm({ super.key, required this.screenWidth, }); final double screenWidth; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(top: gap_m), child: Align( alignment: screenWidth < 520 ? Alignment(0, 0) : Alignment(-0.6, 0), child: Container( width: 420, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), ), child: Form( child: Column( children: [ FormTitle(), FormFieldDetial(), FormSubmit(), ], ), ), ), ), ); } }
 
components/home_header.form
import 'package:flutter/material.dart'; import '../size.dart'; import 'home_header_form.dart'; class HomeHeader extends StatelessWidget { @override Widget build(BuildContext context) { double screenWidth = MediaQuery.of(context).size.width; return SizedBox( width: double.infinity, height: header_heigh, child: Container( decoration: BoxDecoration( image: DecorationImage( image: AssetImage("assets/background.jpeg"), fit: BoxFit.cover, ), ), child: Column( children: [ HomeHeaderForm(screenWidth: screenWidth), ], ), ), ); } }
 
notion image
 

5. AppBar 만들기

 
components/home_header_appbar.dart
import 'package:airbnb_app/size.dart'; import 'package:airbnb_app/style.dart'; import 'package:flutter/material.dart'; class HomeHeaderAppBar extends StatelessWidget { const HomeHeaderAppBar({super.key}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(gap_m), child: Row( children: [ AppBarLogo(), Spacer(), AppBarMenu(), ], ), ); } Widget AppBarLogo() { return Row( children: [ Image.asset( "assets/logo.png", width: 30, height: 30, color: Colors.redAccent, ), SizedBox(width: gap_s), Text( "RoomOfAll", style: h5(mColor: Colors.white), ) ], ); } Widget AppBarMenu() { return Row( children: [ Text("회원가입", style: subTitle1(mColor: Colors.white)), SizedBox(width: gap_m), Text("로그인", style: subTitle1(mColor: Colors.white)), ], ); } }
 
components/home_header.dart
import 'package:airbnb_app/components/home_header_appbar.dart'; import 'package:flutter/material.dart'; import '../size.dart'; import 'home_header_form.dart'; class HomeHeader extends StatelessWidget { @override Widget build(BuildContext context) { double screenWidth = MediaQuery.of(context).size.width; return SizedBox( width: double.infinity, height: header_heigh, child: Container( decoration: BoxDecoration( image: DecorationImage( image: AssetImage("assets/background.jpeg"), fit: BoxFit.cover, ), ), child: Column( children: [ HomeHeaderAppBar(), HomeHeaderForm(screenWidth: screenWidth), ], ), ), ); } }
 

6. home_body.dart 만들기

 

6.1. home_body_banner.dart 만들기

 
components/home_body.dart
import 'package:airbnb_app/components/home_body_banner.dart'; import 'package:airbnb_app/size.dart'; import 'package:flutter/material.dart'; class HomeBody extends StatelessWidget { const HomeBody({super.key}); @override Widget build(BuildContext context) { double bodyWidth = getBodyWidth(context); return Align( child: SizedBox( width: bodyWidth, child: Column( children: [ HomeBodyBanner(), ], ), ), ); } }
 
home_body_banner.dart
import 'package:airbnb_app/style.dart'; import 'package:flutter/material.dart'; import '../size.dart'; class HomeBodyBanner extends StatelessWidget { const HomeBodyBanner({super.key}); @override Widget build(BuildContext context) { return Stack( children: [ ClipRRect( borderRadius: BorderRadius.circular(20), child: Image.asset( "assets/banner.jpg", fit: BoxFit.cover, width: double.infinity, height: 320, ), ), Positioned( top: 40, left: 40, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( constraints: BoxConstraints( maxWidth: 250, ), child: Text( "이제, 여행은 가까운 곳에서", style: h4(mColor: Colors.white), ), ), SizedBox(height: gap_m), Container( constraints: BoxConstraints( maxWidth: 250, ), child: Text( "새로운 공간에 머물러 보세요. 살아보기, 출장, 여행 등 다양한 목적에 맞는 숙소를 찾아보세요.", style: subTitle1(mColor: Colors.white), ), ), SizedBox(height: gap_m), SizedBox( height: 35, width: 170, child: TextButton( style: TextButton.styleFrom( backgroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(5), )), onPressed: () {}, child: Text("가까운 여행지 둘러보기", style: subTitle2()), ), ) ], ), ), ], ); } }
 
컴포넌트를 분리한다.
 
banner_image.dart
import 'package:flutter/material.dart'; class BannerImage extends StatelessWidget { const BannerImage({ super.key, }); @override Widget build(BuildContext context) { return ClipRRect( borderRadius: BorderRadius.circular(20), child: Image.asset( "assets/banner.jpg", fit: BoxFit.cover, width: double.infinity, height: 320, ), ); } }
 
banner_caption.dart
import 'package:flutter/material.dart'; import '../size.dart'; import '../style.dart'; class BannerCaption extends StatelessWidget { const BannerCaption({ super.key, }); @override Widget build(BuildContext context) { return Positioned( top: 40, left: 40, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( constraints: BoxConstraints( maxWidth: 250, ), child: Text( "이제, 여행은 가까운 곳에서", style: h4(mColor: Colors.white), ), ), SizedBox(height: gap_m), Container( constraints: BoxConstraints( maxWidth: 250, ), child: Text( "새로운 공간에 머물러 보세요. 살아보기, 출장, 여행 등 다양한 목적에 맞는 숙소를 찾아보세요.", style: subTitle1(mColor: Colors.white), ), ), SizedBox(height: gap_m), SizedBox( height: 35, width: 170, child: TextButton( style: TextButton.styleFrom( backgroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(5), )), onPressed: () {}, child: Text("가까운 여행지 둘러보기", style: subTitle2()), ), ) ], ), ); } }
 
notion image
 
home_body_banner.dart
import 'package:airbnb_app/style.dart'; import 'package:flutter/material.dart'; import '../size.dart'; import 'banner_caption.dart'; import 'banner_image.dart'; class HomeBodyBanner extends StatelessWidget { const HomeBodyBanner({super.key}); @override Widget build(BuildContext context) { return Stack( children: [ BannerImage(), BannerCaption(), ], ); } }
 

6.2. home_body_popular.dart

 
components/home_body_popular.dart
import 'package:flutter/material.dart'; import 'home_body_popular_item.dart'; class HomeBodyPopular extends StatelessWidget { const HomeBodyPopular({super.key}); @override Widget build(BuildContext context) { return Column( children: [ SizedBox(), Wrap( children: [ HomeBodyPopularItem(id:0), SizedBox(width: 7.5), HomeBodyPopularItem(id:1), SizedBox(width: 7.5), HomeBodyPopularItem(id:2), ], ), ], ); } }
 
components/home_body_popular_item.dart
import 'package:airbnb_app/size.dart'; import 'package:flutter/material.dart'; class HomeBodyPopularItem extends StatelessWidget { final id ; final popularList = [ "p1.jpeg", "p2.jpeg", "p3.jpeg", ]; HomeBodyPopularItem({required this.id}); @override Widget build(BuildContext context) { double popularItemWidth = getBodyWidth(context)/3-5 ; return Container( constraints: BoxConstraints( minWidth: 320, ), child: SizedBox( width: popularItemWidth, child: Column( children: [ PopularItemImage(id: id,popularList: popularList), PopularItemStar(), PopularItemComment(), PopularItemUserInfo(), ], ), ), ); } }
 
components/popular_item_comment.dart
import 'package:flutter/material.dart'; import '../size.dart'; import '../style.dart'; class PopularItemComment extends StatelessWidget { const PopularItemComment({super.key}); @override Widget build(BuildContext context) { return Column( children: [ Text( "깔끔하고 다 갖춰져있어서 좋았어요:) 위치도 완전 좋아용 다들 여기 살고싶다구ㅋㅋㅋㅋㅋ 화장실도 3개예요!!! 5명이서 씻는것도 전혀 불편함 없이 좋았어요^^ 이불도 포근하고 좋습니당ㅎㅎ", style: body1(), maxLines: 3, overflow: TextOverflow.ellipsis, ), SizedBox(height: gap_s), ], ); } }
 
components/popular_item_user_info.dart
import 'package:airbnb_app/style.dart'; import 'package:flutter/material.dart'; import '../size.dart'; class PopularItemUserInfo extends StatelessWidget { const PopularItemUserInfo({super.key}); @override Widget build(BuildContext context) { return Row( children: [ CircleAvatar( backgroundImage: AssetImage("assets/p1.jpeg"), ), SizedBox(width: gap_s), Column( children: [ Text( "데어", style: subTitle1(), ), Text("한국"), ], ), ], ); } }
 
popular_item_image.dart
import 'package:airbnb_app/constants.dart'; import 'package:flutter/material.dart'; import '../size.dart'; class PopularItemStar extends StatelessWidget { const PopularItemStar({super.key}); @override Widget build(BuildContext context) { return Column( children: [ Row( children: [ Icon(Icons.star, color: kAccentColor), Icon(Icons.star, color: kAccentColor), Icon(Icons.star, color: kAccentColor), Icon(Icons.star, color: kAccentColor), Icon(Icons.star, color: kAccentColor), ], ), SizedBox(height: gap_s,) ], ); } }
 
notion image
 
 
notion image
 
 
notion image
 
화면의 크기를 줄이면 반응형으로 적용된다.
Share article
RSSPowered by inblog