#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. Можете ли вы включить фрагмент кода, который будет воспроизводить ту же проблему?