Reputation: 447
My stack widget (fancy_on_boarding) was overflowing and/or getting cut off in landscape mode based on the clipping behavior. Any way to resolve so that the widget just scrolls in landscape and I don't get an overflow error? Background below:
I tried to add a SingleChildScrollView as I've done in other widgets, but a lot was cut off and it no longer swipes. See here:
library fancy_on_boarding;
import 'dart:async';
import 'dart:ui' as ui;
import 'package:fancy_on_boarding/src/fancy_page.dart';
import 'package:fancy_on_boarding/src/page_dragger.dart';
import 'package:fancy_on_boarding/src/page_model.dart';
import 'package:fancy_on_boarding/src/page_reveal.dart';
import 'package:fancy_on_boarding/src/pager_indicator.dart';
import 'package:flutter/material.dart';
class FancyOnBoarding extends StatefulWidget {
final List<PageModel> pageList;
final VoidCallback onDoneButtonPressed;
final VoidCallback onSkipButtonPressed;
final String doneButtonText;
final ShapeBorder doneButtonShape;
final TextStyle doneButtonTextStyle;
final Color doneButtonBackgroundColor;
final String skipButtonText;
final TextStyle skipButtonTextStyle;
final Color skipButtonColor;
final bool showSkipButton;
final double bottomMargin;
final Widget doneButton;
final Widget skipButton;
FancyOnBoarding({
@required this.pageList,
@required this.onDoneButtonPressed,
this.onSkipButtonPressed,
this.doneButtonText = "Done",
this.doneButtonShape,
this.doneButtonTextStyle,
this.doneButtonBackgroundColor,
this.skipButtonText = "Skip",
this.skipButtonTextStyle,
this.skipButtonColor,
this.showSkipButton = true,
this.bottomMargin = 8.0,
this.doneButton,
this.skipButton,
}) : assert(pageList.length != 0 && onDoneButtonPressed != null);
@override
_FancyOnBoardingState createState() => _FancyOnBoardingState();
}
class _FancyOnBoardingState extends State<FancyOnBoarding>
with TickerProviderStateMixin {
StreamController<SlideUpdate> slideUpdateStream;
AnimatedPageDragger animatedPageDragger;
List<PageModel> pageList;
int activeIndex = 0;
int nextPageIndex = 0;
SlideDirection slideDirection = SlideDirection.none;
double slidePercent = 0.0;
bool get isRTL => ui.window.locale.languageCode.toLowerCase() == "ar";
@override
void initState() {
super.initState();
this.pageList = widget.pageList;
this.slideUpdateStream = StreamController<SlideUpdate>();
_listenSlideUpdate();
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Stack(
overflow: Overflow.visible,
children: [
FancyPage(
model: pageList[activeIndex],
percentVisible: 1.0,
),
PageReveal(
revealPercent: slidePercent,
child: FancyPage(
model: pageList[nextPageIndex],
percentVisible: slidePercent,
),
),
Positioned(
bottom: widget.bottomMargin,
child: PagerIndicator(
isRtl: isRTL,
viewModel: PagerIndicatorViewModel(
pageList,
activeIndex,
slideDirection,
slidePercent,
),
),
),
PageDragger(
pageLength: pageList.length - 1,
currentIndex: activeIndex,
canDragLeftToRight: activeIndex > 0,
canDragRightToLeft: activeIndex < pageList.length - 1,
slideUpdateStream: this.slideUpdateStream,
),
Padding(
padding: EdgeInsets.only(top: 425.0),
child: Align(
alignment: Alignment.bottomCenter,
child: Opacity(
opacity: opacity,
child: widget.doneButton ??
FlatButton(
height: 45,
shape: widget.doneButtonShape ??
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30.0)),
color: widget.doneButtonBackgroundColor ??
const Color(0xffE7C24A), //0x88FFFFFF),
child: Text(
widget.doneButtonText,
style: widget.doneButtonTextStyle ??
const TextStyle(
fontFamily: 'Title',
color: Color(0xff012A4C), //Colors.white,
fontSize: 22.0,
),
),
onPressed:
opacity == 1.0 ? widget.onDoneButtonPressed : () {},
),
),
// ),
),
),
widget.showSkipButton
? Padding(
padding: EdgeInsets.only(top: 425.0),
child: Align(
alignment: Alignment.bottomCenter,
child: widget.skipButton ??
FlatButton(
height: 25,
shape: widget.doneButtonShape ??
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30.0)),
color: widget.skipButtonColor ??
const Color(0xffE7C24A), //0x88FFFFFF),
child: Text(
widget.skipButtonText,
style: widget.skipButtonTextStyle ??
const TextStyle(
fontFamily: 'Title',
color: Color(0xff012A4C), //0xffE7C24A
fontSize: 18.0,
),
),
onPressed: widget.onSkipButtonPressed,
),
))
: Offstage()
],
));
}
_listenSlideUpdate() {
slideUpdateStream.stream.listen((SlideUpdate event) {
setState(() {
if (event.updateType == UpdateType.dragging) {
slideDirection = event.direction;
slidePercent = event.slidePercent;
if (slideDirection == SlideDirection.leftToRight) {
nextPageIndex = activeIndex - 1;
} else if (slideDirection == SlideDirection.rightToLeft) {
nextPageIndex = activeIndex + 1;
} else {
nextPageIndex = activeIndex;
}
} else if (event.updateType == UpdateType.doneDragging) {
if (slidePercent > 0.5) {
animatedPageDragger = AnimatedPageDragger(
slideDirection: slideDirection,
transitionGoal: TransitionGoal.open,
slidePercent: slidePercent,
slideUpdateStream: slideUpdateStream,
vsync: this,
);
} else {
animatedPageDragger = AnimatedPageDragger(
slideDirection: slideDirection,
transitionGoal: TransitionGoal.close,
slidePercent: slidePercent,
slideUpdateStream: slideUpdateStream,
vsync: this,
);
nextPageIndex = activeIndex;
}
animatedPageDragger.run();
} else if (event.updateType == UpdateType.animating) {
slideDirection = event.direction;
slidePercent = event.slidePercent;
} else if (event.updateType == UpdateType.doneAnimating) {
activeIndex = nextPageIndex;
slideDirection = SlideDirection.none;
slidePercent = 0.0;
animatedPageDragger.dispose();
}
});
});
}
double get opacity {
if (pageList.length - 2 == activeIndex &&
slideDirection == SlideDirection.rightToLeft) return slidePercent;
if (pageList.length - 1 == activeIndex &&
slideDirection == SlideDirection.leftToRight) return 1 - slidePercent;
if (pageList.length - 1 == activeIndex) return 1.0;
return 0.0;
}
@override
void dispose() {
slideUpdateStream?.close();
super.dispose();
}
}
Looks and works great in portrait mode with this code:
library fancy_on_boarding;
import 'dart:async';
import 'dart:ui' as ui;
import 'package:fancy_on_boarding/src/fancy_page.dart';
import 'package:fancy_on_boarding/src/page_dragger.dart';
import 'package:fancy_on_boarding/src/page_model.dart';
import 'package:fancy_on_boarding/src/page_reveal.dart';
import 'package:fancy_on_boarding/src/pager_indicator.dart';
import 'package:flutter/material.dart';
class FancyOnBoarding extends StatefulWidget {
final List<PageModel> pageList;
final VoidCallback onDoneButtonPressed;
final VoidCallback onSkipButtonPressed;
final String doneButtonText;
final ShapeBorder doneButtonShape;
final TextStyle doneButtonTextStyle;
final Color doneButtonBackgroundColor;
final String skipButtonText;
final TextStyle skipButtonTextStyle;
final Color skipButtonColor;
final bool showSkipButton;
final double bottomMargin;
final Widget doneButton;
final Widget skipButton;
FancyOnBoarding({
@required this.pageList,
@required this.onDoneButtonPressed,
this.onSkipButtonPressed,
this.doneButtonText = "Done",
this.doneButtonShape,
this.doneButtonTextStyle,
this.doneButtonBackgroundColor,
this.skipButtonText = "Skip",
this.skipButtonTextStyle,
this.skipButtonColor,
this.showSkipButton = true,
this.bottomMargin = 8.0,
this.doneButton,
this.skipButton,
}) : assert(pageList.length != 0 && onDoneButtonPressed != null);
@override
_FancyOnBoardingState createState() => _FancyOnBoardingState();
}
class _FancyOnBoardingState extends State<FancyOnBoarding>
with TickerProviderStateMixin {
StreamController<SlideUpdate> slideUpdateStream;
AnimatedPageDragger animatedPageDragger;
List<PageModel> pageList;
int activeIndex = 0;
int nextPageIndex = 0;
SlideDirection slideDirection = SlideDirection.none;
double slidePercent = 0.0;
bool get isRTL => ui.window.locale.languageCode.toLowerCase() == "ar";
@override
void initState() {
super.initState();
this.pageList = widget.pageList;
this.slideUpdateStream = StreamController<SlideUpdate>();
_listenSlideUpdate();
}
@override
Widget build(BuildContext context) {
return
Stack(
clipBehavior: Clip.hardEdge,
children: [
FancyPage(
model: pageList[activeIndex],
percentVisible: 1.0,
),
PageReveal(
revealPercent: slidePercent,
child: FancyPage(
model: pageList[nextPageIndex],
percentVisible: slidePercent,
),
),
Positioned(
bottom: widget.bottomMargin,
child: PagerIndicator(
isRtl: isRTL,
viewModel: PagerIndicatorViewModel(
pageList,
activeIndex,
slideDirection,
slidePercent,
),
),
),
PageDragger(
pageLength: pageList.length - 1,
currentIndex: activeIndex,
canDragLeftToRight: activeIndex > 0,
canDragRightToLeft: activeIndex < pageList.length - 1,
slideUpdateStream: this.slideUpdateStream,
),
Padding(
padding: EdgeInsets.only(top: 425.0),
child: Align(
alignment: Alignment.bottomCenter,
child: Opacity(
opacity: opacity,
child: widget.doneButton ??
FlatButton(
height: 45,
shape: widget.doneButtonShape ??
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30.0)),
color: widget.doneButtonBackgroundColor ??
const Color(0xffE7C24A), //0x88FFFFFF),
child: Text(
widget.doneButtonText,
style: widget.doneButtonTextStyle ??
const TextStyle(
fontFamily: 'Title',
color: Color(0xff012A4C),
fontSize: 22.0,
),
),
onPressed:
opacity == 1.0 ? widget.onDoneButtonPressed : () {},
),
),
),
),
widget.showSkipButton
? Padding(
padding: EdgeInsets.only(top: 425.0),
child: Align(
alignment: Alignment.bottomCenter,
child: widget.skipButton ??
FlatButton(
height: 25,
shape: widget.doneButtonShape ??
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30.0)),
color: widget.skipButtonColor ??
const Color(0xffE7C24A),
child: Text(
widget.skipButtonText,
style: widget.skipButtonTextStyle ??
const TextStyle(
fontFamily: 'Title',
color: Color(0xff012A4C),
fontSize: 18.0,
),
),
onPressed: widget.onSkipButtonPressed,
),
))
: Offstage()
],
);
}
_listenSlideUpdate() {
slideUpdateStream.stream.listen((SlideUpdate event) {
setState(() {
if (event.updateType == UpdateType.dragging) {
slideDirection = event.direction;
slidePercent = event.slidePercent;
if (slideDirection == SlideDirection.leftToRight) {
nextPageIndex = activeIndex - 1;
} else if (slideDirection == SlideDirection.rightToLeft) {
nextPageIndex = activeIndex + 1;
} else {
nextPageIndex = activeIndex;
}
} else if (event.updateType == UpdateType.doneDragging) {
if (slidePercent > 0.5) {
animatedPageDragger = AnimatedPageDragger(
slideDirection: slideDirection,
transitionGoal: TransitionGoal.open,
slidePercent: slidePercent,
slideUpdateStream: slideUpdateStream,
vsync: this,
);
} else {
animatedPageDragger = AnimatedPageDragger(
slideDirection: slideDirection,
transitionGoal: TransitionGoal.close,
slidePercent: slidePercent,
slideUpdateStream: slideUpdateStream,
vsync: this,
);
nextPageIndex = activeIndex;
}
animatedPageDragger.run();
} else if (event.updateType == UpdateType.animating) {
slideDirection = event.direction;
slidePercent = event.slidePercent;
} else if (event.updateType == UpdateType.doneAnimating) {
activeIndex = nextPageIndex;
slideDirection = SlideDirection.none;
slidePercent = 0.0;
animatedPageDragger.dispose();
}
});
});
}
double get opacity {
if (pageList.length - 2 == activeIndex &&
slideDirection == SlideDirection.rightToLeft) return slidePercent;
if (pageList.length - 1 == activeIndex &&
slideDirection == SlideDirection.leftToRight) return 1 - slidePercent;
if (pageList.length - 1 == activeIndex) return 1.0;
return 0.0;
}
@override
void dispose() {
slideUpdateStream?.close();
super.dispose();
}
}
I am calling this Widget from another class as shown below:
return Scaffold(
body: FancyOnBoarding(
doneButton: TwinkleButton(
buttonWidth: 230,
buttonTitle: Text(
'CREATE',
style: TextStyle(
fontFamily: 'Title',
color: Color(0xff012A4C), //0xff012A4C
fontSize: 18,
),
),
buttonColor: Color(0xffE7C24A),
onclickButtonFunction: () async {
// go();
}),
skipButton: TwinkleButton(
// buttonHeight: 35,
buttonWidth: 230,
buttonTitle: Text(
'CREATE',
style: TextStyle(
fontFamily: 'Title',
color: Color(0xff012A4C), //0xff012A4C
fontSize: 18,
),
),
buttonColor: Color(0xffE7C24A),
onclickButtonFunction: () async {
go();
}),
pageList: pageList,
onDoneButtonPressed: () => go(),
onSkipButtonPressed: () => go(),
));
Upvotes: 0
Views: 4077
Reputation: 990
Have you tried?
Expanded(child: FancyOnBoarding(
doneButton: TwinkleButton()
& please share the error message or any screenshot of it. Thanks.
Upvotes: 0
Reputation: 449
You can wrap SingleChildScrollView(...) in a Container like this:
Container(
child: SingleChildScrollView(
//your code here
)
)
Then, give this Container a bound height and width like this:
Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Stack(
//your code here
)
)
Note that you will have to give the stack a smaller height if there are other children in the tree taking up space. In the images below you can see the behavior when I flip the screen. :)
Upvotes: 0
Reputation: 305
There are too many codes, so I may have overlooked it. But you need the stack sizing. Wrap the Stack with a SizedBox if you want, or put a SizedBox inside the stack whose size will include all other children.
Editing:
Certainly. I've tried and confirmed. As you can see in the codes below, vertical scrolling or Positioned.bottom
are only possible when the Stack has bounded height. Similarly horizontal scrolling or Positioned.left possible with Stack has bounded width.
How to give bounded dimension to Stack?
Among the children of the Stack, those whose size and position are determined are placed. The rectangle containing all of these children is the render size of the Stack.
If the right , bottom, left, right parameters of Positioned are to be used in order to determine the position of one of the clidren, they must not be in the scrolling direction.However, if there is even one child whose size and position are certain, all other children are placed as a reference from the child and their sizes/positions are determined.
If there are no children of such size and position, the Stack references the BoxConstraints of its render tree. And if those constraints don't have a bound in the scroll direction, you can't enter a value in that direction (if scroll direction is Axis.vertical you can't give bottom to Positioned).
In the example below it should be one of both SizedBox
. Otherwise it will not scroll (even if we remove the bottom
values) or we will get an error.
(Since the constraints from the top on the horizontal are bounded height and non-scrollable, it will use the screenSize.width value even if I have entered an infinity value.)
class _StackExampleState extends State<StackExample> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: SizedBox(
// height: 2000,
// width: double.infinity,
child: Stack(
children: [
const SizedBox(
height: 2000,
width: double.infinity,
),
Positioned(
height: 200,
left: 0,
right: 0,
child: Container(
color: Colors.red,
)),
Positioned(
height: 200,
bottom: 0,
left: 0,
right: 0,
child: Container(
color: Colors.green,
)),
Positioned(
bottom: 300,
height: 200,
left: 0,
right: 0,
child: Container(
color: Colors.blue,
)),
Positioned(
height: 200,
right: 0,
left: 0,
child: Container(
color: Colors.orange,
))
],
),
),
),
);
}
}
Upvotes: 1