
플러터에서 별도의 파일로 분리된 StatefulWidget은 서로의 State에 관여할 수 없다
만약 공통적으로 사용하려는 State가 필요한 경우 provider라이브러리를 사용한다.
https://pub.dev/packages?q=provider



설치방법을 참고하여 라이브러리를 설치하면 된다.

pubspec.yaml 파일을 열어준다.

dependencies: 안에 'provider: ^6.1.2'를 넣어준다

상단의 'Pub get'을 눌러준다

설치가 완료된 것을 확인한다.
구체적인 사용법은 아래 클론 코딩 내용에서 확인
import 'package:flutter/material.dart';
class LionFlixApp extends StatefulWidget {
const LionFlixApp({super.key});
State<LionFlixApp> createState() => _LionFlixAppState();
}
class _LionFlixAppState extends State<LionFlixApp> {
Widget build(BuildContext context) {
return const Placeholder();
}
}
void main(){
runApp(LionFlixApp());
}
return MaterialApp(
title: 'LionFlix',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue,
// 전체적인 어플의 테마를 어두운 테마로 설정한다.
brightness: Brightness.dark
),
useMaterial3: true,
),
home: Center(child: Text('안녕하세요'),),
);
home: Scaffold(
),
widget 폴더를 만들어준다.
lib/widget
widget 폴더에 dart 파일을 생성해준다.
lib/widget/main_bottom_navigation_bar.dart
StatefulWidget으로 작성해준다.
import 'package:flutter/material.dart';
class MainBottomNavigationBar extends StatefulWidget {
const MainBottomNavigationBar({super.key});
State<MainBottomNavigationBar> createState() => _MainBottomNavigationBarState();
}
class _MainBottomNavigationBarState extends State<MainBottomNavigationBar> {
Widget build(BuildContext context) {
return const Placeholder();
}
}
Widget build(BuildContext context) {
// 하단에 배치될 네비게이션 바
return NavigationBar(
// 네비게이션 바를 구성하는 아이콘들
destinations: [
]
);
}
home: Scaffold(
bottomNavigationBar: MainBottomNavigationBar(),
),
// 하단에 배치될 네비게이션 바
return NavigationBar(
// 네비게이션 바를 구성하는 아이콘들
destinations: [
NavigationDestination(
// 평상시의 아이콘
icon: Icon(Icons.home_outlined),
// 눌러졌을 때의 아이콘
selectedIcon: Icon(Icons.home),
// 아이콘 하단에 표시될 문자열
label: "Home"
),
NavigationDestination(
icon: Icon(Icons.search_outlined),
selectedIcon: Icon(Icons.search),
label: "Search"
),
NavigationDestination(
icon: Icon(Icons.save_alt_outlined),
selectedIcon: Icon(Icons.save_alt),
label: "saved"
),
NavigationDestination(
icon: Icon(Icons.list_outlined),
selectedIcon: Icon(Icons.list),
label: "more"
),
],
);
class _MainBottomNavigationBarState extends State<MainBottomNavigationBar> {
// 네비게이션 바에서 선택되어 있는 아이콘 메뉴의 순서값
int tabPageIndex = 0;
// 네비게이션 바에 배치된 아이콘 메뉴들 중 어떤 것을 선택할 것인가..
selectedIndex: tabPageIndex,
// 하단 메뉴를 눌렀을 때
// value: 사용자가 누른 메뉴 항목의 순서값
onDestinationSelected: (value){
setState(() {
tabPageIndex = value;
});
},
// indicator 색상
indicatorColor: Colors.white12,
화면 파일들을 작성할 폴더를 생성한다.
lib/screen
screen 폴더에 첫 화면 파일을 생성한다.
lib/screen/main_screen.dart
StatefulWidget으로 작성해준다.
import 'package:flutter/material.dart';
class MainScreen extends StatefulWidget {
const MainScreen({super.key});
State<MainScreen> createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
Widget build(BuildContext context) {
return const Placeholder();
}
}
home: Scaffold(
bottomNavigationBar: MainBottomNavigationBar(),
body: MainScreen(),
),
https://pub.dev/ 사이트에 들어간다.
provider로 검색하여 라이브러리 페이지로 들어간다.
provider 라이브러리 설치 문자열을 복사한다(provider: ^6.1.2)
프로젝트에 있는 pubspec.yaml 파일을 열어준다.
dependencies: 로 검색하여 위치를 찾아준다.
dependencies: 안에 라이브러리 설치 문자열을 입력해준다.
Pub get을 눌러준다.
provider 폴더를 만들어준다.
TabPageIndexProvider를 사용하는 코드로 변경한다.
main_bottom_navigation_bar.dart 파일을 수정한다.
class _MainBottomNavigationBarState extends State<MainBottomNavigationBar> {
// 네비게이션 바에서 선택되어 있는 아이콘 메뉴의 순서값
// int tabPageIndex = 0;
Widget build(BuildContext context) {
// 네비게이션 바에서 선택되어 있는 아이콘 메뉴의 순서값을 관리하는 Provider
// listen : true로 설정하면 Provider를 이용할 때 화면 자체가 다시 만들어진다.
// false로 설정하면 화면 전체가 다시 만들어지지 않는다.
var tabPageIndexProvider = Provider.of<TabPageIndexProvider>(context, listen: false);
// 네비게이션 바에 배치된 아이콘 메뉴들 중 어떤 것을 선택할 것인가..
selectedIndex: tabPageIndexProvider.currentPageIndex,
// 하단 메뉴를 눌렀을 때
// value: 사용자가 누른 메뉴 항목의 순서값
onDestinationSelected: (value){
setState(() {
tabPageIndexProvider.setCurrentPageIndex(value);
});
},
Widget build(BuildContext context) {
// Provider
var tabPageIndexProvider = Provider.of<TabPageIndexProvider>(context, listen: false);
// Provider
var tabPageIndexProvider = Provider.of<TabPageIndexProvider>(context, listen: false);
// 보여줄 화면의 순서값
var currentPageIndex = tabPageIndexProvider.currentPageIndex;
// 보여줄 화면의 순서값
var currentPageIndex = tabPageIndexProvider.currentPageIndex;
return Container(
child: [
Center(child: Text("Home")),
Center(child: Text("Search")),
Center(child: Text("Saved")),
Center(child: Text("More")),
][currentPageIndex],
);
// 프로바이더의 리스너를 등록해준다.
// 프로바이더에서 notifyListeners(); 를 호출하면 동작한다.
tabPageIndexProvider.addListener(() {
// 화면의 순서값을 변경한다.
setState(() {
currentPageIndex = tabPageIndexProvider.currentPageIndex;
});
});
// 화면 요소에서 프로바이더를 사용할 수 있도록 설정해준다.
home: ChangeNotifierProvider(
create: (BuildContext context) => TabPageIndexProvider(),
child: Scaffold(
bottomNavigationBar: MainBottomNavigationBar(),
body: MainScreen(),
),
),
왜 이미지태그의 길이 조정이 안되는지 의문이다.
lib 안에 assets 폴더를 만들고 그 안에 images 폴더를 만든다
lib/assets/images
사용할 이미지들을 images 폴더에 복사해 넣어준다.
pubspec.yaml 파일 작업
assets:
- lib/assets/images/
dependencies:
carousel_slider: ^4.2.1
screen 폴더에 home_screen.dart 파일을 만들어준다.
screen/home_screen.dart
Scaffold 구조의 기본 코드를 작성해준다.
import 'package:flutter/material.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
Widget build(BuildContext context) {
return Scaffold(
);
}
}
return Container(
child: [
HomeScreen(),
Center(child: Text("Search")),
Center(child: Text("Saved")),
Center(child: Text("More")),
][currentPageIndex],
);
import 'package:flutter/material.dart';
class HomeTopAppBar extends StatefulWidget {
const HomeTopAppBar({super.key});
State<HomeTopAppBar> createState() => _HomeTopAppBarState();
}
class _HomeTopAppBarState extends State<HomeTopAppBar> {
Widget build(BuildContext context) {
return const Placeholder();
}
}
class _HomeScreenState extends State<HomeScreen> {
Widget build(BuildContext context) {
return Scaffold(
appBar: HomeTopAppBar(),
);
}
}
Image.asset : 이미지를 가져와 보여준다.
Image.asset의 fit : 이미지를 어떻게 채워줄 것인지 설정한다.
BoxFit.fill : 원본 비율을 무시한 채 꽉 채워준다.
BoxFit.contain : 원본 비율이 유지되는 최대 크기. 이미지가 모두 보이게 한다.
BoxFit.cover : 원본 비율이 유지되는 최대 크기. 긴 쪽이 짤린다.
BoxFit.fitWidth : 원본 비율이 유지되는 가로 최대 길이. 높이가 짤릴 수 있다.
BoxFit.fitHeight : 원본 비율이 유지되는 세로 최대 길이. 가로가 짤릴 수 있다.
BoxFit.none : 크기를 조정하지 않으며 이미지가 짤릴 수 있다.
title을 구성해준다.
// 앱바 구성
return AppBar(
title: Row(
children: [
Image.asset(
'lib/assets/images/youtube_logo.png',
fit: BoxFit.contain, height: 25,
),
Padding(padding: EdgeInsets.only(right: 10)),
Text("LionFlix"),
],
),
);
// 앱바 우측에 나타나는 메뉴들
actions: [
// tv 메뉴
IconButton(
onPressed: (){},
icon: Icon(Icons.tv)
),
// 영화 메뉴
IconButton(
onPressed: (){},
icon: Icon(Icons.movie)
),
// 찜 메뉴
IconButton(
onPressed: (){},
icon: Icon(Icons.favorite)
),
],
widget 폴더에 home_carousel_slider.dart 파일을 만든다.
StatefulWidget 기본 코드를 작성해준다.
import 'package:flutter/material.dart';
class HomeCarouselSlider extends StatefulWidget {
const HomeCarouselSlider({super.key});
State<HomeCarouselSlider> createState() => _HomeCarouselSliderState();
}
class _HomeCarouselSliderState extends State<HomeCarouselSlider> {
Widget build(BuildContext context) {
return const Placeholder();
}
}
class _HomeScreenState extends State<HomeScreen> {
Widget build(BuildContext context) {
return Scaffold(
appBar: HomeTopAppBar(),
body: HomeCarouselSlider(),
);
}
}
return Container(
child: Column(
children: [
],
),
);
// 포스터 이미지들
var images = [
Image.asset('lib/assets/images/movie1.jpg'),
Image.asset('lib/assets/images/movie2.jpg'),
Image.asset('lib/assets/images/movie3.jpg'),
Image.asset('lib/assets/images/movie4.jpg'),
Image.asset('lib/assets/images/movie5.jpg'),
Image.asset('lib/assets/images/movie6.jpg'),
Image.asset('lib/assets/images/movie7.jpg'),
Image.asset('lib/assets/images/movie8.jpg'),
Image.asset('lib/assets/images/movie9.jpg'),
Image.asset('lib/assets/images/movie10.jpg'),
];
// 회전목마
CarouselSlider(
items: images,
options: CarouselOptions(
// 회전목마가 이미지를 보여주는 영역안에서 얼만큼의 크기로 이미지를
// 보여줄지에 대한 비율. 1.0이 최대
viewportFraction: 1.0,
// 자동 슬라이드 활성화 여부
autoPlay: true,
// 자동 슬라이드 간격
autoPlayInterval: Duration(seconds: 5),
)
),
// 회전 목마에서 보여주고 있는 이미지의 순서 값
var imagePosition = 0;
onPageChanged: (index, reason) {
// 현재 보여지는 이미지의 순서 값을 설정해준다.
setState(() {
imagePosition = index;
});
},
// 영화 제목들
var movieTitles = [
"영화1", "영화2", "영화3", "영화4", "영화5",
"영화6", "영화7", "영화8", "영화9", "영화10",
];
Padding(padding: EdgeInsets.only(top: 10)),
// 영화 제목
Text(
movieTitles[imagePosition],
style: TextStyle(fontSize: 20),
),
// 버튼들
Row(
),
// 버튼들
Row(
children: [
// 찜
Column(
children: [
movieLike[imagePosition]
? IconButton(onPressed: () {}, icon: Icon(Icons.check))
: IconButton(onPressed: () {}, icon: Icon(Icons.add)),
Text("내가 찜한 콘텐츠", style: TextStyle(fontSize: 11)),
],
),
],
),
// 재생 버튼
TextButton(
onPressed: () {},
child: Row(
children: [
Icon(
Icons.play_arrow,
color: Colors.black,
),
Padding(padding: EdgeInsets.all(3)),
Text("재생", style: TextStyle(color: Colors.black)),
],
),
// 버튼의 모양
style: ButtonStyle(
backgroundColor: MaterialStatePropertyAll(Colors.white),
shape: MaterialStatePropertyAll(
RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(5.0)),
),
),
),
),
// 정보
Column(
children: [
IconButton(
onPressed: () {},
icon: Icon(Icons.info)
),
Text("정보", style: TextStyle(fontSize: 11))
],
)
Row(
// 배치되는 뷰들의 간격을 설정해준다.
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
pubspec.yaml에 인디케이터 라이브러리를 추가해준다.
smooth_page_indicator: ^1.1.0
인디케이터를 추가해준다
// 인디케이터
AnimatedSmoothIndicator(
activeIndex: imagePosition,
count: images.length,
effect: WormEffect(
dotWidth: 5,
dotHeight: 5,
),
),
