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