繁体   English   中英

Flutter Stack Widget - SingleChildScrollView 导致问题

[英]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.

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