이 프로젝트는 개발하는남자님의 유튜브 영상을 참고하여 제작하였습니다. 허나, 원본 영상에서 제작하는 방법과는 다를 수 있습니다. 중간에 이미지의 순서를 바뀌어 잘못된 부분이 있습니다.
이전시간에 이어서 피드위젯을 완성해보도록 하겠습니다. 남은부분은 좋아요 수를 표현하는 부분, 댓글에 대한 부분으로 볼 수 있습니다. 좋아요는 쉽게 구현할 수 있어요.
class Feed extends StatefulWidget {
final String userUrl;
final String userName;
final List<String> images;
final int countLikes; // 좋아요 수
final int countComment; //댓글 수
const Feed(
{super.key,
required this.userUrl,
required this.userName,
required this.images,
required this.countLikes,
required this.countComment});
...
좋아요와 댓글같은 경우 그 숫자는 좋아요를 선택한 그룹의 길이와 같아요. 헌데, 간단하게 표현하기 위해서 그냥 정수형 데이터를 외부에서 받는 것처럼 만들어줄겁니다. 좋아요는 간단하죠? Text위젯으로도 간단하게 만들 수 있습니다.
...
class _FeedState extends State<Feed> {
int _current = 0;
Widget build(BuildContext context) {
return Column(
children: [
_header(),
_images(),
_options(),
_comment(), // 댓글 영역 추가
],
);
}
새로운 댓글 부분 위젯 메소드를 생성할게요. 좋아요와 댓글에 대한 부분은 세로로 위치하니 Column위젯으로 일단 선언하고, 간단하게 좋아요부터 만듭니다.
Widget _comment() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'좋아요 ${widget.countLikes}개',
style: const TextStyle(fontWeight: FontWeight.bold),
),
),
],
),
);
대충 12개라고 정해놓고 전달하면 이렇게 됩니다. 만약 빨간줄이 나타난다면, 댓글 수를 주석처리해주세요. 이제 댓글들을 보면 되겠네요? 헌데, 댓글은 더보기를 통해서 늘였다가 줄일 수 있어야 됩니다. 이것 역시 pub.dev에서 찾아볼게요.
이 패키지를 이용하면 아주 똑같이 만들 수 있습니다. 프로젝트의 루트에서 터미널에 아래와 같이 명령어를 입력해서 패키지를 다운받겠습니다. 물론 flutter clean도 꼭 해주세요.
$ flutter pub add expandable_text
이제 만들어보겠습니다. 앞쪽에는 피드를 게시한 유저의 계정이름이 있어야되고, 그 다음부터는 모두 유저가 작성한 글이에요.
Widget _comment() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'좋아요 ${widget.countLikes}개',
style: const TextStyle(fontWeight: FontWeight.bold),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: ExpandableText(
// 피드 내용
'컨텐츠 입니다.\n컨텐츠 입니다.\n컨텐츠 입니다.\n컨텐츠 입니다.\n컨텐츠 입니다.',
expandText: '더보기', //더보기 글자
linkColor: Colors.grey, //더보기 글자 색 지정
prefixText: widget.userName, // 작성자의 이름
// 작성자의 이름 스타일
prefixStyle:
const TextStyle(fontSize: 13, fontWeight: FontWeight.bold),
),
),
],
),
);
}
주석을 이용해서 간단하게 설명을 추가해봤습니다. 저거말고도 더 다양한 프로퍼티가 있으니 활용해보세요.
![]() |
![]() |
이제 피드의 내용까지 만들어봤어요. 저기서 더보기 버튼을 눌러서 확장이 되면 내용도 보이긴 하는데 댓글도 확인할 수 있더라고요. 그래서 간단하게 그 부분을 추가해줍니다.
Widget _comment() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'좋아요 ${widget.countLikes}개',
style: const TextStyle(fontWeight: FontWeight.bold),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: ExpandableText(
'컨텐츠 입니다.\n컨텐츠 입니다.\n컨텐츠 입니다.\n컨텐츠 입니다.\n컨텐츠 입니다.',
expandText: '더보기',
linkColor: Colors.grey,
prefixText: widget.userName,
prefixStyle:
const TextStyle(fontSize: 13, fontWeight: FontWeight.bold),
),
),
//댓글 모두 보기 부분
Padding(
padding: const EdgeInsets.all(8.0),
child: GestureDetector(
onTap: () {},
child: Text(
'댓글 ${widget.countComment}개 모두 보기',
style: const TextStyle(color: Colors.grey),
),
),
),
],
),
);
}
이제 더 비슷해졌습니다. 그리고 팔로우를 맺은 계정이 작성한 피드는 댓글을 달 수 있는 부분도 있어요. 그 부분도 간단하게 구현해보죠. 이미지와 댓글달기라는 글자가 적혀있어요. 기존에 만든 이미지 아바타로 쉽게 구현할 수 있습니다.
Widget _comment() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'좋아요 ${widget.countLikes}개',
style: const TextStyle(fontWeight: FontWeight.bold),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: ExpandableText(
'컨텐츠 입니다.\n컨텐츠 입니다.\n컨텐츠 입니다.\n컨텐츠 입니다.\n컨텐츠 입니다.',
expandText: '더보기',
linkColor: Colors.grey,
prefixText: widget.userName,
prefixStyle:
const TextStyle(fontSize: 13, fontWeight: FontWeight.bold),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: GestureDetector(
onTap: () {},
child: Text(
'댓글 ${widget.countComment}개 모두 보기',
style: const TextStyle(color: Colors.grey),
),
),
),
// 댓글 달기 영역
Row(
children: [
const Padding(
padding: EdgeInsets.all(8.0),
child: ImageAvatar(
url:
'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTnnnObTCNg1QJoEd9Krwl3kSUnPYTZrxb5Ig&usqp=CAU',
type: AvatarType.BASIC,
),
),
GestureDetector(
onTap: showCommentModal,
child: const Text(
'댓글 달기...',
style: TextStyle(color: Colors.grey),
),
)
],
)
],
),
);
}
짜잔, 이제 피드의 UI는 모두 만들었습니다.
댓글 아이콘을 클릭하면 화면 아래에서 위젯이 올라와서 댓글을 확인할 수 있어요. 해당 위젯은 showModalBottomSheet 메소드를 이용해서 구현할 수 있습니다. 일단 시트로 사용할 커스텀 위젯을 만들겠습니다. comment_bottom_sheet.dart파일을 만듭니다.
class CommentBottomSheet extends StatelessWidget {
const CommentBottomSheet({super.key});
Widget build(BuildContext context) {
return Column(
children: [
_header(),
const Divider(
height: 10,
color: Colors.black26,
),
],
);
}
Widget _header() {
return const Center(
child: Text(
'댓글',
style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
),
);
}
Widget _comment() {
return Expanded(
child: ListView.builder(
itemCount: 50,
itemBuilder: (_, index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
const Padding(
padding: EdgeInsets.all(4.0),
child: Column(
children: [
ImageAvatar(
url:
'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRisv-yQgXGrto6OxQxX62JyvyQGvRsQQ760g&usqp=CAU',
type: AvatarType.BASIC)
],
),
),
Padding(
padding: const EdgeInsets.all(4.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(2.0),
child: Text(
'$index _user',
textAlign: TextAlign.start,
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
),
Padding(
padding: const EdgeInsets.all(2.0),
child: Text(
'$index 번째 댓글입니다.',
),
),
const Padding(
padding: EdgeInsets.all(2.0),
child: Text(
'답글 달기',
style:
TextStyle(color: Colors.black45, fontSize: 13),
),
)
],
),
),
],
),
);
}),
);
}
}
일단 임의의 댓글 데이터를 무작위로 넣어주었습니다.
이제 해당 시트를 눌렀을 때 나타나게 해주기 위한 showModalBottomSheet메소드를 생성합니다.
void showCommentSheet() {
showModalBottomSheet(
context: context,
// 드래그 핸들 표시
showDragHandle: true,
// 드래그 가능 여부
enableDrag: true,
// 스크롤 컨트롤 가능 여부
isScrollControlled: true,
// 시트 내 SafeArea 사용 여부
useSafeArea: true,
//좌우측 상단 둥글게 만들어주기
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(25.0))),
builder: (_) => const CommentBottomSheet());
}
이제 해당 메소드를 채팅 모양의 이미지에 전달해줍니다.
Widget _options() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: GestureDetector(
child: ImageData(path: ImagePath.likeOffIcon)),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: GestureDetector(
onTap: showCommentSheet, //시트 표시 메소드
child: ImageData(path: ImagePath.replyIcon)),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: GestureDetector(child: ImageData(path: ImagePath.dm)),
),
],
),
완성했습니다 ! 해당 버튼을 클릭하면 바텀시트가 나타납니다.