Flutter - 페이지를 상하좌우로 슬라이드해서 이동하기 (PageView)

반응형

 

Overview

한 화면(페이지) 내에서 좌우로 슬라이드하여 화면을 이동하는 효과를 구현해봅시다.
Flutter에서는 슬라이드 화면 넘김 효과를 위해 제공하는 'PageView' 클래스가 있습니다.
이 포스팅에서는 페이지뷰 클래스의 기본 활용법을 설명합니다.
막상 보면 코드는 별 게 없습니다.


PageView Class

페이지 별로 스크롤이 작동하도록 해주는 클래스입니다.
페이지 뷰의 각 하위 항목은 뷰포트와 동일한 크기여야 합니다.
사용 방법은 간단합니다.

  • PageController
    • 페이지 컨트롤러를 사용하여 표시되는 페이지를 제어하는데 사용합니다.
  • PageController.initialPage
    • 가장 먼저 표시되는 페이지를 지정합니다.
    • 위젯이 정의된 순서대로 인덱스는 0부터 시작합니다.
  • 동작 순서
    1. PageView가 먼저 생성됩니다.
    2. 페이지를 제어하기 위해 PageController를 정의합니다.
      (이 단계에서 처음 화면에 표시될 내용(페이지 혹은 위젯)을 설정할 수 있습니다.)
    3. PageController를 이용하여 페이지를 제어합니다. (페이지 슬라이드 이동)

샘플 코드 (예제)

아래의 샘플 코드는 Flutter 공식 문서에서 가져온 코드입니다.

import 'package:flutter/material.dart';

/// Flutter code sample for [PageView].

void main() => runApp(const PageViewExampleApp());

class PageViewExampleApp extends StatelessWidget {
  const PageViewExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('PageView Sample')),
        body: const PageViewExample(),
      ),
    );
  }
}

class PageViewExample extends StatelessWidget {
  const PageViewExample({super.key});

  @override
  Widget build(BuildContext context) {
    final PageController controller = PageController();
    return PageView(
      /// [PageView.scrollDirection] defaults to [Axis.horizontal].
      /// Use [Axis.vertical] to scroll vertically.
      controller: controller,
      children: const <Widget>[
        Center(
          child: Text('First Page'),
        ),
        Center(
          child: Text('Second Page'),
        ),
        Center(
          child: Text('Third Page'),
        ),
      ],
    );
  }
}

구현 : 슬라이드 좌우로 이동하기

프로젝트 생성 및 메인 함수 페이지 연결

  • 새 프로젝트를 생성하고 페이지 슬라이드를 구현할 페이지를 메인 함수에서 연결합니다.
void main() {
  runApp(
    const MaterialApp(
      home: PageSlideExample(),
    ),
  );
}

기본 클래스(페이지) 정의

  • main 함수의 아래 내용을 모두 지우고 기본 페이지를 정의합니다.
  • 공식 문서에서는 예제로 StatelessWidget으로 구현하였지만, 포스팅에서는 Stateful Widget 으로 구현합니다.
  • 이유는 추 후 버튼 클릭이나 특정 액션으로 페이지 이동 효과를 주려면 뼈대를 Stateful Widget으로 만들어두는게 좋습니다.

팁 : 'st'만 입력하여도 추가 클래스를 쉽게 만들 수 있습니다.

 

// 메인과 연결될 클래스
class PageSlideExample extends StatefulWidget {
  const PageSlideExample({super.key});

  @override
  PageSlideExampleState createState() => PageSlideExampleState();
}

class PageSlideExampleState extends State<PageSlideExample> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: // 내용 작성
    );
  }
}

페이지를 제어할 PageController 정의

  • State를 상속한 클래스 아래에 페이지뷰 컨트롤러를 정의합니다.
class PageSlideExampleState extends State<PageSlideExample> {
  // 페이지뷰 컨트롤러 설정
  // initialPage : 0부터 차례대로 위젯들의 인덱스를 의미
  final PageController _pageController = PageController(initialPage: 0);
  
  // 코드 생략
  
 }

각각의 페이지를 담을 PageView 정의

  • 위젯이 빌드되는 영역에 PageView 위젯을 생성하고 컨트롤러를 위에서 정의한 컨트롤러로 설정합니다.
  • PageView의 자식 위젯들은 리스트의 형태로 정의하여야 합니다. (children : [ ] )
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: PageView(
        controller: _pageController, // 페이지 컨트롤러 설정
        children: [
			// 페이지를 이동할 위젯들 정의
        ],
      ),
    );
  }

각각의 페이지(하위 위젯) 정의

  • PageView에 속할 컨텐츠들을 정의합니다.
  • 포스팅에서는 간단한 이해를 위해 컨테이너로 구성된 위젯 3개를 정의하였습니다.
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: PageView(
        controller: _pageController, // 페이지 컨트롤러 설정
        children: [
          // 페이지 컨트롤러의 첫 번째 위젯, 인덱스 : 0
          Container(
            color: Colors.red,
            child: const Center(
              child: Text('Page 1',
                  style: TextStyle(color: Colors.white, fontSize: 24)),
            ),
          ),
          // 페이지 컨트롤러의 두 번째 위젯, 인덱스 : 1
          Container(
            color: Colors.green,
            child: const Center(
              child: Text('Page 2',
                  style: TextStyle(color: Colors.white, fontSize: 24)),
            ),
          ),
          // 페이지 컨트롤러의 세 번째 위젯, 인덱스 : 0
          Container(
            color: Colors.blue,
            child: const Center(
              child: Text('Page 3',
                  style: TextStyle(color: Colors.white, fontSize: 24)),
            ),
          ),
        ],
      ),
    );
  }

실행

이 포스팅은 안드로이드 가상 에뮬레이터를 사용하여 앱을 실행합니다.
플러터, VS Code, Android Studio, Android Virtual Emulator 설치는 아래의 포스팅을 참고해주세요.


전체 코드

더보기
import 'package:flutter/material.dart';

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

class PageSlideExample extends StatefulWidget {
  const PageSlideExample({super.key});

  @override
  PageSlideExampleState createState() => PageSlideExampleState();
}

class PageSlideExampleState extends State<PageSlideExample> {
  // 페이지뷰 컨트롤러 설정
  // initialPage : 0부터 차례대로 위젯들의 인덱스를 의미
  final PageController _pageController = PageController(initialPage: 0);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: PageView(
        controller: _pageController, // 페이지 컨트롤러 설정
        children: [
          // 페이지 컨트롤러의 첫 번째 위젯, 인덱스 : 0
          Container(
            color: Colors.red,
            child: const Center(
              child: Text('Page 1',
                  style: TextStyle(color: Colors.white, fontSize: 24)),
            ),
          ),
          // 페이지 컨트롤러의 두 번째 위젯, 인덱스 : 1
          Container(
            color: Colors.green,
            child: const Center(
              child: Text('Page 2',
                  style: TextStyle(color: Colors.white, fontSize: 24)),
            ),
          ),
          // 페이지 컨트롤러의 세 번째 위젯, 인덱스 : 0
          Container(
            color: Colors.blue,
            child: const Center(
              child: Text('Page 3',
                  style: TextStyle(color: Colors.white, fontSize: 24)),
            ),
          ),
        ],
      ),
    );
  }
}

응용 : 세로로 화면 슬라이드하여 이동하기

화면을 위 아래로 슬라이드하도록 변경해봅시다. 딱 코드 한 줄만 추가하면 됩니다 =)
'PageView' 위젯의 'scrollDirection' 속성을 를 이용하여 이동하는 방향을 왼쪽, 오른쪽이 아닌 위, 아래로 변경할 수 있습니다.
해당 속성은 페이지 뷰의 담긴 컨텐츠(페이지 혹은 위젯)들이 슬라이드 되는 방향을 설정합니다.
기본 값은 수평(좌우)으로 되어있습니다.

  • 위의 전체 코드에서 PageView 부분에 아래의 코드를 추가해줍니다.
PageView(
	// 슬라이드 스크롤의 방향을 지정
	scrollDirection : Axis.vertical // default : Axis.horizontal
    
    // 코드 생략
)

응용 : 버튼으로 페이지 슬라이드하기

간단한 구현을 위해 플로팅 액션 버튼을 사용하며, setState 메서드를 이용하여 페이지 컨트롤러의 인덱스를 변경합니다.
페이지 컨트롤러의 인덱스 변경은 .animateToPage 메서드를 이용하여 동적으로 화면 전환을 할 수 있습니다.
단, 클래스가 StatelessWidget일 경우에는 StatefulWidget으로 변경하여야 합니다.

 

  • 페이지의 인덱스를 저장할 정수형 변수를 하나 선언해줍니다. (pageIndex)
class PageSlideExampleState extends State<PageSlideExample> {
  // 페이지뷰 컨트롤러 설정
  // initialPage : 0부터 차례대로 위젯들의 인덱스를 의미
  final PageController _pageController = PageController(initialPage: 0);

  // 버튼으로 페이지를 이동할 인덱스 카운터 변수
  int pageIndex = 0;
 
  // 코드 생략
 
}

 

  • 플로팅 액션 버튼을 추가해줍니다.
  • (아래와 같은 모양으로 구현할 예정)
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: 
      	// body 코드 생략
        
      // 플로팅 액션 버튼 추가
      floatingActionButton: FloatingActionButton(
      	// 버튼 클릭 이벤트
        onPressed: () {
			// 버튼을 클릭하였을 때 수행할 코드
          });
        },
        // 버튼 색상
        backgroundColor: Colors.amber[400],
        // 버튼 아이콘
        child: const Icon(Icons.arrow_forward),
      ),
    );
  }

 

  • 플로팅 액션 버튼의 버튼 클릭 이벤트를 정의해줍니다.
  • 가장 처음에 선언한 pageIndex를 이용하여 페이지 슬라이드 이벤트를 만듭니다.
  • 페이지 이동은 컨트롤러의 animateToPage 메서드를 활용합니다.

animateToPage 기본 구문

/* animateToPage 구문 */
// _pageController : PageView 클래스의 화면을 제어하는 PageController의 인스턴스를 의미

_pageController.animateToPage(
    // 이동할 페이지의 인덱스
    page,
    // 페이지가 슬라이드되는 시간
    duration: duration,
    // 페이지가 슬라이드 될 때의 애니메이션 효과
    curve: curve;

 

  • animateToPage를 버튼 클릭 이벤트에 적용해줍니다.
  floatingActionButton: FloatingActionButton(
    // 버튼 클릭 이벤트
    onPressed: () {
      // 다음 페이지로 이동하기 위해 페이지 인덱스 +1
      pageIndex++;
      // 마지막 페이지일 경우 페이지는 첫번째 페이지로 되돌아감
      pageIndex = pageIndex % 3;

      // 앱 상태 변경
      setState(() {
        // animateToPage : 페이지뷰의 페이지 인덱스를 특정 인덱스로 이동하게하는 메서드
        _pageController.animateToPage(
            // 이동할 페이지의 인덱스
            pageIndex,
            // 페이지가 슬라이드되는 시간 : 1초동안 이동
            duration: const Duration(milliseconds: 1000),
            // 페이지가 슬라이드 될 때의 애니메이션 효과
            // Curves.ease : 애니메이션 시작과 끝에 속도를 조절하여 자연스러운 효과를 줌
            curve: Curves.ease);
      });
    },
    backgroundColor: Colors.amber[400],
    child: const Icon(Icons.arrow_forward),
  ),

 
전체 소스 코드

  • 이해가 어렵다면 전체 소스 코드를 확인해주세요.
더보기
import 'package:flutter/material.dart';

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

class PageSlideExample extends StatefulWidget {
  const PageSlideExample({super.key});

  @override
  PageSlideExampleState createState() => PageSlideExampleState();
}

class PageSlideExampleState extends State<PageSlideExample> {
  // 페이지뷰 컨트롤러 설정
  // initialPage : 0부터 차례대로 위젯들의 인덱스를 의미
  final PageController _pageController = PageController(initialPage: 0);

  // 버튼으로 페이지를 이동할 인덱스 카운터 변수
  int pageIndex = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: PageView(
        scrollDirection: Axis.vertical,
        controller: _pageController, // 페이지 컨트롤러 설정
        children: [
          // 페이지 컨트롤러의 첫 번째 위젯, 인덱스 : 0
          Container(
            color: Colors.red,
            child: const Center(
              child: Text('Page 1',
                  style: TextStyle(color: Colors.white, fontSize: 24)),
            ),
          ),
          // 페이지 컨트롤러의 두 번째 위젯, 인덱스 : 1
          Container(
            color: Colors.green,
            child: const Center(
              child: Text('Page 2',
                  style: TextStyle(color: Colors.white, fontSize: 24)),
            ),
          ),
          // 페이지 컨트롤러의 세 번째 위젯, 인덱스 : 0
          Container(
            color: Colors.blue,
            child: const Center(
              child: Text('Page 3',
                  style: TextStyle(color: Colors.white, fontSize: 24)),
            ),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        // 버튼 클릭 이벤트
        onPressed: () {
          // 다음 페이지로 이동하기 위해 페이지 인덱스 +1
          pageIndex++;
          // 마지막 페이지일 경우 페이지는 첫번째 페이지로 되돌아감
          pageIndex = pageIndex % 3;

          // 앱 상태 변경
          setState(() {
            // animateToPage : 페이지뷰의 페이지 인덱스를 특정 인덱스로 이동하게하는 메서드
            _pageController.animateToPage(
                // 이동할 페이지의 인덱스
                pageIndex,
                // 페이지가 슬라이드되는 시간 : 1초동안 이동
                duration: const Duration(milliseconds: 1000),
                // 페이지가 슬라이드 될 때의 애니메이션 효과
                // Curves.ease : 애니메이션 시작과 끝에 속도를 조절하여 자연스러운 효과를 줌
                curve: Curves.ease);
          });
        },
        // 버튼 색상
        backgroundColor: Colors.amber[400],
        // 버튼 아이콘
        child: const Icon(Icons.arrow_forward),
      ),
    );
  }
}

참고

 

반응형