# 주요 위젯 Flutter는 다양한 UI 요소를 구현하기 위한 풍부한 위젯 세트를 제공합니다. 이 장에서는 Flutter 앱을 구축할 때 자주 사용되는 핵심 위젯들을 살펴보겠습니다. ## 위젯 카테고리 Flutter 위젯은 기능과 용도에 따라 다양한 카테고리로 분류할 수 있습니다: ```mermaid graph TD A[Flutter 위젯] --> B[디스플레이 위젯] A --> C[입력 위젯] A --> D[레이아웃 위젯] A --> E[네비게이션 위젯] A --> F[정보 위젯] A --> G[컨테이너 위젯] B --> B1[Text] B --> B2[Image] B --> B3[Icon] C --> C1[TextField] C --> C2[Checkbox/Radio] C --> C3[Button] D --> D1[Container] D --> D2[Row/Column] D --> D3[Stack] E --> E1[AppBar] E --> E2[Drawer] E --> E3[BottomNavigationBar] F --> F1[SnackBar] F --> F2[Dialog] F --> F3[ProgressIndicator] G --> G1[Card] G --> G2[ListTile] G --> G3[Scaffold] ``` 이 장에서는 기본적인 디스플레이, 입력, 컨테이너 위젯을 중점적으로 살펴보겠습니다. 레이아웃 위젯은 다음 장에서 자세히 다룰 예정입니다. ## 기본 디스플레이 위젯 ### Text `Text` 위젯은 애플리케이션에 스타일이 지정된 텍스트를 표시합니다. ```dart Text( '안녕하세요, Flutter!', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 20, color: Colors.blue, letterSpacing: 1.2, height: 1.4, ), textAlign: TextAlign.center, maxLines: 2, overflow: TextOverflow.ellipsis, ) ``` **주요 속성**: - `style`: 텍스트의 시각적 형식 지정 (색상, 크기, 굵기 등) - `textAlign`: 텍스트 정렬 방법 - `maxLines`: 최대 표시 줄 수 - `overflow`: 내용이 공간을 초과할 때 처리 방법 - `softWrap`: 줄 바꿈 허용 여부 ### RichText `RichText` 위젯은 서로 다른 스타일의 텍스트를 한 줄에 표시할 수 있게 해줍니다. ```dart RichText( text: TextSpan( text: '안녕하세요, ', style: TextStyle(color: Colors.black), children: [ TextSpan( text: '홍길동', style: TextStyle( fontWeight: FontWeight.bold, color: Colors.blue, ), ), TextSpan( text: '님!', style: TextStyle(color: Colors.black), ), ], ), ) ``` ### Image `Image` 위젯은 다양한 소스에서 이미지를 표시합니다. ```dart // 네트워크 이미지 Image.network( 'https://flutter.dev/images/flutter-logo.png', width: 200, height: 100, fit: BoxFit.cover, loadingBuilder: (context, child, loadingProgress) { if (loadingProgress == null) return child; return CircularProgressIndicator(); }, errorBuilder: (context, error, stackTrace) { return Text('이미지를 불러올 수 없습니다'); }, ) // 에셋 이미지 Image.asset( 'assets/images/flutter_logo.png', width: 200, height: 100, ) // 파일 이미지 Image.file( File('/path/to/image.jpg'), width: 200, height: 100, ) // 메모리 이미지 Image.memory( Uint8List(...), width: 200, height: 100, ) ``` **주요 속성**: - `width`, `height`: 이미지 크기 - `fit`: 이미지가 제공된 영역에 맞는 방식 (cover, contain, fill 등) - `color`, `colorBlendMode`: 이미지에 적용할 색상 필터 - `alignment`: 이미지 정렬 방식 - `repeat`: 이미지 반복 방식 ### Icon `Icon` 위젯은 Material Design 아이콘이나 커스텀 아이콘 폰트의 그래픽 아이콘을 표시합니다. ```dart Icon( Icons.favorite, color: Colors.red, size: 30, ) ``` **주요 속성**: - `color`: 아이콘 색상 - `size`: 아이콘 크기 - `semanticLabel`: 접근성을 위한 텍스트 라벨 ## 입력 위젯 ### Button 위젯 Flutter는 다양한 종류의 버튼 위젯을 제공합니다. #### ElevatedButton Material Design 스타일의 돌출된 버튼입니다. ```dart ElevatedButton( onPressed: () { print('버튼이 눌렸습니다!'); }, style: ElevatedButton.styleFrom( primary: Colors.blue, onPrimary: Colors.white, padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: Text('눌러보세요'), ) ``` #### TextButton 텍스트만 있는 플랫 버튼입니다. ```dart TextButton( onPressed: () { print('텍스트 버튼 클릭'); }, child: Text('자세히 보기'), ) ``` #### OutlinedButton 테두리가 있는 버튼입니다. ```dart OutlinedButton( onPressed: () { print('테두리 버튼 클릭'); }, style: OutlinedButton.styleFrom( side: BorderSide(color: Colors.blue, width: 2), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: Text('등록하기'), ) ``` #### IconButton 아이콘만 있는 버튼입니다. ```dart IconButton( icon: Icon(Icons.favorite), color: Colors.red, onPressed: () { print('좋아요!'); }, ) ``` ### TextField 텍스트 입력을 위한 위젯입니다. ```dart TextField( decoration: InputDecoration( labelText: '이메일', hintText: 'example@email.com', prefixIcon: Icon(Icons.email), border: OutlineInputBorder(), ), keyboardType: TextInputType.emailAddress, textInputAction: TextInputAction.next, obscureText: false, // 비밀번호 필드일 경우 true onChanged: (value) { print('입력 값: $value'); }, onSubmitted: (value) { print('제출된 값: $value'); }, ) ``` **주요 속성**: - `decoration`: 입력 필드의 외관 설정 - `keyboardType`: 표시할 키보드 유형 - `textInputAction`: 키보드의 실행 버튼 유형 - `obscureText`: 비밀번호 필드 여부 - `maxLines`: 여러 줄 텍스트 입력시 최대 줄 수 - `controller`: 입력 값을 관리하는 TextEditingController ### Checkbox와 Radio 상태 선택을 위한 위젯입니다. ```dart // Checkbox 예제 Checkbox( value: isChecked, onChanged: (bool? value) { setState(() { isChecked = value!; }); }, activeColor: Colors.blue, ) // Radio 예제 Radio( value: 'option1', groupValue: selectedOption, onChanged: (String? value) { setState(() { selectedOption = value!; }); }, ) ``` ### Slider 범위 내에서 값을 선택하기 위한 위젯입니다. ```dart Slider( value: _currentValue, min: 0, max: 100, divisions: 10, label: _currentValue.round().toString(), onChanged: (double value) { setState(() { _currentValue = value; }); }, ) ``` ## 컨테이너 위젯 ### Container `Container`는 Flutter에서 가장 유용한 위젯 중 하나로, 패딩, 마진, 테두리, 색상 등을 설정할 수 있는 범용 컨테이너입니다. ```dart Container( width: 200, height: 150, margin: EdgeInsets.all(10), padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), boxShadow: [ BoxShadow( color: Colors.black26, offset: Offset(0, 2), blurRadius: 6, ), ], border: Border.all(color: Colors.grey[300]!), ), alignment: Alignment.center, child: Text('Hello, Container!'), ) ``` **주요 속성**: - `width`, `height`: 컨테이너 크기 - `margin`: 외부 여백 - `padding`: 내부 여백 - `decoration`: 배경색, 테두리, 그림자 등의 장식 - `alignment`: 자식 위젯의 정렬 방식 - `transform`: 변환 행렬 ### Card `Card`는 약간 둥근 모서리와 그림자가 있는 Material Design 카드입니다. ```dart Card( elevation: 4.0, margin: EdgeInsets.all(8), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), child: Padding( padding: EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '카드 제목', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), SizedBox(height: 8), Text('카드 내용이 들어가는 곳입니다.'), ], ), ), ) ``` ### ListTile `ListTile`은 아이콘, 텍스트, 후행 위젯을 포함하는 목록 항목입니다. ```dart ListTile( leading: Icon(Icons.person), title: Text('홍길동'), subtitle: Text('개발자'), trailing: Icon(Icons.arrow_forward_ios), onTap: () { print('항목 클릭됨'); }, ) ``` ### ExpansionTile `ExpansionTile`은 확장 가능한 목록 항목입니다. ```dart ExpansionTile( title: Text('더 보기'), leading: Icon(Icons.info), children: [ Padding( padding: EdgeInsets.all(16.0), child: Text('확장된 내용이 여기에 표시됩니다.'), ), ], ) ``` ## 정보 표시 위젯 ### SnackBar `SnackBar`는 화면 하단에 표시되는 간단한 메시지입니다. ```dart // ScaffoldMessenger를 사용하여 SnackBar 표시 ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('저장되었습니다.'), action: SnackBarAction( label: '실행 취소', onPressed: () { // 실행 취소 작업 }, ), duration: Duration(seconds: 2), behavior: SnackBarBehavior.floating, ), ); ``` ### Dialog 대화 상자는 사용자에게 중요한 정보를 표시하거나 결정을 요구할 때 사용합니다. ```dart // AlertDialog showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: Text('확인'), content: Text('정말 삭제하시겠습니까?'), actions: [ TextButton( child: Text('취소'), onPressed: () { Navigator.of(context).pop(); }, ), TextButton( child: Text('삭제'), onPressed: () { // 삭제 작업 수행 Navigator.of(context).pop(); }, ), ], ); }, ); // SimpleDialog showDialog( context: context, builder: (BuildContext context) { return SimpleDialog( title: Text('옵션 선택'), children: [ SimpleDialogOption( onPressed: () { Navigator.pop(context, '옵션 1'); }, child: Text('옵션 1'), ), SimpleDialogOption( onPressed: () { Navigator.pop(context, '옵션 2'); }, child: Text('옵션 2'), ), ], ); }, ); ``` ### ProgressIndicator 로딩 상태를 표시하는 위젯입니다. ```dart // 원형 진행 표시기 CircularProgressIndicator( value: 0.7, // null이면 불확정 진행 표시기 backgroundColor: Colors.grey[200], valueColor: AlwaysStoppedAnimation(Colors.blue), ) // 선형 진행 표시기 LinearProgressIndicator( value: 0.7, backgroundColor: Colors.grey[200], valueColor: AlwaysStoppedAnimation(Colors.blue), ) ``` ## 상호작용 위젯 ### GestureDetector 여러 제스처를 감지하여 처리하는 위젯입니다. ```dart GestureDetector( onTap: () { print('탭 감지됨'); }, onDoubleTap: () { print('더블 탭 감지됨'); }, onLongPress: () { print('길게 누르기 감지됨'); }, onPanUpdate: (details) { print('드래그: ${details.delta}'); }, child: Container( width: 200, height: 100, color: Colors.amber, child: Center( child: Text('여기를 터치하세요'), ), ), ) ``` ### InkWell Material Design 스타일의 터치 효과(잉크 스플래시)가 있는 터치 영역입니다. ```dart InkWell( onTap: () { print('탭 감지됨'); }, splashColor: Colors.blue.withOpacity(0.5), highlightColor: Colors.blue.withOpacity(0.2), child: Container( width: 200, height: 100, alignment: Alignment.center, child: Text('터치해보세요'), ), ) ``` ## 위젯 조합 예제 위젯은 조합하여 복잡한 UI를 구성할 수 있습니다. 다음은 프로필 카드 예제입니다: ```dart Card( margin: EdgeInsets.all(16.0), elevation: 4.0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)), child: Padding( padding: EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ CircleAvatar( radius: 30, backgroundImage: NetworkImage('https://example.com/avatar.jpg'), ), SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '홍길동', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), Text( '프론트엔드 개발자', style: TextStyle( color: Colors.grey[600], ), ), ], ), ), IconButton( icon: Icon(Icons.more_vert), onPressed: () {}, ), ], ), SizedBox(height: 16), Text( '모바일 앱과 웹 개발에 관심이 많은 개발자입니다. Flutter와 React를 주로 사용합니다.', ), SizedBox(height: 16), Wrap( spacing: 8, children: [ Chip(label: Text('Flutter')), Chip(label: Text('Dart')), Chip(label: Text('Firebase')), ], ), SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Column( children: [ Text( '156', style: TextStyle(fontWeight: FontWeight.bold), ), Text('게시물'), ], ), Column( children: [ Text( '572', style: TextStyle(fontWeight: FontWeight.bold), ), Text('팔로워'), ], ), Column( children: [ Text( '128', style: TextStyle(fontWeight: FontWeight.bold), ), Text('팔로잉'), ], ), ], ), SizedBox(height: 16), Row( children: [ Expanded( child: ElevatedButton( onPressed: () {}, child: Text('팔로우'), ), ), SizedBox(width: 8), OutlinedButton( onPressed: () {}, child: Text('메시지'), ), ], ), ], ), ), ) ``` 위젯 트리: ```mermaid graph TD A[Card] --> B[Padding] B --> C[Column] C --> D[Row: 프로필 정보] C --> E[SizedBox] C --> F[Text: 소개] C --> G[SizedBox] C --> H[Wrap: 기술 태그] C --> I[SizedBox] C --> J[Row: 통계] C --> K[SizedBox] C --> L[Row: 버튼] D --> D1[CircleAvatar] D --> D2[SizedBox] D --> D3[Expanded/Column] D --> D4[IconButton] D3 --> D31[Text: 이름] D3 --> D32[Text: 직업] H --> H1[Chip: Flutter] H --> H2[Chip: Dart] H --> H3[Chip: Firebase] J --> J1[Column: 게시물] J --> J2[Column: 팔로워] J --> J3[Column: 팔로잉] L --> L1[Expanded/ElevatedButton] L --> L2[SizedBox] L --> L3[OutlinedButton] ``` ## 위젯 사용 팁 ### 1. 재사용 가능한 위젯 만들기 자주 사용되는 UI 패턴은 커스텀 위젯으로 만들어 재사용하세요: ```dart class CustomButton extends StatelessWidget { final String text; final VoidCallback onPressed; final Color color; const CustomButton({ Key? key, required this.text, required this.onPressed, this.color = Colors.blue, }) : super(key: key); @override Widget build(BuildContext context) { return ElevatedButton( onPressed: onPressed, style: ElevatedButton.styleFrom( primary: color, padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: Text( text, style: TextStyle(fontSize: 16), ), ); } } // 사용 예 CustomButton( text: '제출하기', onPressed: () { // 처리 로직 }, color: Colors.green, ) ``` ### 2. 조건부 위젯 조건에 따라 다른 위젯을 표시하는 방법: ```dart // 삼항 연산자 사용 isLoading ? CircularProgressIndicator() : Text('데이터 로드됨'), // 조건부 위젯 포함 Column( children: [ Text('항상 표시되는 텍스트'), if (isVisible) Text('조건에 따라 표시되는 텍스트'), for (var item in items) Text(item), ], ) ``` ### 3. const 생성자 사용 가능한 경우 `const` 생성자를 사용하여 위젯 재빌드 성능을 최적화하세요: ```dart // const 위젯: 빌드 시간에 생성되고 재사용됨 const SizedBox(height: 16), const Divider(), const Text('고정된 텍스트'), // 동적 데이터가 있는 경우 const를 사용할 수 없음 Text(dynamicText), ``` ## 결론 Flutter의 기본 위젯들은 앱 UI를 구성하는 핵심 요소입니다. 이 장에서 소개한 위젯들을 조합하여 복잡한 인터페이스를 구현할 수 있습니다. 효과적인 Flutter 개발자가 되기 위해서는 각 위젯의 특성과 용도를 이해하고, 적절한 상황에 올바른 위젯을 선택하는 능력을 기르는 것이 중요합니다. 다음 장에서는 레이아웃 위젯에 대해 더 자세히 알아보겠습니다.