반응형
📝위젯
Widget이란 앱에 있는 요소를 의미한다 (버튼, 글자, 스크롤 박스 등…)
📝위젯 사용 주의점
어떤 위젯이 부모의 크기를 비율로 가져가는 경우 부모의 크기가 이론상 무한대로 확장 가능할 때 제한해야한다 예를 들면 Row가 부모 위젯인 경우 Width가 이론상 무한이고 Column의 경우 Height가 이론상 무한이기 때문에 이 두개가 있을 경우 Expanded따위로 제한해야한다 참고로 Row 안에 Column 안에 있는 위젯의 경우 둘다 Expanded로 제한해줘야한다
📝Flutter 개발 팁
// Size 예시 [Gaps, FontSize, Color 등…]
class Gaps {
static const v1 = SizedBox(height: 1);
...
}
Size 표가 있으면 통일성이 생긴다
📝Flutter 기술
포커스 해제하기 (다른 곳 터치 시 키보드 내리기)
Widget build(BuildContext context) {
return GestureDetector(
onTap: _onScaffoldTap,
...
)
)
void _onScaffoldTap() {
FocusManager.instance.primaryFocus?.unfocus(); // 현재 포커스 제거
}
다크모드 설정
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:tiktok_clone/constants/sizes.dart';
import 'package:tiktok_clone/features/authentication/sign_up_screen.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await SystemChrome.setPreferredOrientations(
[
DeviceOrientation.portraitUp,
],
);
runApp(const TikTokApp());
}
class TikTokApp extends StatelessWidget {
const TikTokApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'TikTok Clone',
themeMode: ThemeMode.system, // 시스템에 따라 다르다
// themeMode: ThemeMode.dark, // 다크모드 강제
// themeMode: ThemeMode.light, // 라이트모드 강제
theme: ThemeData(
// 라이트 모드일 때 사용
brightness: Brightness.light,
scaffoldBackgroundColor: Colors.white,
primaryColor: const Color(0xFFE9435A),
textSelectionTheme: const TextSelectionThemeData(
cursorColor: Color(0xFFE9435A),
),
splashColor: Colors.transparent,
appBarTheme: const AppBarTheme(
foregroundColor: Colors.black,
backgroundColor: Colors.white,
elevation: 0,
titleTextStyle: TextStyle(
color: Colors.black,
fontSize: Sizes.size16 + Sizes.size2,
fontWeight: FontWeight.w600,
),
),
tabBarTheme: TabBarTheme( // 탭바에서 사용하는 다크 모드 내용들
labelColor: Colors.black, // 선택 글자
unselectedLabelColor: Colors.grey.shade500, // 노 선택 글자
indicatorColor: Colors.black, // 아래 글자
),
),
darkTheme: ThemeData(
// 다크모드일 때 설정
brightness: Brightness.dark, // 글자색상??
scaffoldBackgroundColor: Colors.black, // scafoold 색상
bottomAppBarTheme: BottomAppBarTheme(
// 바텀앱바 색상
color: Colors.grey.shade900,
),
primaryColor: const Color(0xFFE9435A),
),
home: const SignUpScreen(),
);
}
}
회전 막기
void main() async {
WidgetsFlutterBinding.ensureInitialized(); // 플러터의 화면이 나오기전에 아래 설정을 사용하겠습니다라는 뜻
await SystemChrome.setPreferredOrientations(
[
DeviceOrientation.portraitUp, // 핸드폰 회전 막기
],
);
runApp(const TikTokApp());
}
Text Theme
// 방법1 - 직접 Theme 정의하기
theme: ThemeData(
// 라이트 모드일 때 사용
brightness: Brightness.light,
textTheme: TextTheme(
// <https://m2.material.io/design/typography/the-type-system.html#type-scale> 해당 사이트에서 폰트에 따른 크기를
// css 또는 flutter전용 textTheme형식을 폰트체만 정해주면 코드를 자동 생성해준다 (Material2)
// textTheme에서 제공하는 것들 displayLarge(이름만 제공)
displayLarge: GoogleFonts.openSans(
fontSize: 95, fontWeight: FontWeight.w300, letterSpacing: -1.5),
displayMedium: GoogleFonts.openSans(
fontSize: 59, fontWeight: FontWeight.w300, letterSpacing: -0.5),
displaySmall:
GoogleFonts.openSans(fontSize: 48, fontWeight: FontWeight.w400),
headlineMedium: GoogleFonts.openSans(
fontSize: 34, fontWeight: FontWeight.w400, letterSpacing: 0.25),
headlineSmall:
GoogleFonts.openSans(fontSize: 24, fontWeight: FontWeight.w400),
titleLarge: GoogleFonts.openSans(
fontSize: 20, fontWeight: FontWeight.w500, letterSpacing: 0.15),
titleMedium: GoogleFonts.openSans(
fontSize: 16, fontWeight: FontWeight.w400, letterSpacing: 0.15),
titleSmall: GoogleFonts.openSans(
fontSize: 14, fontWeight: FontWeight.w500, letterSpacing: 0.1),
bodyLarge: GoogleFonts.roboto(
fontSize: 16, fontWeight: FontWeight.w400, letterSpacing: 0.5),
bodyMedium: GoogleFonts.roboto(
fontSize: 14, fontWeight: FontWeight.w400, letterSpacing: 0.25),
labelLarge: GoogleFonts.roboto(
fontSize: 14, fontWeight: FontWeight.w500, letterSpacing: 1.25),
bodySmall: GoogleFonts.roboto(
fontSize: 12, fontWeight: FontWeight.w400, letterSpacing: 0.4),
labelSmall: GoogleFonts.roboto(
fontSize: 10, fontWeight: FontWeight.w400, letterSpacing: 1.5),
),
listTileTheme: const ListTileThemeData(
iconColor: Colors.black,
),
scaffoldBackgroundColor: Colors.white,
primaryColor: const Color(0xFFE9435A),
textSelectionTheme: const TextSelectionThemeData(
cursorColor: Color(0xFFE9435A),
),
splashColor: Colors.transparent,
appBarTheme: const AppBarTheme(
foregroundColor: Colors.black,
backgroundColor: Colors.white,
elevation: 0,
titleTextStyle: TextStyle(
color: Colors.black,
fontSize: Sizes.size16 + Sizes.size2,
fontWeight: FontWeight.w600,
),
),
),
// 방법2 - 만들어져있는 폰트 Theme 가져오기
// GoogleFonts의 itim이라는 폰트체 내용 전체 가져오기
textTheme: GoogleFonts.itimTextTheme(
ThemeData(brightness: Brightness.dark).textTheme, // 다크모드 테마 가져오기
),
// 상세가 먼저 적용되기 때문에 부분적으로 적용시켜도 된다
Text(
"Sign up for TikTok",
style: GoogleFonts.abrilFatface(
textStyle: const TextStyle(
fontSize: Sizes.size24,
fontWeight: FontWeight.w700,
),
),
)
// theme 데이터 가져오기 예제
style: Theme.of(context)
.textTheme
.headlineSmall!
.copyWith(color: Colors.red), // Theme에서 가져오지만 변형이 필요하면 copyWith을 사용한다
// 방법3 - Typography 사용 (Flutter 자체 내제된 Font Style)
theme: ThemeData(
textTheme: Typography.blackMountainView,
...
darkTheme: ThemeData(
textTheme: Typography.whiteMountainView, // ?
...
// 일부 항목 커스텀 가능
static const TextTheme blackMountainView = TextTheme(
displayLarge: TextStyle(debugLabel: 'blackMountainView displayLarge', fontFamily: 'Roboto', color: Colors.black54, decoration: TextDecoration.none),
displayMedium: TextStyle(debugLabel: 'blackMountainView displayMedium', fontFamily: 'Roboto', color: Colors.black54, decoration: TextDecoration.none),
displaySmall: TextStyle(debugLabel: 'blackMountainView displaySmall', fontFamily: 'Roboto', color: Colors.black54, decoration: TextDecoration.none),
...
) // 예시
📝자주쓰는 위젯 코드
// 색상지정 및 투명도
Colors.white.withOpacity(0.5)
Colors.white
Color(0xFF181818)
// 폰트 두께
FontWeight.w800
// 보더형태
BorderRadius.circular(25)
// 위젯 경계 넘어갈시 자름
clipBehavior: Clip.hardEdge
// 좌우 패딩
EdgeInsets.symmetric(horizontal: 20,)
// Column에서 가로 정렬, Row에서 세로 정렬
crossAxisAlignment: CrossAxisAlignment.start
crossAxisAlignment: CrossAxisAlignment.end
crossAxisAlignment: CrossAxisAlignment.spaceBetween
// Column에서 수직 정렬, Row에서 가로 정렬
mainAxisAlignment: MainAxisAlignment.start
mainAxisAlignment: MainAxisAlignment.end
mainAxisAlignment: MainAxisAlignment.spaceBetween
📝 레이아웃 & UI 위젯
Widget 명 역할 키 : 설명 → 값 사용 예) 활용 예제
Widget명 | 역할 | 키 : 설명 → 값 | 사용 예) | 활용 예제 |
MaterialApp | 구글 기본앱같은 디자인에 쓰인다 → 지원 위젯이 많다 [커스텀디자인을 많이 만들기 때문에 해당 위젯 사용] | home : 위젯 넣을 곳 (일반적 Scaffold 많이 사용) → Widget | MaterialApp(home: Scaffold(..)) | |
아이폰 기본앱같은 디자인에 쓰인다 → 하위 지원 위젯이 별로 없어서 개발하기 힘듬 | ||||
Scaffold | 앱은 대부분 상 / 중 / 하로 나누어져있는데 쉽게 구성하기 위한 위젯 | appBar : 상단에 넣을 위젯 → PreferredSizeWidget body : 중단에 넣을 위젯 -> Widget bottomNavigationBar : 하단에 넣을 위젯 → Widget backgroundColor: 배경색 → Color |
Scaffold(appBar: Padding(..)) | |
Padding | 패딩을 주는 위젯 | padding : 패딩옵션 → EdgeInsetschild : 패딩 안에 들어갈 Widget → Widget | ||
Color | 색을 반환해주는 위젯 | 색 값 → int | Color(0xFF181818) | |
EdgeInsets | 패딩 옵션 위젯 | symmetric → 좌우패딩, 상하패딩 | all → 상하좌우 패딩 | vertical : 상하 패딩 → doublehorizental : 좌우 패딩 → double |
Column | Children 안에 항목을 세로로 배치한다 (이론상 무한정 배치 가능하지만 화면 벗어난다고 에러 발생) [width 제한 : 상위 위젯크기 (없으면 화면 크기), height 제한 : 무한 (화면 벗어날시 에러)]- 화면 세로줄 하나를 다 차지한다 (넓이만 없어) | children : 세로 박스 안에 들어갈 위젯 리스트 → Widget crossAxisAlignment : 수직 정렬 옵션 →CrossAxisAlignment |
children: [const SizedBox(..)] | |
SizedBox | 빈 박스 생성 (Not Div) [마진 주는 용도로 자주 쓰인다] | height : 높이 → int | SizedBox ( height : 80 ) | |
Row | Children 안에 항목을 가로로 배치한다 (이론상 무한정 배치 가능하지만 화면 벗어난다고 에러 발생) [width 제한 : 무한 (화면 벗어날시 에러), height 제한 : 상위 위젯크기 (없으면 화면 크기), ]- 화면 가로줄 하나를 다 차지한다 (높이만 없어) | mainAxisAlignment : 수평 정렬 → MainAxisAlignment children : 가로 박스 안에 들어갈 위젯 리스트 → Widget |
Row(children : [ …]) | |
Text | 텍스트 위젯 | data[생략 가능(필수)] : 텍스트 → String style : 텍스트 스타일 → TextStyle |
Text( 'Hey, Selena', style: TextStyle( ... ), | |
TextStyle | 텍스트 스타일 위젯 | color : 색상 → Color fontSize : 글자크기 → int fontWeight : 폰트 두께 -> FontWeight |
TextStyle( color: Colors.white, fontSize: 28, fontWeight: FontWeight.w800, ), | |
AppBar | 상단 레이아웃 (헤더) 위젯 | title : 헤더에 적을 내용 → String | ||
Center | 수평 및 수직 가운데 정렬된 상태로 정중앙에 위치한다 | child : Center 박스 안에 들어갈 Widget → Widget | Center( child: Text(”hello word”)) | |
SingleChildScrollView | 스크롤 바 위젯 | child : 스크롤바 안에 들어갈 Widget → Widget | ||
Container | child에 있는 위젯 크기만큼 크기가 정해진다 | clipBehavior : 내용물 크기가 커서 넘치는 경우 처리 방식 → Clip decoration : 컨테이너에 설정할 색상 등 옵션 → Decoration |
Container(clipBehavior: Clip.hardEdge, …) | |
BoxDecoration (This is not Widget) | 색상과 둥근 정도 등을 조정한다 | color : 박스 색상 → Color borderRadius : 박스 테두리 둥근 정도 → BorderRadiusGeometry |
BoxDecoration(borderRadius: BorderRadius.circular(25)) | |
Icon | 아이콘 위젯 | icon[생략 가능] : 사용할 아이콘 → IconData color : 아이콘 색상 → Color size : 아이콘 크기 → int |
Icon( icon, color: isInverted ? _blackColor : Colors.white, size: 88, ), | |
Transform.scale | Child에 적은 위젯의 크기 및 색상 등을 조정한다 | scale : 크기 → double child : Scale 조절할 위젯 → Widget |
Transform.scale( scale: 2.2, ... ) | |
Transform.translate | Child에 적은 위젯의 위치를 이동시킨다(scale과 같이 적용시키는 경우 Transform.scale( … child : Transform.translate( .. child : 적용시킬 위젯) 순으로 적용시킨다 | offset : 위치 → Offset child : 회전 시킬 위젯 → Widget |
Transform.translate( offset: const Offset(..)) | |
Offset | 현재 위치 x, y축 이동 위젯 | dx[생략 가능] : 현재 위치에서 x축 이동 → doubledy[생략 가능] : 현재 위치에서 y축 이동 → double | Offset(-5,12) | |
IconButton | 버튼 이벤트가 있는 아이콘 위젯 | icon : 아이콘 종류 → Icon onPressed : 눌렀을 때 동작 시키기 → Function:void iconSize : 아이콘 사이즈 → int |
onPressed: onClicked | |
ThemeData | 공통 스타일을 적용시킬 Data를 설정한다 (이름은 이미 정해져있다 textTheme, focusColor 등..) → 결국 그냥 이름일 뿐이고 반환값만 신경쓰고 문맥에 따른 것만 잘 생각해서 정하면 된다 | textTheme : 텍스트 테마 → TextTheme | textTheme: const TextTheme(...) | |
TextTheme | 텍스트 스타일 적용시킬 Theme을 정한다 이름은 이미 정해져있다 titleLarge 등..) → 결국 그냥 이름일 뿐이고 반환값만 신경쓰고 문맥에 따른 것만 잘 생각해서 정하면 된다 | titleLarge : → TextStyle | textTheme: const TextTheme( titleLarge: TextStyle( color: Colors.red, ), | |
Flexible | Flex박스를 만든다 → 각각의 박스를 비율로 줄 수 있어서 어떤 핸드폰이든 %이기 때문에 깨지지 않는다 | flex : 비율 → int child : FlexBox에 넣을 위젯 → Widget |
Flexible( flex: 1,… | |
Expanded | 사용 가능한 크기만큼 가져갑니다 (최대 화면의 크기) | child : 확장된 Row안에 들어갈에 넣을 위젯 → Widget flex : 나눌 비율 크기 → int |
Expanded( child: Container( | |
리스트의 형태로 데이터를 보여준다 → 많은 데이터 보여줄 때 사용 하지만… 모든 데이터를 가져오기 때문에 메모리 누수 현상 발생 가능성 존재 | ||||
ListView.builder | 리스트 형태로 데이터를 보여주고 화면에 보이는 부분만 렌더링 하기 때문에 메모리 누수 걱정이 없다 | scrollDirection : 리스트뷰 스크롤 방향 → ScrollWidget itemCount : 전체 아이템 수 → int padding : 패딩 옵션 → EdgeInsets itemBuilder : 새로운 인덱스 감지시 itemBuilder함수를 호출해 렌더링할 위젯을 설정한다 추후 스크롤 시 안 보이는 부분은 사라지고 나중에 다시 보일 때 재활용하게 된다 → 일반적으로 itemBuilder : (context, index) 이런형식으로 이용된다 |
||
ListView.seperator | ListView.builder와 동일하며 리스트 사이에 위젯을 넣을 수 있다 | 위와 동일seperatorBuilder : 리스트 사이에 들어갈 위젯 → 일반적으로 speratorBuilder : (BuildContext context, int index) ⇒ 위젯.. | ||
Image.network | 이미지를 가져온다 | |||
CircularProgressIndicator | 로딩바 | X | CircularProgressIndicator() | |
SafeArea | 와이파이, 배터리 등 위에 있는 부분을 제외한 위젯 박스 | child : 모든 위젯 [일반적으로 Column, Container 등..] → Widget | SafeArea(child: Column...) | |
FractionallySizedBox | 부모 위젯의 전체 크기를 비율로 가져가는 위젯 박스 | widthFactor : 0.5 → double heightFactor : 0.5 → double child : 비율로 가져가는 하위 위젯 → Widget |
FractionallySizedBox(widthFactor:0.5, heightFactor:0.5, child: Column…) | |
Positioned.fill | 화면을 꽉채울 수 있는 위젯 | child : 화면에 꽉 채울 위젯 → Widget | Positioned.fill(child:…) | |
Stack | 화면 위에 쌓을 수 있는 위젯 (absolute처럼 위젯 위에 올릴 수 있다) | children : 쌓을 위젯들 → Widget[] | Stack(children: […]) | 박스 안에 [아이콘 |
Positioned | 위젯을 상하좌우 값을 줘서 위치시킬 수 있다 | left : left 위젯 위치 값 → int right : right 위젯 위치 값 → int top : top 위젯 위치 값 → int bottom : bottom 위젯 위치 값 → int |
Positioned(left:20,top:40,child:...) | |
Wrap | Wrap은 가로로 Children Widget들을 채우는데 더 이상 못 채우면 알아서 줄바꿈이 된다 | runSpacing : 줄 간격 → int spacing : 요소들 사이 가로 간격 → int children : 적용시킬 위젯들 → Widget[] |
Wrap(runSpacing: 15, spacing: 15, children: ...) | |
TextField | 인풋박스를 제공해준다. 유효성 검사 등 다양한 기능 제공 | https://api.flutter.dev/flutter/material/TextField-class.html | ||
TabBar | 앱 하단에 3개나 5개 아이콘 등에 쓰이는 Tab들을 의미한다. | https://api.flutter.dev/flutter/material/TabBar-class.html | ||
TabPageSelector | 슬라이드해서 페이지를 넘어가는 탭들의 경우 현재 어떤 탭 위치에 있는지 알려주는 역할을 한다. | https://api.flutter.dev/flutter/material/TabPageSelector-class.html | ||
InputDecoration | TextField 또는 TextFormField와 같은 입력 필드를 꾸미는데 사용되는 위젯입니다. | https://api.flutter.dev/flutter/material/InputDecoration-class.html | ||
UnderlineInputBorder | TextField 또는 TextFormField의 밑줄 스타일로 설정할 때 사용됩니다. | https://api.flutter.dev/flutter/material/UnderlineInputBorder-class.html | ||
BorderSide | 경계선(border)의 색상, 두께 및 스타일을 정의하는 위젯입니다. | https://api.flutter.dev/flutter/painting/BorderSide-class.html | ||
TabBarView | 여러 페이지를 탭 전환을 통해 스와이프할 수 있는 뷰를 제공. | https://api.flutter.dev/flutter/material/TabBarView-class.html | ||
AspectRatio | 자식 위젯의 너비와 높이 비율을 일정하게 유지시켜주는 위젯입니다 | https://api.flutter.dev/flutter/widgets/AspectRatio-class.html | ||
TextFormField | TextField와 비슷하지만 Form 위젯과 함께 사용됩니다. | https://api.flutter.dev/flutter/material/TextFormField-class.html | ||
NavigationDestination | 네비게이션에 보여줄 항목을 정의해주는 위젯입니다. | https://api.flutter.dev/flutter/material/NavigationDestination-class.html | ||
PageView | 수평 또는 수직으로 페이지를 스와이프할 수 있게 해주는 위젯입니다. | https://api.flutter.dev/flutter/widgets/PageView-class.html | ||
FadeInImage.assetNetwork | 이미지가 로드되는 동안 보여줄 Placeholder 이미지입니다. (로딩개념) | https://api.flutter.dev/flutter/widgets/FadeInImage/FadeInImage.assetNetwork.html | ||
NavigationBar | 3.0에서 새롭게 도입된 신개념으로 BottomNavigationBar보다 선호된다. TabBar의 경우 하단이 아니여도 붙일 수 있지만 NavigationBar의 경우는 하단에 붙는다. | https://api.flutter.dev/flutter/material/NavigationBar-class.html | ||
ListTile | List형식인데 알림으로 오는 형식처럼 앞뒤에 붙여서 리스트를 쉽게 만들수 있게 도와준다. | https://api.flutter.dev/flutter/material/ListTile-class.html | ||
Dismissible | List형식인데 왼쪽이나 오른쪽 슬라이드로 삭제하거나 할 수 있는 기능이다. | https://api.flutter.dev/flutter/widgets/Dismissible-class.html | ||
GoRoute | 라우팅, 중첩라우팅기능, 애니메이션전환 등을 제공한다. | https://pub.dev/packages/go_router | ||
VerticalDivider | 위젯들 사이에 수직선을 넣어 구분할 수 있는 위젯 | https://api.flutter.dev/flutter/material/VerticalDivider-class.html | ||
RichText | 위젯을 배열로 가지기 때문에 TextSpan위젯을 사용시 첫글자는 다른 스타일 중간 글자는 다른 스타일을 넣기가 편하다 | https://api.flutter.dev/flutter/widgets/RichText-class.html | ||
DefaultTextStyle | 텍스트 위젯을 여러 개 써야할 때 최상단 스타일 고정하고 여러 항목을 받을 수 있다. | https://api.flutter.dev/flutter/widgets/DefaultTextStyle-class.html | ||
NestedScrollView | 상단의 헤더 영역과 본문 영역이 서로 독립적인 스크롤을 처리하면서도, 상호작용하는 방식으로 동작하는 게 특징입니다. 예를 들어, 상단의 헤더가 먼저 스크롤되고, 그 이후에 본문 영역이 스크롤되는 방식입니다. | https://api.flutter.dev/flutter/widgets/NestedScrollView-class.html |
모달창
// 모달창 (Yes / No) iOS
onTap: () {
showCupertinoDialog(
context: context,
builder: (context) => CupertinoAlertDialog(
// iOS 모달창
title: const Text("Are you sure?"),
content: const Text("Plx dont go"),
actions: [
CupertinoDialogAction(
// iOS 모달 No 버튼
onPressed: () => Navigator.of(context).pop(),
child: const Text("No"),
),
CupertinoDialogAction(
// iOS 모달 Yes 버튼
onPressed: () => Navigator.of(context).pop(),
isDestructiveAction: true,
child: const Text("Yes"),
),
],
),
);
},
// 모달창 (Yes / No) AOS
onTap: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
// AOS 모달창
icon: const FaIcon(FontAwesomeIcons.skull),
title: const Text("Are you sure?"), // 제목 내용
content: const Text("Plx dont go"), // 제목 안에 내용
actions: [
IconButton(
onPressed: () => Navigator.of(context).pop(),
icon: const FaIcon(FontAwesomeIcons.car),
), // 왼쪽
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text("Yes"),
), // 오른쪽
],
),
);
}
// 모달창 하단형 (iOS)
onTap: () {
showCupertinoModalPopup(
// 모달창 하단형
context: context,
builder: (context) => CupertinoActionSheet(
// 모달창 하단형에 들어갈 내용
title: const Text("Are you sure?"),
message: const Text("Please dooooont gooooo"),
actions: [
CupertinoActionSheetAction(
isDefaultAction: true, // OK (파란글씨)
onPressed: () => Navigator.of(context).pop(),
child: const Text("Not log out"),
),
CupertinoActionSheetAction(
isDestructiveAction: true, // 취소 (빨간 글씨)
onPressed: () => Navigator.of(context).pop(),
child: const Text("Yes plz."), // 들어갈 문구
)
],
),
);
},
스크롤 (sliver)
import 'package:flutter/material.dart';
class UserProfileScreen extends StatefulWidget {
const UserProfileScreen({super.key});
@override
State createState() => _UserProfileScreenState();
}
class _UserProfileScreenState extends State {
@override
Widget build(BuildContext context) {
return CustomScrollView(
slivers: [ // 앱의 스크롤에따라 모양이 변하는 것들을 말한다
// <https://api.flutter.dev/flutter/material/SliverAppBar-class.html> 샌드박스 화면으로 보면 이해가 쉽다.
SliverAppBar(
snap:
true, // floating가 ture일 때 동작하며 SliverFixedExtentList에 리스트들을 살짝 위로 스크롤하면 서서히 나오는게 아니라 한번에 짠 하고 나온다
floating:
true, // SliverFixedExtentList에 있는 리스트를 위로 스크롤할 때 올릴 때 해당 앱바가 출현한다(끝까지 올리지 않는 이상 안 나옴 일반적)
stretch: true, // 해당 앱바를 아래로 당길 수 있냐 (그래서 늘어나냐 안 늘어나냐)
pinned: true, // 내렸을 때 앱바를 고정시킬지 여부
backgroundColor: Colors.teal, // 위로 당겼을 때 보여줄 것
collapsedHeight: 80, // 높이 (언제부터 위에 bacground color teal을 실행시킬거냐에 대한)
expandedHeight: 200
flexibleSpace: FlexibleSpaceBar(
stretchModes: const [
StretchMode.blurBackground, // -> 아래로 당길시 뿌얘짐
StretchMode.zoomBackground, // 밑에 선언한 백그라운드를 중심으로 당겨진다
StretchMode.fadeTitle, // Title이 뿌얘진다
],
background: Image.asset(
"assets/images/placeholder.jpg",
fit: BoxFit.cover,
),
title: const Text("Hello!"),
),
),
// 스크롤이 가능한 위젯 생성
const SliverToBoxAdapter(
child: Column(
children: [
CircleAvatar(
backgroundColor: Colors.red,
radius: 20,
),
],
),
),
// 스크롤이 가능한 리스트 위젯 생성
SliverFixedExtentList(
// Body값 (List)
delegate: SliverChildBuilderDelegate(
childCount: 50,
(context, index) => Container(
color: Colors.amber[100 * (index % 9)],
child: Align(
alignment: Alignment.center,
child: Text("Item $index"),
),
),
),
itemExtent: 100, // 높이 차이
),
// SliverAppBar에 비해 사용자 정의가 더 자유롭다.
SliverPersistentHeader(
delegate: CustomDelegate(),
pinned: true,
floating: true,
),
// 스크롤이 가능한 그리드 위젯 생성
SliverGrid(
delegate: SliverChildBuilderDelegate(
childCount: 50,
(context, index) => Container(
color: Colors.blue[100 * (index % 9)],
child: Align(
alignment: Alignment.center,
child: Text("Item $index"),
),
),
),
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 50, // 정사각형 크기
mainAxisSpacing: Sizes.size20, // 세로 마진
crossAxisSpacing: Sizes.size20, // 가로 마진
childAspectRatio: 1, // 정사각형들 비율
),
)
],
);
}
}
// SliverPersistentHeader에 사용하기 위해 따로 정의 필요
class CustomDelegate extends SliverPersistentHeaderDelegate {
@override
Widget build(
BuildContext context,
double shrinkOffset,
bool overlapsContent,
) {
return Container(
color: Colors.indigo,
child: const FractionallySizedBox(
heightFactor: 1, // 차지하는 높이 퍼센티지
child: Center(
child: Text(
'Title!!!!!',
style: TextStyle(
color: Colors.white,
),
),
),
),
);
}
@override
double get maxExtent => 150; // 높이
@override
double get minExtent => 120; // pin사용시 유지되는 높이 영역
@override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
return false; // 필요 시 다시 빌드 여부
}
}
Sliver는 Scroll안에 일반적으로 사용되며 스크롤함에 따라 보여지는 게 다른 걸 의미한다.
에러 위젯
void showFirebaseErrorSnack(
// 하단에 에러 메세지 알림 처럼 튀어나옴
BuildContext context,
Object? error,
) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
showCloseIcon: true, // 닫기 버튼
content: Text( // 보여줄 메시지
(error as FirebaseException).message ?? "Something wen't wrong.",
),
),
);
}
📝그 외 위젯
Widget 명 역할 키 : 설명 → 값 사용 예) 활용 예제
Widget 명 | 역할 | 키 : 설명 → 값 | 사용 예) | 활용 예제 |
FutureBuilder | Http통신 후에 데이터 정보를 담는 위젯 | future : Future타입의 데이터 → Future<T> builder : ****future에서 받은 데이터 정보 → AsyncWidgetBuilder -> 일반적으로 (context, snapshot) 사용 |
||
GestureDetector | 동작 이벤트를 제공해준다 | onTap : 탭 눌렀을 때 → funciton onPanUpdate : 드래깅 감지 → function(DragUpdateDetails details) onPanEnd : 드래깅 끝냈을 때 → function(DragEndDetails details) |
- onPageUpdatedetails.delta.dx > 0 → 오른쪽에서 왼쪽으로 스왑 | |
Navigator.push | 데이터를 다른 위젯으로 보내고 Router로 해당 위젯을 띄운다 (뒤로가기가 쌓인다) | 일반적으로 **(context, Router)**받고 위젯을 Router 객체로 변환하기 위해서MaterialPageRoute를 사용한다 | Navigator.of(context).push(MaterialPageRoute(builder: (context) => const LoginScreen(),),); | |
Navigator.pop | 뒤로가기 | Navigator.*of*(context).pop() | ||
MaterialPageRoute | 위젯을 Router형태로 바꾸는 역할 | builder : 어떤 context를 읽을 지 → buildContext fullscreenDialog : 페이지 이동 효과 → boolean |
MaterialPageRoute( builder: (context) => DetailScreen( title: title, thumb: thumb, id: id, ) → [DetailScreen 위젯을 Router로] |
📝애니메이션 위젯
Widget 명 역할 키 : 설명 → 값 사용 예) 활용 예제
Widget 명 | 역할 | 키 : 설명 → 값 | 사용 예) | 활용 예제 |
Hero | 페이지 Route 애니메이션 효과 (히어로 처럼 슝 나옴) | 필요하면 알아서 사용하세요 | https://docs.flutter.dev/ui/animations/hero-animations | |
AnimatedOpacity | 불투명 애니메이션 위젯 | opacity : 불투명도 → double (0~1) duration : 불투명도 애니메이션 적용 시간 → Duration child : 적용시킬 위젯 → Widget |
AnimatedOpacity(opacity: 1,duration: const Duration(milliseconds: 300), child: …) | |
AnimatedCrossFade | FadeIn FadeOut 전환 애니메이션 위젯 | firstChild : 위젯 → Widget secondChild : 위젯 → Widget duration : 애니메이션 적용 시간 → Duration crossFadeState : 보여줄 위젯 → CrossFadeState.showFirst, CrossFadeState.showSecond |
AnimatedCrossFade(fistChild:…, secondChild:…, duration:…, crossFadeState: …) | |
AnimatedBuilder | 상태 변화를 감지해 화면을 업데이트할 수 있습니다. (컨트롤러로 로직처리를 하며 Builder를 통해 그려줍니다. | https://api.flutter.dev/flutter/widgets/AnimatedBuilder-class.html | ||
AnimatedDefaultTextStyle | 텍스트에 애니메이션을 줄수 있는 위젯입니다. | https://api.flutter.dev/flutter/widgets/AnimatedDefaultTextStyle-class.html | ||
SizeTransition | 위젯의 크기(가로 또는 세로 방향)를 애니메이션을 통해 변환하는 위젯입니다. | https://api.flutter.dev/flutter/widgets/SizeTransition-class.html | ||
SlideTransition | 슬라이드하는 애니메이션을 적용한 위젯입니다. | https://api.flutter.dev/flutter/widgets/SlideTransition-class.html | ||
AnimatedModalBarrier | 모달로서 화면을 가리는 반투명한 배경(Barrier) 위젯을 만듭니다. | https://api.flutter.dev/flutter/widgets/AnimatedModalBarrier-class.html | ||
AnimatedList | 리스트의 항목들이 동적으로 추가되거나 제거될 때 애니메이션을 적용할 수 있는 위젯 | https://api.flutter.dev/flutter/widgets/AnimatedList-class.html | ||
ColorTween | 애니메이션 전환시 색상의 변화되는 과정을 애니메이션으로 보여줍니다. | https://api.flutter.dev/flutter/animation/ColorTween-class.html | ||
Tween | 시작과 종료값을 줄 수 있는 애니메이션기능을 줍니다. | https://api.flutter.dev/flutter/animation/Tween-class.html |
📝Flutter 개발 팁
반응형