1. 기본 세팅

pubspec.yaml 파일을 세팅 후 pub get을 누른다.
2. 앱 뼈대 만들기
main.dart
import 'package:carrot_market_ui/screens/main_screens.dart';
import 'package:carrot_market_ui/theme.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: MainScreens(),
theme: theme(),
);
}
}
screens/main_screen.dart
import 'package:flutter/material.dart';
class MainScreens extends StatefulWidget {
const MainScreens({super.key});
@override
State<MainScreens> createState() => _MainScreensState();
}
class _MainScreensState extends State<MainScreens> {
@override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Text('MainScreens'),
),
);
}
}
theme.dart
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
TextTheme textTheme() {
return TextTheme(
displayLarge: GoogleFonts.openSans(fontSize: 18.0, color: Colors.black),
displayMedium: GoogleFonts.openSans(fontSize: 16.0, color: Colors.black, fontWeight: FontWeight.bold),
bodyLarge: GoogleFonts.openSans(fontSize: 16.0, color: Colors.black),
bodyMedium: GoogleFonts.openSans(fontSize: 14.0, color: Colors.grey),
titleMedium: GoogleFonts.openSans(fontSize: 15.0, color: Colors.black),
);
}
IconThemeData iconTheme() {
return const IconThemeData(
color: Colors.black,
);
}
AppBarTheme appBarTheme() {
return AppBarTheme(
centerTitle: false,
color: Colors.white,
elevation: 0.0,
iconTheme: iconTheme(),
titleTextStyle: GoogleFonts.nanumGothic(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.black,
),
);
}
BottomNavigationBarThemeData bottomNavigatorTheme() {
return const BottomNavigationBarThemeData(
selectedItemColor: Colors.orange,
unselectedItemColor: Colors.black54,
showUnselectedLabels: true,
);
}
ThemeData theme() {
return ThemeData(
scaffoldBackgroundColor: Colors.white,
textTheme: textTheme(),
appBarTheme: appBarTheme(),
bottomNavigationBarTheme: bottomNavigatorTheme(),
primarySwatch: Colors.orange,
);
}

3. BottomNavigationBar 만들기
3.1. BottomNavigationBar 만들기
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class MainScreens extends StatefulWidget {
const MainScreens({super.key});
@override
State<MainScreens> createState() => _MainScreensState();
}
class _MainScreensState extends State<MainScreens> {
int _selectedIndex = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(
index: _selectedIndex,
children: [
// HomeScreen(),
// NeighborhoodLifeScreen(),
// NearMeScreen(),
// MyCarrotScreen(),
],
),
bottomNavigationBar: BottomNavigationBar(
backgroundColor: Colors.white,
type: BottomNavigationBarType.fixed,
currentIndex: _selectedIndex,
onTap: (index) {
setState(() {
_selectedIndex = index;
});
},
items: [
BottomNavigationBarItem(label: "홈", icon: Icon(CupertinoIcons.home)),
BottomNavigationBarItem(
label: "동네생활", icon: Icon(CupertinoIcons.square_on_square)),
BottomNavigationBarItem(
label: "내 근처", icon: Icon(CupertinoIcons.placemark)),
BottomNavigationBarItem(
label: "채팅", icon: Icon(CupertinoIcons.chat_bubble_2)),
BottomNavigationBarItem(
label: "나의 당근", icon: Icon(CupertinoIcons.person)),
],
),
);
}
}

main_screen.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import '../CustomBottomNavigationBar.dart';
class MainScreens extends StatefulWidget {
const MainScreens({super.key});
@override
State<MainScreens> createState() => _MainScreensState();
}
class _MainScreensState extends State<MainScreens> {
int _selectedIndex = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(
index: _selectedIndex,
children: const [
// HomeScreen(),
// NeighborhoodLifeScreen(),
// NearMeScreen(),
// MyCarrotScreen(),
],
),
bottomNavigationBar: CustomBottomNavigationBar(
selectedIndex: _selectedIndex,
onItemSelected: (index) {
setState(() {
_selectedIndex = index;
});
},
),
);
}
}
custom_bottom_navigation_bar.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class CustomBottomNavigationBar extends StatelessWidget {
final int selectedIndex;
final Function(int) onItemSelected;
const CustomBottomNavigationBar({
Key? key,
required this.selectedIndex,
required this.onItemSelected,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return BottomNavigationBar(
backgroundColor: Colors.white,
type: BottomNavigationBarType.fixed,
currentIndex: selectedIndex,
onTap: onItemSelected,
items: const [
BottomNavigationBarItem(label: "홈", icon: Icon(CupertinoIcons.home)),
BottomNavigationBarItem(label: "동네생활", icon: Icon(CupertinoIcons.square_on_square)),
BottomNavigationBarItem(label: "내 근처", icon: Icon(CupertinoIcons.placemark)),
BottomNavigationBarItem(label: "채팅", icon: Icon(CupertinoIcons.chat_bubble_2)),
BottomNavigationBarItem(label: "나의 당근", icon: Icon(CupertinoIcons.person)),
],
);
}
}
3.2. BottomNavigationBar 내부 작성하기
home_screen.dart
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Center(
child: Text("homeScreen"),
);
}
}

동일한 코드로 다른 화면도 만들어준다.

4. HomeScreen 내부 만들기
model/product.dart
class Product {
String title;
String author;
String address;
String urlToImage;
String publishedAt;
String price;
int heartCount;
int commentsCount;
Product({
required this.title,
required this.author,
required this.address,
required this.urlToImage,
required this.publishedAt,
required this.price,
required this.heartCount,
required this.commentsCount,
});
}
// 샘플 데이터
List<Product> productList = [
Product(
title: '니트 조끼',
author: 'author_1',
urlToImage:
'https://github.com/flutter-coder/ui_images/blob/master/carrot_product_7.jpg?raw=true',
publishedAt: '2시간 전',
heartCount: 8,
price: '35000',
address: '좌동',
commentsCount: 3),
Product(
title: '먼나라 이웃나라 12',
author: 'author_2',
urlToImage:
'https://github.com/flutter-coder/ui_images/blob/master/carrot_product_6.jpg?raw=true',
publishedAt: '3시간 전',
heartCount: 3,
address: '중동',
price: '18000',
commentsCount: 1),
Product(
title: '캐나다구스 패딩조',
author: 'author_3',
address: '우동',
urlToImage:
'https://github.com/flutter-coder/ui_images/blob/master/carrot_product_5.jpg?raw=true',
publishedAt: '1일 전',
heartCount: 0,
price: '15000',
commentsCount: 12,
),
Product(
title: '유럽 여행',
author: 'author_4',
address: '우동',
urlToImage:
'https://github.com/flutter-coder/ui_images/blob/master/carrot_product_4.jpg?raw=true',
publishedAt: '3일 전',
heartCount: 4,
price: '15000',
commentsCount: 11,
),
Product(
title: '가죽 파우치 ',
author: 'author_5',
address: '우동',
urlToImage:
'https://github.com/flutter-coder/ui_images/blob/master/carrot_product_3.jpg?raw=true',
publishedAt: '1주일 전',
heartCount: 7,
price: '95000',
commentsCount: 4,
),
Product(
title: '노트북',
author: 'author_6',
address: '좌동',
urlToImage:
'https://github.com/flutter-coder/ui_images/blob/master/carrot_product_2.jpg?raw=true',
publishedAt: '5일 전',
heartCount: 4,
price: '115000',
commentsCount: 0,
),
Product(
title: '미개봉 아이패드',
author: 'author_7',
address: '좌동',
urlToImage:
'https://github.com/flutter-coder/ui_images/blob/master/carrot_product_1.jpg?raw=true',
publishedAt: '5일 전',
heartCount: 8,
price: '85000',
commentsCount: 3,
),
];
screens/home/components/product_detail.dart
import 'package:flutter/cupertino.dart';
import 'package:intl/intl.dart';
import '../../../model/product.dart';
import '../../../theme.dart';
class ProductDetail extends StatelessWidget {
final Product product;
const ProductDetail({required this.product});
@override
Widget build(BuildContext context) {
return Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(product.title, style: textTheme().bodyLarge),
const SizedBox(height: 4.0),
Text('${product.address} • ${product.publishedAt}'),
const SizedBox(height: 4.0),
Text(
'${numberFormat(product.price)}원',
style: textTheme().displayMedium, //
),
const Spacer(),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Visibility(
visible: product.commentsCount > 0,
child: _buildIcons(
product.commentsCount,
CupertinoIcons.chat_bubble_2,
),
),
const SizedBox(width: 8.0),
Visibility(
visible: product.heartCount > 0,
child: _buildIcons(
product.heartCount,
CupertinoIcons.heart,
),
),
],
)
],
),
);
}
String numberFormat(String price) {
final formatter = NumberFormat('#,###');
return formatter.format(int.parse(price));
}
Widget _buildIcons(int count, IconData iconData) {
return Row(
children: [
Icon(iconData, size: 14.0),
const SizedBox(width: 4.0),
Text('$count'),
],
);
}
}
home/components/product_item.dart
import 'package:carrot_market_ui/screens/home/components/product_detail.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import '../../../model/product.dart';
class ProductItem extends StatelessWidget {
final Product product;
ProductItem({required this.product});
@override
Widget build(BuildContext context) {
return Container(
height: 135.0,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(10.0),
child: Image.network(
product.urlToImage,
width: 115,
height: 115,
fit: BoxFit.cover,
),
),
SizedBox(width: 16.0),
ProductDetail(product : product)
],
),
),
);
}
}
screens/home/home_screen.dart
import 'package:carrot_market_ui/model/product.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'components/product_item.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Row(
children: [
Text("좌동"),
SizedBox(width: 4.0),
Icon(
CupertinoIcons.chevron_down,
size: 15.0,
),
],
),
actions: [
IconButton(icon: Icon(CupertinoIcons.search), onPressed: () {}),
IconButton(icon: Icon(CupertinoIcons.list_dash), onPressed: () {}),
IconButton(icon: Icon(CupertinoIcons.bell), onPressed: () {}),
],
bottom: PreferredSize(
preferredSize: Size.fromHeight(0.5),
child: Divider(thickness: 0.5, height: 0.5, color: Colors.grey),
),
),
body: ListView.separated(
separatorBuilder: (context, index) => Divider(
height: 0,
indent: 16,
endIndent: 16,
color: Colors.grey,
),
itemBuilder: (context, index) {
return ProductItem(
product: productList[index],
);
},
itemCount: productList.length,
),
);
}
}

5. MyCarrotScreen 만들기
model/icon_menu.dart
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
class IconMenu {
final String title;
final IconData iconData;
IconMenu({required this.title, required this.iconData});
}
final List<IconMenu> iconMenu1 = [
IconMenu(title: "내 동네 설정", iconData: FontAwesomeIcons.mapMarkerAlt),
IconMenu(title: "동네 인증하기", iconData: FontAwesomeIcons.compressArrowsAlt),
IconMenu(title: "내 동네 설정", iconData: FontAwesomeIcons.tag),
IconMenu(title: "내 동네 설정", iconData: FontAwesomeIcons.borderAll),
];
final List<IconMenu> iconMenu2 = [
IconMenu(title: "동네생활 글",iconData: FontAwesomeIcons.edit),
IconMenu(title: "동네생활 댓글",iconData: FontAwesomeIcons.edit),
IconMenu(title: "동네생활 주제 목록",iconData: FontAwesomeIcons.star),
];
final List<IconMenu> iconMenu3 = [
IconMenu(title: "비즈프로필 관리", iconData: FontAwesomeIcons.store),
IconMenu(title: "지역광고", iconData: FontAwesomeIcons.bullhorn),
];
my_carrot/components/my_carrot_screen.dart
import 'package:flutter/material.dart';
import '../../model/icon_menu.dart';
import 'components/card_item_menu.dart';
import 'components/my_header.dart';
class MyCarrotScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[100],
appBar: AppBar(
title: Text("나의 당근"),
actions: [
IconButton(icon: Icon(Icons.settings), onPressed: () {}),
],
bottom: PreferredSize(
preferredSize: Size.fromHeight(0.5),
child: Divider(thickness: 0.5, height: 0.5, color: Colors.grey),
),
),
body: ListView(
children: [
MyCarrotHeader(),
SizedBox(height: 8.0),
CardIconMenu(iconMenuList: iconMenu1),
SizedBox(height: 8.0),
CardIconMenu(iconMenuList: iconMenu2),
SizedBox(height: 8.0),
CardIconMenu(iconMenuList: iconMenu3),
],
),
);
}
}
screens/my_carrot/components/my_carrot_header.dart
import 'package:carrot_market_ui/theme.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
class MyCarrotHeader extends StatelessWidget {
const MyCarrotHeader({super.key});
@override
Widget build(BuildContext context) {
return Card(
elevation: 0.5,
margin: EdgeInsets.zero,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0.0)),
child: Column(
children: [
_buildProfileRow(),
SizedBox(height: 30),
_buildProfileButton(),
SizedBox(height: 30),
Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildRoundTextButton('판매내역', FontAwesomeIcons.receipt),
_buildRoundTextButton('구매내역', FontAwesomeIcons.shoppingBag),
_buildRoundTextButton('관심목록', FontAwesomeIcons.heart),
],
),
),
],
),
);
}
Widget _buildRoundTextButton(String title, IconData iconData) {
return Column(
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30.0),
color: Color.fromRGBO(255, 226, 208, 1),
border: Border.all(color: Color(0xFFD4D5DD), width: 0.5)),
child: Icon(
iconData,
color: Colors.orange,
),
),
SizedBox(height: 10),
Text(
title,
style: textTheme().titleMedium,
)
],
);
}
Widget _buildProfileButton() {
return InkWell(
onTap: () {},
child: Container(
decoration: BoxDecoration(
border: Border.all(
color: Color(0xFFD4D5DD),
width: 1.0,
),
borderRadius: BorderRadius.circular(6.0),
),
height: 45,
child: Text(
"프로필 보기",
style: textTheme().titleMedium,
),
),
);
}
Widget _buildProfileRow() {
return Row(
children: [
Stack(
children: [
SizedBox(
width: 65,
height: 65,
child: ClipRRect(
borderRadius: BorderRadius.circular(32.5),
child: Image.network(
"https://picsum.photos/200/100",
fit: BoxFit.cover,
),
),
),
Positioned(
bottom: 0,
right: 0,
child: Container(
width: 20,
height: 20,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
color: Colors.grey[100]),
child: Icon(
Icons.camera_alt_outlined,
size: 15,
),
),
)
],
),
SizedBox(width: 16),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("developer",style: textTheme().displayMedium),
SizedBox(height: 10),
Text("좌동 #00912"),
],
),
],
);
}
}
import 'package:carrot_market_ui/theme.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
class MyCarrotHeader extends StatelessWidget {
const MyCarrotHeader({super.key});
@override
Widget build(BuildContext context) {
return Card(
elevation: 0.5,
margin: EdgeInsets.zero,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0.0)),
child: Column(
children: [
_buildProfileRow(),
SizedBox(height: 30),
_buildProfileButton(),
SizedBox(height: 30),
Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildRoundTextButton('판매내역', FontAwesomeIcons.receipt),
_buildRoundTextButton('구매내역', FontAwesomeIcons.shoppingBag),
_buildRoundTextButton('관심목록', FontAwesomeIcons.heart),
],
),
),
],
),
);
}
Widget _buildRoundTextButton(String title, IconData iconData) {
return Column(
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30.0),
color: Color.fromRGBO(255, 226, 208, 1),
border: Border.all(color: Color(0xFFD4D5DD), width: 0.5)),
child: Icon(
iconData,
color: Colors.orange,
),
),
SizedBox(height: 10),
Text(
title,
style: textTheme().titleMedium,
)
],
);
}
Widget _buildProfileButton() {
return InkWell(
onTap: () {},
child: Container(
decoration: BoxDecoration(
border: Border.all(
color: Color(0xFFD4D5DD),
width: 1.0,
),
borderRadius: BorderRadius.circular(6.0),
),
height: 45,
child: Text(
"프로필 보기",
style: textTheme().titleMedium,
),
),
);
}
Widget _buildProfileRow() {
return Row(
children: [
Stack(
children: [
SizedBox(
width: 65,
height: 65,
child: ClipRRect(
borderRadius: BorderRadius.circular(32.5),
child: Image.network(
"https://picsum.photos/200/100",
fit: BoxFit.cover,
),
),
),
Positioned(
bottom: 0,
right: 0,
child: Container(
width: 20,
height: 20,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
color: Colors.grey[100]),
child: Icon(
Icons.camera_alt_outlined,
size: 15,
),
),
)
],
),
SizedBox(width: 16),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("developer",style: textTheme().displayMedium),
SizedBox(height: 10),
Text("좌동 #00912"),
],
),
],
);
}
}
screens/my_carrot/componets/card_item_menu.dart
import 'package:carrot_market_ui/theme.dart';
import 'package:flutter/material.dart';
import '../../../model/icon_menu.dart';
class CardIconMenu extends StatelessWidget {
final List<IconMenu> iconMenuList;
CardIconMenu({required this.iconMenuList});
@override
Widget build(BuildContext context) {
return Card(
elevation: 0.5,
margin: EdgeInsets.zero,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0.0)),
child: Column(
children: List.generate(
iconMenuList.length,
(index) => _buildRowIconItem(
iconMenuList[0].title, iconMenuList[0].iconData),
),
),
);
}
Widget _buildRowIconItem(String title, IconData iconData) {
return Container(
height: 50,
child: Row(
children: [
Icon(iconData, size: 17),
SizedBox(width: 20),
Text(
title,
style: textTheme().titleMedium,
)
],
),
);
}
}

6. chatting_screen 페이지 만들기
model/chatting_message.dart
class ChatMessage {
final String sender;
final String profileImage;
final String location;
final String sendDate;
final String message;
final String? imageUri;
ChatMessage(
{required this.sender,
required this.profileImage,
required this.location,
required this.sendDate,
required this.message,
this.imageUri});
}
List<ChatMessage> chatMessageList = [
ChatMessage(
sender: "당근이",
profileImage: "https://picsum.photos/id/870/200/100?grayscale",
location: "대부동",
sendDate: "1일전",
message: "developer님, 근처에 다양한 물품들이 아주 많이 있습니다."),
ChatMessage(
sender: "Flutter",
profileImage: "https://picsum.photos/id/880/200/100?grayscale",
location: "중동",
sendDate: "2일전",
message: "안녕하세요. 지금 다 예약 상태 인가요?",
imageUri: "https://picsum.photos/id/890/200/100?grayscale",
)
];
screens/chatting_screen.dart
import 'package:carrot_market_ui/model/chatting_message.dart';
import 'package:flutter/material.dart';
import '../../components/appbar_preferred_size.dart';
import 'components/chat_container.dart';
class ChattingScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("채팅"),
bottom: appBarBottomLine(),
),
body: ListView(
children: List.generate(
chatMessageList.length,
(index) => ChatContainer(chatMessage: chatMessageList[index])),
),
);
}
}
screens/components/chat_container.dart
import 'package:carrot_market_ui/components/image_container.dart';
import 'package:carrot_market_ui/model/chatting_message.dart';
import 'package:carrot_market_ui/theme.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class ChatContainer extends StatelessWidget {
final ChatMessage chatMessage;
const ChatContainer({
Key? key,
required this.chatMessage,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: Colors.grey, width: 0.5)),
),
height: 100,
child: Row(
children: [
ImageContainer(
width: 50,
height: 50,
borderRadius: 25,
imageUrl: chatMessage.profileImage,
),
SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Spacer(),
Text.rich(
TextSpan(children: [
TextSpan(
text: chatMessage.sender, style: textTheme().bodyLarge),
TextSpan(text: chatMessage.location),
TextSpan(text: "•${chatMessage.sendDate}"),
]),
),
Spacer(),
Text(
chatMessage.message,
style: textTheme().bodyLarge,
overflow: TextOverflow.ellipsis,
),
Spacer(),
],
),
),
Visibility(visible: chatMessage.imageUri != null,
child: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: ImageContainer(
width: 50,
height: 50,
borderRadius: 8,
imageUrl: chatMessage.imageUri ?? '',
),
),
),)
],
),
);
}
}
components/appbar_preferred_size.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
// 앱바 하단에 선을 추가하는 기능
PreferredSize appBarBottomLine() {
final height = 0.5;
return PreferredSize(
preferredSize: Size.fromHeight(height),
child: Divider(
thickness: height,
height: height,
color: Colors.grey,
),
);
}
components/image_container.dart
import 'package:flutter/material.dart';
class ImageContainer extends StatelessWidget {
final double borderRadius;
final String imageUrl;
final double width;
final double height;
ImageContainer(
{required this.borderRadius,
required this.imageUrl,
required this.width,
required this.height});
@override
Widget build(BuildContext context) {
return ClipRRect(
borderRadius: BorderRadius.circular(borderRadius),
child: Image.network(
imageUrl,
width: width,
height: height,
fit: BoxFit.cover,
),
);
}
}

7. neighborhood_life 페이지 만들기
model/neighborhood_life.dart
class NeighborhoodLife {
final String category;
final String profileImgUri;
final String userName;
final String location;
final String content;
final String contentImgUri;
final int commentCount;
final int authCount;
final String date;
NeighborhoodLife({
required this.category,
required this.profileImgUri,
required this.userName,
required this.location,
required this.content,
required this.contentImgUri,
required this.commentCount,
required this.authCount,
required this.date,
});
}
// 샘플 데이터 1
String lifeTitle = '이웃과 함께 만드는 봄 간식 지도 마음까지 따듯해지는 봄 간식을 만나보세요.';
// 샘플 데이터 2
List<NeighborhoodLife> neighborhoodLifeList = [
NeighborhoodLife(
category: '우리동네질문',
profileImgUri: 'https://picsum.photos/id/871/200/300?grayscale', // TODO 06 수정
userName: '헬로비비',
location: '좌동',
content: '예민한 개도 미용할 수 있는 곳이나 동물 병원 어디 있을까요?\n'
'내일 유기견을 데려오기로 했는데 아직 성향을 잘 몰라서 걱정이 돼요 ㅜㅜ.',
contentImgUri: 'https://picsum.photos/id/872/200/300?grayscale',
commentCount: 11,
authCount: 3,
date: '3시간전',
),
NeighborhoodLife(
category: '우리동네소식',
profileImgUri: 'https://picsum.photos/id/873/200/100?grayscale',
userName: '당근토끼',
location: '우동',
content: '이명 치료 잘 아시는 분 있나요?',
contentImgUri: 'https://picsum.photos/id/874/200/100?grayscale',
commentCount: 2,
authCount: 1,
date: '1일전',
),
NeighborhoodLife(
category: '분실',
profileImgUri: 'https://picsum.photos/id/875/200/100?grayscale',
userName: 'flutter',
location: '중동',
content: '롯데캐슬 방향으로 재래시장 앞쪽 지나 혹시 에어팟 오른쪽 주우신 분 있나요ㅜㅜ',
contentImgUri: '',
commentCount: 11,
authCount: 8,
date: '1일전',
),
NeighborhoodLife(
category: '우리동네질문',
profileImgUri: 'https://picsum.photos/id/880/200/100',
userName: '구름나드리',
location: '우동',
content: '밤부터 새벽까지 하던 토스트 아저씨 언제 다시 오나요ㅜㅠ',
contentImgUri: '',
commentCount: 0,
authCount: 7,
date: '3일전',
),
NeighborhoodLife(
category: '우리동네질문',
profileImgUri: 'https://picsum.photos/id/730/200/100?grayscale',
userName: '아는형',
location: '만덕동',
content: '아니 이 시간에 마이크 들고 노래하는 사람은 정상인가요?',
contentImgUri: 'https://picsum.photos/id/885/200/100',
commentCount: 11,
authCount: 2,
date: '5일전',
),
];
screens/neighborhood_life_screen.dart
import 'package:carrot_market_ui/components/appbar_preferred_size.dart';
import 'package:carrot_market_ui/model/neighborhood_life.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'components/life_body.dart';
import 'components/life_header.dart';
class NeighborhoodLifeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("동네생활"),
actions: [
IconButton(icon: Icon(CupertinoIcons.search), onPressed: () {}),
IconButton(
icon: Icon(CupertinoIcons.plus_rectangle_on_rectangle),
onPressed: () {}),
IconButton(icon: Icon(CupertinoIcons.bell), onPressed: () {}),
],
bottom: appBarBottomLine(),
),
body: ListView(
children: [
LifeHeader(),
...List.generate(
neighborhoodLifeList.length,
(index) => Padding(padding: const EdgeInsets.only(bottom: 8.0),
child: LifeBody(
neighborhoodLife: neighborhoodLifeList[index],
),),
),
],
),
);
}
}
screens/neighborhood_life/components/life_header.dart
import 'package:carrot_market_ui/components/image_container.dart';
import 'package:carrot_market_ui/model/neighborhood_life.dart';
import 'package:carrot_market_ui/theme.dart';
import 'package:flutter/material.dart';
class LifeHeader extends StatelessWidget {
const LifeHeader({super.key});
@override
Widget build(BuildContext context) {
return Card(
margin: EdgeInsets.only(bottom: 12.0),
elevation: 0.5,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0.0)),
child: Row(
children: [
ImageContainer(
borderRadius: 6.0,
imageUrl: "https://picsum.photos/id/780/200/1000",
width: 45.0,
height: 45.0,
),
SizedBox(width: 16.0),
Expanded(
child: Text(
lifeTitle,
style: textTheme().bodyLarge,
maxLines: 2,
overflow: TextOverflow.ellipsis,
))
],
),
);
}
}
screens/neighborhood_life/components/life_body.dart
import 'package:carrot_market_ui/components/image_container.dart';
import 'package:carrot_market_ui/model/neighborhood_life.dart';
import 'package:carrot_market_ui/theme.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
class LifeBody extends StatelessWidget {
final NeighborhoodLife neighborhoodLife;
LifeBody({required this.neighborhoodLife});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border(
bottom: BorderSide(width: 0.5, color: Color(0xFFD4D5DD)),
),
),
child: Column(
children: [
_buildTop(),
_buildWriter(),
_buildWrting(),
_buildImage(),
Divider(
height: 1,
thickness: 1,
color: Colors.grey[300],
),
_buildTail(neighborhoodLife.commentCount),
],
),
);
}
Padding _buildTop() {
return Padding(
padding: EdgeInsets.symmetric(
vertical: 16,
horizontal: 16,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
decoration: BoxDecoration(
shape: BoxShape.rectangle,
borderRadius: BorderRadius.all(Radius.circular(4)),
color: Color.fromRGBO(247, 247, 247, 1),
),
child: Text(neighborhoodLife.category,
style: textTheme().displayMedium),
),
Text(
neighborhoodLife.date,
style: textTheme().displayMedium,
)
],
),
);
}
Padding _buildWriter() {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: [
ImageContainer(
width: 30,
height: 30,
borderRadius: 15,
imageUrl: neighborhoodLife.profileImgUri,
),
Text.rich(
TextSpan(
children: [
TextSpan(
text: "${neighborhoodLife.userName}",
style: textTheme().bodyLarge),
TextSpan(
text: "${neighborhoodLife.location}",
style: textTheme().bodyMedium),
TextSpan(
text: "인증 ${neighborhoodLife.authCount}",
style: textTheme().bodyMedium),
],
),
),
],
),
);
}
Padding _buildWrting() {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 16),
child: Align(
alignment: Alignment.centerLeft,
child: Text(
neighborhoodLife.content,
style: textTheme().bodyLarge,
maxLines: 3,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.start,
),
),
);
}
Visibility _buildImage() {
return Visibility(
visible: neighborhoodLife.contentImgUri != "",
child: Padding(
padding: const EdgeInsets.only(left: 16, right: 16, bottom: 16),
child: Image.network(
neighborhoodLife.contentImgUri,
height: 200,
width: double.infinity,
fit: BoxFit.cover,
),
),
);
}
Padding _buildTail(int commentCount) {
return Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Icon(
FontAwesomeIcons.smile,
color: Colors.grey,
size: 22,
),
SizedBox(width: 8),
Text(
"공감하기",
style: TextStyle(fontSize: 16, color: Colors.black),
),
Icon(
FontAwesomeIcons.commentAlt,
color: Colors.grey,
size: 22,
),
SizedBox(width: 8),
Text(
"${"댓글쓰기"} $commentCount",
style: TextStyle(fontSize: 16, color: Colors.black),
)
],
),
);
}
}

8. near_me 페이지 만들기
model/recommend_store.dart
class RecommendStore {
String storeName;
String location;
String description;
int commentCount;
int likeCount;
String comment;
String commentUser;
List storeImages;
RecommendStore(
{required this.storeName,
required this.location,
required this.description,
required this.commentCount,
required this.likeCount,
required this.comment,
required this.commentUser,
required this.storeImages});
}
final List searchKeyword = ['인테리어', '학원', '이사', '카페', '용달', '네일', '에어콘'];
List<RecommendStore> recommendStoreList = [
RecommendStore(
storeName: '네일가게',
location: '좌동',
description: '꼼꼼한시술로 유지력높은 네일샵입니다. 좌동에 위치하고 있습니다.',
commentCount: 1,
likeCount: 8,
commentUser: '이엘리아님',
comment: '너무편하게 시술해주셔서 잠들었었네요 직모에 짧은 눈썹이라 펌이 잘 안되는 타입인데 너무 이쁘게 됐네요',
storeImages: [
'https://github.com/flutter-coder/ui_images/blob/master/carrot_store_1_1.jpg?raw=true',
'https://github.com/flutter-coder/ui_images/blob/master/carrot_store_1_2.jpg?raw=true',
]),
RecommendStore(
storeName: '아미아미주먹밥',
location: '우동',
description: '2012년 오픈한 해운대도서관 분관쪽에 위치하고 있습니다.',
commentCount: 2,
likeCount: 2,
commentUser: '둘리님',
comment: '도서관이 근처라 시험기간마다 이용하는데 너무 좋습니다.',
storeImages: [
'https://github.com/flutter-coder/ui_images/blob/master/carrot_store_2_1.jpg?raw=true',
'https://github.com/flutter-coder/ui_images/blob/master/carrot_store_2_2.jpg?raw=true',
]),
RecommendStore(
storeName: '영어원어민 논술',
location: '중동',
description: '원어민 영어 고급논술&디베이트&스피치 전문',
commentCount: 7,
likeCount: 1,
commentUser: 'kkglo님',
comment: '저희 아들은 학원 주입식이 아닌 살아있는 영어 수업을 할 수 있어서 너무 좋네요',
storeImages: [
'https://github.com/flutter-coder/ui_images/blob/master/carrot_store_3_1.jpg?raw=true',
'https://github.com/flutter-coder/ui_images/blob/master/carrot_store_3_2.jpg?raw=true',
]),
RecommendStore(
storeName: '삘레빙/코인워시 우동점',
location: '우동',
description: '빨래방 / 크린토비아 코인워시 우동점 신설했습니다 많은 이용 바랍니다.',
commentCount: 11,
likeCount: 5,
commentUser: '코인님',
comment: '처음 방문때 건조기 무료로 서비스 해주셔서 너무 감사 하네요. 앞으로 자주 이용 합니다.',
storeImages: [
'https://github.com/flutter-coder/ui_images/blob/master/carrot_store_4_1.jpg?raw=true',
'https://github.com/flutter-coder/ui_images/blob/master/carrot_store_4_2.jpg?raw=true',
])
];
screens/near_me/components/search_text_field.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class SearchTextField extends StatelessWidget {
const SearchTextField({super.key});
@override
Widget build(BuildContext context) {
return Container(
child: TextField(
cursorColor: Colors.grey,
decoration: InputDecoration(
disabledBorder: _buildOutLineInputBorder(),
enabledBorder: _buildOutLineInputBorder(),
focusedBorder: _buildOutLineInputBorder(),
filled: true,
fillColor: Colors.grey[200],
prefixIcon: Icon(
CupertinoIcons.search,
color: Colors.grey,
),
contentPadding:
EdgeInsets.only(left: 0, bottom: 15, top: 15, right: 0),
hintText: "좌동 주변 가게를 찾아보세요.",
hintStyle: TextStyle(fontSize: 18),
),
),
);
}
OutlineInputBorder _buildOutLineInputBorder() {
return OutlineInputBorder(
borderSide: BorderSide(width: 0.5, color: Color(0xFFD4D5DD)),
borderRadius: BorderRadius.circular(8.0),
);
}
}
round_border_text.dart
import 'package:flutter/material.dart';
class RoundBorderText extends StatelessWidget {
final String title;
final int position;
const RoundBorderText({required this.title, required this.position});
@override
Widget build(BuildContext context) {
var paddingValue = position == 0 ? 16.0 : 8.0;
return Padding(
padding: EdgeInsets.only(left: paddingValue),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 8.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(18.0),
border: Border.all(width: 0.5, color: Colors.grey),
),
child: Text(
title,
textAlign: TextAlign.center,
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
),
),
);
}
}
bottom_title_icon.dart
import 'package:flutter/material.dart';
class BottomTitleIcon extends StatelessWidget {
final IconData iconData;
final String title;
BottomTitleIcon({required this.iconData, required this.title});
@override
Widget build(BuildContext context) {
return Container(
width: 80,
child: Column(
children: [
Icon(iconData, size: 30),
Padding(
padding: EdgeInsets.only(top: 8),
child: Text(
title,
style: TextStyle(fontSize: 14, color: Colors.black),
),
),
],
),
);
}
}
store_item.dart
import 'package:carrot_market_ui/theme.dart';
import 'package:flutter/material.dart';
import '../../../model/recommend_store.dart';
class StoreItem extends StatelessWidget {
final RecommendStore recommendStore;
const StoreItem({required this.recommendStore});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(width: 0.3, color: Colors.grey)),
width: 289,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
buildClipRRect(topLeft: 10),
SizedBox(width: 2),
buildClipRRect(topRight: 10),
],
),
Padding(
padding: EdgeInsets.all(16),
child: Text.rich(
TextSpan(
children: [
TextSpan(
text: "${recommendStore.storeName}",
style: textTheme().displayLarge),
TextSpan(text: "${recommendStore.location}"),
],
),
),
),
SizedBox(height: 8),
Text(
"${recommendStore.description}",
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: textTheme().titleMedium,
),
SizedBox(height: 8),
Container(
margin: EdgeInsets.only(left: 16, right: 16, bottom: 16),
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.circular((10))),
child: Text.rich(
TextSpan(
children: [
TextSpan(
text: "후기 ${recommendStore.commentCount}",
style: TextStyle(
fontSize: 13,
color: Colors.black,
fontWeight: FontWeight.bold)),
TextSpan(
text: "${recommendStore.comment}",
style: TextStyle(fontSize: 12, color: Colors.black)),
],
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
],
),
);
}
ClipRRect buildClipRRect({double topLeft = 0, double topRight = 0}) {
return ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(topLeft),
topRight: Radius.circular(topRight),
),
child: Image.network(
recommendStore.storeImages[0],
width: 143,
height: 100,
fit: BoxFit.cover,
),
);
}
}
near_me_screen.dart
import 'package:carrot_market_ui/components/appbar_preferred_size.dart';
import 'package:carrot_market_ui/model/recommend_store.dart';
import 'package:carrot_market_ui/screens/near_me/components/store_item.dart';
import 'package:carrot_market_ui/theme.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'components/bottom_title_icon.dart';
import 'components/round_border_text.dart';
import 'components/search_text.field.dart';
class NearMeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("내 근처"),
actions: [
IconButton(icon: Icon(CupertinoIcons.pencil), onPressed: () {}),
IconButton(icon: Icon(CupertinoIcons.bell), onPressed: () {}),
],
bottom: appBarBottomLine(),
),
body: ListView(
children: [
SizedBox(height: 10),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: SearchTextField(),
),
SizedBox(
height: 66,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: searchKeyword.length,
itemBuilder: (context, index) {
return Center(
child: RoundBorderText(
title: searchKeyword[index],
position: index,
),
);
},
),
),
Divider(
color: Colors.grey[100],
thickness: 10.0,
),
Padding(
padding: EdgeInsets.only(left: 16, top: 30),
child: Wrap(
alignment: WrapAlignment.start,
spacing: 22.0,
runSpacing: 30,
children: [
BottomTitleIcon(title: '구인구직', iconData: FontAwesomeIcons.user),
BottomTitleIcon(
title: '과외/클래스', iconData: FontAwesomeIcons.edit),
BottomTitleIcon(
title: '농수산물', iconData: FontAwesomeIcons.appleAlt),
BottomTitleIcon(title: '부동산', iconData: FontAwesomeIcons.hotel),
BottomTitleIcon(title: '중고차', iconData: FontAwesomeIcons.car),
BottomTitleIcon(
title: '전시/행사', iconData: FontAwesomeIcons.chessBishop)
],
),
),
SizedBox(height: 50),
Padding(
padding: EdgeInsets.only(left: 16.0),
child: Text("이웃들의 추천 가게", style: textTheme().displayMedium),
),
SizedBox(height: 20),
Container(
height: 288,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: recommendStoreList.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.only(left: 16),
child: StoreItem(
recommendStore: recommendStoreList[index],
),
);
},
),
),
SizedBox(height: 40),
],
),
);
}
}

Share article