I have a layout where I have two scroll views, one is a PageView which is used to display a list of pictures horizontally, the second is a SingleChildScrollView (maybe should be something else?), this scroll view is partially above the first one as initial position and can go completely over. Some screenshots to image this:

enter image description here enter image description here enter image description here

The problem is, if I make the 2nd scroll view take all page and add internal padding to it to make it the good height, the 2nd scroll view work as expected but the first one (witch is behind) isn't working anymore. The 2nd solution would be to wrap the SingleChildScrollView with a Padding widget to make it the good height and add Clip.none parameter to the scroll view, but in this case the scroll view cannot be scrolled in PageView zone even if the SingleChildScrollViewis over.

I wonder if someone already encountered a behavior like this and what is your solution.

Here my actual code with second solution:

  Widget body() {
    return LayoutBuilder(
      builder: (context, boxConstraints) {
        this.boxConstraints = boxConstraints;
        uiHeight = boxConstraints.maxHeight -
            boxConstraints.maxWidth * 9 / 16 +
            scrollableUIBorderRadius;
        return Stack(
          children: [
            coverWidget(),
            scrollableUI(),
          ],
        );
      },
    );
  }

  Widget coverWidget() { //This is the PageView containing pictures
    return Positioned.fill(
        bottom: uiHeight - scrollableUIBorderRadius,
        child: global.coverWidget(
          placeId: widget.place.id,
          galleryItemsLinks: widget.galleryItemsLinks!,
          initialIndex: widget.coverIndex,
          onIndexUpdate: widget.onUpdateIndex,
          circleIndicatorsDistanceFromBottom:
              (boxConstraints.maxHeight - uiHeight) / 15 +
                  scrollableUIBorderRadius,
        ));
  }

  Widget scrollableUI() {
    return Positioned.fill(
      top: boxConstraints.maxHeight - uiHeight,
      child: SingleChildScrollView(
        clipBehavior: Clip.none,
        controller: _scrollController,
        physics: AlwaysScrollableScrollPhysics(parent: BouncingScrollPhysics()),
        scrollDirection: Axis.vertical,
        child: SizedBox(
          height: uiHeight * 2,
          child: Stack(
            children: [
              Positioned.fill(
                child: global.containerWidget(
                  placeId: widget.place.id,
                  borderRadius: BorderRadius.only(
                      topLeft: Radius.circular(scrollableUIBorderRadius),
                      topRight: Radius.circular(scrollableUIBorderRadius)),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

NB: if possible I want to conserve those BouncingScrollPhysics easily so a solution with AnimationBuilder && Transform && GestureDetectoris the last resort.


Solution 1: Milvintsiss

I found a little hack to do this, I passed the PageView containing the photos inside the the SingleChildScrollView so the PageView is on top and can receive touch events, to keep the PageView fixed to the top of the screen I use a Transform widget that reverse the scrollOffset of the SingleChildScrollView.

My implementation:

(here the Transform widget is also used to resize the PageView containing photos when the scrollOffset is under 0)

[...]

late ScrollController _scrollController = ScrollController()
    ..addListener(_scrollListener);
double _scrollOffset = 0.0;

[...]

_scrollListener() {
    setState(() {
      _scrollOffset = _scrollController.offset;
    });
}

[...]

Widget body() {
    return LayoutBuilder(builder: (context, boxConstraints) {
      this.boxConstraints = boxConstraints;
      coverHeight = boxConstraints.maxWidth * 9 / 16;
      return SingleChildScrollView(
        physics: AlwaysScrollableScrollPhysics(parent: BouncingScrollPhysics()),
        controller: _scrollController,
        child: Column(
          children: [
            Transform(
              transform: Matrix4.compose(
                  vector.Vector3(
                      _scrollOffset > 0 ? 0 : _scrollOffset, _scrollOffset, 0),
                  vector.Quaternion.euler(0.0, 0.0, 0.0),
                  vector.Vector3(
                      _scrollOffset > 0
                          ? 1.0
                          : 1.0 - _scrollOffset / boxConstraints.maxHeight * 4,
                      _scrollOffset > 0
                          ? 1.0
                          : 1.0 - _scrollOffset / boxConstraints.maxHeight * 4,
                      1.0)),
              child: SizedBox(
                height: coverHeight,
                child: global.coverWidget(
                  placeId: widget.place.id,
                  galleryItemsLinks: widget.galleryItemsLinks!,
                  initialIndex: widget.coverIndex,
                  onIndexUpdate: widget.onUpdateIndex,
                  circleIndicatorsDistanceFromBottom:
                      coverHeight / 15 + scrollableUIBorderRadius,
                ),
              ),
            ),
            Stack(
              clipBehavior: Clip.none,
              children: [
                Positioned.fill(
                  top: -scrollableUIBorderRadius,
                  child: global.containerWidget(
                    placeId: widget.place.id,
                    borderRadius: BorderRadius.only(
                        topLeft: Radius.circular(scrollableUIBorderRadius),
                        topRight: Radius.circular(scrollableUIBorderRadius)),
                  ),
                ),
                Column(
                  children: [
                    [...]
                  ],
                ),
              ],
            ),
          ],
        ),
      );
    });
  }