플러터) flutter 1 - 위젯의 종류 및 속성 설정
1. 사용자 인터페이스 아키텍처
1) 화면을 구성하는 위젯
- 위젯 : 화면에 보일 뷰를 설명하는 객체
- 플러터 앱의 화면은 이처럼 필요한 위젯을 계층으로 나열해서 구성
import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: Text('Test'),), body: Center(child: GestureDetector(child: Text('HelloWorld'))), )); } }
- MaterialApp : 머티리얼 디자인 적용
- Scaffold : 화면 구조 설계
- AppBar : 화면 위쪽 앱바 구성
- title : 앱바의 제목
- Center : 가운데 정렬
- GestureDetector : 사용자 이벤트 처리
- 문자열
- 부모, 자식 클래스 관계
- Object → DiagnosticableTree → Widget
- Object → DiagnosticableTree → Widget → StatelessWidget → Text
- Object → DiagnosticableTree → Widget → RenderObjectWidget → SingleChildRenderObjectWidget → Align → Center
(1) 플러터 : 선언형 프로그래밍
- 명령형(imperative) 프로그래밍 : 화면 구성과 관련된 모든 코드 작성
- 선언형(declarative) 프로그래밍 : 화면 구성 정보만 작성
(2) 위젯은 불변
- 플러터의 위젯은 불변 객체(immutable object)
- 객체를 생성한 후 상태를 바꿀 수 없다(새 데이터 갱신 시, 새로운 위젯 객체 생성해야함)
2) 위젯 트리
(1) 위젯의 트리 구조
- MyApp > MaterialApp > Scaffold
- AppBar : 상단바
- Center > GestureDetector : 사용자 화면
(2) 화면을 구성하는 3개 트리 구조
- 위젯 트리 : 전체 위젯들을 구성하는 트리
- 엘리먼트 트리(element tree) : 위젯 트리를 각각 렌더링/컴포넌트로 표시한 트리
- 렌더 트리(render tree) : 렌더링 오브젝트만으로 구성된 트리
- 렌더링 오브젝트 : 실제 화면에 그릴 정보
- 컴포넌트 : 다른 객체를 포함하는 역할만 함
3) 정적인 화면 만들기
- 개발자가 상속 받는 위젯 3가지
- StatelessWidget : 정적인 위젯(상태 관리X)
- StatefulWidget : 동적인 위젯(상태 관리O)
- InheritedWidget : 여러 위젯에서 공통으로 이용할 상태 관리 위젯
- 상태(state)란? 화면에 업데이트되는 데이터
- 정적인 화면 만들기(StatelessWidget / Widget build)
import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { bool enabled = false; String stateText = "disable"; void changeCheck() { if (enabled) { stateText = "disable"; enabled = false; } else { stateText = "enable"; enabled = true; } } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: Text('Stateless Test'), ), body: Center( child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ IconButton( icon: (enabled ? Icon(Icons.check_box, size: 20,) : Icon(Icons.check_box_outline_blank, size: 20,)), color: Colors.red, onPressed: changeCheck, ), Container( padding: EdgeInsets.only(left: 16), child: Text('$stateText', style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold),), ), ], ), ) ) ); } }
4) 동적인 화면 만들기
- StatefulWidget : 위젯 클래스
- createState() 함수에서 반환하는 상태 클래스(State)가 중요
- State : StatefulWidget의 상탯값을 유지하고 화면을 구성하는 클래스
- build() 함수를 반드시 재 정의해야 함
(1) 상탯값 변경하기
- StatefulWidget을 사용하는 이유 : State클래스의 상태를 관리할 수 있기 때문
- State에서 화면 다시 빌드 == setState()함수를 호출하는 순간
- state의 모든 속성이 화면과 관련되었다고 보기 힘들기 때문
- setState() : 매개변수로 지정된 함수가 끝난 후 자동으로 build()함수 호출
(2) 상태 클래스의 역할 알아보기
- StatefulWidget에서 build()함수를 이용하지 않는 이유 → 성능문제
- StatefulWidget이 다시 생성되더라도 State 객체는 다시 생성할 필요가 없기 때문(createState()함수는 생성되는 순간에만 호출 됨)
import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } // 정적화면 class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: Text('Stateless Test'), ), body: MyWidget(), ) ); } } class MyWidget extends StatefulWidget { @override State<StatefulWidget> createState() { return _MyWidgetState(); } } class _MyWidgetState extends State<MyWidget> { bool enabled = false; String stateText = "disable"; void changeCheck() { setState(() { if (enabled) { stateText = "disable"; enabled = false; } else { stateText = "enable"; enabled = true; } }); } @override Widget build(BuildContext context) { return Center( child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ IconButton( icon: (enabled ? Icon( Icons.check_box, size: 20, ) : Icon( Icons.check_box_outline_blank, size: 20, )), color: Colors.red, onPressed: changeCheck, ), Container( padding: EdgeInsets.only(left: 16), child: Text( '$stateText', style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold), ), ), ], ), ); } }
5) 상태의 생명 주기
- State는 한 번 생선된 후 메모리에 유지되므로 생명 주기를 가짐.
(1) initState() 함수 호출 시점
- State 객체 생성 시점(최초 1번)
(2) didChangeDependencies() 함수 호출 시점
- initState()함수가 호출된 후에 이어서 호출
(3) didUpdateWidget() 함수 호출 시점
- State와 연결된 StatefulWidget이 다시 생성되었을 때 호출
(4) build() 함수 호출 시점
- 최초호출
- setState() 함수에 의해 호출
- didUpdateWidget() 함수에 의해 호출
- state객체 생성 시
- initState() → didChangeDependencies() → build()
- state 내용 변경 시
- setState() → (상태 : Clean→Dirty) → build()
- didUpdateWidget() → (상태 : Clean→Dirty) → build()
- state객체 생성 시
(5) dispose() 함수 호출 시점
- State 객체를 소멸할 때 자동 호출(최후 1번)
✨ 모든 위젯을 StatefulWidget으로 만들면 안되는 이유
- 메모리에 불필요한 객체를 유지해야 하므로 앱 상태 관리가 복잡해질 수 있다.
6) BuildContext 객체와 위젯 키
(1) 위젯 정보를 나타내는 BuildContext 객체
- BuildContext 객체는 위젯 하나당 하나씩 만들어짐
- 엘리먼트 트리 == BuildContext객체의 트리
- 상위 위젯 객체 얻기
MyApp? app = context.findAncestorWidgetOfExactType<MyApp>();
- Element 선언문
abstract class Element extends DiagnosticableTree implements BuildContext {}
(2) 위젯을 식별하는 키
- Widget 클래스의 생성자
Widget({Key? key})
- 모든 위젯의 상위 클래스인 Widget은 Key라는 매개변수가 선언 되어 있어 모든 위젯은 객체를 생성할 때 생성자 정보로 (객체를 식별할) 키를 지정할 수 있다.
- StatelessWidget일 때 식별하기 - 키 미사용
- 상태 표현 불가 → 객체 식별 필요성 X
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: MyListWidget() ); } } class MyColorItemWidget extends StatelessWidget { Color color; MyColorItemWidget(this.color); @override Widget build(BuildContext context) { return Expanded( child: Container( color: color, width: 150, height: 150, ) ); } } class MyListWidget extends StatefulWidget { @override State<StatefulWidget> createState() { return _MyListWidgetState(); } } class _MyListWidgetState extends State<MyListWidget> { List<Widget> widgetList = [ MyColorItemWidget(Colors.red), MyColorItemWidget(Colors.blue), ]; onChange() { setState(() { widgetList.insert(1, widgetList.removeAt(0)); }); } @override Widget build(BuildContext context) { print('print.... ${widgetList.length}'); return Scaffold( appBar: AppBar(title: Text('Key Test'),), body: Column( children: [ Row(children: widgetList,), ElevatedButton(onPressed: onChange, child: Text("toggle")) ], ) ); } }
- 다른 타입의 StatefulWidget 식별하기 - 키 미사용
- 위젯의 타입이 다름 → 객체 식별 필요성 X
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: MyListWidget() ); } } class MyREDItemWidget extends StatefulWidget { @override State<StatefulWidget> createState() { return _MyREDItemWidgetState(Colors.red); } } class _MyREDItemWidgetState extends State<MyREDItemWidget> { Color color; _MyREDItemWidgetState(this.color); @override Widget build(BuildContext context) { return Expanded( child: Container( color: color, width: 150, height: 150, )); } } class MyBLUEItemWidget extends StatefulWidget { @override State<StatefulWidget> createState() { return _MyBLUEItemWidgetState(Colors.blue); } } class _MyBLUEItemWidgetState extends State<MyBLUEItemWidget> { Color color; _MyBLUEItemWidgetState(this.color); @override Widget build(BuildContext context) { return Expanded( child: Container( color: color, width: 150, height: 150, )); } } class MyListWidget extends StatefulWidget { @override State<StatefulWidget> createState() { return _MyListWidgetState(); } } class _MyListWidgetState extends State<MyListWidget> { List<Widget> widgetList = [ MyREDItemWidget(), MyBLUEItemWidget() ]; onChange() { setState(() { widgetList.insert(1, widgetList.removeAt(0)); }); } @override Widget build(BuildContext context) { print('print.... ${widgetList.length}'); return Scaffold( appBar: AppBar(title: Text('Key Test'),), body: Column( children: [ Row(children: widgetList,), ElevatedButton(onPressed: onChange, child: Text("toggle")) ], ) ); } }
- 위젯의 타입이 다름 → 객체 식별 필요성 X
- 같은 타입의 StatefulWidget 식별하기 - 키 미사용
- 위젯 트리는 바뀌나 화면은 안바뀐다.
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: MyListWidget() ); } } class MyColorItemWidget extends StatefulWidget { Color color; MyColorItemWidget(this.color, {Key? key}) : super(key: key); @override State<StatefulWidget> createState() { return _MyColorItemWidgetState(this.color); } } class _MyColorItemWidgetState extends State<MyColorItemWidget> { Color color; _MyColorItemWidgetState(this.color); @override Widget build(BuildContext context) { return Expanded( child: Container( color: color, width: 150, height: 150, )); } } class MyListWidget extends StatefulWidget { @override State<StatefulWidget> createState() { return _MyListWidgetState(); } } class _MyListWidgetState extends State<MyListWidget> { List<Widget> widgetList = [ MyColorItemWidget(Colors.red), MyColorItemWidget(Colors.blue) ]; onChange() { setState(() { widgetList.insert(1, widgetList.removeAt(0)); }); } @override Widget build(BuildContext context) { print('print.... ${widgetList.length}'); return Scaffold( appBar: AppBar(title: Text('Key Test'),), body: Column( children: [ Row(children: widgetList,), ElevatedButton(onPressed: onChange, child: Text("toggle")) ], ) ); } }
- 위젯 트리는 바뀌나 화면은 안바뀐다.
- 같은 타입의 StatefulWidget 식별하기 - 키 사용
- 위젯 트리, 화면 모두 바뀐다.
class _MyListWidgetState extends State<MyListWidget> { List<Widget> widgetList = [ MyColorItemWidget(Colors.red, key: UniqueKey()), MyColorItemWidget(Colors.blue, key: UniqueKey())
- 위젯 트리, 화면 모두 바뀐다.
(3) 키 클래스
- GlobalKey > GlobalObjectKey, LabelGlobalKey
- 앱 전체에서 유일한 값
- LocalKey > ValueKey<문자열, 숫자>, UniqueKey<유일한 난수="">, ObjectKey<객체>
객체>유일한>
- 지정된 위젯의 부모부터 자식 위젯에서 유일한 값
- → 부모부터 자식에서 유일한 값을 지정할 때 사용(식별 키)
- 지정된 위젯의 부모부터 자식 위젯에서 유일한 값
- 다양한 형태의 키값 지정
class _MyListWidgetState extends State<MyListWidget> { List<Widget> widgetList = [ MyColorItemWidget(Colors.red, key: UniqueKey()), MyColorItemWidget(Colors.blue, key: ValueKey(10)), MyColorItemWidget(Colors.blue, key: ObjectKey(User('kkang)',30))
2. 기본 위젯 활용하기
1) 애셋을 활용하는 방법
(1) 애셋 등록하기
- 애셋 : 앱을 구성하는 데 활용할 자원
- 예시 : 아이콘 이미지, JSON, 폰트
- ✨ pubspec.ymal에 등록해야 함
flutter: # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in # the material Icons class. uses-material-design: true # To add assets to your application, add an assets section, like this: assets: - images/ - images/sub1/
- 객체로 사용
Image.asset('images/icon.jpg')
(2) 애셋 변형하기
- 애셋 변형 : 상황에 맞는 애셋을 적용하는 개념
- 애셋 등록한 폴더의 하위폴더로
2.0x
,3.0x
생성 후 알맞게 이미지 삽입(1.0x
,2.0x
,3.0x
적용)- 저장된 파일 : ~/img.png, ~/2.0x/img.png, ~/3.0x/img.png
(3) 애셋 (코드 에서) 이용하기
- AssetBundle 클래스 이용(추상 클래스)
- loadString() : 애셋의 데이터를 문자열로 불러오는 함수
- load() : 반환 타입이 Byte Data인 이미지나 바이너리 데이터를 불러오는 함수
- ✨ 추상 클래스라 해당 클래스 타입의 객체로 사용해야 함
- rootBundle : 애플리케이션 전역에서 사용하는 AssetBundle
await rootBundle.loadString('assets/text/my.text.txt'); // 경로 알맞게
- DefaultAssetBundle : 위젯에서 사용하는 AssetBundle
await DefaultAssetBundle.of(context).loadString('assets/text/my_text.txt');
- rootBundle : 애플리케이션 전역에서 사용하는 AssetBundle
- 예제
- 1번 : pubspec.yaml 수정
assets: - assets/text/
- 2번 : 다트 파일 작성
import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; // 애셋 이용을 위한 rootBundle 제공 void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { // rootBundle을 이용해 애셋 파일을 읽어 반환하는 함수 // Future는 비동기 데이터를 의미하며 이후에 자세히 다룸 Future<String> useRootBundle() async { return await rootBundle.loadString('assets/text/my_text.txt'); } Future<String> useDefaultAssetBundle(BuildContext context) async { return await DefaultAssetBundle.of(context).loadString('assets/text/my_text.txt'); } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: Text('Test'), ), body: Column( children: [ Image.asset('images/icon.png'), Image.asset('images/icon/user.png'), // FutureBuilder는 비동기 데이터를 이용해 화면을 구성하는 위젯 FutureBuilder( future: useRootBundle(), // useRootBundle() 함수 호출 builder: (context, snapshot){ // useRootBundle() 함수의 // 결괏값이 snapshot에 전달되며 // 이 값으로 화면 구성 return Text('rootBundle : ${snapshot.data}'); } ), FutureBuilder( future: useDefaultAssetBundle(context), builder: (context, snapshot) { return Text('DefaultAssetBundle : ${snapshot.data}'); } ) ], )), ); } }
2) 텍스트 위젯
- 텍스트 위젯 생성자
Text(String data, {}) Text.rich(InlineSpan textSpan, {})
(1) 텍스트 정렬하기 - textAlign
Text('Hello World',
textAlign: TextAlign.center)
(2) 텍스트 스타일 지정하기 - TextStyle
Text('Hello World',
Style: TextStyle(
fontWeight: FontWeight.bold, // 굵게
fontStyle: FontStyle.italic, // 기울임꼴
color: Colors.red, // 글꼴 색상
fontSize: 20, // 글꼴 크기
height: 2, // 세로 크기(줄 간격)
backgroundColor: Colors.yellow, // 바탕색
decoration: TextDecoration.underline, // 밑줄 장식
decorationColor: Colors.red, // 장식 색상
decorationStyle: TextDecorationStyle.wavy, // 장식 모양(물결)
),
)
- height : 위젯이 세로 방향으로 차지하는 크기 / fontSize의 배수
(3) 줄 수 제한하기 - maxLines
Text(longTxt,
Style : TextStyle(
fontSize: 20
),
maxLines: 2, // 줄 수 제한
overflow: TextOverflow.ellipsis // 생략됐음을 알리는 효과
)
- overflow 파라미터
- visible: 자동 개행(기본값)
- ellipsis: 말 줄임표(…) 표시
- fade: 흐리게 표시
- clip: 생략 효과 없음
(4) 문자열 일부만 꾸미기 - TextSpan
- 문자열 일부분에만 스타일 지정 - Text.rich(), RichText()
Text.rich( // RichText() TextSpan( text: 'HE', children: [ TextSpan( text: 'L', style: TextStyle(fontStyle: FontStyle.italic), children:[ TextSpan(text: 'L0'), TextSpan(text: 'W0', style: TextStyle(color: Colors.red)), ] ), TextSpan(text: 'RLD', style: TextStyle(fontWeight: FontWeight.bold)) ] ), style: TextStyle(fontSize: 20), )
✨ 코드 예시
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
String longTxt =
'동해물과 백두산이 마르고 닳도록 하느님이 보우하사 우리나라만세, 무궁화 삼천리 화려강산 대한 사람 대한으로 기리 보전하세';
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Test'),
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
'Hello World',
style: TextStyle(
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic,
color: Colors.red,
fontSize: 20,
height: 2,
backgroundColor: Colors.yellow,
decoration: TextDecoration.underline,
decorationColor: Colors.red,
decorationStyle: TextDecorationStyle.wavy,
),
),
Text(
longTxt,
style: TextStyle(
fontSize: 20
),
maxLines: 2,
overflow: TextOverflow.fade,
),
RichText(
text: TextSpan(
text: 'HE',
style: TextStyle(fontSize: 20, color: Colors.black),
children: [
TextSpan(
text: 'L',
style: TextStyle(fontStyle: FontStyle.italic),
children: [
TextSpan(text: 'L0'),
TextSpan(
text: 'W0', style: TextStyle(color: Colors.red))
]
)
]
)
)
],
)
)
);
}
}
3) 이미지 위젯
- 이미지 출력 : Image 위젯
- 이미지 가져오기 : ImageProvider(추상 클래스)
- AssetImage : 애셋 이미지
- FileImage : 파일 이미지
- MemoryImage : 메모리의 데이터 이미지
- NetworkImage : 네트워크 이미지
- ResizeImage : 이미지 크기 변경
- 생성자로 이미지 가져오기
- Image.asset() : AssetImage 이용
- Image.network() : NetworkImage 이용
- Image.file() : FileImage 이용
- Image.memory() : MemoryImage 이용
- 코드 예시
- 이미지 출력
Image(image: AssetImage('images/icon/user.png'),),
- 이미지 크기 변경
Image(image: ResizeImage(AssetImage('images/icon/user.png'), width: 70, height: 80)),
- 네트워크의 이미지 가져오기
Image(image: NetworkImage('https://flutter.github.io/assets-for-api-docs/assets/widgets/owl.jpg'),),
- 생성자로 이미지 애셋 출력하기
Image.asset('images/icon/user.png'),
- 이미지 출력
(1) 이미지 채우기
- 이미지 크기와 위젯의 크기가 다를 때(fit 파라미터)
- BoxFit.fill : 높이와 너비를 가득 채워 이미지 출력(비율이 변경될 수 있음)
- BoxFit.contain : 이미지가 잘리거나 비율 변화 없이 가능한 한 크게 출력
- BoxFit.cover : 비율 변화 없이 위젯에 꽉 채워 출력(이미지가 잘릴 수 있음)
- BoxFit.fitWidth : 너비를 채워 출력(이미지가 잘릴 수 있음)
- BoxFit.fitHeight : 높이를 채워 출력(이미지가 잘릴 수 있음)
- BoxFit.none : 이미지 원본을 그대로 출력(이미지가 잘릴 수 있음)
- BoxFit.scaleDown : 이미지 전체가 나오도록 크기 조절 후 출력
✨ 코드예시
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Test'),
),
body: Column(children: [
Image(
image: NetworkImage(
'https://flutter.github.io/assets-for-api-docs/assets/widgets/owl.jpg'),
),
Container(
color: Colors.red,
child: Image.asset(
'images/big.jpg',
width: 200,
height: 100,
fit: BoxFit.fill,
),
)
])));
}
}
4) 아이콘과 아이콘 버튼
- 플러터 아이콘 모음 : ‘https://api.flutter.dev/flutter/material/Icons-class.html’
(1) 폰트 어섬 아이콘 사용하기
- FontAwesomeIcons 패키지 등록(pubspec.yaml)
dependencies: font_awesome_flutter: ^10.1.0
FaIcon( FontAwesomeIcons.bell ),
(2) 아이콘으로 버튼 만들기 - IconButton
- 아이콘 버튼 사용법
IconButton( onPressed: onPressed, icon: Icon(Icons.alarm) )
✨ 코드 예시
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
onPressed() {
print('icon button click....');
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Test'),
),
body: Column(children: [
Icon(Icons.alarm, size: 100, color: Colors.red),
FaIcon(
FontAwesomeIcons.bell,
size: 100,
),
IconButton(
onPressed: onPressed,
icon: Icon(
Icons.alarm,
size: 100,
))
])));
}
}
5) 제스처 감지기와 엘리베이트 버튼
(1 ) 제스처 감지기 - GestureDetector
- 화면을 탭하거나 드래그하는 등의 행위를 감지해 특정 로직을 실행(화면 구성 X)
- 사용자 이벤트 처리하기
GestureDetector( child: Image.asset('images/icon/user.png'), onTap: () { print('image click...'); }, )
- 드래그 좌푯값 가져오기
GestureDetector( child: Image.asset('images/icon/user.png'), onVerticalDragStart: (DragStartDetails details) { print( 'vertical drag start...global position : ' '${details.globalPosition.dx}, ' '${details.globalPosition.dy}'); print( 'vertical drag start...local position : ' '${details.localPosition.dx}, ' '${details.localPosition.dy}'); } )
(2 ) 엘리베이트 버튼 - ElevatedButton
- 자체에서 이벤트 처리 기능 지원(gesturedetector 상속)
ElevatedButton( onPressed: () { print('ElevatedButton click....'); }, child: Text('Click Me'), style: ButtonStyle( backgroundColor: MaterialStateProperty.all<Color>(Colors.red) ) )
✨ 코드 예시
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Test'),
),
body: Column(children: [
GestureDetector(
child: Image.asset('images/big.jpg'),
onTap: () {
print('image click...');
},
onVerticalDragStart: (DragStartDetails details) {
print('vertical drag start...global position : ${details.globalPosition.dx}, ${details.globalPosition.dy}');
print('vertical drag start...local position : ${details.localPosition.dx}, ${details.localPosition.dy}');
}),
ElevatedButton(
onPressed: () {
print('ElevatedButton click....');
},
child: Text('Click Me'),
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all<Color>(Colors.red)),
)
])));
}
}
6) 컨테이너와 센터 위젯
(1) 영역을 표현하는 컨테이너 - Container
- 색깔 컨테이너
Container( width: 100, height: 100, color: Colors.red )
- 사진 컨테이너
Container( decoration: BoxDecoration( border: Border.all(width: 10, color: Colors.black), borderRadius: BorderRadius.all(const Radius.circular(8)), ), margin: const EdgeInsets.all(10), padding: EdgeInsets.all(10), child: Image.asset('images/big.jpg'), )
(2) 마진과 패딩값 지정하기 - EdgeInsets
- 한 방향(왼쪽) 마진 설정
Container( margin: EdgeInsets.only(left: 30, top: 60) )
- 세로 방향(위아래) 마진 설정
Container( margin: EdgeInsets.symmetric(vertical: 30.0) )
- ✨원 영역 출력하기
decoration: BoxDecoration( color: Colors.orange, shape: BoxShape.circle )
- ✨ 원 영역 이미지 출력하기
decoration: BoxDecoration( color: Colors.orange, shape: BoxShape.circle, image: DecorationImage(image: AssetImage('images/big.jpg'), fit: BoxFit.cover) )
(3) 그래디언트 색상 표현하기
Container(
Decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Colors.red,
Colors.yellow,
])))
(4) 가운데 정렬하는 센터 - Center
Center(
child: Text('Hello',
style: TextStyle(
fontSize: 48.0,
fontWeight: FontWeight.bold,
color: Colors.blue
)),
heightFactor: 2,
widthFactor: 2)
- widthFactor, heightFactor 미지정 시 가능한 최대 크기 차지
- child에 추가하는 위젯의 배수로 지정
✨ 코드 예시
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Test'),
),
body: Container(
height: Size.infinite.height,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Colors.red,
Colors.yellow,
]
)
),
child: Center(
child: Container(
margin: EdgeInsets.all(10.0),
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
image: AssetImage('images/big.jpg'), fit: BoxFit.cover),
),
width: 200,
height: 200,
)))));}}
3. 위젯 배치하기
1) 방향 설정하기
(1) 가로로 배치하기 - Row
Row(
Children: [
Container(
width: 100,
height: 100,
color: Colors.red
),
Container(
width: 100,
height: 100,
color: Colors.blue
)])
(2) 세로로 배치하기 - Column
Column(
Children: [
Container(
width: 100,
height: 100,
color: Colors.red
),
Container(
width: 100,
height: 100,
color: Colors.blue
)])
(3) 레이아웃 중첩하기
Column(
children: [
Row(),
Row(),
Column()
])
(4) 크기 설정하기 - mainAxisSize
- 기본축(main axis) : 각 레이아웃의 배치축
- 교차축(cross axis) : 각 레이아웃의 배치축의 반대
- 위젯 크기만큼 영역 설정
Row( mainAxisSize: MainAxisSize.min, children: [] )
(5) 배치 설정하기 - Alignment
- MainAxisAlignment
- center : 중앙에 배치
- end : 끝에 배치
- start : 시작에 배치
- spaceAround : 각 위젯의 앞뒤 공백을 균등하게 배치
- spaceBetween : 위젯 간 공백을 균등하게 배치
- spaceEvenly : 앞뒤 그리고 각 위젯 간 공백을 균등하게 배치
- crossAxisAlignment
- baseline : 기준선에 맞춰 배치
- center : 가운데에 배치
- end : 끝에 배치
- start : 시작에 배치
- stretch : 교차축을 모두 차지하게 배치
Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start )
(6) 겹처서 모두 보이기 - Stack
Stack(
children: [
container(), // 출력
container(), // 출력
container() // 출력
])
- Stack이 차지하는 크기는 children에 추가한 위젯 중 가장 큰 위젯의 크기
(7) 겹처서 하나만 보이기 - IndexedStack
Stack(
index: 1,
children: [
container(),
container(), // 여기만 출력
container()
])
✨ 코드 예시
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Test'),
),
body: SingleChildScrollView(
child: Column(children: [
Container(
margin: EdgeInsets.only(bottom: 5),
color: Colors.yellow,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 50,
height: 100,
color: Colors.red,
),
Container(
width: 50,
height: 50,
color: Colors.green,
),
Container(
width: 50,
height: 150,
color: Colors.blue,
),
],
),
),
Container(
color: Colors.yellow,
margin: EdgeInsets.only(bottom: 5),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Container(
width: 50,
height: 100,
color: Colors.red,
),
Container(
width: 50,
height: 50,
color: Colors.green,
),
Container(
width: 50,
height: 150,
color: Colors.blue,
),
],
)),
Container(
color: Colors.yellow,
margin: EdgeInsets.only(bottom: 5),
height: 200,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container(
width: 50,
height: 100,
color: Colors.red,
),
Container(
width: 50,
height: 50,
color: Colors.green,
),
Container(
width: 50,
height: 150,
color: Colors.blue,
),
],
)),
Container(
color: Colors.yellow,
margin: EdgeInsets.only(bottom: 5),
height: 200,
child: Stack(children: [
Container(
color: Colors.red,
),
Container(
width: 100,
height: 100,
color: Colors.green,
),
Container(
width: 50,
height: 50,
color: Colors.yellow,
)
]))
]))));
}
}
2) 위치 설정하기
(1) 특정 위치에 배치하기 - Align
- 독립 사용
Align( alignment: Alignment.topRight )
- 스택과 함께 사용
Stack( children: [ Align( alignment: Alignment.bottomRight // Alignment(x, y) ★ 생성자로 사용 )])
- Align위젯이 기준(해당 위젯 내부에서의 위치)
- bottomCenter : Alignment(0.0, 1.0)
- bottomLeft : Alignment(-1.0, 1.0)
- bottomRight : Alignment(1.0, 1.0)
- center : Alignment(0.0, 0.0)
- centerLeft : Alignment(-1.0, 0.0)
- centerRight : Alignment(1.0, 0.0)
- topCenter : Alignment(0.0, -1.0)
- topLeft : Alignment(-1.0, -1.0)
- topRight : Alignment(1.0, -1.0)
(2) 왼쪽 위를 기준으로 배치하기 - FractionalOffset
Align(
alignment: FractionalOffset(0.5, 0.5)
)
- 전체 좌표가 기준
- bottomCenter : FractionalOffset(0.5, 1.0)
- bottomLeft : FractionalOffset(0.0, 1.0)
- bottomRight : FractionalOffset(1.0, 1.0)
- center : FractionalOffset(0.5, 0.5)
- centerLeft : FractionalOffset(0.0, 0.5)
- centerRight : FractionalOffset(1.0, 0.5)
- topCenter : FractionalOffset(0.5, 0.0)
- topLeft : FractionalOffset(0.0, 0.0)
- topRight : FractionalOffset(1.0, 0.0)
(3) 상대 위칫값으로 배치하기 - Positioned
- 독립적으로 사용 불가(Stack의 하위에서 사용 가능)
- 부모 위젯에서 얼마나 떨어져야 하는 지를 표현
- 코드 예시
Stack( Positioned( right: 40.0, top: 40.0 ))
✨ 코드 예시
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Test'),
),
body: Stack(
children: [
Align(
alignment: Alignment(0.0, 0.0),
child: Container(
width: 150,
height: 150,
color: Colors.yellow,
)
),
Align(
alignment: FractionalOffset(1.0, 0.0),
child: Container(
width: 150,
height: 150,
color: Colors.blue,
),
),
Positioned(
left: 40.0,
top: 40.0,
child : Container(
color: Colors.pink,
height: 150.0,
width: 150.0,
))],)));}}
3) 크기 설정하기
(1) 똑같은 크기로 배치하기 - IntrinsicWidth, IntrinsicHeight
- Row나 Column에 추가한 위젯들의 크기를 똑같이 설정할 때 사용(가장 큰 것이 기준)
Container( child: IntrinsicWidth( child: Column( children: [ container(), container(), container() ])))
(2) 최소, 최대 범위로 배치하기 - ConstrainedBox
- 위젯 크기의 허용 범위 설정
ConstrainedBox( constraints: BoxConstraints.expand() // 최대로 확장하기 constraints: BoxConstraints.expand(width: 300, height: 300) // 확장할 크기 설정 constraints: BoxConstraints( minWidth: 300, maxHeight: 50 ))
✨ 코드 예시
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Test'),
),
body: Column(children: [
IntrinsicWidth(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(color: Colors.red, width: 50, height: 50.0),
Container(color: Colors.green, width: 150, height: 150.0),
Container(color: Colors.blue, width: 100, height: 100.0),
],
),
),
ConstrainedBox(
constraints: BoxConstraints(minWidth: 300, maxHeight: 50),
child:
Container(color: Colors.amber, width: 150, height: 150.0),
)
])));
}
}
4) 기타 배치와 관련된 위젯
(1) 비율로 배치하기 - Expanded
Row(
children: <Widget>[
Container(width: 300)
Expanded(flex: 2), // 남은 너비 중 2/3
Expanded(flex: 1), // 남은 너비 중 1/3
])
(2) 빈 공간 넣기 - Spacer
Row(
children:[
Container(color: Colors.blue)
Spacer()
Container(color: Colors.red)
])
(3) 스크롤 제공하기 - SingleChildScrollView
SingleChildScrollView(
scrollDirection: Axis.vertical
)
✨ 코드 예시
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Test'),
),
body: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Column(children: <Widget>[
Container(
height: 300,
child: Row(children: <Widget>[
Container(
color: Colors.red,
width: 100,
),
Expanded(
flex: 1,
child: Container(
color: Colors.amber,
)),
Expanded(
flex: 1,
child: Container(
color: Colors.yellow,
))
])),
Container(
color: Colors.green,
height: 300,
child: Row(children: <Widget>[
Image.asset('images/lab_instagram_icon_1.jpg'),
Image.asset('images/lab_instagram_icon_2.jpg'),
Image.asset('images/lab_instagram_icon_3.jpg'),
Spacer(),
Image.asset('images/lab_instagram_icon_4.jpg')
])),
Container(
color: Colors.blue,
height: 300,
)
]))));
}
}
댓글남기기