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(),
),
],
);
}
}
<실행 화면>