简体   繁体   English

Flutter Stack Widget - SingleChildScrollView 导致问题

[英]Flutter Stack Widget - SingleChildScrollView Causing Issues

My stack widget ( fancy_on_boarding ) was overflowing and/or getting cut off in landscape mode based on the clipping behavior.我的堆栈小部件 ( fancy_on_boarding ) 在横向模式下溢出和/或被截断,基于剪切行为。 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.我尝试像在其他小部件中那样添加一个 SingleChildScrollView,但是很多都被切断了,它不再滑动。 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(),

));

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.如果需要,可以用 SizedBox 包裹堆栈,或者在堆栈中放置一个大小将包括所有其他子级的 SizedBox。

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.正如您在下面的代码中看到的那样,只有在 Stack 具有有限高度时,才能进行垂直滚动或Positioned.bottom Similarly horizontal scrolling or Positioned.left possible with Stack has bounded width.类似的水平滚动或 Positioned.left 可能与 Stack 有界宽度。

How to give bounded dimension to Stack?如何为 Stack 赋予有界维度?

Among the children of the Stack, those whose size and position are determined are placed.在 Stack 的子节点中,放置大小和位置确定的那些。 The rectangle containing all of these children is the render size of the Stack.包含所有这些子项的矩形是 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.如果要使用 Positioned 的 right 、bottom 、 left 、 right 参数来确定其中一个clidren的位置,则它们不能在滚动方向上。但是,如果甚至有一个child,其大小和位置都是当然,所有其他孩子都被放置为孩子的参考,并确定了他们的大小/位置。

If there are no children of such size and position, the Stack references the BoxConstraints of its render tree.如果没有这样大小和位置的子节点,则 Stack 引用其渲染树的 BoxConstraints。 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).如果这些约束在滚动方向上没有界限,则不能在该方向输入值(如果滚动方向为 Axis.vertical,则不能将底部指定给 Positioned)。

In the example below it should be one of both SizedBox .在下面的示例中,它应该是两个SizedBox Otherwise it will not scroll (even if we remove the bottom values) or we will get an error.否则它不会滚动(即使我们删除了bottom值)或者我们会得到一个错误。

(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.) (由于水平方向顶部的约束是有界高度且不可滚动的,因此即使我输入了无穷大值,它也会使用 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,
                  ))
            ],
          ),
        ),
      ),
    );
  }
}

You can wrap SingleChildScrollView(...) in a Container like this:您可以像这样将SingleChildScrollView(...)包装在 Container 中:

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.在下图中,您可以看到我翻转屏幕时的行为。 :) :) 肖像 景观

Have you tried?你有没有尝试过?

    Expanded(child: FancyOnBoarding(
        doneButton: TwinkleButton()

& please share the error message or any screenshot of it. &请分享错误消息或任何屏幕截图。 Thanks.谢谢。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM