Post

Flutter 이미지 뷰어

Flutter 이미지 뷰어

이미지 뷰어 앱은 이미지 5개를 보여주고(전체화면) 스와이프를 통해서 이미지를 변경할 수 있다 또한 Timer를 통해서 일정시간이 지나면 다음 사진으로 넘어가도록 한다

위젯 생명주기

위젯 생명주기는 위젯이 화면에 그려지는 순간부터 삭제되는 순간까지를 의미한다

  • StatelessWidget: 상태가 없는 위젯이다 StatelessWidget이 빌드되면 생성자가 실행된다 필수로 오버라이드해야 하는 build( )함수가 실행된다 이후 build 함수에 반환한 위젯이 화면에 랜더링 된다 플러터에서 모든 위젯은 Widget클래스를 상속하고 Widget 클래스는 불변 특성을 가지고 있다 하지만 위젯의 속성을 변경해야할 때가 있는다 build 함수에서 매개변수를 받고 이 매개변수 값이 바뀐다면 build 함수를 재실행해야한다 하지만 StatelessWidget으로 선언된다면 불변이기 때문에 인스턴스를 아예 새로 생성한 후 기존 인스턴스를 대체해야한다
  • StatefulWidget: 위젯 내부에서 build() 함수를 재실행해야하는 상황이 존재한다 이때 StatefulWidget을 사용하면된다 StatefulWidget는 위젯 클래스와 스테이트 클래스 2개로 구성되어있다

 

StatefulWidget의 생성

1. 위젯 생성
  • StatefulWidget 생성자 호출하면 StatefulWidget 인스턴스가 생성되고 이과정에서 위젯의 설정값(프로퍼티)가 초기화된다
  • flutter가 createState 메서드를 호출하여 State 객체를 생성한다

 

2. 초기화 단계

super.initState()를 반드시 호출해서 초기 상태를 설정하거나, 비동기 작업을 시작할 때 사용한다

 

3. UI 생성 및 업데이트

build를 통해서 위젯의 UI를 생성한다 상태가 변경되면 다시 호출한다 항상 UI를 리턴해야 한다

 

4. 상태 변경

setState는 상태를 변경하고 변경된 상태를 기반으로 build 메서드를 다시 호출해 UI를 업데이트 한다

 

5. 위젯 변경 감지

didUpdateWidget은 부모 위젯에 의해 현재 위젯이 업데이트 될 때 호출된다 이전위젯 (oldWidget)과 새로운 위젯(this.widget)을 비교해 차이점을 처리할 수 있다

 

6. 종료 단계
  • deactivate 위젯이 위젯 트리에서 제거될 때 호출된다 이 메서드가 호출된 후 위젯이 다시 트리에 추가될 수도 있다
  • dispose는 State객체가 영구적으로 제거될 때 호출된다 리소스 해제 작업을 수행 super.dispose()를 호출해야한다

 

전체 생명 주기

StatefulWidget 생성자 → createState → initState → build</br> 여기서 상태 변경시: setState → build</br> 위젯 업데이트시: didUpdateWidget → build</br> 제거시: deactive → dispose

 

Timer

타이머는 특정시간이 지난 후에 함수를 실행한다 이번 프로젝트에서는 Timer.periodic( )을 사용해서 주기적으로 콜백 함수를 실행한다

1
2
3
4
Timer.periodic(
	Duration(seconds:3), //주기
  (Timer timer) {}, //콜백함수
);

Duration을 통해서 콜백함수의 실행주기를 결정하고 { }에 실행할 콜백함수를 작성한다

생성자설명
Timer( )Timer의 기본 생성자 첫번째 매개변수로 Duration을 입력하고 두 번째 매개변수에 실행할 콜백함수를 작성한다
Timer(Duration(seconds: 3), (Timer timer) {print('hello')}) 3초뒤 hello문자열 출력
Timer.periodic( )Timer의 유일한 네임드 생성자, 주기적으로 콜백함수를 실행할 때 사용한다
Timer.periodic(Duration(second: 3), (Timer timer){ print('hello') } ) 3초마다 hello 출력

 

프로젝트 설정하기

1. 이미지 추가

이미지 5개정도를 asset/img 폴더를 만들어서 넣는다

이후 yaml을 설정해서 이미지 위치를 넣어준다

1
2
3
4
5
flutter:

  uses-material-design: true
  assets:
    - asset/img/

 

2. 프로젝트 초기화

lib 폴더에서 screen 폴더를 생성하고 내부에 home_screen.dart 파일을 생성한다 이 파일은 기본 홈 화면이 들어갈 위젯이다

1
2
3
4
5
6
7
8
9
10
11
12
13
// home_screen.dart
import 'package:flutter/material.dart';

class HomeScreen extends StatelessWidget {
  const HomeScreen({Key? key}) : super(key:key);
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Text('home')
    );
  }
}

main.dart에서 HomeScreen을 홈 위젯으로 등록해야한다

1
2
3
4
5
6
7
8
9
10
import 'package:flutter/material.dart';
import 'package:image_view/screen/home_screen.dart';

void main() {
  runApp(
    MaterialApp(
      home: HomeScreen(),
    )
  );
}

 

3. 페이지 구현하기

PageView는 여러개의 위젯을 단독 페이지로 생성하고 가로 또는 세로 스와이프로 페이지를 넘길 수 있게 하는 위젯이다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//home_screen.dart
import 'package:flutter/material.dart';

class HomeScreen extends StatelessWidget {
  const HomeScreen({Key? key}) : super(key:key);
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: PageView(
        children: [1,2,3,4,5]
          .map(
            (number) => Image.asset('asset/img/image_$number.png'),
          )
          .toList(),
      ),
    );
  }
}

pageview위젯을 추가해서 사진을 슬라이드로 넘길수 있게 만들었다

 

4. 상태바 색상 변경

상태바는 시간, 와이파이, 배터리등을 나타내는 공간이다 배경등이 전체화면을 차지할 때 배경색상과 비슷해서 상태바의 글씨가 안보이는 경우가 존재한다 이때에 대비해서 상태바의 색상을 변경하는 코드를 포함한다( 아이폰 최신 버전에서는 배경에 따라 검은색, 흰색이 자동으로 변경된다)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class HomeScreen extends StatelessWidget {
  const HomeScreen({Key? key}) : super(key:key);
  
  @override
  Widget build(BuildContext context) {
    SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.dark); //검은색으로 변경
    return Scaffold(
      body: PageView(
        children: [1,2,3,4,5]
          .map(
            (number) => Image.asset('asset/img/image_$number.png',
              fit: BoxFit.cover, // 이미지 비율 화면 꽉채우도록 변경
            ),
          )
          .toList(),
      ),
    );
  }
}

 

SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.dark)외에도 SystemChrome클래스는 다른 함수들도 가지고 있다

함수설명
setEnabledSystemUIMode()앱의 풀스크린 모드를 지정한다 상태바를 보이지 않게 할 수 있다
setPreferredOrientations()앱을 실행하는 방향을 지정한다 가로, 세로, 가로 좌우 반전등
setSystemUIChangeCallback()시스템 UI가 변경되면 콜백함수를 실행한다
setSystemUIOverlayStyle()시스템 UI 색상을 변경한다

 

5. 타이머 추가하기

타이머를 추가하기 위해서는 StatelessWidget이 아닌 StatefulWidget으로 변경해야한다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class HomeScreen extends StatefulWidget {
  const HomeScreen({Key? key}) : super(key:key);

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: PageView(
        children: [1,2,3,4,5]
          .map(
            (number) => Image.asset('asset/img/image_$number.png',
              fit: BoxFit.fitWidth, // 이미지 비율 화면 꽉채우도록 변경
            ),
          )
          .toList(),
      ),
    );
  }
}

StatefulWidget은 StatefulWidget 클래스를 상속해서 정의할 수 있다 StatefulWidget은 createState( )함수를 정의해야 하며 State를 반환한다

_HomeScreenState클래스는 먼저 생성한 StatefulWidget 클래스를 매개변수로 받는 State 클래스를 상속한다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:async'; //async 패키지 불러오기

class HomeScreen extends StatefulWidget {
  const HomeScreen({Key? key}) : super(key:key);

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  @override
  void initState() {
    super.initState(); //부모 initState 실행

    Timer.periodic(
      Duration(seconds: 3),
      (tiemr) {
        print('실행');
      },
    );
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: PageView(
        children: [1,2,3,4,5]
          .map(
            (number) => Image.asset('asset/img/image_$number.png',
              fit: BoxFit.fitWidth, // 이미지 비율 화면 꽉채우도록 변경
            ),
          )
          .toList(),
      ),
    );
  }
}

print 함수가 3초마다 한번씩 실행되는것을 확인할 수 있다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:async'; //async 패키지 불러오기

class HomeScreen extends StatefulWidget {
  const HomeScreen({Key? key}) : super(key:key);

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {

  final PageController pageController = PageController(); // 페이지 컨트롤러 생성

  @override
  void initState() {
    super.initState(); //부모 initState 실행

    Timer.periodic(
      Duration(seconds: 3),
      (tiemr) {
        int? nextPage = pageController.page?.toInt();

        if (nextPage == null) {
          return;
        }

        if (nextPage == 4) {
          nextPage = 0;
        } else {
          nextPage++;
        }

        pageController.animateToPage( // 페이지 변경
          nextPage,
          duration: Duration(milliseconds: 500),
          curve: Curves.ease,
        );
      },
    );
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: PageView(
        controller: pageController, //컨트롤러 등록
        children: [1,2,3,4,5]
          .map(
            (number) => Image.asset('asset/img/image_$number.png',
              fit: BoxFit.fitWidth,
            ),
          )
          .toList(),
      ),
    );
  }
}

위 코드를 분석하면 pageController.page 게터를 사용해서 PageView의 현재 페이지를 가져올 수 있다 하지만 double이라 toInt()로 정수로 변환한다 PageController의 animateToPage() 함수를 통해서 현재 페이지를 변경할 수 있다 매개변수 첫번째에 이동할 페이지(정수)가 입력, duration이동 소요시간, curve를 통해서 애니메이션 작동방식을 정의하면 된다

This post is licensed under CC BY 4.0 by the author.