176 lines
7.2 KiB
Dart
176 lines
7.2 KiB
Dart
import 'dart:math' as math;
|
|
import 'dart:ui' show lerpDouble;
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
|
|
class SliverTransitionGridDelegateWithCrossAxisCount extends SliverGridDelegate {
|
|
const SliverTransitionGridDelegateWithCrossAxisCount({
|
|
@required this.crossAxisCount,
|
|
this.mainAxisSpacing = 0.0,
|
|
this.crossAxisSpacing = 0.0,
|
|
this.childAspectRatio = 1.0,
|
|
}) : assert(crossAxisCount != null && crossAxisCount > 0),
|
|
assert(mainAxisSpacing != null && mainAxisSpacing >= 0),
|
|
assert(crossAxisSpacing != null && crossAxisSpacing >= 0),
|
|
assert(childAspectRatio != null && childAspectRatio > 0);
|
|
|
|
/// The number of children in the cross axis.
|
|
final double crossAxisCount;
|
|
|
|
/// The number of logical pixels between each child along the main axis.
|
|
final double mainAxisSpacing;
|
|
|
|
/// The number of logical pixels between each child along the cross axis.
|
|
final double crossAxisSpacing;
|
|
|
|
/// The ratio of the cross-axis to the main-axis extent of each child.
|
|
final double childAspectRatio;
|
|
|
|
@override
|
|
SliverGridLayout getLayout(SliverConstraints constraints) {
|
|
final t = crossAxisCount - crossAxisCount.truncateToDouble();
|
|
return SliverTransitionGridTileLayout(
|
|
current: _buildSettings(constraints, crossAxisCount),
|
|
floor: t != 0 ? _buildSettings(constraints, crossAxisCount.floorToDouble()) : null,
|
|
ceil: t != 0 ? _buildSettings(constraints, crossAxisCount.ceilToDouble()) : null,
|
|
t: t,
|
|
reverseCrossAxis: axisDirectionIsReversed(constraints.crossAxisDirection),
|
|
);
|
|
}
|
|
|
|
SliverTransitionGridTileLayoutSettings _buildSettings(SliverConstraints constraints, double crossAxisCount) {
|
|
final double usableCrossAxisExtent = constraints.crossAxisExtent - crossAxisSpacing * (crossAxisCount - 1);
|
|
final double childCrossAxisExtent = usableCrossAxisExtent / crossAxisCount;
|
|
final double childMainAxisExtent = childCrossAxisExtent / childAspectRatio;
|
|
final current = SliverTransitionGridTileLayoutSettings(
|
|
crossAxisCount: crossAxisCount,
|
|
mainAxisStride: childMainAxisExtent + mainAxisSpacing,
|
|
crossAxisStride: childCrossAxisExtent + crossAxisSpacing,
|
|
childMainAxisExtent: childMainAxisExtent,
|
|
childCrossAxisExtent: childCrossAxisExtent,
|
|
);
|
|
return current;
|
|
}
|
|
|
|
@override
|
|
bool shouldRelayout(SliverTransitionGridDelegateWithCrossAxisCount oldDelegate) {
|
|
return oldDelegate.crossAxisCount != crossAxisCount || oldDelegate.mainAxisSpacing != mainAxisSpacing || oldDelegate.crossAxisSpacing != crossAxisSpacing || oldDelegate.childAspectRatio != childAspectRatio;
|
|
}
|
|
}
|
|
|
|
class SliverTransitionGridTileLayoutSettings {
|
|
final double crossAxisCount;
|
|
|
|
/// The number of pixels from the leading edge of one tile to the leading edge
|
|
/// of the next tile in the main axis.
|
|
final double mainAxisStride;
|
|
|
|
/// The number of pixels from the leading edge of one tile to the leading edge
|
|
/// of the next tile in the cross axis.
|
|
final double crossAxisStride;
|
|
|
|
/// The number of pixels from the leading edge of one tile to the trailing
|
|
/// edge of the same tile in the main axis.
|
|
final double childMainAxisExtent;
|
|
|
|
/// The number of pixels from the leading edge of one tile to the trailing
|
|
/// edge of the same tile in the cross axis.
|
|
final double childCrossAxisExtent;
|
|
|
|
const SliverTransitionGridTileLayoutSettings({
|
|
@required this.crossAxisCount,
|
|
@required this.mainAxisStride,
|
|
@required this.crossAxisStride,
|
|
@required this.childMainAxisExtent,
|
|
@required this.childCrossAxisExtent,
|
|
}) : assert(crossAxisCount != null && crossAxisCount > 0),
|
|
assert(mainAxisStride != null && mainAxisStride >= 0),
|
|
assert(crossAxisStride != null && crossAxisStride >= 0),
|
|
assert(childMainAxisExtent != null && childMainAxisExtent >= 0),
|
|
assert(childCrossAxisExtent != null && childCrossAxisExtent >= 0);
|
|
}
|
|
|
|
class SliverTransitionGridTileLayout extends SliverGridLayout {
|
|
/// Creates a layout that uses equally sized and spaced tiles.
|
|
///
|
|
/// All of the arguments must not be null and must not be negative. The
|
|
/// `crossAxisCount` argument must be greater than zero.
|
|
const SliverTransitionGridTileLayout({
|
|
@required this.current,
|
|
this.floor,
|
|
this.ceil,
|
|
this.t = 0,
|
|
@required this.reverseCrossAxis,
|
|
}) : assert(reverseCrossAxis != null);
|
|
|
|
final SliverTransitionGridTileLayoutSettings current, floor, ceil;
|
|
final double t;
|
|
|
|
/// Whether the children should be placed in the opposite order of increasing
|
|
/// coordinates in the cross axis.
|
|
///
|
|
/// For example, if the cross axis is horizontal, the children are placed from
|
|
/// left to right when [reverseCrossAxis] is false and from right to left when
|
|
/// [reverseCrossAxis] is true.
|
|
///
|
|
/// Typically set to the return value of [axisDirectionIsReversed] applied to
|
|
/// the [SliverConstraints.crossAxisDirection].
|
|
final bool reverseCrossAxis;
|
|
|
|
@override
|
|
int getMinChildIndexForScrollOffset(double scrollOffset) {
|
|
final settings = t == 0 ? current : floor;
|
|
final index = settings.mainAxisStride > 0.0 ? (settings.crossAxisCount * (scrollOffset ~/ settings.mainAxisStride)).floor() : 0;
|
|
return index;
|
|
}
|
|
|
|
@override
|
|
int getMaxChildIndexForScrollOffset(double scrollOffset) {
|
|
final settings = t == 0 ? current : floor;
|
|
if (settings.mainAxisStride > 0.0) {
|
|
final int mainAxisCount = (scrollOffset / settings.mainAxisStride).ceil();
|
|
final index = math.max(0, settings.crossAxisCount * mainAxisCount - 1).ceil();
|
|
return index;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
double _getScrollOffset(int index, SliverTransitionGridTileLayoutSettings settings) {
|
|
return (index ~/ settings.crossAxisCount) * settings.mainAxisStride;
|
|
}
|
|
|
|
double _getCrossAxisOffset(int index, SliverTransitionGridTileLayoutSettings settings) {
|
|
final double crossAxisStart = (index % settings.crossAxisCount) * settings.crossAxisStride;
|
|
if (reverseCrossAxis) {
|
|
return settings.crossAxisCount * settings.crossAxisStride - crossAxisStart - settings.childCrossAxisExtent - (settings.crossAxisStride - settings.childCrossAxisExtent);
|
|
}
|
|
return crossAxisStart;
|
|
}
|
|
|
|
@override
|
|
SliverGridGeometry getGeometryForChildIndex(int index) {
|
|
return SliverGridGeometry(
|
|
scrollOffset: t == 0 ? _getScrollOffset(index, current) : lerpDouble(_getScrollOffset(index, floor), _getScrollOffset(index, ceil), t),
|
|
crossAxisOffset: t == 0 ? _getCrossAxisOffset(index, current) : lerpDouble(_getCrossAxisOffset(index, floor), _getCrossAxisOffset(index, ceil), t),
|
|
mainAxisExtent: current.childMainAxisExtent,
|
|
crossAxisExtent: current.childCrossAxisExtent,
|
|
);
|
|
}
|
|
|
|
@override
|
|
double computeMaxScrollOffset(int childCount) {
|
|
assert(childCount != null);
|
|
|
|
if (t != 0) {
|
|
final index = childCount - 1;
|
|
var maxScrollOffset = lerpDouble(_getScrollOffset(index, floor), _getScrollOffset(index, ceil), t) + current.mainAxisStride;
|
|
return maxScrollOffset;
|
|
}
|
|
|
|
final int mainAxisCount = ((childCount - 1) ~/ current.crossAxisCount) + 1;
|
|
final double mainAxisSpacing = current.mainAxisStride - current.childMainAxisExtent;
|
|
final maxScrollOffset = current.mainAxisStride * mainAxisCount - mainAxisSpacing;
|
|
return maxScrollOffset;
|
|
}
|
|
}
|