Анимация героя с помощью глобальной клавиши

#flutter

Вопрос:

Я использую класс Hero с каруселью, в которой есть смесь изображений и видео, и большая проблема, с которой я сталкиваюсь, заключается в том, что видео удаляются и повторно инициализируются при активации анимации героя, поэтому они снова загружаются и начинаются с начала видео в конце анимации героя.

Обертывание ребенка Героя в дерево с ключом с помощью глобального ключа устраняет проблему. Это прекрасно работает, насколько я могу судить! Проблема в том, что каждый раз, когда я активирую анимацию героя, я получаю повторяющееся исключение глобального ключа. Я полагаю, это связано с тем, что источник и место назначения Героя существуют одновременно во время анимации…

Я не вижу никаких видимых проблем, но я чувствую, что, возможно, было бы плохой идеей игнорировать это исключение. Я попытался последовать совету здесь, предоставив конструктор-заполнитель, который создает пустое поле размера, такого же размера, как у ребенка исходного героя, но, похоже, это не прояснило исключение.

Есть какие-нибудь идеи о том, что я мог бы сделать?

Обновлено с помощью кода. Это довольно большая проблема, поэтому краткий обзор кода: «product_slider» — это карусель, о которой идет речь. Он использует просмотр страниц для отображения изображений и видео (video.dart). Содержимое карусели завернуто в full_screen_widget, который обеспечивает функциональность героя, а также навигацию на/со страницы с полноэкранным героем назначения. Глобальный ключ также настраивается в full_screen_widget.

product_slider_controller.дротик

 import 'package:flutter/cupertino.dart';
import 'package:get/get.dart';
import 'package:patra_flutter/pb/customer/product.pb.dart';
import 'package:patra_flutter/widgets/video/video.dart';

class ProductSliderController extends GetxController {
  ProductSliderController(List<Medium> media)
      : mediaWidgets = _buildMediaList(media).obs;

  final pageController = PageController().obs;
  final isFullscreen = false.obs;
  final RxList<Widget> mediaWidgets;
}

List<Widget> _buildMediaList(List<Medium> media) {
  final mediaList = media.map((medium) {
    if (medium.contentType == ContentType.IMAGE) {
      return Image.network(medium.url);
    }
    return Video(medium.url);
  }).toList();

  return mediaList;
}
 

product_slider.dart

 import 'package:flutter/cupertino.dart';
import 'package:get/get.dart';
import 'package:patra_flutter/pages/product_detail/widgets/product_slider_controller.dart';
import 'package:patra_flutter/pb/customer/product.pb.dart';
import 'package:patra_flutter/widgets/full_screen_widget/full_screen_widget.dart';
import 'package:smooth_page_indicator/smooth_page_indicator.dart';

class ProductSlider extends StatelessWidget {
  final List<Medium> media;

  final ProductSliderController controller;

  ProductSlider({Key? key, required this.media})
      : controller = Get.put(ProductSliderController(media)),
        super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
        height: MediaQuery.of(context).size.width,
        child: FullScreenWidget(
          useGlobalKey: true,
          onChange: (isFullscreen) {
            controller.isFullscreen(isFullscreen);
          },
          child: Stack(
            alignment: Alignment.bottomCenter,
            children: [
              Obx(
                () => PageView(
                  allowImplicitScrolling: true,
                  controller: controller.pageController.value,
                  children: controller.mediaWidgets,
                ),
              ),
              Obx(
                () => Positioned(
                  bottom: controller.isFullscreen.value ? 30 : 15,
                  child: SmoothPageIndicator(
                    controller: controller.pageController.value,
                    count: media.length,
                    effect: WormEffect(
                      activeDotColor: CupertinoColors.black,
                      dotHeight: 6,
                      dotWidth: 6,
                    ),
                  ),
                ),
              )
            ],
          ),
          disposeLevel: DisposeLevel.Medium,
        ));
  }
}
 

full_screen_widget.dart

 library full_screen_image;

import 'package:flutter/material.dart';

class FullScreenWidget extends StatelessWidget {
  FullScreenWidget({
    required child,
    this.backgroundColor = Colors.black,
    this.backgroundIsTransparent = true,
    required this.disposeLevel,
    this.onChange,
    this.useGlobalKey = false,
  }) : child =
            useGlobalKey ? KeyedSubtree(child: child, key: GlobalKey()) : child;

  final Widget child;
  final Color backgroundColor;
  final bool backgroundIsTransparent;
  final DisposeLevel disposeLevel;
  final Function(bool fullScreen)? onChange;

  final bool useGlobalKey;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        Navigator.of(context, rootNavigator: true).push(PageRouteBuilder(
            opaque: false,
            barrierColor: backgroundIsTransparent
                ? Colors.white.withOpacity(0)
                : backgroundColor,
            pageBuilder: (BuildContext context, _, __) {
              return FullScreenPage(
                child: Hero(
                  child: child,
                  tag: "heroTest",
                ),
                backgroundColor: backgroundColor,
                backgroundIsTransparent: backgroundIsTransparent,
                disposeLevel: disposeLevel,
                onChange: onChange,
              );
            }));
        onChange?.call(true);
      },
      child: Hero(
        child: child,
        tag: "heroTest",
        placeholderBuilder: (_, size, ___) =>
            SizedBox(width: size.width, height: size.height),
      ),
    );
  }
}

enum DisposeLevel { High, Medium, Low }

class FullScreenPage extends StatefulWidget {
  FullScreenPage({
    required this.child,
    this.backgroundColor = Colors.black,
    this.backgroundIsTransparent = true,
    this.disposeLevel = DisposeLevel.Medium,
    this.onChange,
  });

  final Widget child;
  final Color backgroundColor;
  final bool backgroundIsTransparent;
  final DisposeLevel disposeLevel;
  final Function(bool fullScreen)? onChange;

  @override
  _FullScreenPageState createState() => _FullScreenPageState();
}

class _FullScreenPageState extends State<FullScreenPage> {
  double initialPositionY = 0;

  double currentPositionY = 0;

  double positionYDelta = 0;

  double opacity = 1;

  double disposeLimit = 150;

  Duration animationDuration = Duration.zero;

  @override
  void initState() {
    super.initState();
    setDisposeLevel();
    animationDuration = Duration.zero;
  }

  setDisposeLevel() {
    setState(() {
      if (widget.disposeLevel == DisposeLevel.High)
        disposeLimit = 300;
      else if (widget.disposeLevel == DisposeLevel.Medium)
        disposeLimit = 200;
      else
        disposeLimit = 100;
    });
  }

  void _startVerticalDrag(details) {
    setState(() {
      initialPositionY = details.globalPosition.dy;
    });
  }

  void _whileVerticalDrag(details) {
    setState(() {
      currentPositionY = details.globalPosition.dy;
      positionYDelta = currentPositionY - initialPositionY;
      setOpacity();
    });
  }

  setOpacity() {
    double tmp = positionYDelta < 0
        ? 1 - ((positionYDelta / 1000) * -1)
        : 1 - (positionYDelta / 1000);

    if (tmp > 1)
      opacity = 1;
    else if (tmp < 0)
      opacity = 0;
    else
      opacity = tmp;

    if (positionYDelta > disposeLimit || positionYDelta < -disposeLimit) {
      opacity = 0.5;
    }
  }

  _endVerticalDrag(DragEndDetails details) {
    if (positionYDelta > disposeLimit || positionYDelta < -disposeLimit) {
      this.widget.onChange?.call(false);
      Navigator.of(context).pop();
    } else {
      setState(() {
        animationDuration = Duration(milliseconds: 300);
        opacity = 1;
        positionYDelta = 0;
      });

      Future.delayed(animationDuration).then((_) {
        setState(() {
          animationDuration = Duration.zero;
        });
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: widget.backgroundIsTransparent
          ? Colors.transparent
          : widget.backgroundColor,
      body: GestureDetector(
        onVerticalDragStart: (details) => _startVerticalDrag(details),
        onVerticalDragUpdate: (details) => _whileVerticalDrag(details),
        onVerticalDragEnd: (details) => _endVerticalDrag(details),
        child: Container(
          color: widget.backgroundColor.withOpacity(opacity),
          constraints: BoxConstraints.expand(
            height: MediaQuery.of(context).size.height,
          ),
          child: Stack(
            children: <Widget>[
              AnimatedPositioned(
                duration: animationDuration,
                curve: Curves.fastOutSlowIn,
                top: 0   positionYDelta,
                bottom: 0 - positionYDelta,
                left: 0,
                right: 0,
                child: widget.child,
              )
            ],
          ),
        ),
      ),
    );
  }
}
 

видео.дротик

 import 'package:chewie/chewie.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
import 'package:visibility_detector/visibility_detector.dart';
import 'package:uuid/uuid.dart';

class Video extends StatefulWidget {
  final String url;
  final id = Uuid().v4();

  Video(this.url, {Key? key}) : super(key: key);

  @override
  _VideoState createState() => _VideoState();
}

class _VideoState extends State<Video> {
  late VideoPlayerController _videoPlayerController;
  ChewieController? _chewieController;

  @override
  initState() {
    super.initState();
    initializePlayer();
  }

  @override
  void dispose() {
    _videoPlayerController.dispose();
    _chewieController?.dispose();
    super.dispose();
  }

  Future<void> initializePlayer() async {
    _videoPlayerController = VideoPlayerController.network(this.widget.url);

    await _videoPlayerController.initialize();

    _videoPlayerController.setVolume(0.0);
    _createChewieController();
    if (this.mounted) setState(() {});
  }

  void _createChewieController() {
    _chewieController = ChewieController(
      videoPlayerController: _videoPlayerController,
      looping: true,
      showControls: false,
    );
  }

  @override
  Widget build(BuildContext context) {
    return VisibilityDetector(
        key: Key(this.widget.id),
        child: Material(
            child: _chewieController != null amp;amp;
                    _chewieController!.videoPlayerController.value.isInitialized
                ? Chewie(
                    controller: _chewieController!,
                  )
                : Center(child: const CircularProgressIndicator())),
        onVisibilityChanged: (visibilityInfo) {
          if (!this.mounted) return;
          if (_videoPlayerController.value.isPlaying amp;amp;
              visibilityInfo.visibleFraction < 0.5)
            _videoPlayerController.pause();
          else if (!_videoPlayerController.value.isPlaying amp;amp;
              visibilityInfo.visibleFraction >= 0.5) {
            _videoPlayerController.play();
          }
        });
  }
}
 

Комментарии:

1. Можете ли вы включить фрагмент кода, который будет воспроизводить ту же проблему?