32 분 소요

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()

(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"))
              ],
            )
          );
        }
      }
    
  • 같은 타입의 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');
        
  • 예제
  • 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) 아이콘과 아이콘 버튼

(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,
                  )
                ]))));
  }
}

댓글남기기