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

fix autoscrolling for non standard orientations #13

Merged
merged 5 commits into from
Jun 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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