https://github.com/qoridhc/Flutter_Project
GitHub - qoridhc/Flutter_Project
Contribute to qoridhc/Flutter_Project development by creating an account on GitHub.
github.com
플러그인 설치
- Firebase_Core : https://pub.dev/packages/firebase_core/install
- Cloud_firestore : https://pub.dev/packages/cloud_firestore
- Carousel_slider : https://pub.dev/packages/carousel_slider
하단 네비게이션바 만들기
import 'package:flutter/material.dart';
import 'package:mini_netflix_clone_app/screen/home_screen.dart';
import 'widget/bottom_bar.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'CloneFlix',
theme: ThemeData(
brightness: Brightness.dark,
primaryColor: Colors.black,
),
home: DefaultTabController(
length: 4, // 네비게이션 바 메뉴 갯수
child: Scaffold(
body: TabBarView(
physics: NeverScrollableScrollPhysics(), // 스크롤로 화면 넘기기 제한
children: [
HomeScreen(),
Container(
child: Center(
child: Text('search'),
),
),
Container(
child: Center(
child: Text('save'),
),
),
MoreScreen(),
],
),
bottomNavigationBar: Bottom(),
),
),
);
}
}
DefaultTabController위젯의 child로 Scaffold를 넣어주고 body에 TabBarVIew를 넣어주고 네이게이션바를 클릭 했을때 이동할 스크린을 각각 넣어준다.
Scaffold의 bottomNavigationBar속성에 네이게이션바의 탭들을 TabBar 위젯에 만들어 넣어준다. TabBarView에 넣은 스크린들과 bottomNavigationBar에 넣어준 탭들이 1:1로 매칭에 된다.
만약 home을 누르면 homeScreen(), search를 누르면 Text(’search’)를 담은 스크린이 각각 출력되는식
네비게이션바를 커스텀해주기위해 따로 Bottom이라는 클래스로 빼서 커스텀한 TabBar를 넣어준다.
import 'package:flutter/material.dart';
class Bottom extends StatelessWidget {
const Bottom({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
color: Colors.black,
child: Container(
height: 50,
child: TabBar(
labelColor: Colors.white,
unselectedLabelColor: Colors.white60,
indicatorColor: Colors.transparent,
tabs: [
Tab(
icon: Icon(
Icons.home,
size: 18,
),
child: Text(
'홈',
style: TextStyle(fontSize: 9),
),
),Tab(
icon: Icon(
Icons.search,
size: 18,
),
child: Text(
'검색',
style: TextStyle(fontSize: 9),
),
),Tab(
icon: Icon(
Icons.save_alt,
size: 18,
),
child: Text(
'저장한 컨텐츠',
style: TextStyle(fontSize: 9),
),
),Tab(
icon: Icon(
Icons.list,
size: 18,
),
child: Text(
'더보기',
style: TextStyle(fontSize: 9),
),
),
]),
),
);
}
}
Movie 데이터 모델링
class Movie {
final String title;
final String keyword;
final String poster;
final bool like;
Movie.fromMap(Map<String, dynamic> map)
: title = map['title'],
keyword = map['keyword'],
poster = map['poster'],
like = map['like'] as bool;
@override
String toString() => "Movie<$title:$keyword>";
}
영화 데이터를 받을 변수들을 선언하고 map형태로 전달받은 영화 데이터를 생성자를통해 저장.
class _HomeScreenState extends State<HomeScreen> {
List<Movie> movies = [
Movie.fromMap(
{
'title' : '사랑의 불시착',
'keyword' : '사랑/로맨스/판타지',
'poster' : 'test_movie_1.png',
'like' : false
}
),Movie.fromMap(
{
'title' : '사랑의 불시착',
'keyword' : '사랑/로맨스/판타지',
'poster' : 'test_movie_1.png',
'like' : false
}
),Movie.fromMap(
{
'title' : '사랑의 불시착',
'keyword' : '사랑/로맨스/판타지',
'poster' : 'test_movie_1.png',
'like' : false
}
),Movie.fromMap(
{
'title' : '사랑의 불시착',
'keyword' : '사랑/로맨스/판타지',
'poster' : 'test_movie_1.png',
'like' : false
}
),
];
}
데이터를 관리해야하므로 HomeScreen을 Stateful위젯으로 바꿔주고 더미영화데이터를 생성해준다. 이후에 실제 데이터를 연동할때 바꿔줌.
캐루셀 슬라이더만들기
- 자동 스크롤 슬라이딩을 할 수 있는 위젯
class CarouselImage extends StatefulWidget {
final List<Movie> movies;
CarouselImage({
required this.movies,
Key? key,
}) : super(key: key);
@override
State<CarouselImage> createState() => _CarouselImageState();
}
class _CarouselImageState extends State<CarouselImage> {
List<Movie> movies = [];
List<Widget> images = [];
List<String> keywords = [];
List<bool> likes = [];
int _currentPage = 0;
late String _currentKeyword;
@override
void initState() {
super.initState();
movies = widget.movies;
images = movies.map((m) => Image.asset('./images/' + m.poster)).toList();
keywords = movies.map((m) => m.keyword).cast<String>().toList();
likes = movies.map((m) => m.like).cast<bool>().toList();
_currentKeyword = keywords[0];
}
homeScreen에서 관리하는 더미데이터(movies)를 생성자로 받아온뒤 map을 돌면서 각각 데이터들을 List안에 넣어준다.
CarouselSlider(
items: images,
options: CarouselOptions(
onPageChanged: (index, reason) {
setState(
() {
_currentPage = index;
_currentKeyword = keywords[_currentPage];
},
);
},
),
),
- items : 슬라이드하면서 보여줄 이미지들을 넣어준다
- onPageChanged : 화면을 슬라이드하여 페이지가 변경되면 실행, setState로 현재 페이지값과 키워드값을 변경된 페이지값으로 리빌드 시켜준다.
인디케이터 만들기
List<Widget> makeIndicator(List list, int _currentPage) {
List<Widget> results = [];
for (var i = 0; i < list.length; i++) {
results.add(Container(
width: 8,
height: 8,
margin: EdgeInsets.symmetric(vertical: 10, horizontal: 2),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: _currentPage == i
? Color.fromRGBO(255, 255, 255, 0.9)
: Color.fromRGBO(255, 255, 255, 0.4)),
));
}
return results;
}
현재 보고있는 페이지위치를 알려주는 인디케이터를 만들어주는 makeIndicator함수 생성
파라미터로 들어온 list의 갯수만큼 반복문을 돌면서 인디케이터를 생성해준다. 만약 현재 스크린에서 보고있는 페이지인경우 Color를 다르게줘서 현재 보고있는 페이지가 몇번째인지 알게해준다.
FireBase 연동하기
FireBase가 Flutter와 연동하기를 원하더라도 Flutter의 runApp() 이 실행되어 플러터 엔진이 초기화 되기 전까지는 접근을 할 수 없다. 따라서 플러터 코어 엔진을 먼저 실행 시켜주어야 하는데 그 코드가 바로 WidgetsFlutterBinding.ensureInitialized()
메인 메소드에서 비동기메소드를 실행하려면 WidgetsFlutterBinding.ensureInitialized()를 무조건 먼저 불러와줘야함
그다음에 Firebase 초기화 메소드 Firebase.initializeApp()실행
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(const MyApp());
}
안해주면 에러발생
데이터 모델링 수정
import 'package:cloud_firestore/cloud_firestore.dart';
class Movie {
final String title;
final String keyword;
final String poster;
final bool like;
final DocumentReference? reference;
Movie.fromMap(Map<String, dynamic> map, {this.reference})
: title = map['title'],
keyword = map['keyword'],
poster = map['poster'],
like = map['like'] as bool;
Movie.fromSnapshot(DocumentSnapshot snapshot)
: this.fromMap(snapshot.data as Map<String, dynamic> , reference : snapshot.reference);
@override
String toString() => "Movie<$title:$keyword>";
}
- snapshot으로 데이터를 가져올수 있도록 fromShapshot 함수 생성
- DocumentReference : 실제 firebase firesotre에 있는 데이터 칼럼을 참조할 수 있는 링크 → CRUD기능을 간단하게 처리 가능
데이터 받아오기
class _HomeScreenState extends State<HomeScreen> {
FirebaseFirestore firebaseFirestore = FirebaseFirestore.instance;
late Stream<QuerySnapshot> streamData;
@override
void initState() {
// TODO: implement initState
super.initState();
streamData = firebaseFirestore
.collection('movie')
.snapshots(); // 파이어 베이스 콘솔에서 입력한 컬렉션 이름
}
}
FireBase에 생성해둔 movie라는 이름의 컬렉션을 불러와 Stream 변수에 저장
Widget _fetchData(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance.collection('movie').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return LinearProgressIndicator();
}
return _buildBody(context, snapshot.data!.docs);
});
}
Widget _buildBody(BuildContext context, List<DocumentSnapshot> snapshot) {
List<Movie> movies = snapshot.map((e) => Movie.fromSnapshot(e)).toList();
return ListView(
children: [
Stack(
children: [
CarouselImage(
movies: movies,
),
TopBar(),
],
),
CircleSlider(
movies: movies,
),
BoxSlider(
movies: movies,
)
],
);
}
StreamBuilder를 활용하여 데이터가 들어오면 _buildBody위젯을 실행해서 Body 그려줌
_buidBody 위젯은 받아온 데이터를 List형태로 변환시킨뒤 각 위젯들에 넘겨준다
DB 데이터 업데이트 하기
InkWell(
onTap: () {
setState(() {
like = !like;
widget.movie.reference!.update({'like': like});
});
},
찜하기 버튼 위젯을 눌러 onTap()이 실행되면 setState()를 통해 like상태를 반대로 바꿔주고 바뀐 like값을 movie컬랙션의 like값으로 업데이트 시켜준다.
찜하기 버튼을 누르면 like 상태가 실시간으로 업데이트되는것을 볼 수 있음
링크 달기
import 'package:flutter_linkify/flutter_linkify.dart';
import 'package:url_launcher/url_launcher.dart';
Linkify(
onOpen: (link) async {
if (await canLaunchUrl(Uri.parse(link.url))) {
await launchUrl(Uri.parse(link.url));
}
},
text: "<https://github.com/qoridhc/>",
)
linkify + urlLauncher를 활용해서 깃허브 링크 구현
코드 출처
'플러터 공부 > 프로젝트기록' 카테고리의 다른 글
플러터 - 구글API 지도앱 만들기 (0) | 2023.06.13 |
---|---|
플러터 - 상태관리(Provider) (0) | 2023.06.12 |