Last active
January 20, 2020 08:23
-
-
Save ChangJoo-Park/d1b12bf552edd6005b8b81b95aedea97 to your computer and use it in GitHub Desktop.
Flutter로 당근마켓 만들기
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file | |
// for details. All rights reserved. Use of this source code is governed by a | |
// BSD-style license that can be found in the LICENSE file. | |
import 'package:flutter/material.dart'; | |
import 'package:market/image_list_tile.dart'; | |
List<Map<String, dynamic>> list = <Map<String, dynamic>>[ | |
{ | |
'image': | |
'https://dnvefa72aowie.cloudfront.net/origin/article/202001/245bb54195ae001a87e09830c0a3140e30cba0366a275c64b2b23afdf1043c43.webp?q=82&s=300x300&t=crop', | |
'name': '김치냉장고 무료 나눔합니다', | |
'place': '서울 성북구 상월곡동', | |
'uploadAt': '방금', | |
'price': '10,000원', | |
'numberOfChat': 40, | |
'numberOfHeart': 21, | |
'numberOfComment': 0, | |
}, | |
{ | |
'image': | |
'https://dnvefa72aowie.cloudfront.net/origin/article/202001/c84830f43b5b9912b4871673a99cd74bb7e9ccb02273e6ff89a9445cddc08b57.webp?q=82&s=300x300&t=crop', | |
'name': '이사정리 ( 침대 화장대 통돌이 드럼 세탁기 쇼파 냉장고 팝니다)', | |
'place': '제주 제주시 조천읍', | |
'uploadAt': '9시간 전', | |
'price': '300,000원', | |
'numberOfChat': 11, | |
'numberOfHeart': 0, | |
'numberOfComment': 24, | |
}, | |
{ | |
'image': | |
'https://dnvefa72aowie.cloudfront.net/origin/article/202001/737cfa849fd985b80cbe174fe1b79e040dd4efdd72fed3a28911ecf2467948ea.webp?q=82&s=300x300&t=crop', | |
'name': '제습기', | |
'place': '대전 서구 둔산동', | |
'uploadAt': '9시간 전', | |
'price': '50,000원', | |
'numberOfChat': 3, | |
'numberOfHeart': 1, | |
'numberOfComment': 24, | |
}, | |
{ | |
'image': | |
'https://dnvefa72aowie.cloudfront.net/origin/article/202001/d684cfb333962675f8d7b526f7b1b97bea10d0d7c837a97ab0428c63f77ad454.webp?q=82&s=300x300&t=crop', | |
'name': '전자레인지', | |
'place': '전북 전주시 덕진구 송천동 2가', | |
'uploadAt': '9시간 전', | |
'price': '10,000원', | |
'numberOfChat': 10, | |
'numberOfHeart': 1, | |
'numberOfComment': 27, | |
}, | |
{ | |
'image': | |
'https://dnvefa72aowie.cloudfront.net/origin/article/202001/ae156634f7b41d8f8f5655488e03bcf885f4f3e651cde3e22017cd307c483a42.webp?q=82&s=300x300&t=crop', | |
'name': '무료나눔', | |
'place': '제주 서귀포시 성산읍', | |
'uploadAt': '7시간 전', | |
'price': '무료나눔', | |
'numberOfChat': 2, | |
'numberOfHeart': 0, | |
'numberOfComment': 7, | |
} | |
]; | |
void main() => runApp(MyApp()); | |
class MyApp extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
title: 'Flutter Demo', | |
debugShowCheckedModeBanner: false, | |
theme: ThemeData( | |
primaryColor: Colors.white, | |
accentColor: Colors.black, | |
), | |
home: MyHomePage(), | |
); | |
} | |
} | |
class MyHomePage extends StatelessWidget { | |
MyHomePage({Key key}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
return DefaultTabController( | |
length: choices.length, | |
child: Scaffold( | |
appBar: AppBar( | |
title: Container( | |
child: GestureDetector( | |
child: Row(children: [ | |
Text('서초동', | |
style: | |
TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold)), | |
Icon(Icons.expand_more) | |
]), | |
), | |
), | |
actions: <Widget>[ | |
IconButton(icon: Icon(Icons.search), onPressed: () {}), | |
IconButton(icon: Icon(Icons.settings), onPressed: () {}), | |
IconButton(icon: Icon(Icons.notifications_none), onPressed: () {}), | |
], | |
bottom: TabBar( | |
tabs: choices.map((Choice choice) { | |
return Tab( | |
text: choice.title, | |
); | |
}).toList(), | |
), | |
), | |
body: TabBarView( | |
children: choices.map((Choice choice) { | |
return ListView.builder( | |
itemCount: list.length, | |
itemBuilder: (BuildContext context, int index) { | |
Map<String, dynamic> item = list[index]; | |
return ImageListTile( | |
imageWidth: 80, | |
imageHeight: 80, | |
image: item['image'], | |
customChild: Container( | |
padding: EdgeInsets.only(right: 8.0), | |
child: Column( | |
crossAxisAlignment: CrossAxisAlignment.stretch, | |
children: <Widget>[ | |
Text( | |
item['name'], | |
style: TextStyle( | |
fontSize: 14.0, fontWeight: FontWeight.bold), | |
overflow: TextOverflow.ellipsis, | |
), | |
Text( | |
'${item["place"]} · ${item["uploadAt"]}', | |
style: TextStyle(fontSize: 12.0), | |
overflow: TextOverflow.ellipsis, | |
), | |
Text( | |
item['price'], | |
style: TextStyle(fontSize: 12.0), | |
overflow: TextOverflow.ellipsis, | |
), | |
Spacer(flex: 1), | |
Row( | |
mainAxisAlignment: MainAxisAlignment.end, | |
children: <Widget>[ | |
item['numberOfComment'] > 0 | |
? Row( | |
children: <Widget>[ | |
Icon( | |
Icons.comment, | |
size: 14.0, | |
color: const Color(0xff8785A4), | |
), | |
Text(" ${item['numberOfComment']}", | |
style: TextStyle( | |
fontSize: 14.0, | |
color: const Color(0xff8785A4))), | |
SizedBox(width: 8.0), | |
], | |
) | |
: SizedBox(width: 0, height: 0), | |
item['numberOfChat'] > 0 | |
? Row( | |
children: <Widget>[ | |
Icon(Icons.chat_bubble_outline, | |
size: 14.0, | |
color: const Color(0xff8785A4)), | |
Text(" ${item['numberOfChat']}", | |
style: TextStyle( | |
fontSize: 14.0, | |
color: const Color(0xff8785A4))), | |
SizedBox(width: 8.0), | |
], | |
) | |
: SizedBox(width: 0, height: 0), | |
item['numberOfHeart'] > 0 | |
? Row( | |
children: <Widget>[ | |
Icon(Icons.favorite_border, | |
size: 14.0, | |
color: const Color(0xff8785A4)), | |
Text(" ${item['numberOfHeart']}", | |
style: TextStyle( | |
fontSize: 14.0, | |
color: const Color(0xff8785A4))), | |
], | |
) | |
: SizedBox(width: 0, height: 0) | |
], | |
) | |
], | |
), | |
), | |
); | |
}, | |
); | |
}).toList(), | |
), | |
bottomNavigationBar: BottomNavigationBar( | |
items: <BottomNavigationBarItem>[ | |
BottomNavigationBarItem( | |
icon: Icon(Icons.home, color: Colors.black), | |
title: Text('홈', style: TextStyle(color: Colors.black))), | |
BottomNavigationBarItem( | |
icon: Icon(Icons.menu, color: Colors.grey), | |
title: Text('카테고리', style: TextStyle(color: Colors.grey))), | |
BottomNavigationBarItem( | |
icon: Icon(Icons.create, color: Colors.grey), | |
title: Text('글쓰기', style: TextStyle(color: Colors.grey))), | |
BottomNavigationBarItem( | |
icon: Icon(Icons.favorite, color: Colors.grey), | |
title: Text('채팅', style: TextStyle(color: Colors.grey))), | |
BottomNavigationBarItem( | |
icon: Icon(Icons.person, color: Colors.grey), | |
title: Text('나의당근', style: TextStyle(color: Colors.grey))), | |
], | |
type: BottomNavigationBarType.fixed, | |
currentIndex: 0, | |
fixedColor: Colors.black, | |
onTap: (int index) {}, | |
), | |
), | |
); | |
} | |
} | |
class Choice { | |
const Choice({this.title}); | |
final String title; | |
} | |
const List<Choice> choices = const <Choice>[ | |
const Choice(title: '중고거래'), | |
const Choice(title: '동네생활'), | |
]; | |
int hexToInt(String hex) { | |
int val = 0; | |
int len = hex.length; | |
for (int i = 0; i < len; i++) { | |
int hexDigit = hex.codeUnitAt(i); | |
if (hexDigit >= 48 && hexDigit <= 57) { | |
val += (hexDigit - 48) * (1 << (4 * (len - 1 - i))); | |
} else if (hexDigit >= 65 && hexDigit <= 70) { | |
// A..F | |
val += (hexDigit - 55) * (1 << (4 * (len - 1 - i))); | |
} else if (hexDigit >= 97 && hexDigit <= 102) { | |
// a..f | |
val += (hexDigit - 87) * (1 << (4 * (len - 1 - i))); | |
} else { | |
throw new FormatException("Invalid hexadecimal value"); | |
} | |
} | |
return val; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
@immutable | |
class ImageListTile extends StatelessWidget { | |
ImageListTile({ | |
@required this.image, | |
this.imageWidth = 80.0, | |
this.imageHeight = 80.0, | |
this.imageBorderRadius = 8.0, | |
borderRadius, | |
foregroundImage, | |
BoxDecoration imageBoxDecoration, | |
this.customChild, | |
String title, | |
String subtitle, | |
this.onPressed, | |
this.onLongPressed, | |
}) { | |
if ((title != null && subtitle != null) && customChild != null) { | |
throw Exception('Title, Subtitle cannot using with customChild'); | |
} | |
this.borderRadius = | |
borderRadius ?? BorderRadius.circular(imageBorderRadius); | |
this.foregroundImage = foregroundImage ?? Container(); | |
this.imageBoxDecoration = imageBoxDecoration ?? | |
BoxDecoration( | |
image: DecorationImage( | |
image: NetworkImage( | |
'https://m.media-amazon.com/images/I/51jrNN1OU1L._AC_UY218_ML3_.jpg'), | |
fit: BoxFit.contain, | |
), | |
); | |
if (this.customChild == null) { | |
this.title = title ?? ''; | |
this.subtitle = subtitle ?? ''; | |
this.titleWidget = Text(title, style: titleTextStyle); | |
this.subtitleWidget = Text(subtitle, style: subtitleTextStyle); | |
} | |
} | |
final double imageWidth; | |
final double imageHeight; | |
final double imageBorderRadius; | |
final String image; | |
final TextStyle titleTextStyle = TextStyle(fontWeight: FontWeight.bold); | |
final TextStyle subtitleTextStyle = TextStyle(fontSize: 12.0); | |
final VoidCallback onPressed; | |
final VoidCallback onLongPressed; | |
String title; | |
String subtitle; | |
Text titleWidget; | |
Text subtitleWidget; | |
Widget customChild; | |
BoxDecoration imageBoxDecoration; | |
BorderRadius borderRadius; | |
Widget foregroundImage; | |
@override | |
Widget build(BuildContext context) { | |
return InkWell( | |
onTap: () {}, | |
child: Column( | |
children: <Widget>[ | |
Row(children: <Widget>[_buildImage(), _buildBody()]), | |
], | |
), | |
); | |
} | |
Expanded _buildBody() { | |
return Expanded( | |
flex: 1, | |
child: Container( | |
alignment: Alignment.topLeft, | |
height: imageHeight, | |
child: customChild ?? _buildSpecificContainer(), | |
), | |
); | |
} | |
Widget _buildSpecificContainer() { | |
return Container( | |
child: Column( | |
crossAxisAlignment: CrossAxisAlignment.stretch, | |
mainAxisAlignment: MainAxisAlignment.start, | |
mainAxisSize: MainAxisSize.max, | |
children: <Widget>[ | |
titleWidget, | |
subtitleWidget, | |
], | |
), | |
); | |
} | |
Padding _buildImage() { | |
return Padding( | |
padding: EdgeInsets.all(imageBorderRadius), | |
child: Container( | |
child: ClipRRect( | |
borderRadius: BorderRadius.circular(imageBorderRadius), | |
child: Ink( | |
width: imageWidth, | |
height: imageHeight, | |
decoration: BoxDecoration( | |
borderRadius: BorderRadius.circular(imageBorderRadius), | |
image: DecorationImage( | |
image: NetworkImage(image), | |
fit: BoxFit.contain, | |
), | |
), | |
child: foregroundImage, | |
), | |
), | |
), | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment