Dart & Flutter
Flutter__007
HELLICAT
2024. 8. 21. 19:59
반응형
Infinity Scolling
PageView
PageView.builder
개발과정에서 asset을 사용하는 경우
pubspec.yaml에서 assets부분에 명시해주어야한다.
import 'package:flutter/material.dart';
import 'package:tictok_clone/screens/home/video_post.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final PageController _pageController = PageController();
final _scrollDuration = Duration(milliseconds: 200);
final _scrollCurves = Curves.linear;
int _videoLength = 4;
void _onPageChanged(int page) {
_pageController.animateToPage(page,
duration: _scrollDuration, curve: _scrollCurves);
if (page == _videoLength - 1) {
_videoLength = _videoLength + 3;
}
setState(() {});
}
void _onVideoFinished() {
_pageController.nextPage(duration: _scrollDuration, curve: _scrollCurves);
}
@override
void dispose() {
_pageController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return PageView.builder(
controller: _pageController,
itemCount: _videoLength,
itemBuilder: (context, index) =>
VideoPost(onVideoFinished: _onVideoFinished),
scrollDirection: Axis.vertical,
onPageChanged: _onPageChanged,
);
}
}
video_player
https://pub.dev/packages/video_player
video_player | Flutter package
Flutter plugin for displaying inline video with other Flutter widgets on Android, iOS, and web.
pub.dev
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
class VideoPost extends StatefulWidget {
final Function onVideoFinished;
const VideoPost({
super.key,
required this.onVideoFinished,
});
@override
State<VideoPost> createState() => _VideoPostState();
}
class _VideoPostState extends State<VideoPost> {
final VideoPlayerController _videoPlayerController =
VideoPlayerController.asset('./assets/videos/sample.mp4');
void _onVideoChange() {
if (_videoPlayerController.value.isInitialized) {
if (_videoPlayerController.value.duration ==
_videoPlayerController.value.position) {
widget.onVideoFinished();
}
}
}
void _initVideoPlayer() async {
await _videoPlayerController.initialize();
_videoPlayerController.play();
setState(() {});
_videoPlayerController.addListener(_onVideoChange);
}
@override
void initState() {
super.initState();
_initVideoPlayer();
}
@override
void dispose() {
_videoPlayerController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
Positioned.fill(
child: _videoPlayerController.value.isInitialized
? VideoPlayer(_videoPlayerController)
: Container(
color: Color.fromARGB(255, 161, 244, 255),
))
],
);
}
}
visibility detector
IgnorePointer
AnimationController
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:tictok_clone/constants/sizes.dart';
import 'package:video_player/video_player.dart';
import 'package:visibility_detector/visibility_detector.dart';
class VideoPost extends StatefulWidget {
final Function onVideoFinished;
final int index;
const VideoPost({
super.key,
required this.onVideoFinished,
required this.index,
});
@override
State<VideoPost> createState() => _VideoPostState();
}
class _VideoPostState extends State<VideoPost>
with SingleTickerProviderStateMixin {
bool _isPaused = false;
final VideoPlayerController _videoPlayerController =
VideoPlayerController.asset('./assets/videos/sample.mp4');
late final AnimationController _animationController;
final Duration _animatedDuration = const Duration(milliseconds: 300);
void _onVideoChange() {
if (_videoPlayerController.value.isInitialized) {
if (_videoPlayerController.value.duration ==
_videoPlayerController.value.position) {
widget.onVideoFinished();
}
}
}
void _initVideoPlayer() async {
await _videoPlayerController.initialize();
setState(() {});
_videoPlayerController.addListener(_onVideoChange);
}
@override
void initState() {
super.initState();
_initVideoPlayer();
_animationController = AnimationController(
vsync: this,
lowerBound: 1.0,
upperBound: 1.5,
value: 1.3,
duration: _animatedDuration,
);
}
@override
void dispose() {
_videoPlayerController.dispose();
super.dispose();
}
void _onVisibleChaged(VisibilityInfo info) {
if (info.visibleFraction == 1 && !_videoPlayerController.value.isPlaying) {
_videoPlayerController.play();
}
}
void _onTogglePlay() {
if (_videoPlayerController.value.isPlaying) {
_videoPlayerController.pause();
_animationController.reverse();
} else {
_videoPlayerController.play();
_animationController.forward();
}
setState(() {
_isPaused = !_isPaused;
});
}
@override
Widget build(BuildContext context) {
return VisibilityDetector(
key: Key("${widget.index}"),
onVisibilityChanged: _onVisibleChaged,
child: Stack(
children: [
Positioned.fill(
child: _videoPlayerController.value.isInitialized
? VideoPlayer(_videoPlayerController)
: Container(
color: Color.fromARGB(255, 161, 244, 255),
)),
Positioned.fill(
child: GestureDetector(
onTap: _onTogglePlay,
)),
Positioned.fill(
child: IgnorePointer(
child: Center(
child: AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Transform.scale(
scale: _animationController.value,
child: child,
);
},
child: Transform.scale(
scale: _animationController.value,
child: AnimatedOpacity(
opacity: _isPaused ? 1 : 0,
duration: _animatedDuration,
child: FaIcon(
FontAwesomeIcons.play,
color: Colors.white,
size: Sizes.size52,
),
),
),
),
),
))
],
),
);
}
}
SingleTickerProviderStateMixin
Vsync는 offscreen 애니메이션의 불필요한 리소스를 막아준다. 즉 위젯이 안보일 때 애니메이션이 작동하지 않도록 해준다.
위젯이 화면에 보일 때만 Ticker를 제공함
하나의 애니메이션을 사용한다면 SingleTickerProviderStateMixin을 사용하고 여러 애니메이션을 사용한다면 TickerProviderStateMixin을 사용한다.
RefreshIndicator
return RefreshIndicator(
edgeOffset: 10,
displacement: 80,
onRefresh: _onRefresh,
child: PageView.builder(
728x90