1. 기본 세팅
![notion image](https://inblog.ai/_next/image?url=https%3A%2F%2Fwww.notion.so%2Fimage%2Fhttps%253A%252F%252Fprod-files-secure.s3.us-west-2.amazonaws.com%252F404a9fb6-ab9b-45cc-8074-ee63a3334890%252F67161106-3097-45c2-9782-968d111316db%252FUntitled.png%3Ftable%3Dblock%26id%3D841f87c9-2dc8-4835-bb26-9aa71011f8bf%26cache%3Dv2&w=3840&q=75)
assets 폴더를 만든 후 사진을 넣는다.
![notion image](https://inblog.ai/_next/image?url=https%3A%2F%2Fwww.notion.so%2Fimage%2Fhttps%253A%252F%252Fprod-files-secure.s3.us-west-2.amazonaws.com%252F404a9fb6-ab9b-45cc-8074-ee63a3334890%252Fafebbb20-49a8-48ae-9331-a612663861b1%252FUntitled.png%3Ftable%3Dblock%26id%3D94184f84-81fc-40fa-be12-390ee641cde1%26cache%3Dv2&w=3840&q=75)
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](https://inblog.ai/_next/image?url=https%3A%2F%2Fwww.notion.so%2Fimage%2Fhttps%253A%252F%252Fprod-files-secure.s3.us-west-2.amazonaws.com%252F404a9fb6-ab9b-45cc-8074-ee63a3334890%252F0ebe3d90-8472-40c7-beef-792a5747d35c%252FUntitled.png%3Ftable%3Dblock%26id%3D964a8794-a462-4727-be0a-818a394ad89d%26cache%3Dv2&w=3840&q=75)
가독성을 위해 컴포넌트를 분리한다.
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](https://inblog.ai/_next/image?url=https%3A%2F%2Fwww.notion.so%2Fimage%2Fhttps%253A%252F%252Fprod-files-secure.s3.us-west-2.amazonaws.com%252F404a9fb6-ab9b-45cc-8074-ee63a3334890%252F2a737ca4-d56f-4e54-a63a-4eef68505fa6%252FUntitled.png%3Ftable%3Dblock%26id%3D05bfb2c7-dbd5-428f-8502-5ee74ebe24cc%26cache%3Dv2&w=3840&q=75)
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](https://inblog.ai/_next/image?url=https%3A%2F%2Fwww.notion.so%2Fimage%2Fhttps%253A%252F%252Fprod-files-secure.s3.us-west-2.amazonaws.com%252F404a9fb6-ab9b-45cc-8074-ee63a3334890%252F329fb765-f060-43fb-88dd-e63fa8f81ad7%252FUntitled.png%3Ftable%3Dblock%26id%3D6741028a-7b8d-41c2-8345-1f094d77b189%26cache%3Dv2&w=3840&q=75)
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](https://inblog.ai/_next/image?url=https%3A%2F%2Fwww.notion.so%2Fimage%2Fhttps%253A%252F%252Fprod-files-secure.s3.us-west-2.amazonaws.com%252F404a9fb6-ab9b-45cc-8074-ee63a3334890%252F5d8033d2-d964-4114-a8be-4d72122a2848%252FUntitled.png%3Ftable%3Dblock%26id%3Db5b2c9a9-c302-4723-ae44-b5a77dfddee7%26cache%3Dv2&w=3840&q=75)
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](https://inblog.ai/_next/image?url=https%3A%2F%2Fwww.notion.so%2Fimage%2Fhttps%253A%252F%252Fprod-files-secure.s3.us-west-2.amazonaws.com%252F404a9fb6-ab9b-45cc-8074-ee63a3334890%252F6d2b4ec9-9653-4612-978f-34076e060248%252FUntitled.png%3Ftable%3Dblock%26id%3Dceca9cd5-40fb-4786-9e2f-dc453f28c47b%26cache%3Dv2&w=3840&q=75)
![notion image](https://inblog.ai/_next/image?url=https%3A%2F%2Fwww.notion.so%2Fimage%2Fhttps%253A%252F%252Fprod-files-secure.s3.us-west-2.amazonaws.com%252F404a9fb6-ab9b-45cc-8074-ee63a3334890%252Fcac2ea27-c40a-49e8-85fb-d587747bfc59%252FUntitled.png%3Ftable%3Dblock%26id%3Db4ccd3ba-80a9-402d-bb82-448b3b130140%26cache%3Dv2&w=3840&q=75)
![notion image](https://inblog.ai/_next/image?url=https%3A%2F%2Fwww.notion.so%2Fimage%2Fhttps%253A%252F%252Fprod-files-secure.s3.us-west-2.amazonaws.com%252F404a9fb6-ab9b-45cc-8074-ee63a3334890%252Fb4e34c19-ebac-4d9f-a5c0-7fe00cef8a36%252FUntitled.png%3Ftable%3Dblock%26id%3De6397c30-de8c-4c44-acfd-b8a5f50a10ac%26cache%3Dv2&w=3840&q=75)
화면의 크기를 줄이면 반응형으로 적용된다.
Share article