[Flutter]인스타그램 클론 - 10. 업로드 화면 UI

한상욱·2023년 8월 9일
0
post-thumbnail

들어가며

이 프로젝트는 개발하는남자님의 유튜브 영상을 참고하여 제작하였습니다. 허나, 원본 영상에서 제작하는 방법과는 다를 수 있습니다.

업로드 화면 개요

업로드 버튼을 클릭하면 해당 페이지로 라우팅되면서 현재 디바이스의 갤러리의 모든 미디어파일들을 불러올 수 있습니다. 그리고 이미지를 선택하면 해당 이미지를 자세하게 확인할 수 있고, 앨범의 종류를 선택할수도 있습니다. 하단은 완성하면 볼 수 있는 업로드 화면입니다.

그러면 바로 시작하겠습니다.

업로드 화면으로 라우팅

업로드 화면은 다른 바텀 네비게이션과는 다르게 새로운 화면으로 이동하는 애니메이션을 갖고 있습니다. 따라서, 해당 부분은 기존의 인덱스를 변경하는 방식보다는 네비게이션을 직접하는 방식이 좋을 것 같습니다. 실제로 인스타그램은 Dismissible한 네비게이션 효과가 나타나지만, 간단하게 기본 네비게이션으로 만들어주겠습니다.

바텀 네비게이션의 컨트롤러를 수정하면 되겠죠? bottom_nav_controller.dart파일을 엽니다.

...
  void changeIndex(int value) {
    var page = Page.values[value];
    switch (page) {
      case Page.HOME:
      case Page.SEARCH:
      case Page.REELS:
      case Page.MYPAGE:
        moveToPage(value);
      case Page.UPLOAD:
      	//업로드 화면 선택시
        moveToUpload();
    }
  }
  ...
  
  //업로드 화면으로 이동
  void moveToUpload() {
    Get.to(() => const Upload());
  }

이렇게 하면 업로드화면을 선택할 경우에만 기본 라우팅 방식을 사용할 수 있습니다.

업로드 화면 UI

업로드 화면은 앱바를 제외하고 크게 3가지 영역으로 나눌 수 있습니다. 선택한 사진을 볼 수 있는 preview 영역, 앨범과 여러가지 옵션을 선택하는 header()영역, 현재 앨범의 미디어파일을 볼 수 있는 images()영역으로 나눌 수 있어요. 하나하나 만들어주겠습니다.

AppBar 영역

앱바는 굉장히 간단하니까 코드보면 금방 이해될겁니다.

import ...

class Upload extends StatelessWidget {
  const Upload({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
      	//앱바 타이틀
        title: const Text('새 게시물'),
        titleTextStyle: const TextStyle(
            fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black),
        //뒤로 가기 버튼
        leading: Padding(
          padding: const EdgeInsets.all(15.0),
          child: GestureDetector(
              onTap: Get.back, child: ImageData(path: ImagePath.closeImage)),
        ),
        //actions
        actions: [
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: ImageData(path: ImagePath.nextImage),
          )
        ],
      ),
    );
  }

앱바는 특별히 설명할게 없습니다. 뒤로가기 버튼이 기존의 arrow_back과는 다르다는 차이가 있습니다. 이는 직접 leading에 원하는 아이콘 버튼과 이벤트를 설정해주면 됩니다.

preview 영역

preview 영역은 선택한 사진을 확인하는 영역입니다. 하지만, 아직 사진을 불러올 수 없기 때문에 간단하게 Container로 표현해주겠습니다.

class Upload extends GetView<UploadController> {
  const Upload({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('새 게시물'),
        titleTextStyle: const TextStyle(
            fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black),
        leading: Padding(
          padding: const EdgeInsets.all(15.0),
          child: GestureDetector(
              onTap: Get.back, child: ImageData(path: ImagePath.closeImage)),
        ),
        actions: [
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: ImageData(path: ImagePath.nextImage),
          )
        ],
      ),
      body: Column(
          children: [
          	//preview 영역 추거
          	_preview(),
          ],
       ),
    );
  }

  ...
  Widget _preview() {
    return Container(
            height: Get.size.width,
            width: Get.size.width,
            color: Colors.black,
          );
  }

이 영역은 이 후에 기능을 완성하면 추가로 완성시켜보겠습니다.

header 영역

header 영역은 앨범을 선택할 수 있는 버튼 영역과 다양한 옵션 버튼들이 존재합니다. 가로를 기준으로 여러 위젯들끼리 뭉쳐져 있으니 Row위젯을 여러번 적용하면 손쉽게 만들 수 있습니다.

앨범을 표시하는 부분부터 만들겠습니다. 앨범은 아직 알 수 없으니, Recent라는 글자를 임의로 적겠습니다. 옆에 dropdown 메뉴는 이미 asset에 존재합니다. 따라서 그냥 asset을 사용해주면 됩니다.

	  ...
      body: Column(
          children: [
          	_preview(),
            // header 영역 추가
            _header(),
          ],
       ),
    );
  }
  ...
  Widget _header() {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: [
        Row(
          children: [
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: Text(
                  'Recent',
                  style: const TextStyle(
                      fontSize: 18,
                      color: Colors.black,
                      fontWeight: FontWeight.bold),
                
              ),
            ),
            //드롭다운 메뉴 이미지
            ImageData(
              path: ImagePath.arrowDownIcon,
              width: 60,
            )
          ],
        ),
      ],
    );
  }
...

옆 옵션영역도 마찬가지로 이미 asset에 해당 이미지가 존재합니다. 다만, 해당 이미지가 회색 동그라미 안에 적용되어 있죠. Container위젯을 이용해서 쉽게 표현해보겠습니다.

...
  Widget _header() {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: [
		...
        // 왼쪽 영역 제외
        ...
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: [
          	//동그라미 효과
            Container(
                padding: const EdgeInsets.all(8.0),
                margin: const EdgeInsets.all(4.0),
                decoration: const BoxDecoration(
                    color: Color(0xff808080), shape: BoxShape.circle),
                child: ImageData(
                  path: ImagePath.imageSelectIcon,
                  width: 60,
                )),
            Container(
                padding: const EdgeInsets.all(8.0),
                margin: const EdgeInsets.all(4.0),
                decoration: const BoxDecoration(
                    color: Color(0xff808080), shape: BoxShape.circle),
                child: ImageData(
                  path: ImagePath.cameraIcon,
                  width: 60,
                )),
          ],
        )
      ],
    );
  }
  ...

Container 위젯의 padding과 margin을 이용해서 자식위젯과 외부위젯사이의 간격을 조절할 수 있습니다. 그리고 shape에 BoxShape.circle을 사용하면 Container 위젯을 사각형에서 원으로 바꿀 수 있습니다.

이렇게 중단의 header 영역도 완성했습니다.

images 영역

images 영역은 사진들을 갤러리처럼 볼 수 있어야합니다. GridView위젯을 사용하면 손쉽게 구현할 수 있을 것 같습니다. 아직 디바이스의 갤러리와 연결시키지 않을것이기 때문에 단순하게 Container를 이용해서 GridView를 구현하겠습니다.

	  ...
      body: Column(
          children: [
          	_preview(),
            _header(),
            //images 영역 추가
            _images(),
          ],
       ),
    );
  }
  ...
  Widget _images() {
    return Expanded(
        child: GridView.builder(
            gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 4, mainAxisSpacing: 1.0, crossAxisSpacing: 1.0),
            itemCount: 50,
            itemBuilder: (context, index) => Container(
                  color: Colors.blue,
                )));
  }
  ...

미디어 파일은 가로에 4개씩 보여지고 있기 때문에 SliverGridDelegateWithFixedCrossAxisCount의 crossAxisCount는 4가되어야 합니다. 미디어들의 간격은 1.0이 적절한 것 같아서 1.0으로 전달해주었습니다.

이제, 업로드화면의 UI는 모두 완성했습니다.

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

0개의 댓글

관련 채용 정보