Dandy Now!
  • [Flutter] "모두가 할 수 있는 플러터 UI 입문" - 프로필 앱 만들기
    2022년 02월 02일 00시 57분 58초에 업로드 된 글입니다.
    작성자: DandyNow
    728x90
    반응형

    "최주호, 정호준, & 정동진. (2021). 모두가 할 수 있는 플러터 UI 입문. 앤써북"으로 플러터 공부를 하고 있다. 이번 챕터에서는 Scaffold의 endDrawer 속성과 CircleAvatar, GridView, InkWell, TabBar, TabBarView 위젯 등을 다뤘다. components 폴더 아래에 dart 파일을 만든 후 main.dart에서 import하여 Scaffold의 body 영역에서 사용하는 방식은 앞서 만들었던 앱들과 다른 점이다. InkWell을 이용해 버튼을 만들고, TabBar와 TabBarView를 이용해 Tab을 구현하고, GridView.builder와 Image.network를 이용하여 특정 url주소의 이미지를 동적으로 처리하는 방법을 잘 기억하자!

     

    완성된 프로필 앱

     


     

    // main.dart
    import 'package:flutter/material.dart';
    import 'package:flutter_profile/components/profile_buttons.dart';
    import 'package:flutter_profile/components/profile_count_info.dart';
    import 'package:flutter_profile/components/profile_drawer.dart';
    import 'package:flutter_profile/components/profile_header.dart';
    import 'package:flutter_profile/components/profile_tab.dart';
    import 'package:flutter_profile/theme.dart';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          debugShowCheckedModeBanner: false, // 우측 상단 debug 표시 제거
          theme: theme(),
          home: ProfilePage(),
        );
      }
    }
    
    class ProfilePage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          endDrawer:
              ProfileDrawer(), // AppBar 오른쪽에서 왼쪽으로 슬라이드 하는 Drawer, 만약 왼쪽에서 오른쪽으로 구현하고자 한다면 drawer를 사용한다.
          appBar: _buildProfileAppBar(),
          body: Column(
            children: [
              SizedBox(height: 20),
              ProfileHeader(),
              SizedBox(height: 20),
              ProfileCountInfo(),
              SizedBox(height: 20),
              ProfileButtons(),
              Expanded(child: ProfileTab()), // 남아 있는 세로 공간을 모두 차지하게 한다.
            ],
          ),
        );
      }
    
      AppBar _buildProfileAppBar() {
        return AppBar(
          leading: Icon(Icons.arrow_back_ios),
          title: Text(
            "Profile",
            style: TextStyle(color: Colors.grey[700]),
          ),
          centerTitle: true,
        );
      }
    }

     

    // theme.dart
    import 'package:flutter/material.dart';
    
    ThemeData theme() {
      return ThemeData(
        primaryColor: Colors
            .white, // PrimaryColor는 브랜드의 아이덴티티를 나타내는 색. 기본값은 blue이다. ※AccentColor는 앱의 상호작용 요소에 사용하는 색이다.
        appBarTheme: AppBarTheme(
          backgroundColor: Colors.white,
          iconTheme: IconThemeData(color: Colors.blue),
        ),
      );
    }

     

    // components/profile_drawer.dart
    import 'package:flutter/material.dart';
    
    class ProfileDrawer extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Container(
          width: 200,
          height: double.infinity, // 해당 위젯이 차지할 수 있는 최대 범위로 확장할 때 사용
          color: Colors.blue,
        );
      }
    }

     

    // components/profile_header.dart
    import 'package:flutter/material.dart';
    
    class ProfileHeader extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Row(
          children: [
            SizedBox(width: 20),
            _buildHeaderAvatar(),
            SizedBox(width: 20),
            _buildHeaderProfile(),
          ],
        );
      }
    
      Widget _buildHeaderAvatar() {
        return SizedBox(
          width: 100,
          height: 100,
          // 이미지를 둥글게 만드는 법 3가지
          // 1) Container 사용 decoration 속성 이용해 둥글게 만들고 Image 이용해 child로 추가
          // 2) Image 위젯 만들고 ClipOver 위젯으로 감싸기
          // 3) CircleAvatar 이용
          child: CircleAvatar(
            backgroundImage: AssetImage("assets/avatar.jpg"),
          ),
        );
      }
    
      Widget _buildHeaderProfile() {
        return Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              "Sewol",
              style: TextStyle(
                fontSize: 25,
                fontWeight: FontWeight.w700,
              ),
            ),
            Text(
              "Full Stack Developer",
              style: TextStyle(
                fontSize: 20,
              ),
            ),
            Text(
              "it | insite",
              style: TextStyle(
                fontSize: 15,
              ),
            )
          ],
        );
      }
    }

     

    // components/profile_count_info.dart
    import 'package:flutter/material.dart';
    
    class ProfileCountInfo extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: [
            _buildInfo("77", "Posts"),
            _buildLine(),
            _buildInfo("55", "Likes"),
            _buildLine(),
            _buildInfo("33", "share"),
          ],
        );
      }
    
      // 재사용 가능한 함수로 만든다.
      Widget _buildInfo(String count, String title) {
        return Column(
          children: [
            Text(
              count,
              style: TextStyle(fontSize: 15),
            ),
            SizedBox(height: 2),
            Text(
              title,
              style: TextStyle(fontSize: 15),
            ),
          ],
        );
      }
    
      Widget _buildLine() {
        return Container(width: 2, height: 60, color: Colors.blue);
      }
    }

     

    // components/profile_buttons.dart
    import 'package:flutter/material.dart';
    
    class ProfileButtons extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: [
            _buildFollowButton(),
            _buildMessageButton(),
          ],
        );
      }
    
      Widget _buildFollowButton() {
        // InkWell은 모든 위젯을 버튼화 시킨다. 그 밖에 TextButton, ElevatedButton, OutlineButton으로 버튼을 만들 수 있다.
        return InkWell(
          onTap: () {
            print("Follow 버튼 클릭됨");
          },
          child: Container(
            alignment: Alignment.center,
            width: 150,
            height: 45,
            child: Text(
              "Follow",
              style: TextStyle(color: Colors.white),
            ),
            decoration: BoxDecoration(
              color: Colors.blue,
              borderRadius: BorderRadius.circular(10),
            ),
          ),
        );
      }
    
      Widget _buildMessageButton() {
        return InkWell(
          onTap: () {
            print("Message 버튼 클릭됨");
          },
          child: Container(
            alignment: Alignment.center, // 내부 Text 가운데 정렬
            width: 150,
            height: 45,
            child: Text(
              "Message",
              style: TextStyle(color: Colors.black),
            ),
            decoration: BoxDecoration(
              color: Colors.white,
              borderRadius: BorderRadius.circular(10), // 모서리 둥글게 처리
              border: Border.all(), // 테두리 선 적용
            ),
          ),
        );
      }
    }

     

    // components/profile_tab.dart
    import 'package:flutter/material.dart';
    
    // StatefulWidget은 변경 가능한 상태를 가진 위젯이다.
    class ProfileTab extends StatefulWidget {
      @override
      _ProfileTabState createState() => _ProfileTabState();
    }
    
    class _ProfileTabState extends State<ProfileTab>
    // SingleTickerProviderStateMixin은 Mixin 타입으로 한 개의 애니메이션을 가진 위젯을 정의할 때 사용한다. with로 다중 상속 가능하다.
        with
            SingleTickerProviderStateMixin {
      TabController? _tabController;
    
      @override
      // initState는 StatefulWidget에만 존재하는 초기화를 위한 함수이다. 단 한번 실행된다.
      void initState() {
        super.initState();
        _tabController = new TabController(
            length: 2,
            vsync:
                this); // vsync: this는 해당 위젯의 싱크를 SingleTickerProviderStateMixin에 맞춘다는 뜻이다.
      }
    
      @override
      Widget build(BuildContext context) {
        return Column(
          children: [
            _buildTabBar(),
            Expanded(child: _buildTabBarView()),
          ],
        );
      }
    
      Widget _buildTabBar() {
        return TabBar(
          controller: _tabController,
          tabs: [
            Tab(icon: Icon(Icons.directions_car)),
            Tab(icon: Icon(Icons.directions_transit)),
          ],
        );
      }
    
      Widget _buildTabBarView() {
        return TabBarView(
          controller: _tabController,
          children: [
            // Container(color: Colors.green), // GridView.builder로 대체
            // GridView는 수평방향이나 수직방향으로 고정 수의 위젯을 생성하고 반복해서 List를 출력해주는 위젯이다.
            // GridView에 들어오는 item 개수가 동적이라면 GridView.builder를 사용해야 한다.
            GridView.builder(
              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisSpacing: 10,
                crossAxisCount: 3,
                mainAxisSpacing: 10,
              ),
              itemCount: 42,
              itemBuilder: (context, index) {
                // Image.network를 사용하면 url의 이미지를 다운로드 한 뒤 화면에 표시할 수 있다.
                return Image.network(
                    "https://picsum.photos/id/${index + 1}/200/200"); // https://picsum.photos 무료 이미지 사이트
              },
            ),
            Container(color: Colors.red),
          ],
        );
      }
    }
    728x90
    반응형
    댓글