-
Notifications
You must be signed in to change notification settings - Fork 28.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement PageView using SliverLayoutBuilder, Deprecate RenderSliverFillViewport #37024
Implement PageView using SliverLayoutBuilder, Deprecate RenderSliverFillViewport #37024
Conversation
655bf62
to
11f23ef
Compare
@@ -1030,6 +1028,7 @@ class SliverGrid extends SliverMultiBoxAdaptorWidget { | |||
/// the main axis extent of each item. | |||
/// * [SliverList], which does not require its children to have the same | |||
/// extent in the main axis. | |||
@Deprecated('Use SliverLayoutBuilder instead.') | |||
class SliverFillViewport extends SliverMultiBoxAdaptorWidget { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we keep this widget and just implement it in terms of SliverLayoutBuilder and SliverFixedExtendList? That would limit the scope of the API breakage and we'd only remove the RenderSliverFillViewport, I think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aye
48aa1af
to
1d0d78d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
overall looks good, but one concern about the behavior when viewportFraction > 1.0
@@ -556,13 +556,59 @@ void main() { | |||
|
|||
await tester.pumpWidget(build(controller)); | |||
|
|||
expect(tester.getTopLeft(find.text('Alabama')), const Offset(-100.0, 0.0)); | |||
expect(tester.getBottomRight(find.text('Alabama')), const Offset(900.0, 600.0)); | |||
expect(tester.getTopLeft(find.text('Alabama')), Offset.zero); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it looks like we changed the behavior when viewportFraction > 1
The page start at the very left edge of the page instead of after extra space.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah it does change the behavior when viewportFraction > 1
and breaks the API (although I doubt it would really affect anyone, and starting from 0 makes more sense to me) . Context: #8539
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the motivation for changing this?
Please add a description of the changed behavior to the PR description.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the original behavior make more sense to me. I would expect the viewport is center align with the page, that is why you have the padding when viewportfraction<1. If the same thing apply when viewportfraction>1, the original behavior is expected. and i am not quite sure how #8539 related to this behavior. Can you elaborate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I thought a weird viewportFraction
like 1.3 would mess up the snapping behavior but that's not true. Thanks for pointing out. But SliverPadding
s can't go negative. I'll find an alternative for it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you update the PR's description? Please also include all changes in behavior there.
You'll also have to follow https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes for both of the breaking changes.
final double fixedExtent = constraints.viewportMainAxisExtent * viewportFraction; | ||
final double padding = math.max(0, constraints.viewportMainAxisExtent - fixedExtent) / 2; | ||
|
||
final bool isHorizontal = constraints.axisDirection == AxisDirection.left |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you just use constraints.axis?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, with enums instead of checking individual values use a switch/case. That way the analyzer tells us that we need to update this code if we ever add new enum values.
@@ -556,13 +556,59 @@ void main() { | |||
|
|||
await tester.pumpWidget(build(controller)); | |||
|
|||
expect(tester.getTopLeft(find.text('Alabama')), const Offset(-100.0, 0.0)); | |||
expect(tester.getBottomRight(find.text('Alabama')), const Offset(900.0, 600.0)); | |||
expect(tester.getTopLeft(find.text('Alabama')), Offset.zero); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the motivation for changing this?
Please add a description of the changed behavior to the PR description.
}); | ||
|
||
testWidgets('All visible pages are able to receive touch events', | ||
(WidgetTester tester) async { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: either place this at the end of the previous line (preferred) or indent this more.
itemBuilder: (BuildContext context, int index) { | ||
return GestureDetector( | ||
onTap: () => tappedIndex = index, | ||
child: SizedBox.expand(child: Text(index.toString())), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
child: SizedBox.expand(child: Text(index.toString())), | |
child: SizedBox.expand(child: Text('$index')), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also below - I think that improves readability.
(WidgetTester tester) async { | ||
final PageController controller = PageController(viewportFraction: 1/4, initialPage: 0); | ||
int tappedIndex; | ||
Iterable<int> visiblePages; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Declare this were the first value is assigned to it?
expect(find.text(index.toString()), findsOneWidget); | ||
// The center of page 2's x-coordinate is 800, so we have to manually | ||
// offset it a bit to make sure the tap lands within the screen. | ||
final Offset center = tester.getCenter(find.text(index.toString())).translate(-3, 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could also just subtract a small offset, which may make the intend here clearer:
final Offset center = tester.getCenter(find.text(index.toString())).translate(-3, 0); | |
final Offset center = tester.getCenter(find.text('$index')) - const Offset(3, 0); |
56c16e2
to
9662143
Compare
bffde64
to
8cc50bf
Compare
8cc50bf
to
439a869
Compare
if (oldPage != null) | ||
forcePixels(getPixelsFromPage(oldPage)); | ||
// This part should only be reacheable when it will definitely trigger a rebuild. | ||
correctPixels(getPixelsFromPage(oldPage)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this feel dangerous to me. That is true when you change this property in didupdatewidget. If you are certain that is the case for the future as well, you should add an assert to verify that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure if this is better but how about
if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.persistentCallbacks) {
correctPixels(getPixelsFromPage(oldPage));
} else {
forcePixels(getPixelsFromPage(oldPage));
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class itself is private, and the setter is only used in PageController.attach
. I'm not sure if we should call notifyListeners
in ScrollController.attach
, I think it should be up to the caller?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am more worry about we change the scroll offset but not markneedbuild, and yes this should be fine because this is private class and as of now it only called during the build stage. However, it is possible this might change. If you think that is unlikely, we should still add an assert here to guard it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah let me get rid of the setter altogether. The implementation should probably live in PageController.attach
which has more context.
@@ -364,22 +364,24 @@ class _PagePosition extends ScrollPositionWithSingleContext implements PageMetri | |||
return; | |||
final double oldPage = page; | |||
_viewportFraction = value; | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit, extra line
@@ -556,13 +556,95 @@ void main() { | |||
|
|||
await tester.pumpWidget(build(controller)); | |||
|
|||
expect(tester.getTopLeft(find.text('Alabama')), const Offset(-100.0, 0.0)); | |||
expect(tester.getTopLeft(find.text('Alabama')), const Offset(-100, 0)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's revert this change
@goderbauer It no longer changes the behavior when |
@@ -252,7 +252,14 @@ class PageController extends ScrollController { | |||
void attach(ScrollPosition position) { | |||
super.attach(position); | |||
final _PagePosition pagePosition = position; | |||
pagePosition.viewportFraction = viewportFraction; | |||
|
|||
if (pagePosition.viewportFraction == viewportFraction) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why move this out of the setter for viewportFraction?
forcePixel (in this new version) and correctPixel also have slightly different semantics regarding notifications...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
from ScrollPosition.forcePixel
documentation:
/// This should not be called during layout (e.g. when setting the initial
/// scroll offset). Consider [correctPixels] if you find you need to adjust
/// the position during layout.
Also it doesn't sound right to notify the listeners in ScrollController.attach
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why move it out of the setter though?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO since using either forcePixel
or correctPixel
needs to be decided on a case-by-case basis, it feels a bit error-prone to have a viewportFraction
setter which sounds like a silver bullet for changing viewportFraction
.
Also attach
is the only caller of the setter right now, so I got rid of the setter and moved the implementation to attach
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is build, not layout. should be fine to call during build.
forcePixels(getPixelsFromPage(oldPage)); | ||
} | ||
|
||
double get _initialPageOffset => math.max(0, viewportDimension * (viewportFraction - 1) / 2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It may be worthwhile to document what the "initial page offset" is.
|
||
testWidgets( | ||
'All visible pages are able to receive touch events', | ||
(WidgetTester tester) async { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same nit as above here regarding formatting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I double checked the indentation, line 607 does have 2 more spaces than 606, it's just (
takes much less space than a regular character.
final double oldPage = pagePosition.page; | ||
pagePosition._viewportFraction = viewportFraction; | ||
if (oldPage != null) { | ||
pagePosition.correctPixels(pagePosition.getPixelsFromPage(oldPage)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
according to documentation of correctPixels, this function should only be called during layout phase
can you add an assert something like
assert(SchedulerBinding.instance.schedulerPhase == SchedulerPhase.persistentCallbacks, 'attach can only be called during SchedulerPhase.persistentCallbacks')
If this assumption no longer held, would be better to update the documentation of correctPixels to include this case
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should really only be called when debugDoingLayout is true for the viewport in question
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
@@ -930,8 +930,7 @@ class RawGestureDetectorState extends State<RawGestureDetector> { | |||
} | |||
} | |||
|
|||
/// This method can be called outside of the build phase to filter the list of | |||
/// available semantic actions. | |||
/// This method can be called to filter the list of available semantic actions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should document when it is valid to call this method (e.g. what the assert is asserting).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, with some nit
double getPixelsFromPage(double page) { | ||
return page * viewportDimension * viewportFraction; | ||
} | ||
double getPixelsFromPage(double page) => page * viewportDimension * viewportFraction + _initialPageOffset; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it has to be a good old function if the line is too long
7770d61
to
b545282
Compare
It looks like this regressed the "stock_build_iteration" microbenchmark by about 2%. @LongCatIsLooong can you investigate? #39060 |
@@ -27,6 +27,7 @@ import 'sliver_multi_box_adaptor.dart'; | |||
/// * [RenderSliverFixedExtentList], which has a configurable [itemExtent]. | |||
/// * [RenderSliverList], which does not require its children to have the same | |||
/// extent in the main axis. | |||
@Deprecated('Use SliverLayoutBuilder instead.') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking into this further, I'm not sure this deprecation makes sense. If someone is using RenderSliverFillViewport directly, then they're not using widgets, which means that using SliverLayoutBuilder isn't an option.
We should probably remove this render object if we're not supporting it any more (which I presume we're not, since we didn't try to fix the hit test bug in this render object).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we move the padding from rendering layer to widget layer. I can't think of a way to achieve the same behavior by using our current rendering library. I agree we should remove this given we have deprecated warning for a while.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is no longer used and should just be removed: #44612
…rSliverFillViewport (flutter#37024)" This reverts commit 9aea03f.
…te RenderSliverFillViewport (flutter#37024)" (flutter#44778)" This reverts commit 851d699.
Description
RenderSliverFillViewport
PageView
'sbuild
method to useSliverLayoutBuilder
instead.Breaking Changes
RenderSliverFillViewport
is now marked asdeprecated
.Related Issues
Closes #23873
Closes #27744
Tests
I added the following tests:
All visible pages are able to receive touch events.
Checklist
Before you create this PR confirm that it meets all requirements listed below by checking the relevant checkboxes (
[x]
). This will ensure a smooth and quick review process.///
).flutter analyze --flutter-repo
) does not report any problems on my PR.Breaking Change
Does your PR require Flutter developers to manually update their apps to accommodate your change?