Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: variable front page height #73

Merged
merged 16 commits into from
Feb 22, 2021
Merged
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## [0.5.0]

* **BREAKING**: requires Dart >= 2.3
* `BackdropScaffold`: added `frontLayerActiveFactor` [#73][]

[#73]: https://github.com/fluttercommunity/backdrop/pull/73

## [0.4.7] - 28 October 2020

* `BackdropScaffold`: added `scaffoldKey` [https://github.com/fluttercommunity/backdrop/pull/64]
Expand Down
170 changes: 109 additions & 61 deletions lib/scaffold.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,39 +79,41 @@ class BackdropScaffold extends StatefulWidget {
/// The widget that is shown on the front layer.
final Widget frontLayer;

/// The widget that is shown as sub-header on top of the front layer.
/// The widget shown at the top of the front layer.
///
/// When the front layer is minimized (back layer revealed), the entire [subHeader] will be visible unless
/// [headerHeight] is specified.
final Widget subHeader;

/// This boolean flag keeps subHeader active when [backLayer] is visible. Defaults to true.
/// If true, the scrim applied to the front layer while minimized (back layer revealed) will not
/// cover the [subHeader]. See [frontLayerScrim].
///
/// Defaults to true.
final bool subHeaderAlwaysActive;

/// Deprecated. Use [BackdropAppBar.actions].
///
/// Actions passed to [AppBar.actions].
final List<Widget> actions;

/// Defines the height of the front layer when it is in the opened state.
///
/// This height value is only applied, if [stickyFrontLayer]
/// is set to `false` or if [stickyFrontLayer] is set to `true` and the back
/// layer's height is less than
/// [BoxConstraints.biggest.height]-[headerHeight].
/// Defines the front layer's height when minimized (back layer revealed)).
/// Defaults to measured height of [subHeader] if provided, else 32.
///
/// [headerHeight] is interpreted as the height of the front layer's visible
/// part, when being opened. The back layer's height corresponds to
/// [BoxConstraints.biggest.height]-[headerHeight].
/// To automatically use the difference of the screen height and back layer's height,
/// see [stickyFrontLayer]. Note [headerHeight] is ignored if it is less
/// than the available size and [stickyFrontLayer] is `true`.
///
///
/// If [subHeader] is defined then height of subHeader otherwise defaults to 32.0.
/// To vary the front layer's height when active (back layer concealed),
/// see [frontLayerActiveFactor].
final double headerHeight;

/// Defines the [BorderRadius] applied to the front layer.
///
/// Defaults to
/// ```dart
/// const BorderRadius.only(
/// topLeft: Radius.circular(16.0),
/// topRight: Radius.circular(16.0),
/// topLeft: Radius.circular(16),
/// topRight: Radius.circular(16),
/// )
/// ```
final BorderRadius frontLayerBorderRadius;
Expand All @@ -124,10 +126,12 @@ class BackdropScaffold extends StatefulWidget {
/// Defaults to [BackdropIconPosition.leading].
final BackdropIconPosition iconPosition;

/// A flag indicating whether the front layer should stick to the height of
/// the back layer when being opened.
///
/// Indicates the front layer should minimize to the back layer's
/// bottom edge. Otherwise, see [headerHeight] to specify this value.
/// Defaults to `false`.
///
/// This parameter has no effect if the back layer's measured height
/// is greater than or equal to the screen height.
final bool stickyFrontLayer;

/// The animation curve passed to [Tween.animate]() when triggering
Expand All @@ -146,27 +150,41 @@ class BackdropScaffold extends StatefulWidget {
/// If null, the color is handled automatically according to the theme.
final Color frontLayerBackgroundColor;

/// Defines the color for the inactive front layer.
/// A default opacity of 0.7 is applied to the passed color.
/// See [inactiveOverlayOpacity].
/// Fraction of the available height the front layer will occupy,
/// when active (back layer concealed). Clamped to (0, 1).
///
/// Defaults to `const Color(0xFFEEEEEE)`.
/// Note the front layer will not fully conceal the back layer when
/// this value is less than 1. A scrim will cover the
/// partially concealed back layer; see [backLayerScrim].
///
/// Defaults to 1.
final double frontLayerActiveFactor;

/// Deprecated. Use [frontLayerScrim] instead.
@Deprecated('Replace with frontLayerScrim.')
final Color inactiveOverlayColor;

/// The opacity value applied to [inactiveOverlayColor] when used on the
/// inactive layer.
/// Deprecated. Use [frontLayerScrim] instead.
@Deprecated('Replace with frontLayerScrim. Use Color#withOpacity, or pass'
'the opacity value in the Color constructor.')
final double inactiveOverlayOpacity;

/// Defines the scrim color for the front layer when minimized
/// (revealing the back layer) and animating. Defaults to [Colors.white70].
///
/// The inactive layer overlays the [frontLayer] when the back layer is
/// revealed and the front layer is in a concealed state.
/// See [subHeaderAlwaysActive] to leave the [subHeader] outside the scrim.
final Color frontLayerScrim;

/// Defines the scrim color for the back layer when partially concealed
/// (with [frontLayerActiveFactor] less than 1).
///
/// Must be a value between `0.0` and `1.0`.
/// Defaults to `0.7`.
final double inactiveOverlayOpacity;
/// Defaults to [Colors.black54].
final Color backLayerScrim;

/// Will be called when [backLayer] have been concealed.
/// Will be called when [backLayer] has been concealed.
final VoidCallback onBackLayerConcealed;

/// Will be called when [backLayer] have been revealed.
/// Will be called when [backLayer] has been revealed.
final VoidCallback onBackLayerRevealed;

// ------------- PROPERTIES TAKEN OVER FROM SCAFFOLD ------------- //
Expand Down Expand Up @@ -261,8 +279,8 @@ class BackdropScaffold extends StatefulWidget {
this.actions = const <Widget>[],
this.headerHeight,
this.frontLayerBorderRadius = const BorderRadius.only(
topLeft: Radius.circular(16.0),
topRight: Radius.circular(16.0),
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
@Deprecated("Replace by use of BackdropAppBar. See BackdropAppBar.leading"
"and BackdropAppBar.automaticallyImplyLeading."
Expand All @@ -271,9 +289,14 @@ class BackdropScaffold extends StatefulWidget {
this.stickyFrontLayer = false,
this.animationCurve = Curves.easeInOut,
this.frontLayerBackgroundColor,
double frontLayerActiveFactor = 1,
this.backLayerBackgroundColor,
this.inactiveOverlayColor = const Color(0xFFEEEEEE),
this.inactiveOverlayOpacity = 0.7,
@Deprecated('See frontLayerScrim. This was deprecated after v0.4.7.')
this.inactiveOverlayColor = const Color(0xFFEEEEEE),
@Deprecated('See frontLayerScrim. This was deprecated after v0.4.7.')
this.inactiveOverlayOpacity = 0.7,
this.frontLayerScrim = Colors.white70,
this.backLayerScrim = Colors.black54,
this.onBackLayerConcealed,
this.onBackLayerRevealed,
this.scaffoldKey,
Expand All @@ -297,6 +320,7 @@ class BackdropScaffold extends StatefulWidget {
this.drawerEnableOpenDragGesture = true,
this.endDrawerEnableOpenDragGesture = true,
}) : assert(inactiveOverlayOpacity >= 0.0 && inactiveOverlayOpacity <= 1.0),
frontLayerActiveFactor = frontLayerActiveFactor.clamp(0, 1).toDouble(),
super(key: key);

@override
Expand All @@ -315,6 +339,7 @@ class BackdropScaffoldState extends State<BackdropScaffold>
with SingleTickerProviderStateMixin {
bool _shouldDisposeController = false;
AnimationController _controller;
ColorTween _backLayerScrimColorTween;

/// Key for accessing the [ScaffoldState] of [BackdropScaffold]'s internally
/// used [Scaffold].
Expand All @@ -327,7 +352,7 @@ class BackdropScaffoldState extends State<BackdropScaffold>
/// Defaults to
/// ```dart
/// AnimationController(
/// vsync: this, duration: Duration(milliseconds: 200), value: 1.0)
/// vsync: this, duration: Duration(milliseconds: 200), value: 1)
/// ```
AnimationController get controller => _controller;

Expand All @@ -339,9 +364,12 @@ class BackdropScaffoldState extends State<BackdropScaffold>
// initialize _controller
_controller = widget.controller ??
AnimationController(
vsync: this, duration: Duration(milliseconds: 200), value: 1.0);
vsync: this, duration: Duration(milliseconds: 200), value: 1);
if (widget.controller == null) _shouldDisposeController = true;

_backLayerScrimColorTween = _buildBackLayerScrimColorTween();

_controller.addListener(() => setState(() {}));
daadu marked this conversation as resolved.
Show resolved Hide resolved
_controller.addStatusListener((status) {
setState(() {
// This is intentionally left empty. The state change itself takes
Expand All @@ -353,6 +381,14 @@ class BackdropScaffoldState extends State<BackdropScaffold>
});
}

@override
void didUpdateWidget(covariant BackdropScaffold oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.backLayerScrim != widget.backLayerScrim) {
_backLayerScrimColorTween = _buildBackLayerScrimColorTween();
}
}

@override
void dispose() {
super.dispose();
Expand All @@ -361,12 +397,12 @@ class BackdropScaffoldState extends State<BackdropScaffold>

/// Deprecated. Use [isBackLayerConcealed] instead.
///
/// Wether the back layer is concealed or not.
/// Whether the back layer is concealed or not.
@Deprecated("Replace by the use of `isBackLayerConcealed`."
"This feature was deprecated after v0.3.2.")
bool get isTopPanelVisible => isBackLayerConcealed;

/// Wether the back layer is concealed or not.
/// Whether the back layer is concealed or not.
bool get isBackLayerConcealed =>
controller.status == AnimationStatus.completed ||
controller.status == AnimationStatus.forward;
Expand Down Expand Up @@ -407,7 +443,7 @@ class BackdropScaffoldState extends State<BackdropScaffold>
/// Animates the back layer to the "revealed" state.
void revealBackLayer() {
if (isBackLayerConcealed) {
controller.animateBack(-1.0);
controller.animateBack(-1);
widget.onBackLayerRevealed?.call();
}
}
Expand All @@ -422,7 +458,7 @@ class BackdropScaffoldState extends State<BackdropScaffold>
/// Animates the back layer to the "concealed" state.
void concealBackLayer() {
if (isBackLayerRevealed) {
controller.animateTo(1.0);
controller.animateTo(1);
widget.onBackLayerConcealed?.call();
}
}
Expand All @@ -431,8 +467,8 @@ class BackdropScaffoldState extends State<BackdropScaffold>
// if defined then use it
if (widget.headerHeight != null) return widget.headerHeight;

// if no subHeader then 32.0
if (widget.subHeader == null) return 32.0;
// if no subHeader then 32
if (widget.subHeader == null) return 32;

// if subHeader then height of subHeader
return _subHeaderHeight;
Expand All @@ -441,21 +477,20 @@ class BackdropScaffoldState extends State<BackdropScaffold>
Animation<RelativeRect> _getPanelAnimation(
BuildContext context, BoxConstraints constraints) {
double backPanelHeight, frontPanelHeight;

if (widget.stickyFrontLayer &&
_backPanelHeight < constraints.biggest.height - _headerHeight) {
final availableHeight = constraints.biggest.height - _headerHeight;
if (widget.stickyFrontLayer && _backPanelHeight < availableHeight) {
// height is adapted to the height of the back panel
backPanelHeight = _backPanelHeight;
frontPanelHeight = -_backPanelHeight;
} else {
// height is set to fixed value defined in widget.headerHeight
final height = constraints.biggest.height;
backPanelHeight = height - _headerHeight;
backPanelHeight = availableHeight;
frontPanelHeight = -backPanelHeight;
}
return RelativeRectTween(
begin: RelativeRect.fromLTRB(0.0, backPanelHeight, 0.0, frontPanelHeight),
end: RelativeRect.fromLTRB(0.0, 0.0, 0.0, 0.0),
begin: RelativeRect.fromLTRB(0, backPanelHeight, 0, frontPanelHeight),
end: RelativeRect.fromLTRB(
0, availableHeight * (1 - widget.frontLayerActiveFactor), 0, 0),
).animate(CurvedAnimation(
parent: controller,
curve: widget.animationCurve,
Expand All @@ -466,21 +501,21 @@ class BackdropScaffoldState extends State<BackdropScaffold>
return Offstage(
offstage: controller.status == AnimationStatus.completed,
child: FadeTransition(
opacity: Tween(begin: 1.0, end: 0.0).animate(controller),
opacity: Tween<double>(begin: 1, end: 0).animate(controller),
child: GestureDetector(
onTap: () => fling(),
behavior: HitTestBehavior.opaque,
child: Column(
children: <Widget>[
// if subHeaderAlwaysActive then do not apply inactiveOverlayColor for area with _headerHeight
// if subHeaderAlwaysActive then do not apply frontLayerScrim for area with _subHeaderHeight
widget.subHeader != null && widget.subHeaderAlwaysActive
? Container(height: _headerHeight)
? Container(height: _subHeaderHeight)
daadu marked this conversation as resolved.
Show resolved Hide resolved
: Container(),
Expanded(
child: Container(
color: widget.inactiveOverlayColor
.withOpacity(widget.inactiveOverlayOpacity),
),
color: widget.frontLayerScrim ??
widget.inactiveOverlayColor
.withOpacity(widget.inactiveOverlayOpacity)),
),
],
),
Expand All @@ -493,8 +528,8 @@ class BackdropScaffoldState extends State<BackdropScaffold>
return FocusScope(
canRequestFocus: isBackLayerRevealed,
child: Material(
color: this.widget.backLayerBackgroundColor ??
Theme.of(context).primaryColor,
color:
widget.backLayerBackgroundColor ?? Theme.of(context).primaryColor,
child: Column(
children: <Widget>[
Flexible(
Expand All @@ -512,8 +547,8 @@ class BackdropScaffoldState extends State<BackdropScaffold>

Widget _buildFrontPanel(BuildContext context) {
return Material(
color: this.widget.frontLayerBackgroundColor,
elevation: 1.0,
color: widget.frontLayerBackgroundColor,
elevation: 1,
borderRadius: widget.frontLayerBorderRadius,
child: ClipRRect(
borderRadius: widget.frontLayerBorderRadius,
Expand Down Expand Up @@ -551,6 +586,11 @@ class BackdropScaffoldState extends State<BackdropScaffold>
return true;
}

ColorTween _buildBackLayerScrimColorTween() => ColorTween(
begin: Colors.transparent,
end: widget.backLayerScrim,
);

Widget _buildBody(BuildContext context) {
return WillPopScope(
onWillPop: () => _willPopCallback(context),
Expand All @@ -562,7 +602,7 @@ class BackdropScaffoldState extends State<BackdropScaffold>
actions: widget.iconPosition == BackdropIconPosition.action
? <Widget>[BackdropToggleButton()] + widget.actions
: widget.actions,
elevation: 0.0,
elevation: 0,
leading: widget.iconPosition == BackdropIconPosition.leading
? BackdropToggleButton()
: null,
Expand All @@ -574,6 +614,7 @@ class BackdropScaffoldState extends State<BackdropScaffold>
fit: StackFit.expand,
children: <Widget>[
_buildBackPanel(),
if (_hasBackLayerScrim) _buildBackLayerScrim(),
PositionedTransition(
rect: _getPanelAnimation(context, constraints),
child: _buildFrontPanel(context),
Expand Down Expand Up @@ -605,6 +646,13 @@ class BackdropScaffoldState extends State<BackdropScaffold>
);
}

Container _buildBackLayerScrim() => Container(
color: _backLayerScrimColorTween.evaluate(controller),
height: _backPanelHeight);

bool get _hasBackLayerScrim =>
isBackLayerConcealed && widget.frontLayerActiveFactor < 1;

@override
Widget build(BuildContext context) {
return Backdrop(
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ authors:
- Felix Wortmann <https://github.com/felixwortmann>

environment:
sdk: ">=1.19.0 <3.0.0"
sdk: ">=2.3.0 <3.0.0"

dependencies:
flutter:
Expand Down