Skip to content

Commit

Permalink
Merge pull request #13 from casvanluijtelaar/autoscroll_respect_reverse
Browse files Browse the repository at this point in the history
fix autoscrolling for non standard orientations
  • Loading branch information
casvanluijtelaar committed Jun 10, 2022
2 parents 6a2c590 + a55b5e2 commit 4de4eda
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 75 deletions.
16 changes: 6 additions & 10 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,14 @@ jobs:
- name: Install dependencies
run: flutter pub get

# Uncomment this step to verify the use of 'dart format' on each commit.
# - name: Verify formatting
# run: dart format --output=none --set-exit-if-changed .
- name: Verify formatting
run: dart format --output=none --set-exit-if-changed .

# Your project will need to have tests in test/ and a dependency on
# package:test for this step to succeed. Note that Flutter projects will
# want to change this to 'flutter test'.
- name: Run tests
run: flutter test --coverage

- uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
verbose: true # optional (default = false)
# - uses: codecov/codecov-action@v1
# with:
# token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
# verbose: true # optional (default = false)

102 changes: 72 additions & 30 deletions lib/src/reorderable_grid.dart
Original file line number Diff line number Diff line change
Expand Up @@ -263,8 +263,10 @@ class ReorderableGridState extends State<ReorderableGrid> {
itemCount: widget.itemCount,
onReorder: widget.onReorder,
proxyDecorator: widget.proxyDecorator,
reverse: widget.reverse,
autoScroll: widget.autoScroll ??
widget.physics is! NeverScrollableScrollPhysics,
scrollDirection: widget.scrollDirection,
),
),
],
Expand Down Expand Up @@ -303,8 +305,10 @@ class SliverReorderableGrid extends StatefulWidget {
required this.itemCount,
required this.onReorder,
required this.gridDelegate,
this.reverse = false,
this.proxyDecorator,
this.autoScroll = true,
this.scrollDirection = Axis.vertical,
}) : assert(itemCount >= 0),
super(key: key);

Expand All @@ -326,6 +330,12 @@ class SliverReorderableGrid extends StatefulWidget {
/// physics are [NeverScrollableScrollPhysics]
final bool autoScroll;

/// {@macro flutter.widgets.scroll_view.reverse}
final bool reverse;

/// {@macro flutter.widgets.scroll_view.scrollDirection}
final Axis scrollDirection;

@override
SliverReorderableGridState createState() => SliverReorderableGridState();

Expand Down Expand Up @@ -357,11 +367,8 @@ class SliverReorderableGrid extends StatefulWidget {
'No SliverReorderableGrid ancestor could be found starting from the context that was passed to SliverReorderableGrid.of().',
),
ErrorHint(
'This can happen when the context provided is from the same StatefulWidget that '
'built the SliverReorderableGrid. Please see the SliverReorderableGrid documentation for examples '
'of how to refer to an SliverReorderableGrid object:\n'
' https://api.flutter.dev/flutter/widgets/SliverReorderableGridState-class.html',
),
'This can happen when the context provided is from the same StatefulWidget that '
'built the SliverReorderableGrid. Please see the SliverReorderableGrid documentation for examples'),
context.describeElement('The context used was'),
]);
}
Expand Down Expand Up @@ -608,35 +615,70 @@ class SliverReorderableGridState extends State<SliverReorderableGrid>
return;
}

final ScrollPosition position = _dragInfo!.scrollable!.position;
final position = _dragInfo!.scrollable!.position;
double? newOffset;

const Duration duration = Duration(milliseconds: 14);
const double step = 1.0;
const double overDragMax = 20.0;
const double overDragCoef = 10;
const duration = Duration(milliseconds: 14);
const step = 1.0;
const overDragMax = 20.0;
const overDragCoef = 10;

final isVertical = widget.scrollDirection == Axis.vertical;
final isReversed = widget.reverse;

final RenderBox scrollRenderBox =
/// get the scroll window position on the screen
final scrollRenderBox =
_dragInfo!.scrollable!.context.findRenderObject()! as RenderBox;
final Offset scrollOrigin = scrollRenderBox.localToGlobal(Offset.zero);

final scrollStart = scrollOrigin.dy;
final scrollEnd = scrollStart + scrollRenderBox.size.height;

final double proxyStart =
(_dragInfo!.dragPosition - _dragInfo!.dragOffset).dy;
final double proxyEnd = proxyStart + _dragInfo!.itemSize.height;

if (proxyStart < scrollStart &&
position.pixels > position.minScrollExtent) {
final double overDrag = max(scrollStart - proxyStart, overDragMax);
newOffset = max(position.minScrollExtent,
position.pixels - step * overDrag / overDragCoef);
} else if (proxyEnd > scrollEnd &&
position.pixels < position.maxScrollExtent) {
final double overDrag = max(proxyEnd - scrollEnd, overDragMax);
newOffset = min(position.maxScrollExtent,
position.pixels + step * overDrag / overDragCoef);
final Offset scrollPosition = scrollRenderBox.localToGlobal(Offset.zero);

/// calculate the start and end position for the scroll window
double scrollWindowStart =
isVertical ? scrollPosition.dy : scrollPosition.dx;
double scrollWindowEnd = scrollWindowStart +
(isVertical ? scrollRenderBox.size.height : scrollRenderBox.size.width);

/// get the proxy (dragged) object's position on the screen
final proxyObjectPosition = _dragInfo!.dragPosition - _dragInfo!.dragOffset;

/// calculate the start and end position for the proxy object
double proxyObjectStart =
isVertical ? proxyObjectPosition.dy : proxyObjectPosition.dx;
double proxyObjectEnd = proxyObjectStart +
(isVertical ? _dragInfo!.itemSize.height : _dragInfo!.itemSize.width);


if (!isReversed) {
/// if start of proxy object is before scroll window
if (proxyObjectStart < scrollWindowStart &&
position.pixels > position.minScrollExtent) {
final overDrag = max(scrollWindowStart - proxyObjectStart, overDragMax);
newOffset = max(position.minScrollExtent,
position.pixels - step * overDrag / overDragCoef);
}

/// if end of proxy object is after scroll window
else if (proxyObjectEnd > scrollWindowEnd &&
position.pixels < position.maxScrollExtent) {
final overDrag = max(proxyObjectEnd - scrollWindowEnd, overDragMax);
newOffset = min(position.maxScrollExtent,
position.pixels + step * overDrag / overDragCoef);
}
} else {
/// if start of proxy object is before scroll window
if (proxyObjectStart < scrollWindowStart &&
position.pixels < position.maxScrollExtent) {
final overDrag = max(scrollWindowStart - proxyObjectStart, overDragMax);
newOffset = max(position.minScrollExtent,
position.pixels + step * overDrag / overDragCoef);
}

/// if end of proxy object is after scroll window
else if (proxyObjectEnd > scrollWindowEnd &&
position.pixels > position.minScrollExtent) {
final overDrag = max(proxyObjectEnd - scrollWindowEnd, overDragMax);
newOffset = min(position.maxScrollExtent,
position.pixels - step * overDrag / overDragCoef);
}
}

if (newOffset != null && (newOffset - position.pixels).abs() >= 1.0) {
Expand Down
2 changes: 2 additions & 0 deletions lib/src/reorderable_grid_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,8 @@ class _ReorderableGridViewState extends State<ReorderableGridView> {
proxyDecorator: widget.proxyDecorator ?? _proxyDecorator,
autoScroll: widget.autoScroll ??
widget.physics is! NeverScrollableScrollPhysics,
scrollDirection: widget.scrollDirection,
reverse: widget.reverse,
),
),
],
Expand Down
1 change: 1 addition & 0 deletions test/src/reorderable_grid_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@ class _TestGridState extends State<TestGrid> {
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: widget.crossAxisCount,
),
reverse: widget.reverse,
itemBuilder: (BuildContext context, int index) {
return Container(
key: ValueKey<int>(items[index]),
Expand Down
74 changes: 39 additions & 35 deletions test/src/reorderable_grid_view_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,44 +6,48 @@ import 'package:reorderable_grid/reorderable_grid.dart';
void main() {
const double itemHeight = 48.0;

testWidgets('ReorderableGridView.builder asserts on negative childCount',
(WidgetTester tester) async {
expect(
() => ReorderableGridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
testWidgets(
'ReorderableGridView.builder asserts on negative childCount',
(WidgetTester tester) async {
expect(
() => ReorderableGridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
),
itemBuilder: (BuildContext context, int index) {
return const SizedBox();
},
itemCount: -1,
onReorder: (int from, int to) {},
),
itemBuilder: (BuildContext context, int index) {
return const SizedBox();
},
itemCount: -1,
onReorder: (int from, int to) {},
),
throwsAssertionError);
});
throwsAssertionError);
},
);

testWidgets('ReorderableGridView.builder only creates the children it needs',
(WidgetTester tester) async {
final Set<int> itemsCreated = <int>{};
await tester.pumpWidget(MaterialApp(
home: ReorderableGridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
testWidgets(
'ReorderableGridView.builder only creates the children it needs',
(WidgetTester tester) async {
final Set<int> itemsCreated = <int>{};
await tester.pumpWidget(MaterialApp(
home: ReorderableGridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
),
itemBuilder: (BuildContext context, int index) {
itemsCreated.add(index);
return Text(index.toString(), key: ValueKey<int>(index));
},
itemCount: 1000,
onReorder: (int from, int to) {},
),
itemBuilder: (BuildContext context, int index) {
itemsCreated.add(index);
return Text(index.toString(), key: ValueKey<int>(index));
},
itemCount: 1000,
onReorder: (int from, int to) {},
),
));

expect(itemsCreated, <int>{
...{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
...{10, 11, 12, 13, 14, 15, 16, 17, 18, 19},
});
});
));

expect(itemsCreated, <int>{
...{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
...{10, 11, 12, 13, 14, 15, 16, 17, 18, 19},
});
},
);

testWidgets('Animation test when placing an item in place',
(WidgetTester tester) async {
Expand Down

0 comments on commit 4de4eda

Please sign in to comment.