opt: 视频详情页appbar下滑动画
This commit is contained in:
@ -1,7 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:ui';
|
|
||||||
|
|
||||||
import 'package:extended_image/extended_image.dart';
|
|
||||||
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
|
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
|
||||||
import 'package:flutter_meedu_media_kit/meedu_player.dart';
|
import 'package:flutter_meedu_media_kit/meedu_player.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@ -11,9 +9,10 @@ import 'package:pilipala/pages/video/detail/reply/index.dart';
|
|||||||
import 'package:pilipala/pages/video/detail/controller.dart';
|
import 'package:pilipala/pages/video/detail/controller.dart';
|
||||||
import 'package:pilipala/pages/video/detail/introduction/index.dart';
|
import 'package:pilipala/pages/video/detail/introduction/index.dart';
|
||||||
import 'package:pilipala/pages/video/detail/related/index.dart';
|
import 'package:pilipala/pages/video/detail/related/index.dart';
|
||||||
import 'package:pilipala/pages/video/detail/replyReply/index.dart';
|
|
||||||
import 'package:wakelock/wakelock.dart';
|
import 'package:wakelock/wakelock.dart';
|
||||||
|
|
||||||
|
import 'widgets/app_bar.dart';
|
||||||
|
|
||||||
class VideoDetailPage extends StatefulWidget {
|
class VideoDetailPage extends StatefulWidget {
|
||||||
const VideoDetailPage({Key? key}) : super(key: key);
|
const VideoDetailPage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@ -29,7 +28,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
Get.put(VideoDetailController(), tag: Get.arguments['heroTag']);
|
Get.put(VideoDetailController(), tag: Get.arguments['heroTag']);
|
||||||
MeeduPlayerController? _meeduPlayerController;
|
MeeduPlayerController? _meeduPlayerController;
|
||||||
final ScrollController _extendNestCtr = ScrollController();
|
final ScrollController _extendNestCtr = ScrollController();
|
||||||
late AnimationController animationController;
|
late StreamController<double> appbarStream;
|
||||||
|
|
||||||
StreamSubscription? _playerEventSubs;
|
StreamSubscription? _playerEventSubs;
|
||||||
bool isPlay = false;
|
bool isPlay = false;
|
||||||
@ -62,19 +61,12 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
animationController = AnimationController(
|
appbarStream = StreamController<double>();
|
||||||
vsync: this, duration: const Duration(milliseconds: 600));
|
|
||||||
|
|
||||||
_extendNestCtr.addListener(
|
_extendNestCtr.addListener(
|
||||||
() {
|
() {
|
||||||
double offset = _extendNestCtr.position.pixels;
|
double offset = _extendNestCtr.position.pixels;
|
||||||
if (offset > doubleOffset) {
|
appbarStream.add(offset);
|
||||||
animationController.forward();
|
|
||||||
} else {
|
|
||||||
animationController.reverse();
|
|
||||||
}
|
|
||||||
doubleOffset = offset;
|
|
||||||
setState(() {});
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -145,30 +137,6 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
bottom: false,
|
bottom: false,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Positioned(
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
child: Obx(
|
|
||||||
() => NetworkImgLayer(
|
|
||||||
type: 'emote',
|
|
||||||
src: videoDetailController.bgCover.value,
|
|
||||||
width: Get.size.width,
|
|
||||||
height: videoHeight,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Positioned.fill(
|
|
||||||
child: BackdropFilter(
|
|
||||||
filter: ImageFilter.blur(sigmaX: 100, sigmaY: 100), //可以看源码
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color:
|
|
||||||
Theme.of(context).colorScheme.background.withOpacity(0.1),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
resizeToAvoidBottomInset: false,
|
resizeToAvoidBottomInset: false,
|
||||||
key: videoDetailController.scaffoldKey,
|
key: videoDetailController.scaffoldKey,
|
||||||
@ -185,8 +153,8 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
scrolledUnderElevation: 0,
|
scrolledUnderElevation: 0,
|
||||||
forceElevated: innerBoxIsScrolled,
|
forceElevated: innerBoxIsScrolled,
|
||||||
expandedHeight: videoHeight,
|
expandedHeight: videoHeight,
|
||||||
backgroundColor: Colors.transparent,
|
// backgroundColor: Colors.transparent,
|
||||||
// backgroundColor: Theme.of(context).colorScheme.background,
|
backgroundColor: Theme.of(context).colorScheme.background,
|
||||||
flexibleSpace: FlexibleSpaceBar(
|
flexibleSpace: FlexibleSpaceBar(
|
||||||
background: Padding(
|
background: Padding(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
@ -320,52 +288,17 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
// 播放完成/暂停播放
|
// 播放完成/暂停播放
|
||||||
Positioned(
|
StreamBuilder(
|
||||||
top: -statusBarHeight +
|
stream: appbarStream.stream,
|
||||||
(doubleOffset / (videoHeight - kToolbarHeight)) *
|
initialData: 0,
|
||||||
(kToolbarHeight - 9),
|
builder: ((context, snapshot) {
|
||||||
left: 0,
|
return ScrollAppBar(
|
||||||
right: 0,
|
snapshot.data!.toDouble(),
|
||||||
child: Opacity(
|
continuePlay,
|
||||||
opacity: doubleOffset / (videoHeight - kToolbarHeight),
|
playerStatus,
|
||||||
child: Container(
|
);
|
||||||
height: statusBarHeight + kToolbarHeight,
|
}),
|
||||||
color: Theme.of(context).colorScheme.background,
|
)
|
||||||
padding: EdgeInsets.only(top: statusBarHeight),
|
|
||||||
child: AppBar(
|
|
||||||
primary: false,
|
|
||||||
elevation: 0,
|
|
||||||
scrolledUnderElevation: 0,
|
|
||||||
centerTitle: true,
|
|
||||||
title: TextButton(
|
|
||||||
onPressed: () => continuePlay(),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.play_arrow_rounded),
|
|
||||||
Text(
|
|
||||||
playerStatus == PlayerStatus.paused
|
|
||||||
? '继续播放'
|
|
||||||
: playerStatus == PlayerStatus.completed
|
|
||||||
? '重新播放'
|
|
||||||
: '播放中',
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {},
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.share,
|
|
||||||
size: 20,
|
|
||||||
)),
|
|
||||||
const SizedBox(width: 12)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
64
lib/pages/video/detail/widgets/app_bar.dart
Normal file
64
lib/pages/video/detail/widgets/app_bar.dart
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_meedu_media_kit/meedu_player.dart';
|
||||||
|
|
||||||
|
class ScrollAppBar extends StatelessWidget {
|
||||||
|
final double scrollVal;
|
||||||
|
Function callback;
|
||||||
|
final PlayerStatus playerStatus;
|
||||||
|
|
||||||
|
ScrollAppBar(
|
||||||
|
this.scrollVal,
|
||||||
|
this.callback,
|
||||||
|
this.playerStatus,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final double statusBarHeight = MediaQuery.of(context).padding.top;
|
||||||
|
final videoHeight = MediaQuery.of(context).size.width * 9 / 16;
|
||||||
|
return Positioned(
|
||||||
|
top: -videoHeight + scrollVal + kToolbarHeight + 0.5,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: Opacity(
|
||||||
|
opacity: scrollVal / (videoHeight - kToolbarHeight),
|
||||||
|
child: Container(
|
||||||
|
height: statusBarHeight + kToolbarHeight,
|
||||||
|
color: Theme.of(context).colorScheme.background,
|
||||||
|
padding: EdgeInsets.only(top: statusBarHeight),
|
||||||
|
child: AppBar(
|
||||||
|
primary: false,
|
||||||
|
elevation: 0,
|
||||||
|
scrolledUnderElevation: 0,
|
||||||
|
centerTitle: true,
|
||||||
|
title: TextButton(
|
||||||
|
onPressed: () => callback(),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.play_arrow_rounded),
|
||||||
|
Text(
|
||||||
|
playerStatus == PlayerStatus.paused
|
||||||
|
? '继续播放'
|
||||||
|
: playerStatus == PlayerStatus.completed
|
||||||
|
? '重新播放'
|
||||||
|
: '播放中',
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {},
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.share,
|
||||||
|
size: 20,
|
||||||
|
)),
|
||||||
|
const SizedBox(width: 12)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user