-
Notifications
You must be signed in to change notification settings - Fork 129
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
Prevent Scaffold rebuilds #38
Comments
Same problem. Every time I open or close the drawer, everything starts rebuilding like crazy :( This happens in example_1.dart as well, so it should not be a faulty integration on my part. |
@StevenKek I need to check it out. I'll keep you posted |
@Dn-a Thank you for the quick follow-up. This issue is particularly noticeable in our app due to a use of a WebView. Rebuilding WebView is just incredibly resource intensive. |
seem as me |
I am having the same issue, this should be fixed as soon as possible |
same here :( |
remove setState on animate controller may help to solve this problem, using AnimatedBuilder to make sure scaffold not rebuild for heavy widget tree. https://medium.com/flutter-community/flutter-laggy-animations-how-not-to-setstate-f2dd9873b8fc This might be helpful. |
i already modified this bug for you all need. hope this can be helpful @Dn-a @d3fkon @StepanMynarik @DirtyNative // InnerDrawer is based on Drawer.
// The source code of the Drawer has been re-adapted for Inner Drawer.
// more details:
// https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/material/drawer.dart
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';
/// Signature for the callback that's called when a [InnerDrawer] is
/// opened or closed.
typedef InnerDrawerCallback = void Function(bool isOpened);
/// Signature for when a pointer that is in contact with the screen and moves to the right or left
/// values between 1 and 0
typedef InnerDragUpdateCallback = void Function(
double value, InnerDrawerDirection direction);
/// The possible position of a [InnerDrawer].
enum InnerDrawerDirection {
start,
end,
}
/// Animation type of a [InnerDrawer].
enum InnerDrawerAnimation {
static,
linear,
quadratic,
}
//width before initState
const double _kWidth = 400;
const double _kMinFlingVelocity = 365.0;
const double _kEdgeDragWidth = 25.0;
const Duration _kBaseSettleDuration = Duration(milliseconds: 200);
class InnerDrawer extends StatefulWidget {
const InnerDrawer(
{GlobalKey key,
this.leftChild,
this.rightChild,
@required this.scaffold,
this.leftOffset = 0.4,
this.rightOffset = 0.4,
this.leftScale = 1,
this.rightScale = 1,
this.offset,
this.scale,
this.proportionalChildArea = true,
this.borderRadius = 0,
this.onTapClose = false,
this.tapScaffoldEnabled = false,
this.swipe = true,
this.duration,
this.velocity = 1,
this.boxShadow,
this.colorTransitionChild,
this.colorTransitionScaffold,
this.leftAnimationType = InnerDrawerAnimation.static,
this.rightAnimationType = InnerDrawerAnimation.static,
this.backgroundDecoration,
this.innerDrawerCallback,
this.onDragUpdate})
: assert(leftChild != null || rightChild != null),
assert(scaffold != null),
super(key: key);
/// Left child
final Widget leftChild;
/// Right child
final Widget rightChild;
/// A Scaffold is generally used but you are free to use other widgets
final Widget scaffold;
/// DEPRECATED:
/// Use `offset` field. Will be removed in 0.6.0
///
/// Left offset of [InnerDrawer] width; (default 0.4)
final double leftOffset;
/// DEPRECATED:
/// Use `offset` field. Will be removed in 0.6.0
///
/// Right offset of [InnerDrawer] width; default 0.4
final double rightOffset;
/// When the [InnerDrawer] is open, it's possible to set the offset of each of the four cardinal directions
final IDOffset offset;
/// DEPRECATED:
/// Use `scale` field. Will be removed in 0.6.0
///
/// When the left [InnerDrawer] is open
/// Values between 1 and 0. (default 1)
final double leftScale;
/// DEPRECATED:
/// Use `scale` field. Will be removed in 0.6.0
///
/// When the right [InnerDrawer] is open
/// Values between 1 and 0. (default 1)
final double rightScale;
/// When the [InnerDrawer] is open to the left or to the right
/// values between 1 and 0. (default 1)
final IDOffset scale;
/// The proportionalChild Area = true dynamically sets the width based on the selected offset.
/// On false it leaves the width at 100% of the screen
final bool proportionalChildArea;
/// edge radius when opening the scaffold - (defalut 0)
final double borderRadius;
/// Closes the open scaffold
final bool tapScaffoldEnabled;
/// Closes the open scaffold
final bool onTapClose;
/// activate or deactivate the swipe. NOTE: when deactivate, onTap Close is implicitly activated
final bool swipe;
/// duration animation controller
final Duration duration;
/// possibility to set the opening and closing velocity
final double velocity;
/// BoxShadow of scaffold open
final List<BoxShadow> boxShadow;
///Color of gradient background
final Color colorTransitionChild;
///Color of gradient background
final Color colorTransitionScaffold;
/// Static or Linear or Quadratic
final InnerDrawerAnimation leftAnimationType;
/// Static or Linear or Quadratic
final InnerDrawerAnimation rightAnimationType;
/// Color of the main background
final BoxDecoration backgroundDecoration;
/// Optional callback that is called when a [InnerDrawer] is open or closed.
final InnerDrawerCallback innerDrawerCallback;
/// when a pointer that is in contact with the screen and moves to the right or left
final InnerDragUpdateCallback onDragUpdate;
@override
InnerDrawerState createState() => InnerDrawerState();
}
class InnerDrawerState extends State<InnerDrawer>
with SingleTickerProviderStateMixin {
ColorTween _colorTransitionChild =
ColorTween(begin: Colors.transparent, end: Colors.black54);
ColorTween _colorTransitionScaffold =
ColorTween(begin: Colors.black54, end: Colors.transparent);
double _initWidth = _kWidth;
Orientation _orientation = Orientation.portrait;
InnerDrawerDirection _position;
@override
void initState() {
_position = widget.leftChild != null
? InnerDrawerDirection.start
: InnerDrawerDirection.end;
_controller = AnimationController(
value: 1,
duration: widget.duration ?? _kBaseSettleDuration,
vsync: this)
..addListener(_animationChanged)
..addStatusListener(_animationStatusChanged);
super.initState();
}
@override
void dispose() {
_historyEntry?.remove();
_controller.dispose();
_focusScopeNode.dispose();
super.dispose();
}
void _animationChanged() {
if (widget.colorTransitionChild != null)
_colorTransitionChild = ColorTween(
begin: widget.colorTransitionChild.withOpacity(0.0),
end: widget.colorTransitionChild);
if (widget.colorTransitionScaffold != null)
_colorTransitionScaffold = ColorTween(
begin: widget.colorTransitionScaffold,
end: widget.colorTransitionScaffold.withOpacity(0.0));
if (widget.onDragUpdate != null && _controller.value < 1) {
widget.onDragUpdate((1 - _controller.value), _position);
}
}
LocalHistoryEntry _historyEntry;
final FocusScopeNode _focusScopeNode = FocusScopeNode();
void _ensureHistoryEntry() {
if (_historyEntry == null) {
final ModalRoute<dynamic> route = ModalRoute.of(context);
if (route != null) {
_historyEntry = LocalHistoryEntry(onRemove: _handleHistoryEntryRemoved);
route.addLocalHistoryEntry(_historyEntry);
FocusScope.of(context).setFirstFocus(_focusScopeNode);
}
}
}
void _animationStatusChanged(AnimationStatus status) {
final bool opened = _controller.value < 0.5 ? true : false;
switch (status) {
case AnimationStatus.reverse:
break;
case AnimationStatus.forward:
break;
case AnimationStatus.dismissed:
if (_previouslyOpened != opened) {
_previouslyOpened = opened;
if (widget.innerDrawerCallback != null)
widget.innerDrawerCallback(opened);
}
_ensureHistoryEntry();
break;
case AnimationStatus.completed:
if (_previouslyOpened != opened) {
_previouslyOpened = opened;
if (widget.innerDrawerCallback != null)
widget.innerDrawerCallback(opened);
}
_historyEntry?.remove();
_historyEntry = null;
}
}
void _handleHistoryEntryRemoved() {
_historyEntry = null;
close();
}
AnimationController _controller;
void _handleDragDown(DragDownDetails details) {
_controller.stop();
//_ensureHistoryEntry();
}
final GlobalKey _drawerKey = GlobalKey();
double get _width {
return _initWidth;
}
double get _velocity {
return widget.velocity;
}
/// get width of screen after initState
void _updateWidth() {
WidgetsBinding.instance.addPostFrameCallback((_) {
final RenderBox box = _drawerKey.currentContext.findRenderObject();
//final RenderBox box = context.findRenderObject();
if (box != null && box.size != null && box.size.width > 300)
setState(() {
_initWidth = box.size.width;
});
});
}
bool _previouslyOpened = false;
void _move(DragUpdateDetails details) {
double delta = details.primaryDelta / _width;
if (delta > 0 && _controller.value == 1 && widget.leftChild != null)
_position = InnerDrawerDirection.start;
else if (delta < 0 && _controller.value == 1 && widget.rightChild != null)
_position = InnerDrawerDirection.end;
//TEMP
final double left =
widget.offset != null ? widget.offset.left : widget.leftOffset;
final double right =
widget.offset != null ? widget.offset.right : widget.rightOffset;
double offset = _position == InnerDrawerDirection.start ? left : right;
double ee = 1;
if (offset <= 0.2)
ee = 1.7;
else if (offset <= 0.4)
ee = 1.2;
else if (offset <= 0.6) ee = 1.05;
offset = 1 -
pow(offset / ee,
1 / 2); //(num.parse(pow(offset/2,1/3).toStringAsFixed(1)));
switch (_position) {
case InnerDrawerDirection.end:
break;
case InnerDrawerDirection.start:
delta = -delta;
break;
}
switch (Directionality.of(context)) {
case TextDirection.rtl:
_controller.value -= delta + (delta * offset);
break;
case TextDirection.ltr:
_controller.value += delta + (delta * offset);
break;
}
final bool opened = _controller.value < 0.5 ? true : false;
if (opened != _previouslyOpened && widget.innerDrawerCallback != null)
widget.innerDrawerCallback(opened);
_previouslyOpened = opened;
}
void _settle(DragEndDetails details) {
if (_controller.isDismissed) return;
if (details.velocity.pixelsPerSecond.dx.abs() >= _kMinFlingVelocity) {
double visualVelocity =
(details.velocity.pixelsPerSecond.dx + _velocity) / _width;
switch (_position) {
case InnerDrawerDirection.end:
break;
case InnerDrawerDirection.start:
visualVelocity = -visualVelocity;
break;
}
switch (Directionality.of(context)) {
case TextDirection.rtl:
_controller.fling(velocity: -visualVelocity);
break;
case TextDirection.ltr:
_controller.fling(velocity: visualVelocity);
break;
}
} else if (_controller.value < 0.5) {
open();
} else {
close();
}
}
void open({InnerDrawerDirection direction}) {
if (direction != null) _position = direction;
_controller.fling(velocity: -_velocity);
}
void close({InnerDrawerDirection direction}) {
if (direction != null) _position = direction;
_controller.fling(velocity: _velocity);
}
/// Open or Close InnerDrawer
void toggle({InnerDrawerDirection direction}) {
if (_previouslyOpened)
close(direction: direction);
else
open(direction: direction);
}
final GlobalKey _gestureDetectorKey = GlobalKey();
/// Outer Alignment
AlignmentDirectional get _drawerOuterAlignment {
switch (_position) {
case InnerDrawerDirection.start:
return AlignmentDirectional.centerEnd;
case InnerDrawerDirection.end:
return AlignmentDirectional.centerStart;
}
return null;
}
/// Inner Alignment
AlignmentDirectional get _drawerInnerAlignment {
switch (_position) {
case InnerDrawerDirection.start:
return AlignmentDirectional.centerStart;
case InnerDrawerDirection.end:
return AlignmentDirectional.centerEnd;
}
return null;
}
/// returns the left or right animation type based on InnerDrawerDirection
InnerDrawerAnimation get _animationType {
return _position == InnerDrawerDirection.start
? widget.leftAnimationType
: widget.rightAnimationType;
}
/// returns the left or right scale based on InnerDrawerDirection
double get _scaleFactor {
//TEMP
final double left =
widget.scale != null ? widget.scale.left : widget.leftScale;
final double right =
widget.scale != null ? widget.scale.right : widget.rightScale;
return _position == InnerDrawerDirection.start ? left : right;
}
/// returns the left or right offset based on InnerDrawerDirection
double get _offset {
//TEMP
final double left =
widget.offset != null ? widget.offset.left : widget.leftOffset;
final double right =
widget.offset != null ? widget.offset.right : widget.rightOffset;
return _position == InnerDrawerDirection.start ? left : right;
}
/// return width with specific offset
double get _widthWithOffset {
return (_width / 2) - (_width / 2) * _offset;
//return _width - _width * _offset;
}
/// return swipe
bool get _swipe {
//if( _offset == 0 ) return false;
return widget.swipe;
}
/// return widget with specific animation
Widget _animatedChild() {
final Widget container = Container(
width: widget.proportionalChildArea ? _width - _widthWithOffset : _width,
height: MediaQuery.of(context).size.height,
child: _position == InnerDrawerDirection.start
? widget.leftChild
: widget.rightChild,
);
switch (_animationType) {
case InnerDrawerAnimation.linear:
return Align(
alignment: _drawerOuterAlignment,
widthFactor: 1 - (_controller.value),
child: container,
);
case InnerDrawerAnimation.quadratic:
return Align(
alignment: _drawerOuterAlignment,
widthFactor: 1 - (_controller.value / 2),
child: container,
);
default:
return container;
}
}
/// Trigger Area
Widget _trigger(AlignmentDirectional alignment, Widget child) {
assert(alignment != null);
final bool drawerIsStart = _position == InnerDrawerDirection.start;
final EdgeInsets padding = MediaQuery.of(context).padding;
double dragAreaWidth = drawerIsStart ? padding.left : padding.right;
if (Directionality.of(context) == TextDirection.rtl)
dragAreaWidth = drawerIsStart ? padding.right : padding.left;
dragAreaWidth = max(dragAreaWidth, _kEdgeDragWidth);
if (_controller.status == AnimationStatus.completed &&
_swipe &&
child != null)
return Align(
alignment: alignment,
child: Container(color: Colors.transparent, width: dragAreaWidth),
);
else
return null;
}
///Disable the scaffolding tap when the drawer is open
Widget _invisibleCover() {
final Container container = Container(
color: _colorTransitionScaffold.evaluate(_controller),
);
if (_controller.value != 1.0 && !widget.tapScaffoldEnabled)
return BlockSemantics(
child: GestureDetector(
// On Android, the back button is used to dismiss a modal.
excludeFromSemantics: defaultTargetPlatform == TargetPlatform.android,
onTap: widget.onTapClose || !_swipe ? close : null,
child: Semantics(
label: MaterialLocalizations.of(context)?.modalBarrierDismissLabel,
child: container,
),
),
);
return null;
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, Widget scaffold) {
//assert(debugCheckHasMaterialLocalizations(context));
/// initialize the correct width
if (_initWidth == 400 ||
MediaQuery.of(context).orientation != _orientation) {
_updateWidth();
_orientation = MediaQuery.of(context).orientation;
}
/// wFactor depends of offset and is used by the second Align that contains the Scaffold
final double offset = 0.5 - _offset * 0.5;
//final double offset = 1 - _offset * 1;
final double wFactor = (_controller.value * (1 - offset)) + offset;
return Container(
decoration: widget.backgroundDecoration ??
BoxDecoration(
color: Theme.of(context).backgroundColor,
),
child: Stack(
alignment: _drawerInnerAlignment,
children: <Widget>[
FocusScope(node: _focusScopeNode, child: _animatedChild()),
GestureDetector(
key: _gestureDetectorKey,
onTap: () {},
onHorizontalDragDown: _swipe ? _handleDragDown : null,
onHorizontalDragUpdate: _swipe ? _move : null,
onHorizontalDragEnd: _swipe ? _settle : null,
excludeFromSemantics: true,
child: RepaintBoundary(
child: Stack(
children: <Widget>[
///Gradient
Container(
width: _controller.value == 0 ||
_animationType == InnerDrawerAnimation.linear
? 0
: null,
color: _colorTransitionChild.evaluate(_controller),
),
Align(
alignment: _drawerOuterAlignment,
child: Align(
alignment: _drawerInnerAlignment,
widthFactor: wFactor,
child: Builder(
builder: (BuildContext context) {
assert(widget.borderRadius >= 0);
Widget scaffoldChild = Stack(
children: <Widget>[
scaffold,
_invisibleCover() ?? const SizedBox()
],
);
Widget container = Container(
key: _drawerKey,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(
widget.borderRadius *
(1 - _controller.value)),
boxShadow: widget.boxShadow ??
[
BoxShadow(
color: Colors.black
.withOpacity(0.5),
blurRadius: 5,
)
]),
child: widget.borderRadius != 0
? ClipRRect(
borderRadius: BorderRadius.circular(
(1 - _controller.value) *
widget.borderRadius),
child: scaffoldChild)
: scaffoldChild);
if (_scaleFactor < 1)
container = Transform.scale(
alignment: _drawerInnerAlignment,
scale: ((1 - _scaleFactor) *
_controller.value) +
_scaleFactor,
child: container,
);
// Vertical translate
if (widget.offset != null &&
(widget.offset.top > 0 ||
widget.offset.bottom > 0)) {
final double translateY =
MediaQuery.of(context).size.height *
(widget.offset.top > 0
? -widget.offset.top
: widget.offset.bottom);
container = Transform.translate(
offset: Offset(0,
translateY * (1 - _controller.value)),
child: container,
);
}
return container;
},
)),
),
///Trigger
_trigger(
AlignmentDirectional.centerStart, widget.leftChild),
_trigger(
AlignmentDirectional.centerEnd, widget.rightChild),
].where((a) => a != null).toList(),
),
),
),
],
),
);
},
child: widget.scaffold,
);
}
}
///An immutable set of offset in each of the four cardinal directions.
class IDOffset {
const IDOffset.horizontal(
double horizontal,
) : left = horizontal,
top = 0.0,
right = horizontal,
bottom = 0.0;
const IDOffset.only({
this.left = 0.0,
this.top = 0.0,
this.right = 0.0,
this.bottom = 0.0,
}) : assert(top >= 0.0 &&
top <= 1.0 &&
left >= 0.0 &&
left <= 1.0 &&
right >= 0.0 &&
right <= 1.0 &&
bottom >= 0.0 &&
bottom <= 1.0),
assert(top >= 0.0 && bottom == 0.0 || top == 0.0 && bottom >= 0.0);
/// The offset from the left.
final double left;
/// The offset from the top.
final double top;
/// The offset from the right.
final double right;
/// The offset from the bottom.
final double bottom;
} |
@virskor thanks for the suggestion. Unfortunately, I don't have much time to run any tests at the moment. I'll let you know very soon |
I think it works, just the other way round. |
I have a ton of animations in the
scaffold
widget of theInnerDrawer
.Most of them play only on
buidl()
and moving the drawer constantly calls rebuild on thescaffold
widget.This causes a very jittery experience.
I tried using
AutomaticKeepAliveClientMixin
and set thewantKeepAlive
totrue
but,InnerDrawer
somehow bypasses itThe text was updated successfully, but these errors were encountered: