[Flutter] 5. Profile App v1 : Prototype

문정준's avatar
May 27, 2025
[Flutter] 5. Profile App v1 : Prototype
 

1. Project 생성

  • 기본 세팅
import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: HomePage(), ); } } // 기본 구조 : 추후 작성하고 옮길 것 class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Placeholder(), ); } }
 

기본 세팅 Live Template 생성 : Custom Snippet

  • Settings → Live Template → Flutter에서 + 클릭 → New Live Template
  • Abbreviation : 이름
  • Description : 설명
  • Template Text : 작성할 코드
  • 아래쪽의 Define 혹은 Change 버튼을 눌러 적용할 코드의 범위 설정
notion image
  • Template Text
import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp(); @override Widget build(BuildContext context) { return MaterialApp( home: $NAME$(), ); } } class $NAME$ extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Placeholder(), ); } }
 

2. Tab Bar : Sampling

  • 예제 코드를 통해 기능을 빠르게 숙지하고 본 프로젝트에 적용
    • TabBar의 개수(length) = Tab 개수 = TabBarView 개수
    • initialIndex : 시작할 탭 번호 (0부터 시작)
  • Sampling을 통해 해당 기능의 Scale 파악을 쉽게 가능
import 'package:flutter/material.dart'; /// Flutter code sample for [TabBar]. void main() => runApp(const TabBarApp()); class TabBarApp extends StatelessWidget { const TabBarApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp(home: TabBarExample()); } } class TabBarExample extends StatelessWidget { const TabBarExample({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('TabBar Sample'), ), body: DefaultTabController( initialIndex: 0, length: 2, child: Column( children: [ Container( height: 300, color: Colors.yellow, ), Expanded( child: Column( children: [ TabBar( tabs: <Widget>[ Tab(icon: Icon(Icons.cloud_outlined)), Tab(icon: Icon(Icons.beach_access_sharp)), ], ), Expanded( child: TabBarView( children: <Widget>[ Center(child: Text("It's cloudy here")), Center(child: Text("It's rainy here")), ], ), ), ], ), ), ], ), ), ); } }
 

3. 코드 작성

  • homepage.dart
    • 메인 페이지를 작성하는 파일
    • 여러 컴포넌트를 모아서 작성하는 페이지
 

Widgets

  • Scaffold
    • endDrawer : 끝에 메뉴를 추가하여 메뉴 화면을 열 수 있는 속성
  • AppBar
    • leading : 맨 처음 오는 아이콘
    • Title : AppBar 제목
    • centerTitle : 제목을 중앙에 오게 하는 속성
      • iOS 기반일 경우 default가 중앙
import 'package:flutter/material.dart'; import 'package:flutter_prac_profile/component/profile_buttons.dart'; import 'package:flutter_prac_profile/component/profile_counter.dart'; import 'package:flutter_prac_profile/component/profile_drawer.dart'; import 'package:flutter_prac_profile/component/profile_header.dart'; import 'package:flutter_prac_profile/component/profile_tab.dart'; class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( endDrawer: ProfileDrawer(), appBar: _appBar(), body: Column( children: [ ProfileHeader(), SizedBox(height: 20), ProfileCounter(), SizedBox(height: 20), ProfileButtons(), SizedBox(height: 20), Expanded( child: ProfileTab(), ), ], ), ); } AppBar _appBar() => AppBar(leading: Icon(Icons.arrow_back_ios_new), title: Text("Profile"), centerTitle: true); }
 
  • profile_drawer.dart
    • Drawer 메뉴를 열어 나오는 화면을 작성하는 컴포넌트
import 'package:flutter/material.dart'; class ProfileDrawer extends StatelessWidget { @override Widget build(BuildContext context) { return Container(width: 300, color: Colors.lightBlueAccent); } }
 
  • profile_header.dart
    • 프로필 화면의 프로필 사진, 이름, 소개글 등을 작성하는 곳
    • 프로필 사진을 둥글게 만들 수 있음
 

Widgets

  • CircleAvatar : 배경 사진을 둥글게 만들 수 있는 위젯
import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; class ProfileHeader extends StatelessWidget { @override Widget build(BuildContext context) { return Row( children: [ SizedBox(width: 20), SizedBox( width: 80, height: 80, child: CircleAvatar( backgroundImage: AssetImage("assets/profile.png"), ), ), SizedBox(width: 40), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Sxias", style: GoogleFonts.nanumGothic(fontSize: 30, fontWeight: FontWeight.w500)), Text("Full Stack Programmer / Student", style: GoogleFonts.nanumGothic()), Text("sxias.inblog.ai", style: GoogleFonts.nanumGothic()), ], ), ], ); } }
 
  • profile_counter.dart
    • 게시글, 좋아요, 공유 수를 표시하는 텍스트 및 구분 선을 모은 컴포넌트
    • 구분 선은 SizedBox를 이용하여 제작
import 'package:flutter/material.dart'; class ProfileCounter extends StatelessWidget { @override Widget build(BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Column( children: [Text("50"), Text("Posts")], ), Container( width: 1, height: 50, color: Colors.lightBlueAccent, ), Column( children: [Text("10"), Text("Likes")], ), Container( width: 1, height: 50, color: Colors.lightBlueAccent, ), Column( children: [Text("3"), Text("Share")], ), ], ); } }
 
  • profile_buttons.dart
    • “Follow”, “Message” 버튼을 표시하는 컴포넌트
import 'package:flutter/material.dart'; import 'package:flutter_prac_profile/component/m_follow_button.dart'; import 'package:flutter_prac_profile/component/m_message_button.dart'; class ProfileButtons extends StatelessWidget { @override Widget build(BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ MFollowButton(), MMessageButton(), ], ); } }
 
  • m_follow_button.dart
    • InkWell과 Container를 이용하여, 해당 Container를 버튼으로 만드는 컴포넌트
    • ElevatedButton 등 다른 버튼 클래스가 있지만, 가장 간편하게 버튼을 만들 수 있는 방법
 

Widgets

  • InkWell : child를 버튼으로 만드는 위젯, onTap 속성에서 이벤트 등록 가능
  • Align : 해당 item 내부를 정렬하는 위젯, 기본은 중앙 정렬
import 'package:flutter/material.dart'; class MFollowButton extends StatelessWidget { @override Widget build(BuildContext context) { return InkWell( onTap: () {}, child: Container( width: 150, height: 45, decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), border: Border.all(), color: Colors.blueAccent, ), child: Align( child: Text( "Follow", style: TextStyle(color: Colors.white), ), ), ), ); } }
 
  • m_message_button.dart
    • InkWell과 Container를 이용하여, 해당 Container를 버튼으로 만드는 컴포넌트
import 'package:flutter/material.dart'; class MMessageButton extends StatelessWidget { @override Widget build(BuildContext context) { return InkWell( onTap: () {}, child: Container( width: 150, height: 45, decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), border: Border.all(), color: Colors.white, ), child: Align( child: Text( "Message", ), ), ), ); } }
 
  • profile_tab.dart
    • TabBar와 TabBarView를 묶어서 보여주는 TabController
    • 부모의 높이를 정하고, 내부 item의 높이도 정해야 화면에 출력이 가능함
      • Expanded 활용
    • GridView의 Delegate 전략에 따라 다른 item 배치 가능
    • 외부 이미지를 다운로드 받아 화면에 출력 가능
    • builder를 사용하면 ViewResolver를 사용하여 메모리 사용량을 줄일 수 있음
      • 화면에 필요한 item만을 불러옴

Widgets

  • DefaultTabController : TabBar, TabBarView를 제어하는 컨트롤러
    • length : Tab의 갯수
    • initialIndex : 시작할 Tab의 번호
  • TabBar : Tab 메뉴
  • TabBarView : Tab을 선택했을 시에 보여줄 화면
  • GridView.builder : 내부 Item을 그리드 배치할 수 있도록 만드는 Builder
    • gridDelegate : 그리드 배치 전략 (고정갯수, 고정길이) - FixedCrossAxisCount
      • crossAxisCount : 한 줄에 표시할 item 개수
      • mainAxisSpacing : 메인 축 (세로축) Gap
      • crossAxisSpacing : 보조 축 (가로축) Gap
    • itemBuilder : 내부 아이템 - Expression 혹은 Statement로 표현 가능
    • itemCount : 표시할 총 아이템 개수
  • Image.network : url를 통해 받아올 수 있는 외부 이미지
import 'package:flutter/material.dart'; class ProfileTab extends StatelessWidget { @override Widget build(BuildContext context) { return DefaultTabController( length: 2, initialIndex: 0, child: Expanded( child: Column( children: [ TabBar( tabs: [ Tab(icon: Icon(Icons.notes)), Tab(icon: Icon(Icons.play_arrow_rounded)), ], ), Expanded( child: TabBarView( children: [ GridView.builder( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, crossAxisSpacing: 2, mainAxisSpacing: 2, ), itemBuilder: (context, index) { print("index : $index"); return Image.network("https://picsum.photos/id/${index + 20}/200/200"); }, itemCount: 42, ), Center(child: Text("test2")), ], ), ), ], ), ), ); } }
 

외부 이미지 사용

  • picsum.photos/id/{id}/width/height 방식으로 요청 시 해당 사이즈에 맞는 사진을 전송해주는 사이트
 

Result

notion image
Share article

sxias