Flutter - Carousel Slider 기본 구문, 사용 방법, 캐러셀 인디케이터 구현하기

반응형

Overview

이번 포스팅에서는 Carousel Slider 를 사용하여 페이지들을 슬라이드로 넘기는 기능을 구현해 보도록 합니다.

또한, 사용자의 입력 없이 자동으로 일정한 간격으로 페이지가 슬라이드 되는 기능과

하단의 인디케이터를 통해 현재 화면 표시 및 특정 페이지로 이동하는 기능을 구현합니다.

포스팅에서는 각 페이지당 이미지를 배치하여 식별할 수 있도록 하였습니다.


포스팅에서 설명하는 플러터 프로젝트 파일은 깃허브에서 다운로드 가능합니다.


사전 작업

Carousel Slider 패키지 설치

flutter pub add carousel_slider


이미지 파일 세팅

포스팅에서는 asset을 이용하여 이미지를 표현합니다.

이미지는 포켓몬 위키 홈페이지(https://pokemon.fandom.com/ko/wiki/)에서 가져왔습니다 :)

 

플러터 프로젝트 내에 이미지 파일이 위치할 경로를 생성하고 이미지 파일을 이동합니다.

  • assets > images

 

pubspec.yaml 파일을 열고 flutter 하위에 assets 문구를 추가해줍니다.

assets에 추가할 내용은 이미지 파일이 위치한 경로를 입력해 주면 됩니다.

flutter:
  assets:
    - assets/images/	# 이미지 파일 경로

 


Carousel Slider 기본 구문

Carousel Slider의 공식 문서의 기본 구문은 다음과 같습니다.

  • 캐러셀 슬라이더 위젯 생성 기본 구문
CarouselSlider(
  options: CarouselOptions(height: 400.0),
  items: [1,2,3,4,5].map((i) {
    return Builder(
      builder: (BuildContext context) {
        return Container(
          width: MediaQuery.of(context).size.width,
          margin: EdgeInsets.symmetric(horizontal: 5.0),
          decoration: BoxDecoration(
            color: Colors.amber
          ),
          child: Text('text $i', style: TextStyle(fontSize: 16.0),)
        );
      },
    );
  }).toList(),
)

 

  • 옵션 구문
CarouselSlider(
   items: items,
   options: CarouselOptions(
      height: 400,
      aspectRatio: 16/9,
      viewportFraction: 0.8,
      initialPage: 0,
      enableInfiniteScroll: true,
      reverse: false,
      autoPlay: true,
      autoPlayInterval: Duration(seconds: 3),
      autoPlayAnimationDuration: Duration(milliseconds: 800),
      autoPlayCurve: Curves.fastOutSlowIn,
      enlargeCenterPage: true,
      enlargeFactor: 0.3,
      onPageChanged: callbackFunction,
      scrollDirection: Axis.horizontal,
   )
 )

CarouselSlider 옵션 설명

  •  height
    • 캐러셀 슬라이더의 높이를 지정합니다.
    • aspectRatio를 설정한 높이에 맞게 비율을 재정의합니다.
  • aspectRatio
    • 가로 세로 비율을 지정합니다.
    • 기본값으로 가로:세로 비율은 16:9입니다.
    • height를 지정하지 않을 경우에 사용됩니다. height를 사용한 경우에는 따로 지정하지 않아도 됩니다.
  • viewportFraction
    • 각 페이지에 이미지가 차지하는 비율을 지정합니다.
    • 기본값으로 0.8 (80%)이며, 1.0 (100%)이 최대 크기입니다. (최솟값 0.1)
    • 예 1) 0.5를 설정하면 페이지에 메인 이미지가 중앙에서 50%를 차지하고 양쪽 여백이 각각 25%로 다음 이미지를 보여줍니다.
    • 예 2) 기본값인 0.8을 하면 남는 양쪽 여백이 각각 10%씩 다른 이미지를 보여주게 됩니다.

 

  • autoPlay
    • 한 번에 한 페이지씩 슬라이딩하여 자동으로 재생됩니다.
    • 기본값은 false입니다.
    • 슬라이드의 재생 빈도를 설정하려면 autoPlayInterval을 추가로 사용하여야 합니다.
  • autoPlayInterval
    • 슬라이드의 자동 재생 빈도를 설정합니다.
    • 기본값은 4초이며, Duration을 사용하여 설정해야 합니다.
  • onPageChanged
    • 슬라이더(뷰포트) 중앙의 메인 이미지가 변경될 때마다 호출되는 기능을 정의합니다.
    • 쉽게 말해서 페이지가 슬라이드 될 때마다 수행할 기능을 정의하면 됩니다.
  • items
    • 캐러셀 슬라이더에 표시될 기본 위젯을 초기화합니다.
    • 쉽게 말해서 캐러셀 슬라이더에 보일 객체들을 나열하면 됩니다.
    • 직접 나열하는 방법도 있지만, 포스팅에서는 변수를 리스트로 묶어서 .map 메서드를 이용하여 구성 요소를 반복 생성하는 방법을 사용합니다.

Carousel Slider 기본 기능 구현하기

캐러셀 슬라이더에 이미지가 나오고 슬라이드 할 수 있는 기본 기능을 구현해 봅니다.

 

// 파일 경로 리스트
final List<String> _list = [
    'assets/images/pi01.png',
    'assets/images/pi02.png'
  ];

Center(
  child: CarouselSlider(
    options: CarouselOptions(
      height: 300,
      aspectRatio: 16 / 9, // 화면 비율 default : 16/9
      viewportFraction: 0.8, // 페이지 차지 비율 default : 0.8
      autoPlay: false, // 자동 슬라이드 default : false
      autoPlayInterval: const Duration(seconds: 4), // 자동 슬라이드 주기 default : 4seconds
      onPageChanged: ((index, reason) {
        // 페이지가 슬라이드될 때의 기능 정의
      }),
    ),
    items: _list.map((String item) {
      return Image.asset(item,
          fit: BoxFit.contain); // 원본 비율을 유지한 채 가능한 최대로 맞춤
    }).toList(),
  ),
);

 

<전체 코드>

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

void main() {
  runApp(const MaterialApp(
    home: Scaffold(body: CarouselSliderPage()),
  ));
}

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

  @override
  State<CarouselSliderPage> createState() => _CarouselSliderPageState();
}

class _CarouselSliderPageState extends State<CarouselSliderPage> {
  final List<String> _list = [
    'assets/images/pi01.png',
    'assets/images/pi02.png'
  ];

  @override
  Widget build(BuildContext context) {
    return _imageSlider();
  }

  Widget _imageSlider() {
    return Center(
      child: CarouselSlider(
        options: CarouselOptions(
          height: 300,
          viewportFraction: 0.8, // 페이지 차지 비율 default : 0.8
          autoPlay: false, // 자동 슬라이드 default : false
          autoPlayInterval: const Duration(seconds: 4), // 자동 슬라이드 주기 default : 4seconds
          onPageChanged: ((index, reason) {
            // 페이지가 슬라이드될 때의 기능 정의
          }),
        ),
        items: _list.map((String item) {
          return Image.asset(item,
              fit: BoxFit.contain); // 원본 비율을 유지한 채 가능한 최대로 맞춤
        }).toList(),
      ),
    );
}

Carousel Indicator 구현하기

캐러셀 인디케이터는 페이지의 위치와 개수를 확인할 수 있는 표시 기능을 합니다.

인디케이터를 통해 형성되어 있는 전체 페이지 수를 확인할 수 있으며,

페이지 이동 시 현재의 페이지를 확인할 수 있도록 하이라이트 주는 기능과

특정 페이지의 인디케이터를 눌러서 해당 페이지로 바로 이동할 수 있는 기능을 구현하도록 합니다.

 

캐러셀 슬라이더에 인디케이터를 추가하기 위해 변수를 추가해 줍니다.

final CarouselController carouselController =
  CarouselController(); // 캐러셀 컨트롤러
int currentIndex = 0; // 캐러셀 인디케이터 인덱스

 

캐러셀 슬라이더 위젯에 컨트롤러를 연결해 주고

페이지가 슬라이드 될 때마다 현재 슬라이더의 인덱스가 업데이트될 수 있도록 해줍니다.

CarouselSlider(
  carouselController: carouselController,
  onPageChanged: ((index, reason) {
      // 페이지 슬라이드 시 인덱스 변경
      setState(() {
        currentIndex = index;
      });
    }),
  // 생략
);

 

인디케이터로 사용될 위젯을 생성합니다.

포스팅에서 사용한 인디케이터 요구사항 정의는 다음과 같습니다.

  • 위치 : 화면 아래의 중앙
  • 표시 : 원 도형 사용
  • 기본 표시색 : 회색, 불투명도 40%
  • 하이라이트 표시 : 현재 인덱스에 해당하는 경우 좀 더 진한색으로 표시
  • 하이라이트 표시 색 : 회색, 불투명도 90%
  • 인디케이터 터치 시 기능 : 캐러셀 슬라이더의 특정 페이지로 이동 (컨트롤러의 .animateToPage 메서드 이용)

<예시이미지>

 

<코드>

Row(
  mainAxisAlignment: MainAxisAlignment.center,
  children:
  // 리스트에 있는 값을 map 함수를 이용하여 MapEntry 객체 컬렉션으로 생성된 Iterable을 List로 변환
  _list.asMap().entries.map((entry) {
    return Align(
      alignment: Alignment.bottomCenter,
      child: GestureDetector(
        onTap: () {
          // 인디케이터에서 선택한 페이지로 이동
          carouselController.animateToPage(entry.key);
        },
        // 인디케이터에 표시될 위젯
        child: Container(
          width: 12.0,
          height: 12.0,
          margin: const EdgeInsets.symmetric(
              vertical: 45.0, horizontal: 4.0),
          decoration: BoxDecoration(
              shape: BoxShape.circle,
              color: const Color.fromARGB(255, 133, 133, 133)
                  .withOpacity(currentIndex == entry.key ? 0.9 : 0.4)), // 현재 인덱스일 경우 진하게 표시
        ),
      ),
    );
  }).toList(),
),

 

<전체 코드>

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

void main() {
  runApp(const MaterialApp(
    home: Scaffold(body: CarouselSliderPage()),
  ));
}

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

  @override
  State<CarouselSliderPage> createState() => _CarouselSliderPageState();
}

class _CarouselSliderPageState extends State<CarouselSliderPage> {
  final List<String> _list = [
    'assets/images/pi01.png',
    'assets/images/pi02.png'
  ];
  final CarouselController carouselController =
      CarouselController(); // 캐러셀 컨트롤러
  int currentIndex = 0; // 캐러셀 인디케이터 인덱스

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        CarouselSlider(
          carouselController: carouselController,
          options: CarouselOptions(
            height: double.infinity, // 최대 크기로 지정
            viewportFraction: 0.8, // 이미지 100% 비율로 보여줌
            autoPlay: true, // 자동 슬라이드 허용
            autoPlayInterval: const Duration(seconds: 5), // 5초마다 자동 슬라이드
            onPageChanged: ((index, reason) {
              // 페이지 슬라이드 시 인덱스 변경
              setState(() {
                currentIndex = index;
              });
            }),
          ),
          items: _list.map((String item) {
            return Image.asset(item,
                fit: BoxFit.contain); // 이미지를 화면에 맞게 조절, 가로 세로 비율 무시
          }).toList(),
        ),
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children:
              // 리스트에 있는 값을 map 함수를 이용하여 MapEntry 객체 컬렉션으로 생성된 Iterable을 List로 변환
              _list.asMap().entries.map((entry) {
            return Align(
              alignment: Alignment.bottomCenter,
              child: GestureDetector(
                onTap: () {
                  // 인디케이터에서 선택한 페이지로 이동
                  carouselController.animateToPage(entry.key);
                },
                // 인디케이터에 표시될 위젯
                child: Container(
                  width: 12.0,
                  height: 12.0,
                  margin: const EdgeInsets.symmetric(
                      vertical: 45.0, horizontal: 4.0),
                  decoration: BoxDecoration(
                      shape: BoxShape.circle,
                      color: const Color.fromARGB(255, 133, 133, 133)
                          .withOpacity(currentIndex == entry.key
                              ? 0.9
                              : 0.4)), // 현재 인덱스일 경우 진하게 표시
                ),
              ),
            );
          }).toList(),
        ),
      ],
    );
  }
}

 

<실행 화면>


참고

반응형