mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-07-12 12:20:13 +08:00
139
lib/common/widgets/expandable.dart
Normal file
139
lib/common/widgets/expandable.dart
Normal file
@@ -0,0 +1,139 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ExpandablePanel extends StatelessWidget {
|
||||
final bool expand;
|
||||
|
||||
final Widget collapsed;
|
||||
|
||||
final Widget expanded;
|
||||
|
||||
const ExpandablePanel({
|
||||
super.key,
|
||||
required this.expand,
|
||||
required this.collapsed,
|
||||
required this.expanded,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _AnimatedCross(
|
||||
alignment: .topLeft,
|
||||
firstChild: collapsed,
|
||||
secondChild: expanded,
|
||||
sizeCurve: Curves.linear,
|
||||
crossFadeState: expand ? .showSecond : .showFirst,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// ref [AnimatedCrossFade]
|
||||
class _AnimatedCross extends StatefulWidget {
|
||||
const _AnimatedCross({
|
||||
required this.firstChild,
|
||||
required this.secondChild,
|
||||
this.sizeCurve = Curves.linear,
|
||||
this.alignment = Alignment.topCenter,
|
||||
required this.crossFadeState,
|
||||
required this.duration,
|
||||
});
|
||||
|
||||
final Widget firstChild;
|
||||
|
||||
final Widget secondChild;
|
||||
|
||||
final CrossFadeState crossFadeState;
|
||||
|
||||
final Duration duration;
|
||||
|
||||
final Curve sizeCurve;
|
||||
|
||||
final AlignmentGeometry alignment;
|
||||
|
||||
@override
|
||||
State<_AnimatedCross> createState() => _AnimatedCrossState();
|
||||
}
|
||||
|
||||
class _AnimatedCrossState extends State<_AnimatedCross> {
|
||||
late bool _showFirst;
|
||||
AnimationStatus? _status;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
switch (widget.crossFadeState) {
|
||||
case .showFirst:
|
||||
_showFirst = true;
|
||||
case .showSecond:
|
||||
_showFirst = false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(_AnimatedCross oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.crossFadeState != oldWidget.crossFadeState) {
|
||||
switch (widget.crossFadeState) {
|
||||
case .showFirst:
|
||||
_status = .reverse;
|
||||
case .showSecond:
|
||||
_status = .forward;
|
||||
}
|
||||
_showFirst = false;
|
||||
}
|
||||
}
|
||||
|
||||
void _onEnd() {
|
||||
if (_status == .reverse) {
|
||||
_showFirst = true;
|
||||
}
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
Widget get firstChild => _widgetBuilder(_showFirst, widget.firstChild);
|
||||
|
||||
Widget get secondChild => _widgetBuilder(!_showFirst, widget.secondChild);
|
||||
|
||||
static Widget _widgetBuilder(bool visible, Widget child) =>
|
||||
Opacity(opacity: visible ? 1.0 : 0.0, child: child);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const Key kFirstChildKey = ValueKey<CrossFadeState>(.showFirst);
|
||||
const Key kSecondChildKey = ValueKey<CrossFadeState>(.showSecond);
|
||||
|
||||
final Key topKey;
|
||||
Widget topChild;
|
||||
final Key bottomKey;
|
||||
Widget bottomChild;
|
||||
|
||||
switch (widget.crossFadeState) {
|
||||
case .showFirst:
|
||||
topKey = kFirstChildKey;
|
||||
topChild = firstChild;
|
||||
bottomKey = kSecondChildKey;
|
||||
bottomChild = secondChild;
|
||||
case .showSecond:
|
||||
topKey = kSecondChildKey;
|
||||
topChild = secondChild;
|
||||
bottomKey = kFirstChildKey;
|
||||
bottomChild = firstChild;
|
||||
}
|
||||
|
||||
return ClipRect(
|
||||
child: AnimatedSize(
|
||||
clipBehavior: .none,
|
||||
alignment: widget.alignment,
|
||||
duration: widget.duration,
|
||||
curve: widget.sizeCurve,
|
||||
onEnd: _onEnd,
|
||||
child: AnimatedCrossFade.defaultLayoutBuilder(
|
||||
topChild,
|
||||
topKey,
|
||||
bottomChild,
|
||||
bottomKey,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -430,49 +430,51 @@ class _PageViewState<T extends HorizontalDragGestureRecognizer>
|
||||
widget.scrollBehavior?.getScrollPhysics(context),
|
||||
);
|
||||
|
||||
return NotificationListener<ScrollNotification>(
|
||||
onNotification: (ScrollNotification notification) {
|
||||
if (notification.depth == 0 &&
|
||||
widget.onPageChanged != null &&
|
||||
notification is ScrollUpdateNotification) {
|
||||
final metrics = notification.metrics as PageMetrics;
|
||||
final int currentPage = metrics.page!.round();
|
||||
if (currentPage != _lastReportedPage) {
|
||||
_lastReportedPage = currentPage;
|
||||
widget.onPageChanged!(currentPage);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
final child = Scrollable<T>(
|
||||
dragStartBehavior: widget.dragStartBehavior,
|
||||
axisDirection: axisDirection,
|
||||
controller: _controller,
|
||||
physics: physics,
|
||||
restorationId: widget.restorationId,
|
||||
hitTestBehavior: widget.hitTestBehavior,
|
||||
scrollBehavior:
|
||||
widget.scrollBehavior ??
|
||||
ScrollConfiguration.of(context).copyWith(scrollbars: false),
|
||||
viewportBuilder: (BuildContext context, ViewportOffset position) {
|
||||
return Viewport(
|
||||
scrollCacheExtent: widget.scrollCacheExtent,
|
||||
axisDirection: axisDirection,
|
||||
offset: position,
|
||||
clipBehavior: widget.clipBehavior,
|
||||
slivers: <Widget>[
|
||||
SliverFillViewport(
|
||||
viewportFraction: _controller.viewportFraction,
|
||||
delegate: widget.childrenDelegate,
|
||||
padEnds: widget.padEnds,
|
||||
allowImplicitScrolling: widget.allowImplicitScrolling,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
child: Scrollable<T>(
|
||||
dragStartBehavior: widget.dragStartBehavior,
|
||||
axisDirection: axisDirection,
|
||||
controller: _controller,
|
||||
physics: physics,
|
||||
restorationId: widget.restorationId,
|
||||
hitTestBehavior: widget.hitTestBehavior,
|
||||
scrollBehavior:
|
||||
widget.scrollBehavior ??
|
||||
ScrollConfiguration.of(context).copyWith(scrollbars: false),
|
||||
viewportBuilder: (BuildContext context, ViewportOffset position) {
|
||||
return Viewport(
|
||||
scrollCacheExtent: widget.scrollCacheExtent,
|
||||
axisDirection: axisDirection,
|
||||
offset: position,
|
||||
clipBehavior: widget.clipBehavior,
|
||||
slivers: <Widget>[
|
||||
SliverFillViewport(
|
||||
viewportFraction: _controller.viewportFraction,
|
||||
delegate: widget.childrenDelegate,
|
||||
padEnds: widget.padEnds,
|
||||
allowImplicitScrolling: widget.allowImplicitScrolling,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
horizontalDragGestureRecognizer: widget.horizontalDragGestureRecognizer,
|
||||
),
|
||||
horizontalDragGestureRecognizer: widget.horizontalDragGestureRecognizer,
|
||||
);
|
||||
if (widget.onPageChanged != null) {
|
||||
return NotificationListener<ScrollEndNotification>(
|
||||
onNotification: (notification) {
|
||||
if (notification.depth == 0) {
|
||||
final metrics = notification.metrics as PageMetrics;
|
||||
final int currentPage = metrics.page!.round();
|
||||
if (currentPage != _lastReportedPage) {
|
||||
_lastReportedPage = currentPage;
|
||||
widget.onPageChanged!(currentPage);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -78,7 +78,7 @@ class RenderSliverToBoxWithVisibilityAdapter extends RenderSliverToBoxAdapter {
|
||||
if (_visible != visible) {
|
||||
_visible = visible;
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) => onVisibilityChanged(visible),
|
||||
(_) => onVisibilityChanged(!visible),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user