[Flutter]인스타그램 클론 - 5. 피드 제작(2)

한상욱·2023년 7월 31일
0
post-thumbnail

들어가며

이 프로젝트는 개발하는남자님의 유튜브 영상을 참고하여 제작하였습니다. 허나, 원본 영상에서 제작하는 방법과는 다를 수 있습니다. 중간에 이미지의 순서를 바뀌어 잘못된 부분이 있습니다.

설명 영역 만들기

이전시간에 이어서 피드위젯을 완성해보도록 하겠습니다. 남은부분은 좋아요 수를 표현하는 부분, 댓글에 대한 부분으로 볼 수 있습니다. 좋아요는 쉽게 구현할 수 있어요.

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는 모두 만들었습니다.

modalBottomSheet

댓글 아이콘을 클릭하면 화면 아래에서 위젯이 올라와서 댓글을 확인할 수 있어요. 해당 위젯은 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)),
            ),
          ],
        ),

완성했습니다 ! 해당 버튼을 클릭하면 바텀시트가 나타납니다.

profile
자기주도적, 지속 성장하는 모바일앱 개발자가 되기 위해

0개의 댓글

관련 채용 정보