ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Flutter - 코딩마스터하기|프로필스크린만들기
    개발 2021. 10. 27. 13:31
    반응형

    프로필 스크린

    우선 프로필 스크린을 만들기 전에 screens 폴더를 만들어 주고 fee_screen을 refactor로 옮겨준다. 또 profile_screens.dart를 생성해준다. StatelessWidget로 만들어 주고 Container을 Scaffold로 변경해준다. 그리고 생성됐는지 확인하기 위해 backgroundcolor을 black로 만든후 실행해본다. 정상 작동하는것을 알 수 있다. 그리고 appbar를 만들어 줄건데 이번엔 appbar옵션에서 하는것이 아닌 커스텀으로 직접 만들어 보자. Column으로 만들어 준후 children 안에 Row를 넣어준다. 그 안에 children에 text로 username과 우측 메뉴 버튼을 IconButton으로 만들어 준다. Text는 center로 정렬해준다. 그리고 공간을 차지하기 위해 Expanded로 감싸준다. 우측 아이콘때문에 글씨가 중앙에서 왼쪽으로 약간 되어있는데 이 부분을 위해 SizedBox를 Expanded 앞에 width 44로 줘서 균형을 맞춰준다. 그리고 Row를 method로 refactor해줘서 빼낸다.


    class ProfileScreen extends StatelessWidget {
      const ProfileScreen({Key key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          backgroundColor: Colors.grey[100],
          body: SafeArea(
            child: Column(
              children: [
                _appbar(),
              ],
            ),
          ),
        );
      }
    
      Row _appbar() {
        return Row(
          children: [
            SizedBox(
              width: 44,
            ),
            Expanded(
              child: Text(
                'username',
                textAlign: TextAlign.center,
              ),
            ),
            IconButton(
              onPressed: () {},
              icon: Icon(Icons.menu),
            ),
          ],
        );
      }
    }

    username in profile

    프로필 body 부분 상단 username부분 만들기

    _username() 이란 메소드를 _appbar() 밑에 만들어주고 하단에도 생성한다. 그리고 Text로 username을 주고 fontweight로 bold를 준다. 이렇게하면 중앙 정렬된 username이 나오는데 Column에 crossAxisAlignment를 start로 주면 _appBar는 textAlign을 center로 줘서 안움직이고, _username부분만 왼쪽으로 간다. 그리고 너무 왼쪽에 붙어있기 때문에 Padding으로 감싸주고 symmetric으로 horizontal을 common_gap만큼 준다.


      Widget _username() {
        return Padding(
          padding: const EdgeInsets.symmetric(horizontal: common_gap,),
          child: Text(
            'username',
            style: TextStyle(
              fontWeight: FontWeight.bold,
            ),
          ),
        );
      }

    custom scroll view

    우선 _username()를 복사해서 똑같이 만들고 _userbio()로 만들어 준 후, username을 아무 문장정도 되는 글로 바꿔주고 bold를 w400으로 바꿔준다. 그리고 상단에 Column에 _username 밑에 _userbio를 넣어준다. 그 후 밑에 editing button을 만들어 줄 것인데 우선은 flutter2.0전 강의라 OutlineButton으로 만들어 준다. onPressed는 일단 (){} 빈걸로 해주고, borderSide는 BorderSide에 color를 black45를 줘서 테두리를 만들어 준다. 그리고 shape에서 RoundedRectangleBorder를 줘서 옵션으로 borderRadius에 BorderRadius.circular(6)을 줘서 꼭지점을 둥굴게 만들어 준다. 그러면 버튼이 만들어 진것을 알 수 있다. 이제 버튼의 높이를 조절하기 위해서, SizedBox로 감싸주고 높이를 24로 해준다. 그리고 Padding 으로 다시 감싸줘서 symmetric으로 horizontal만 common_gap만큼 준다. 그리고 버튼이 쭉 길게 되있는것은 나중에 해준다. 그리고 method로 refactor로 _editProfileBtn으로 만들어 준다. Column은 스크롤이 안되는데 인스타는 되므로 이부분을 스크롤이 되게 custom scroll view를 만들어주고 지금 만든 것들을 넣어준다. CustomScrollView에 slivers 옵션에 []안에 넣어준다. slivers를 이용하면 위쪽은 List 형태 아래쪽은 Gird등의 뷰등을 섞어서 쓸 수 있다. 일반적인 위젯을 Sliver에 넣고 싶으면 SliverToBoxAdapter를 넣어주면 된다. 여기서는 SliverList에 delegater옵션에 SliverChildListDelegate를 주고 다른 옵션 선언 없이 바로 [] 안에 넣어주면 된다. 그런데 이렇게 바로 옮기면 앱상에서 사라진다. CustomScrollView를 Expanded로 감싸주면 꽉 채워서 나타나는 것을 알 수 있다. 늘려서 꽉채워줘야 CustomScrollView가 보인다. 우선은 이해가 안되더라도 따라서 해보자.


    class ProfileScreen extends StatelessWidget {
      const ProfileScreen({Key key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          backgroundColor: Colors.grey[100],
          body: SafeArea(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                _appbar(),
                Expanded(
                  child: CustomScrollView(
                    slivers: [
                      SliverList(
                        delegate: SliverChildListDelegate([
                          _username(),
                          _userbio(),
                          _editProfileBtn(),
                        ]),
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ),
        );
      }
    
      Padding _editProfileBtn() {
        return Padding(
          padding: const EdgeInsets.symmetric(
            horizontal: common_gap,
          ),
          child: SizedBox(
            height: 24,
            child: OutlineButton(
              onPressed: () {},
              borderSide: BorderSide(
                color: Colors.black45,
              ),
              shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(6),
              ),
              child: Text(
                'Editi Profile',
                style: TextStyle(
                  fontWeight: FontWeight.bold,
                ),
              ),
            ),
          ),
        );
      }
    
      Row _appbar() {
        return Row(
          children: [
            SizedBox(
              width: 44,
            ),
            Expanded(
              child: Text(
                'username',
                textAlign: TextAlign.center,
              ),
            ),
            IconButton(
              onPressed: () {},
              icon: Icon(Icons.menu),
            ),
          ],
        );
      }
    
      Widget _username() {
        return Padding(
          padding: const EdgeInsets.symmetric(
            horizontal: common_gap,
          ),
          child: Text(
            'username',
            style: TextStyle(
              fontWeight: FontWeight.bold,
            ),
          ),
        );
      }
    
      Widget _userbio() {
        return Padding(
          padding: const EdgeInsets.symmetric(
            horizontal: common_gap,
          ),
          child: Text(
            'this is waht I believe!!',
            style: TextStyle(
              fontWeight: FontWeight.w400,
            ),
          ),
        );
      }
    }

    custom select button

    우선 코드를 정리하게 위해 방금 만든 Expanded를 잘라낸뒤 widgets에 profile_body.dart를 만들고 statelessWidget를 만들고 ProfileBody로 클래스명 지정해준 후, Container 부분에 붙여 넣어 준다. 그리고 _username, _userbio, _editProfileBtn도 잘라서 profile_body에 옮겨준다. 그리고 edit button에 위가 너무 사이가 좁아서 vertical에 xxs_gap을 주면 된다. 그리고 내가 올린 이미지와 내가 태깅된 이미지 를 보는 탭부분의 버튼을 만들어 준다. Row에 children에 IconButton에 ImageIcon으로 만들어 주고 그 안에 AssetImage로 grid와 saved인 버튼을 2개를 생성한다. 그리고 두 버튼의 일정한 간격을 위해서 mainAxisAlignmnet를 spaceAround로 해준다. 이 옵션이 아니면 IconButton을 Expanded로 두개다 감싸주면 나머지 공간을 똑같이 차지하게때문에 똑같은 효과를 준다. 두개의 차이는 클릭 공간의 차이이다. mainAxisAlignmnet는 버튼의 크기만큼 클릭할 수 있고 나머지는 빈 공간이지만, Expanded는 버튼이 양쪽의 반씩 차지하는 것을 볼 수있다. 버튼이 눌려있고 안눌려있을때 색의 변화를 줘보자. 일단 상단에 bool로 selectedLeft를 주고 기본값을 true로 준다. 그리고 grid의 IconButton의 onPressed에 selectedLeft = true;로 save는 false로 준다. 색은 grid는 selectedLeft? Colors.black : Colors.black26, saved는 색을 반대로 해주면 된다. 그러나 변하지 않는 것을 알 수 있는데 stateless여서 그렇기 때문에 stateful로 바꿔준다. convert로 해주면 자동으로 변경해서 들어간다. 그 후 onPressed안에 setState를 만들어 주고 그 안에 selectedLeft를 넣어준다. 그러면 색이 변하는 것을 알 수 있다.


      @override
      Widget build(BuildContext context) {
        return Expanded(
          child: CustomScrollView(
            slivers: [
              SliverList(
                delegate: SliverChildListDelegate([
                  _username(),
                  _userbio(),
                  _editProfileBtn(),
                  Row(
                    //mainAxisAlignment: MainAxisAlignment.spaceAround,
                    children: [
                      Expanded(
                        child: IconButton(
                          onPressed: () {
                            setState(() {
                              selectedLeft = true;
                            });
                          },
                          icon: ImageIcon(
                            AssetImage('assets/images/grid.png'),
                            color: selectedLeft? Colors.black : Colors.black26,
                          ),
                        ),
                      ),
                      Expanded(
                        child: IconButton(
                          onPressed: () {
                            setState(() {
                              selectedLeft = false;
                            });
                          },
                          icon: ImageIcon(
                            AssetImage('assets/images/saved.png'),
                            color: selectedLeft? Colors.black26: Colors.black,
                          ),
                        ),
                      ),
                    ],
                  ),
                ]),
              ),
            ],
          ),
        );
      }

    animation with animated container

    애니메이션 배워보기

    탭버튼 아래 선 애니메이션으로 만들어서 보기. 기존에 만들어 뒀던 탭 버튼 Row는 _tabButtons라는 method로 refactor해준다. 그리고 Widget로 _selectedIndicator라는 메소드를 만들어 준다. return에는 AnimatedContainer을 만들어 준다. 그리고 밑의 줄을 화면의 width의 반절의 크기를 해주는데, 이 부분은 많이 쓰므로 따로 만들어 준다. constants에 Screen_size.dart를 만들어 준 후, Size size;를 넣어 준다. import를 해주고, 기존 size를 쓰던 post 파일에서 상단에 Size size;를 삭제하고 하단에 size에서 import를 새로 해주면 된다. 그리고 그 하단에 if 문으로 MediaQuery를 쓰는 부분을 잘라내서 home_page에서 build에서 사용해주면 된다. 이러면 앱이 실행될때 size를 가지게 된다. 그래서 width를 size.width/2로 해주고 color도 black87로 준다. alignment는 selectedLeft를 이용해서 if문으로 selectedLeft ? Alignment.centerLeft : Alignment.centerRight 로 해주면 된다. duration은 Duration에 milliseconds를 1000으로 줘서 확인해 보자. 좌우 버튼을 누를 때 그에 맞게 되는 모습을 볼 수 있다. 이때 애니메이션의 모양을 변경 시키고 싶으면 curve 에 Curves.옵션 으로 다양하게 변경시킬 수 있다.


      Widget _selectedIndicator() {
        return AnimatedContainer(
          child: Container(height: 3,width: size.width/2, color: Colors.black87,),
          alignment: selectedLeft? Alignment.centerLeft : Alignment.centerRight,
          duration: Duration(
            milliseconds: 300,
          ),
          curve: Curves.fastOutSlowIn,
        );
      }

    enum 사용법

    현재 만드는 중에 selectedLetf를 bool로 사용해서 if문으로 제어했는데 가독성을 위해 enum을 사용해도 된다. 하단에 enum SelectedTap {left,right}를 만들어 주고 bool 부분을 지워준뒤 SelectedTab _selectedTap = Selected.left로 만들어 주면 된다. 그리고 기존 selectedLeft 였던 부분을 다 수정해주면 된다.


    enum SelectedTab {left,right}
      SelectedTab _selectedTab = SelectedTab.left;
    
      @override
      Widget build(BuildContext context) {
        return Expanded(
          child: CustomScrollView(
            slivers: [
              SliverList(
                delegate: SliverChildListDelegate([
                  _username(),
                  _userbio(),
                  _editProfileBtn(),
                  _tabButtons(),
                  _selectedIndicator(),
                ]),
              ),
            ],
          ),
        );
      }
    
      Widget _selectedIndicator() {
        return AnimatedContainer(
          child: Container(height: 3,width: size.width/2, color: Colors.black87,),
          alignment: selectedLeft? Alignment.centerLeft : Alignment.centerRight,
          duration: Duration(
            milliseconds: 300,
          ),
          curve: Curves.fastOutSlowIn,
        );
      }
    
      Row _tabButtons() {
        return Row(
          //mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: [
            Expanded(
              child: IconButton(
                onPressed: () {
                  setState(() {
                    _selectedTab = SelectedTab.left;
                  });
                },
                icon: ImageIcon(
                  AssetImage('assets/images/grid.png'),
                  color: _selectedTab==SelectedTab.left ? Colors.black : Colors.black26,
                ),
              ),
            ),
            Expanded(
              child: IconButton(
                onPressed: () {
                  setState(() {
                    _selectedTab = SelectedTab.right;
                  });
                },
                icon: ImageIcon(
                  AssetImage('assets/images/saved.png'),
                  color: _selectedTab==SelectedTab.right ? Colors.black26 : Colors.black,
                ),
              ),
            ),
          ],
        );
      }

    gridview & slivertobox

    하단의 부분은 grid 뷰인데 SliverGrid로 사용해줘둬 된다. 그러나 강의에서는 GridView.count를 쓰고 이걸 SliverToBoxAdapter로 감싸서 써보자. 근데 여기서 문제점은 SliverList는 안의 위젯 빼고는 자리를 차지하지 않는다. 그러나 ListView나 GridView 같은 일반적인 Scrollable 위젯들은 자리를 무한으로 차지한다. 그래서 shirinkWrap 옵션을 사용해야한다. 기본값은 false인데 true 바꿔준다. 그러면 이미지를 넣었을때 그 갯수 만큼만 공간을 차지한다. 그래서 true로 주고 childAspectRatio를 1로 줘서 사진 비율을 1대1로 해준다. 근데 CustomScrollView와 GridView 둘다 스크롤이 되는데 GridView에서 스크롤을 하면 그 부분만 스크롤이 되서 위는 올라가지 않는다. 이를 위해 옵션 physics를 NeverScrollableScrollPhysics() 로 주면 여기서 스크롤은 무시하고 상단의 CustomScrollView의 스크롤 제스쳐를 받는다. 그리고 children은 List.generate로 생성해준다. 30개를 만들어 주고 imageUrl은 랜덤이미지 주소를 이용한다. fit은 BoxFit.cover를 해서 꽉차게 해준다. 실행해보면 GridView에서 스크롤해도 전체가 움직이는 것을 알 수 있다.


              SliverToBoxAdapter(
                child: GridView.count(
                  crossAxisCount: 3,
                  shrinkWrap: true,
                  childAspectRatio: 1,
                  physics: NeverScrollableScrollPhysics(),
                  children: List.generate(
                    30,
                    (index) => CachedNetworkImage(
                      fit: BoxFit.cover,
                        imageUrl: 'https://picsum.photos/id/$index/100/100'),
                  ),
                ),
              ),

     

    반응형

    댓글

Designed by Tistory.