![](/img/trans.png)
[英]SingleChildScrollView is not scrolling with Stack Widget Flutter
[英]Flutter Stack Widget - SingleChildScrollView Causing Issues
我的堆栈小部件 ( fancy_on_boarding ) 在横向模式下溢出和/或被截断,基于剪切行为。 有什么办法可以解决,使小部件只在横向滚动,而我没有收到溢出错误? 背景如下:
我尝试像在其他小部件中那样添加一个 SingleChildScrollView,但是很多都被切断了,它不再滑动。 看这里:
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();
}
}
使用以下代码在纵向模式下看起来和工作得很好:
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();
}
}
我从另一个类调用这个小部件,如下所示:
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(),
));
代码太多了,可能我忽略了。 但是你需要堆栈大小。 如果需要,可以用 SizedBox 包裹堆栈,或者在堆栈中放置一个大小将包括所有其他子级的 SizedBox。
编辑:
当然。 我已经尝试并确认了。 正如您在下面的代码中看到的那样,只有在 Stack 具有有限高度时,才能进行垂直滚动或Positioned.bottom
。 类似的水平滚动或 Positioned.left 可能与 Stack 有界宽度。
如何为 Stack 赋予有界维度?
在 Stack 的子节点中,放置大小和位置确定的那些。 包含所有这些子项的矩形是 Stack 的渲染大小。
如果要使用 Positioned 的 right 、bottom 、 left 、 right 参数来确定其中一个clidren的位置,则它们不能在滚动方向上。但是,如果甚至有一个child,其大小和位置都是当然,所有其他孩子都被放置为孩子的参考,并确定了他们的大小/位置。
如果没有这样大小和位置的子节点,则 Stack 引用其渲染树的 BoxConstraints。 如果这些约束在滚动方向上没有界限,则不能在该方向输入值(如果滚动方向为 Axis.vertical,则不能将底部指定给 Positioned)。
在下面的示例中,它应该是两个SizedBox
。 否则它不会滚动(即使我们删除了bottom
值)或者我们会得到一个错误。
(由于水平方向顶部的约束是有界高度且不可滚动的,因此即使我输入了无穷大值,它也会使用 screenSize.width 值。)
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,
))
],
),
),
),
);
}
}
您可以像这样将SingleChildScrollView(...)包装在 Container 中:
Container(
child: SingleChildScrollView(
//your code here
)
)
然后,给这个容器一个绑定的高度和宽度,如下所示:
Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Stack(
//your code here
)
)
你有没有尝试过?
Expanded(child: FancyOnBoarding(
doneButton: TwinkleButton()
&请分享错误消息或任何屏幕截图。 谢谢。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.