1. 기본 세팅하기
- 파일 구조 잡기
- 이미지 경로 잡기
2. 기본 셋팅하기
- 색깔 정의
import 'package:flutter/material.dart'; // 색상을 정의합니다. const kAccentColor = Color(0xFFFF385C);
- 여백 정의
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_height = 620; // MediaQuery 클래스로 화면 사이즈를 받을 수 있다. double getBodyWidth(BuildContext context) { return MediaQuery.of(context).size.width * 0.7; }
- 스타일 정의
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, color: mColor); }
import 'package:flutter/cupertino.dart'; class CommonFormField extends StatelessWidget { const CommonFormField({super.key}); @override Widget build(BuildContext context) { return Container(); } }
import 'package:airbnb_app/size.dart'; import 'package:flutter/cupertino.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: [ _buildAppBarLogo(), Spacer(), _build_AppBarMenu(), ], ), ); } Widget _buildAppBarLogo() { return SizedBox(); } Widget _build_AppBarMenu() { return SizedBox(); } }
import 'package:airbnb_app/size.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; class HomeHeaderForm extends StatelessWidget { const HomeHeaderForm({super.key}); @override Widget build(BuildContext context) { return Padding( padding: EdgeInsets.only(top: gap_m), child: Align( alignment: Alignment(-0.6, 0), child: Container( width: 40, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), ), child: Form( child: Column( children: [ _buildFormTitle(), _buildFormField(), _buildFormSubmit(), ], ), ), ), ), ); } Widget _buildFormTitle() { return SizedBox(); } Widget _buildFormField() { return SizedBox(); } Widget _buildFormSubmit() { return SizedBox(); } }
import 'package:airbnb_app/components/home/home_header_appbar.dart'; import 'package:airbnb_app/size.dart'; import 'package:flutter/cupertino.dart'; import 'home_header_form.dart'; class HomeHeader extends StatelessWidget { const HomeHeader({super.key}); @override Widget build(BuildContext context) { return SizedBox( width: double.infinity, height: header_height, child: Container( decoration: BoxDecoration( image: DecorationImage( image: AssetImage("assets/background.jpeg"), fit: BoxFit.cover, ), ), child: Column( children: [ HomeHeaderAppBar(), HomeHeaderForm(), ], ), ), ); } }
import 'package:flutter/material.dart'; import '../../size.dart'; class HomeBodyBanner extends StatelessWidget { const HomeBodyBanner({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Padding( // 1. 상단에 마진을 준다. padding: const EdgeInsets.only(top: gap_m), // 2 이미지와 글자를 겹치게 하기 위해서 Stack 위젯을 사용한다. child: Stack( children: [ _buildBannerImage(), _buildBannerCaption(), ], ), ); } Widget _buildBannerImage() { return SizedBox(); } Widget _buildBannerCaption() { return SizedBox(); } }
import 'package:airbnb_app/size.dart'; import 'package:flutter/cupertino.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 Padding( padding: EdgeInsets.only(bottom: gap_xl), child: Container( constraints: BoxConstraints( minWidth: 320, ), child: SizedBox( width: popularItemWidth, child: Column( children: [ _buildPopularvItemImage(), _buildPopularvItemStar(), _buildPopularvItemComment(), _buildPopularvItemUserInfo(), ], ), ), ), ); } Widget _buildPopularvItemImage(){ return SizedBox(); } Widget _buildPopularvItemStar(){ return SizedBox(); } Widget _buildPopularvItemComment(){ return SizedBox(); } Widget _buildPopularvItemUserInfo(){ return SizedBox(); } }
import 'package:airbnb_app/components/home/home_body_popular_item.dart'; import 'package:airbnb_app/size.dart'; import 'package:flutter/cupertino.dart'; class HomeBodyPopular extends StatelessWidget { const HomeBodyPopular({super.key}); @override Widget build(BuildContext context) { return Padding( padding: EdgeInsets.only(top: gap_m), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildPopularTitle(), _buildPopularList(), ], ),); } Widget _buildPopularTitle() { return SizedBox(); } Widget _buildPopularList() { return Wrap( children: [ HomeBodyPopularItem(id: 0), SizedBox(width: 7.5), HomeBodyPopularItem(id: 1), SizedBox(width: 7.5), HomeBodyPopularItem(id: 2), ], ); } }
import 'package:airbnb_app/components/home/home_body_popular.dart'; import 'package:airbnb_app/size.dart'; import 'package:flutter/cupertino.dart'; import 'home_body_banner.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(), HomeBodyPopular(), ], ), ), ); } }
import 'package:airbnb_app/components/home/home_body.dart'; import 'package:airbnb_app/components/home/home_header.dart'; import 'package:flutter/material.dart'; class HomePage extends StatelessWidget { const HomePage({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: ListView( children: [ HomeHeader(), HomeBody(), ], ), ); }
import 'package:flutter/material.dart'; import 'package:flutter/pages/home_page.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: HomePage(), ); } }
import 'package:airbnb_app/constants.dart'; import 'package:airbnb_app/size.dart'; import 'package:airbnb_app/style.dart'; import 'package:flutter/cupertino.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: [ _buildAppBarLogo(), Spacer(), _build_AppBarMenu(), ], ), ); } Widget _buildAppBarLogo() { return Row( children: [ Image.asset("assets/logo.png", width: 30,height: 30, color: kAccentColor), SizedBox(width: gap_s), Text("RoomOfAll", style: h5(mColor: Colors.white)), ], ); } Widget _build_AppBarMenu() { return Row( children: [ Text("회원가입", style: subtitle1(mColor: Colors.white)), SizedBox(width: gap_m), Text("로그인", style: subtitle1(mColor: Colors.white)), ], ); } }
import 'package:flutter/material.dart'; import '../../style.dart'; class CommonFormField extends StatelessWidget { final prefixText; final hintText; const CommonFormField({Key? key, required this.prefixText, required this.hintText}) : super(key: key); @override Widget build(BuildContext context) { return Stack( children: [ TextFormField( textAlignVertical: TextAlignVertical.bottom, // 2. TextFormField 내부 세로 정렬 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(), ), ), ], ); } }
import 'package:flutter/material.dart'; import '../../constants.dart'; import '../../size.dart'; import '../../style.dart'; import '../common/common_form_field.dart'; class HomeHeaderForm extends StatelessWidget { const HomeHeaderForm({Key? key}) : super(key: key); @override Widget build(BuildContext context) { double screenWidth = MediaQuery .of(context) .size .width; // 추가 return Padding( padding: const EdgeInsets.only(top: gap_m), // 1. AppBar와 거리주기 // 2. 정렬 위젯 child: Align( // 3. -1.0 부터 1.0 까지 가로 범위에서 0.1의 값은 5%이다. 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: Padding( padding: const EdgeInsets.all(gap_l), // 4. Form 내부 여백 child: Column( children: [ _buildFormTitle(), // 5. Form 위젯 제목 영역 _buildFormField(), // 6. Form 위젯 Text 입력 양식 영역 _buildFormSubmit(), // 7. Form 위젯 전송 버튼 영역 ], ), ), ), ), ), ); } Widget _buildFormTitle() { return Column( children: [ Text( "모두의 숙소에서 숙소를 검색하세요.", style: h4(), // overflow: TextOverflow.ellipsis, 앱에선 터지고 크롬에선 안터짐 ), SizedBox(height: gap_xs), Text( "혼자하는 여행에 적합한 개인실부터 여럿이 함께하는 여행에 좋은 '공간전체' 숙소까지, 모두의숙소에 다 있습니다.", style: body1(), ), SizedBox(height: gap_m), ], ); } Widget _buildFormField() { 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", )), ], ), SizedBox(height: gap_m), ], ); } Widget _buildFormSubmit() { 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), ), ), ); } }
import 'package:flutter/material.dart'; import '../../size.dart'; import '../../style.dart'; class HomeBodyBanner extends StatelessWidget { const HomeBodyBanner({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Padding( // 1. 상단에 마진을 준다. padding: const EdgeInsets.only(top: gap_m), // 2 이미지와 글자를 겹치게 하기 위해서 Stack 위젯을 사용한다. child: Stack( children: [ _buildBannerImage(), _buildBannerCaption(), ], ), ); } Widget _buildBannerImage() { return ClipRRect( // 3. 이미지 모서리 둥글게 borderRadius: BorderRadius.circular(20), child: Image.asset( "assets/banner.jpg", fit: BoxFit.cover, width: double.infinity, height: 320, ), ); } Widget _buildBannerCaption() { return Positioned( // 4. Stack 위젯 내부에 위치 설정을 위해 top: 40, left: 40, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( constraints: BoxConstraints( // 5. 글자 범위 최대 제약 조건 주기 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(), ), ), ), ], ), ); } }
import 'package:airbnb_app/size.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import '../../constants.dart'; import '../../style.dart'; class HomeBodyPopularItem extends StatelessWidget { final id; final popularList = [ "p1.jpeg", "p2.jpeg", "p3.jpeg", ]; HomeBodyPopularItem({Key? key, required this.id}) : super(key: key); @override Widget build(BuildContext context) { // 1. 인기아이템은 전체화면의 70%의 1/3만큼의 사이즈의 -5 의 크기를 가진다. double popularItemWidth = getBodyWidth(context) / 3 - 5; return Padding( padding: const EdgeInsets.only(bottom: gap_xl), child: Container( // 2. 화면이 줄어들 때 너무 작게 줄어드는 것을 방지하기 위해 최소 제약조건을 잡아준다. constraints: BoxConstraints( minWidth: 320, ), child: SizedBox( width: popularItemWidth, child: Column( children: [ _buildPopularItemImage(), _buildPopularItemStar(), _buildPopularItemComment(), _buildPopularItemUserInfo(), ], ), ), ), ); } Widget _buildPopularItemImage() { return Column( children: [ ClipRRect( borderRadius: BorderRadius.circular(10), child: Image.asset("assets/${popularList[id]}", fit: BoxFit.cover), ), SizedBox(height: gap_s), ], ); } Widget _buildPopularItemStar() { 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), ], ); } Widget _buildPopularItemComment() { return Column( children: [ Text( "깔끔하고 다 갖춰져있어서 좋았어요:) 위치도 완전 좋아용 다들 여기 살고싶다구ㅋㅋㅋㅋㅋ 화장실도 3개예요!!! 5명이서 씻는것도 전혀 불편함 없이 좋았어요^^ 이불도 포근하고 좋습니당ㅎㅎ", style: body1(), maxLines: 3, overflow: TextOverflow.ellipsis, // 3. 글자가 3 라인을 벗어나면 ... 처리된다. ), SizedBox(height: gap_s), ], ); } Widget _buildPopularItemUserInfo() { return Row( children: [ CircleAvatar( backgroundImage: AssetImage("assets/p1.jpeg"), ), SizedBox(width: gap_s), Column( children: [ Text( "데어", style: subtitle1(), ), Text("한국"), ], ) ], ); } }
Share article