Repository: flutter
Issue: Mistake in child layout of RenderViewportBase
Labels: framework,f: scrolling,P2,team-framework,triaged-framework

https://github.com/flutter/flutter/blob/0e7257e698db105ec035d164df672b43322a729c/packages/flutter/lib/src/rendering/viewport.dart#L555

The line above determines how the layoutOffset is calculated for the next child sliver in the viewport.
This calculation however does not take the paintOrigin into account unlike the maxPaintoffset which uses the `effectiveLayoutOffset` variable. This has an unfortunate implication on how the overlap works for the children that follow.

### Actual behaviour

Take for example the following list of slivers:
```dart
[
  PinnedHeader1(minHeight: 50, maxHeight: 50),
  PinnedHeader2(minHeight: 50, maxHeight: 100), // this pinned header will shrink by (scrollOffset + overlap)
  PinnedHeader3(minHeight: 50, maxHeight: 100),
  Box(height: 5000),
]
```

When the user for instance scrolls by 20 the first pinned header will receive a scrollOffset of 20, but since it doesnt shrink it will still paint the full height of 50 giving the following geometry and constraints for the second sliver:
```dart
SliverGeometry(
  paintExtent: 50,
  layoutExtent: 30,
  paintOrigin: 0,
)
SliverConstraints(
  scrollOffset: 0,
  overlap: 20,
)
```
This results in the maxPaintoffset so far being 50 and the layoutOffset so far being 30.

The second pinned header will thus get an overlap in and will thus move its paintOrigin by that same amount and shrink in size resulting in the following geometry (note that SliverPersistentHeader currently does not handle incoming overlap to shrink yet but this pinned header does):
```dart
SliverGeometry(
  paintExtent: 80,
  layoutExtent: 80,
  paintOrigin: 20,
)
```
With the current implementation this results in the maxPaintoffset so far being 130 and the layoutOffset so far being 110.
Thus resulting in an overlap of 20 again for the third pinned header. 
But the second pinned header meant to consume the overlap not to pass it on to the next sliver.

Because of this the third pinned header will also shrink by 20 and put its paintOrigin at 20 again resulting in an overlap of 20 for the next sliver.
The maxPaintoffset so far will thus be 210 and the layoutOffset will be 190.

The box that comes after the pinned headers will thus get the following constraints:
```dart
SliverConstraints(
  scrollOffset: 0,
  overlap: 20,
)
```
It however doesnt do anything with the overlap and thus outputs a geometry with a paintOrigin of 0.
This means that the box will be painted at the layoutOffset of 190 and thus the third pinned header will overlap it by 20 (it paints to 210).

### Desired behaviour

The third pinned header should not get any overlap in its constraints.
The box should not be overlapped by the pinned headers until they are both to their minimum height.

### Solution(s)

To resolve this a change will be needed in how the layoutOffset is calculated.
From my testing changing it to the following calculation would solve this issue:
```dart
layoutOffset = math.max(layoutOffset, effectiveLayoutOffset) + childParentData.geometry.layoutExtent;
```
This will include a positive paintOrigin into the layoutOffset, including a negative paintOrigin as well would break the feature where the header sticks to the top of the viewport (it gets a negative overlap).
However, I'm not 100% certain whether this is the correct path to take (hence no PR yet).