abstract class ScrollView extends StatelessWidget {
const ScrollView({
super.key,
this.scrollDirection = Axis.vertical,
this.reverse = false,
this.controller,
this.primary,
ScrollPhysics? physics,
this.scrollBehavior,
this.shrinkWrap = false,
this.center,
this.anchor = 0.0,
this.cacheExtent,
this.semanticChildCount,
this.dragStartBehavior = DragStartBehavior.start,
this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
this.restorationId,
this.clipBehavior = Clip.hardEdge,
})
: assert(scrollDirection != null),
assert(reverse != null),
assert(shrinkWrap != null),
assert(dragStartBehavior != null),
assert(clipBehavior != null),
assert(
!(controller != null && (primary ?? false)),
'Primary ScrollViews obtain their ScrollController via inheritance '
'from a PrimaryScrollController widget. You cannot both set primary to '
'true and pass an explicit controller.',
),
assert(!shrinkWrap || center == null),
assert(anchor != null),
assert(anchor >= 0.0 && anchor <= 1.0),
assert(semanticChildCount == null || semanticChildCount >= 0),
physics = physics ?? ((primary ?? false) ||
(primary == null && controller == null &&
identical(scrollDirection, Axis.vertical))
? const AlwaysScrollableScrollPhysics()
: null);
final Axis scrollDirection;
final bool reverse;
final ScrollController? controller;
final bool? primary;
final ScrollPhysics? physics;
final ScrollBehavior? scrollBehavior;
final bool shrinkWrap;
final Key? center;
final double anchor;
final double? cacheExtent;
final int? semanticChildCount;
final DragStartBehavior dragStartBehavior;
final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior;
final String? restorationId;
final Clip clipBehavior;
@protected
AxisDirection getDirection(BuildContext context) {
return getAxisDirectionFromAxisReverseAndDirectionality(
context, scrollDirection, reverse);
}
@protected
List<Widget> buildSlivers(BuildContext context);
@protected
Widget buildViewport(BuildContext context,
ViewportOffset offset,
AxisDirection axisDirection,
List<Widget> slivers,) {
assert(() {
switch (axisDirection) {
case AxisDirection.up:
case AxisDirection.down:
return debugCheckHasDirectionality(
context,
why: 'to determine the cross-axis direction of the scroll view',
hint: 'Vertical scroll views create Viewport widgets that try to determine their cross axis direction '
'from the ambient Directionality.',
);
case AxisDirection.left:
case AxisDirection.right:
return true;
}
}());
if (shrinkWrap) {
return ShrinkWrappingViewport(
axisDirection: axisDirection,
offset: offset,
slivers: slivers,
clipBehavior: clipBehavior,
);
}
return Viewport(
axisDirection: axisDirection,
offset: offset,
slivers: slivers,
cacheExtent: cacheExtent,
center: center,
anchor: anchor,
clipBehavior: clipBehavior,
);
}
@override
Widget build(BuildContext context) {
final List<Widget> slivers = buildSlivers(context);
final AxisDirection axisDirection = getDirection(context);
final bool effectivePrimary = primary
?? controller == null &&
PrimaryScrollController.shouldInherit(context, scrollDirection);
final ScrollController? scrollController = effectivePrimary
? PrimaryScrollController.maybeOf(context)
: controller;
final Scrollable scrollable = Scrollable(
dragStartBehavior: dragStartBehavior,
axisDirection: axisDirection,
controller: scrollController,
physics: physics,
scrollBehavior: scrollBehavior,
semanticChildCount: semanticChildCount,
restorationId: restorationId,
viewportBuilder: (BuildContext context, ViewportOffset offset) {
return buildViewport(context, offset, axisDirection, slivers);
},
clipBehavior: clipBehavior,
);
final Widget scrollableResult = effectivePrimary && scrollController != null
? PrimaryScrollController.none(child: scrollable)
: scrollable;
if (keyboardDismissBehavior == ScrollViewKeyboardDismissBehavior.onDrag) {
return NotificationListener<ScrollUpdateNotification>(
child: scrollableResult,
onNotification: (ScrollUpdateNotification notification) {
final FocusScopeNode focusScope = FocusScope.of(context);
if (notification.dragDetails != null && focusScope.hasFocus) {
focusScope.unfocus();
}
return false;
},
);
} else {
return scrollableResult;
}
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(EnumProperty<Axis>('scrollDirection', scrollDirection));
properties.add(FlagProperty(
'reverse', value: reverse, ifTrue: 'reversed', showName: true));
properties.add(DiagnosticsProperty<ScrollController>(
'controller', controller, showName: false, defaultValue: null));
properties.add(FlagProperty('primary', value: primary,
ifTrue: 'using primary controller',
showName: true));
properties.add(DiagnosticsProperty<ScrollPhysics>(
'physics', physics, showName: false, defaultValue: null));
properties.add(FlagProperty('shrinkWrap', value: shrinkWrap,
ifTrue: 'shrink-wrapping',
showName: true));
}
}