Skip to content
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

Add clampToMaxScrollExtent parameter and fix lint warnings #114

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 76 additions & 50 deletions lib/scroll_to_index.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ Rect defaultViewportBoundaryGetter() => Rect.zero;

abstract class AutoScrollController implements ScrollController {
factory AutoScrollController(
{double initialScrollOffset: 0.0,
bool keepScrollOffset: true,
{double initialScrollOffset = 0.0,
bool keepScrollOffset = true,
double? suggestedRowHeight,
ViewportBoundaryGetter viewportBoundaryGetter:
bool clampToMaxScrollExtent = true,
ViewportBoundaryGetter viewportBoundaryGetter =
defaultViewportBoundaryGetter,
Axis? axis,
String? debugLabel,
Expand All @@ -39,6 +40,7 @@ abstract class AutoScrollController implements ScrollController {
initialScrollOffset: initialScrollOffset,
keepScrollOffset: keepScrollOffset,
suggestedRowHeight: suggestedRowHeight,
clampToMaxScrollExtent: clampToMaxScrollExtent,
viewportBoundaryGetter: viewportBoundaryGetter,
beginGetter: axis == Axis.horizontal ? (r) => r.left : (r) => r.top,
endGetter: axis == Axis.horizontal ? (r) => r.right : (r) => r.bottom,
Expand All @@ -56,6 +58,7 @@ abstract class AutoScrollController implements ScrollController {
/// used to choose which direction you are using.
/// e.g. axis == Axis.horizontal ? (r) => r.left : (r) => r.top
AxisValueGetter get beginGetter;

AxisValueGetter get endGetter;

/// detect if it's in scrolling (scrolling is a async process)
Expand All @@ -72,14 +75,14 @@ abstract class AutoScrollController implements ScrollController {

/// scroll to the giving index
Future scrollToIndex(int index,
{Duration duration: scrollAnimationDuration,
{Duration duration = scrollAnimationDuration,
AutoScrollPosition? preferPosition});

/// highlight the item
Future highlight(int index,
{bool cancelExistHighlights: true,
Duration highlightDuration: _highlightDuration,
bool animated: true});
{bool cancelExistHighlights = true,
Duration highlightDuration = _highlightDuration,
bool animated = true});

/// cancel all highlight item immediately.
void cancelAllHighlights();
Expand All @@ -94,17 +97,20 @@ class SimpleAutoScrollController extends ScrollController
@override
final double? suggestedRowHeight;
@override
final bool clampToMaxScrollExtent;
@override
final ViewportBoundaryGetter viewportBoundaryGetter;
@override
final AxisValueGetter beginGetter;
@override
final AxisValueGetter endGetter;

SimpleAutoScrollController(
{double initialScrollOffset: 0.0,
bool keepScrollOffset: true,
{double initialScrollOffset = 0.0,
bool keepScrollOffset = true,
this.suggestedRowHeight,
this.viewportBoundaryGetter: defaultViewportBoundaryGetter,
this.clampToMaxScrollExtent = true,
this.viewportBoundaryGetter = defaultViewportBoundaryGetter,
required this.beginGetter,
required this.endGetter,
AutoScrollController? copyTagsFrom,
Expand All @@ -122,18 +128,21 @@ class PageAutoScrollController extends PageController
@override
final double? suggestedRowHeight;
@override
final bool clampToMaxScrollExtent;
@override
final ViewportBoundaryGetter viewportBoundaryGetter;
@override
final AxisValueGetter beginGetter = (r) => r.left;
@override
final AxisValueGetter endGetter = (r) => r.right;

PageAutoScrollController(
{int initialPage: 0,
bool keepPage: true,
double viewportFraction: 1.0,
{int initialPage = 0,
bool keepPage = true,
double viewportFraction = 1.0,
this.suggestedRowHeight,
this.viewportBoundaryGetter: defaultViewportBoundaryGetter,
this.clampToMaxScrollExtent = true,
this.viewportBoundaryGetter = defaultViewportBoundaryGetter,
AutoScrollController? copyTagsFrom,
String? debugLabel})
: super(
Expand All @@ -145,16 +154,24 @@ class PageAutoScrollController extends PageController
}

enum AutoScrollPosition { begin, middle, end }

mixin AutoScrollControllerMixin on ScrollController
implements AutoScrollController {
@override
final Map<int, AutoScrollTagState> tagMap = <int, AutoScrollTagState>{};

double? get suggestedRowHeight;

bool get clampToMaxScrollExtent;

ViewportBoundaryGetter get viewportBoundaryGetter;

AxisValueGetter get beginGetter;

AxisValueGetter get endGetter;

bool __isAutoScrolling = false;

set _isAutoScrolling(bool isAutoScrolling) {
__isAutoScrolling = isAutoScrolling;
if (!isAutoScrolling &&
Expand All @@ -166,6 +183,7 @@ mixin AutoScrollControllerMixin on ScrollController
bool get isAutoScrolling => __isAutoScrolling;

ScrollController? _parentController;

@override
set parentController(ScrollController parentController) {
if (_parentController == parentController) return;
Expand Down Expand Up @@ -202,7 +220,7 @@ mixin AutoScrollControllerMixin on ScrollController
static const maxBound = 30; // 0.5 second if 60fps
@override
Future scrollToIndex(int index,
{Duration duration: scrollAnimationDuration,
{Duration duration = scrollAnimationDuration,
AutoScrollPosition? preferPosition}) async {
return co(
this,
Expand All @@ -211,7 +229,7 @@ mixin AutoScrollControllerMixin on ScrollController
}

Future _scrollToIndex(int index,
{Duration duration: scrollAnimationDuration,
{Duration duration = scrollAnimationDuration,
AutoScrollPosition? preferPosition}) async {
assert(duration > Duration.zero);

Expand Down Expand Up @@ -268,12 +286,11 @@ mixin AutoScrollControllerMixin on ScrollController
prevOffset = currentOffset;
final nearest = _getNearestIndex(index);

if (tagMap[nearest ?? 0] == null)
return null;
if (tagMap[nearest ?? 0] == null) return null;

final moveTarget =
_forecastMoveUnit(index, nearest, usedSuggestedRowHeightIfAny)!;

// assume suggestRowHeight will move to correct offset in just one time.
// if the rule doesn't work (in variable row height case), we will use backup solution (non-suggested way)
final suggestedDuration =
Expand Down Expand Up @@ -330,9 +347,9 @@ mixin AutoScrollControllerMixin on ScrollController

@override
Future highlight(int index,
{bool cancelExistHighlights: true,
Duration highlightDuration: _highlightDuration,
bool animated: true}) async {
{bool cancelExistHighlights = true,
Duration highlightDuration = _highlightDuration,
bool animated = true}) async {
final tag = tagMap[index];
return tag == null
? null
Expand Down Expand Up @@ -379,7 +396,7 @@ mixin AutoScrollControllerMixin on ScrollController
} else {
final offsetToLastState =
_offsetToRevealInViewport(currentNearestIndex, alignment);

absoluteOffsetToViewport = offsetToLastState?.offset;
if (absoluteOffsetToViewport == null)
absoluteOffsetToViewport = defaultScrollDistanceOffset;
Expand All @@ -402,7 +419,6 @@ mixin AutoScrollControllerMixin on ScrollController
/// bring the state node (already created but all of it may not be fully in the viewport) into viewport
Future _bringIntoViewportIfNeed(int index, AutoScrollPosition? preferPosition,
Future move(double offset)) async {

if (preferPosition != null) {
double targetOffset = _directionalOffsetToRevealInViewport(
index, _positionToAlignment(preferPosition));
Expand All @@ -415,8 +431,9 @@ mixin AutoScrollControllerMixin on ScrollController
// physics are set to clamp. To prevent this, we limit the
// offset to not overshoot the extent in either direction.
targetOffset = targetOffset.clamp(
position.minScrollExtent, position.maxScrollExtent);

position.minScrollExtent,
clampToMaxScrollExtent ? position.maxScrollExtent : double.maxFinite,
);
await move(targetOffset);
} else {
final begin = _directionalOffsetToRevealInViewport(index, 0);
Expand Down Expand Up @@ -477,9 +494,8 @@ mixin AutoScrollControllerMixin on ScrollController
if (ctx == null) return null;

final renderBox = ctx.findRenderObject()!;
assert(Scrollable.of(ctx) != null);
final RenderAbstractViewport viewport =
RenderAbstractViewport.of(renderBox)!;
RenderAbstractViewport.of(renderBox);
final revealedOffset = viewport.getOffsetToReveal(renderBox, alignment);

return revealedOffset;
Expand All @@ -493,7 +509,9 @@ void _cancelAllHighlights([AutoScrollTagState? state]) {
_highlights.clear();
}

typedef Widget TagHighlightBuilder(BuildContext context, Animation<double> highlight);
typedef Widget TagHighlightBuilder(
BuildContext context, Animation<double> highlight);

class AutoScrollTag extends StatefulWidget {
final AutoScrollController controller;
final int index;
Expand All @@ -511,8 +529,9 @@ class AutoScrollTag extends StatefulWidget {
this.builder,
this.color,
this.highlightColor,
this.disabled: false})
: assert(child != null || builder != null), super(key: key);
this.disabled = false})
: assert(child != null || builder != null),
super(key: key);

@override
AutoScrollTagState createState() {
Expand Down Expand Up @@ -578,9 +597,13 @@ class AutoScrollTagState<W extends AutoScrollTag> extends State<W>
@override
Widget build(BuildContext context) {
final animation = _controller ?? kAlwaysDismissedAnimation;
return widget.builder?.call(context, animation)
?? buildHighlightTransition(context: context, highlight: animation, child: widget.child!,
background: widget.color, highlightColor: widget.highlightColor);
return widget.builder?.call(context, animation) ??
buildHighlightTransition(
context: context,
highlight: animation,
child: widget.child!,
background: widget.color,
highlightColor: widget.highlightColor);
}

//used to make sure we will drop the old highlight
Expand All @@ -589,9 +612,9 @@ class AutoScrollTagState<W extends AutoScrollTag> extends State<W>

/// this function can be called multiple times. every call will reset the highlight style.
Future highlight(
{bool cancelExisting: true,
Duration highlightDuration: _highlightDuration,
bool animated: true}) async {
{bool cancelExisting = true,
Duration highlightDuration = _highlightDuration,
bool animated = true}) async {
if (!mounted) return null;

if (cancelExisting) {
Expand Down Expand Up @@ -637,7 +660,7 @@ class AutoScrollTagState<W extends AutoScrollTag> extends State<W>
return null;
}

void _cancelController({bool reset: true}) {
void _cancelController({bool reset = true}) {
if (_controller != null) {
if (_controller!.isAnimating) _controller!.stop();

Expand All @@ -646,17 +669,20 @@ class AutoScrollTagState<W extends AutoScrollTag> extends State<W>
}
}

Widget buildHighlightTransition({required BuildContext context, required Animation<double> highlight,
required Widget child, Color? background, Color? highlightColor}) {
Widget buildHighlightTransition(
{required BuildContext context,
required Animation<double> highlight,
required Widget child,
Color? background,
Color? highlightColor}) {
return DecoratedBoxTransition(
decoration: DecorationTween(
begin: background != null ?
BoxDecoration(color: background) :
BoxDecoration(),
end: background != null ?
BoxDecoration(color: background) :
BoxDecoration(color: highlightColor)
).animate(highlight),
child: child
);
decoration: DecorationTween(
begin: background != null
? BoxDecoration(color: background)
: BoxDecoration(),
end: background != null
? BoxDecoration(color: background)
: BoxDecoration(color: highlightColor))
.animate(highlight),
child: child);
}