grid padding

This commit is contained in:
Thibault Deckers 2022-03-25 17:46:24 +09:00
parent 3a81d6c655
commit 5a5e8d6728
7 changed files with 69 additions and 35 deletions

View file

@ -70,6 +70,7 @@ class _CollectionGridState extends State<CollectionGrid> {
extentMin: CollectionGrid.extentMin,
extentMax: CollectionGrid.extentMax,
spacing: CollectionGrid.spacing,
horizontalPadding: 2,
);
return TileExtentControllerProvider(
controller: _tileExtentController!,
@ -90,12 +91,13 @@ class _CollectionGridContent extends StatelessWidget {
final sectionedListLayoutProvider = ValueListenableBuilder<double>(
valueListenable: context.select<TileExtentController, ValueNotifier<double>>((controller) => controller.extentNotifier),
builder: (context, thumbnailExtent, child) {
return Selector<TileExtentController, Tuple3<double, int, double>>(
selector: (context, c) => Tuple3(c.viewportSize.width, c.columnCount, c.spacing),
return Selector<TileExtentController, Tuple4<double, int, double, double>>(
selector: (context, c) => Tuple4(c.viewportSize.width, c.columnCount, c.spacing, c.horizontalPadding),
builder: (context, c, child) {
final scrollableWidth = c.item1;
final columnCount = c.item2;
final tileSpacing = c.item3;
final horizontalPadding = c.item4;
return GridTheme(
extent: thumbnailExtent,
child: EntryListDetailsTheme(
@ -117,6 +119,7 @@ class _CollectionGridContent extends StatelessWidget {
tileLayout: tileLayout,
columnCount: columnCount,
spacing: tileSpacing,
horizontalPadding: horizontalPadding,
tileExtent: thumbnailExtent,
tileBuilder: (entry) => AnimatedBuilder(
animation: favourites,
@ -242,7 +245,9 @@ class _CollectionScaler extends StatelessWidget {
@override
Widget build(BuildContext context) {
final tileSpacing = context.select<TileExtentController, double>((controller) => controller.spacing);
final metrics = context.select<TileExtentController, Tuple2<double, double>>((v) => Tuple2(v.spacing, v.horizontalPadding));
final tileSpacing = metrics.item1;
final horizontalPadding = metrics.item2;
return GridScaleGestureDetector<AvesEntry>(
scrollableKey: scrollableKey,
tileLayout: tileLayout,
@ -253,6 +258,7 @@ class _CollectionScaler extends StatelessWidget {
tileCenter: center,
tileSize: tileSize,
spacing: tileSpacing,
horizontalPadding: horizontalPadding,
borderWidth: DecoratedThumbnail.borderWidth,
borderRadius: Radius.zero,
color: DecoratedThumbnail.borderColor,

View file

@ -16,6 +16,7 @@ class SectionedEntryListLayoutProvider extends SectionedListLayoutProvider<AvesE
required TileLayout tileLayout,
required int columnCount,
required double spacing,
required double horizontalPadding,
required double tileExtent,
required Widget Function(AvesEntry entry) tileBuilder,
required Duration tileAnimationDelay,
@ -26,6 +27,7 @@ class SectionedEntryListLayoutProvider extends SectionedListLayoutProvider<AvesE
tileLayout: tileLayout,
columnCount: columnCount,
spacing: spacing,
horizontalPadding: horizontalPadding,
tileWidth: tileExtent,
tileHeight: tileExtent,
tileBuilder: tileBuilder,

View file

@ -108,19 +108,26 @@ class _GridScaleGestureDetectorState<T> extends State<GridScaleGestureDetector<T
_startSize = renderMetaData.size;
_scaledSizeNotifier = ValueNotifier(_startSize!);
// not the same as `MediaQuery.size.width`, because of screen insets/padding
final gridWidth = scrollableBox.size.width;
// not the same as `MediaQuery` metrics, because of screen insets/padding
final scrollViewWidth = scrollableBox.size.width;
final scrollViewXMin = scrollableBox.localToGlobal(Offset.zero).dx;
final scrollViewXMax = scrollableBox.localToGlobal(Offset(scrollViewWidth, 0)).dx;
final horizontalPadding = tileExtentController.horizontalPadding;
final xMin = scrollViewXMin + horizontalPadding;
final xMax = scrollViewXMax - horizontalPadding;
_extentMin = tileExtentController.effectiveExtentMin;
_extentMax = tileExtentController.effectiveExtentMax;
final halfSize = _startSize! / 2;
final tileCenter = renderMetaData.localToGlobal(Offset(halfSize.width, halfSize.height));
final tileLayout = widget.tileLayout;
_overlayEntry = OverlayEntry(
builder: (context) => _ScaleOverlay(
builder: (scaledTileSize) {
late final double themeExtent;
switch (widget.tileLayout) {
switch (tileLayout) {
case TileLayout.grid:
themeExtent = scaledTileSize.width;
break;
@ -136,9 +143,10 @@ class _GridScaleGestureDetectorState<T> extends State<GridScaleGestureDetector<T
),
);
},
tileLayout: widget.tileLayout,
tileLayout: tileLayout,
center: tileCenter,
viewportWidth: gridWidth,
xMin: xMin,
xMax: xMax,
gridBuilder: widget.gridBuilder,
scaledSizeNotifier: _scaledSizeNotifier!,
),
@ -220,7 +228,7 @@ class _ScaleOverlay extends StatefulWidget {
final Widget Function(Size scaledTileSize) builder;
final TileLayout tileLayout;
final Offset center;
final double viewportWidth;
final double xMin, xMax;
final ValueNotifier<Size> scaledSizeNotifier;
final Widget Function(Offset center, Size tileSize, Widget child) gridBuilder;
@ -229,7 +237,8 @@ class _ScaleOverlay extends StatefulWidget {
required this.builder,
required this.tileLayout,
required this.center,
required this.viewportWidth,
required this.xMin,
required this.xMax,
required this.scaledSizeNotifier,
required this.gridBuilder,
}) : super(key: key);
@ -243,7 +252,9 @@ class _ScaleOverlayState extends State<_ScaleOverlay> {
Offset get center => widget.center;
double get gridWidth => widget.viewportWidth;
double get xMin => widget.xMin;
double get xMax => widget.xMax;
// `Color(0x00FFFFFF)` is different from `Color(0x00000000)` (or `Colors.transparent`)
// when used in gradients or lerping to it
@ -269,8 +280,6 @@ class _ScaleOverlayState extends State<_ScaleOverlay> {
final width = scaledSize.width;
final height = scaledSize.height;
// keep scaled thumbnail within the screen
final xMin = context.select<MediaQueryData, double>((mq) => mq.padding.left);
final xMax = xMin + gridWidth;
var dx = .0;
if (center.dx - width / 2 < xMin) {
dx = xMin - (center.dx - width / 2);
@ -309,7 +318,7 @@ class _ScaleOverlayState extends State<_ScaleOverlay> {
gradientCenter = center;
break;
case TileLayout.list:
gradientCenter = Offset(context.isRtl ? gridWidth : 0, center.dy);
gradientCenter = Offset(context.isRtl ? xMax : xMin, center.dy);
break;
}
@ -351,7 +360,7 @@ class GridPainter extends CustomPainter {
final TileLayout tileLayout;
final Offset tileCenter;
final Size tileSize;
final double spacing, borderWidth;
final double spacing, horizontalPadding, borderWidth;
final Radius borderRadius;
final Color color;
final TextDirection textDirection;
@ -361,6 +370,7 @@ class GridPainter extends CustomPainter {
required this.tileCenter,
required this.tileSize,
required this.spacing,
required this.horizontalPadding,
required this.borderWidth,
required this.borderRadius,
required this.color,
@ -394,7 +404,7 @@ class GridPainter extends CustomPainter {
case TileLayout.list:
chipSize = Size.square(tileSize.shortestSide);
final chipCenterToEdge = chipSize.width / 2;
chipCenter = Offset(textDirection == TextDirection.rtl ? size.width - chipCenterToEdge : chipCenterToEdge, tileCenter.dy);
chipCenter = Offset(textDirection == TextDirection.rtl ? size.width - (chipCenterToEdge + horizontalPadding) : chipCenterToEdge + horizontalPadding, tileCenter.dy);
deltaColumn = 0;
strokeShader = ui.Gradient.linear(
tileCenter - Offset(0, chipSize.shortestSide * 3),

View file

@ -15,7 +15,7 @@ abstract class SectionedListLayoutProvider<T> extends StatelessWidget {
final double scrollableWidth;
final TileLayout tileLayout;
final int columnCount;
final double spacing, tileWidth, tileHeight;
final double spacing, horizontalPadding, tileWidth, tileHeight;
final Widget Function(T item) tileBuilder;
final Duration tileAnimationDelay;
final Widget child;
@ -26,6 +26,7 @@ abstract class SectionedListLayoutProvider<T> extends StatelessWidget {
required this.tileLayout,
required int columnCount,
required this.spacing,
required this.horizontalPadding,
required double tileWidth,
required this.tileHeight,
required this.tileBuilder,
@ -33,7 +34,7 @@ abstract class SectionedListLayoutProvider<T> extends StatelessWidget {
required this.child,
}) : assert(scrollableWidth != 0),
columnCount = tileLayout == TileLayout.list ? 1 : columnCount,
tileWidth = tileLayout == TileLayout.list ? scrollableWidth : tileWidth,
tileWidth = tileLayout == TileLayout.list ? scrollableWidth - (horizontalPadding * 2) : tileWidth,
super(key: key);
@override
@ -98,6 +99,7 @@ abstract class SectionedListLayoutProvider<T> extends StatelessWidget {
tileWidth: tileWidth,
tileHeight: tileHeight,
spacing: spacing,
horizontalPadding: horizontalPadding,
sectionLayouts: sectionLayouts,
);
}
@ -129,12 +131,15 @@ abstract class SectionedListLayoutProvider<T> extends StatelessWidget {
);
children.add(animate ? _buildAnimation(context, itemGridIndex, item) : item);
}
return _GridRow(
width: tileWidth,
height: tileHeight,
spacing: spacing,
textDirection: Directionality.of(context),
children: children,
return Padding(
padding: EdgeInsets.symmetric(horizontal: horizontalPadding),
child: _GridRow(
width: tileWidth,
height: tileHeight,
spacing: spacing,
textDirection: Directionality.of(context),
children: children,
),
);
}
@ -168,6 +173,7 @@ abstract class SectionedListLayoutProvider<T> extends StatelessWidget {
properties.add(DoubleProperty('scrollableWidth', scrollableWidth));
properties.add(IntProperty('columnCount', columnCount));
properties.add(DoubleProperty('spacing', spacing));
properties.add(DoubleProperty('horizontalPadding', horizontalPadding));
properties.add(DoubleProperty('tileWidth', tileWidth));
properties.add(DoubleProperty('tileHeight', tileHeight));
properties.add(DiagnosticsProperty<bool>('showHeaders', showHeaders));
@ -178,7 +184,7 @@ class SectionedListLayout<T> {
final Map<SectionKey, List<T>> sections;
final bool showHeaders;
final int columnCount;
final double tileWidth, tileHeight, spacing;
final double tileWidth, tileHeight, spacing, horizontalPadding;
final List<SectionLayout> sectionLayouts;
const SectionedListLayout({
@ -188,6 +194,7 @@ class SectionedListLayout<T> {
required this.tileWidth,
required this.tileHeight,
required this.spacing,
required this.horizontalPadding,
required this.sectionLayouts,
});
@ -205,7 +212,7 @@ class SectionedListLayout<T> {
final row = (sectionItemIndex / columnCount).floor();
final listIndex = sectionLayout.firstIndex + 1 + row;
final left = tileWidth * column + spacing * (column - 1);
final left = horizontalPadding + tileWidth * column + spacing * (column - 1);
final top = sectionLayout.indexToLayoutOffset(listIndex);
return Rect.fromLTWH(left, top, tileWidth, tileHeight);
}
@ -225,7 +232,7 @@ class SectionedListLayout<T> {
if (dy < 0) return null;
final row = dy ~/ (tileHeight + spacing);
final column = position.dx ~/ (tileWidth + spacing);
final column = max(0, position.dx - horizontalPadding) ~/ (tileWidth + spacing);
final index = row * columnCount + column;
if (index >= section.length) return null;

View file

@ -7,7 +7,7 @@ import 'package:flutter/widgets.dart';
class TileExtentController {
final String settingsRouteKey;
final int columnCountMin, columnCountDefault;
final double spacing, extentMin, extentMax;
final double extentMin, extentMax, spacing, horizontalPadding;
final ValueNotifier<double> extentNotifier = ValueNotifier(0);
late double userPreferredExtent;
@ -22,6 +22,7 @@ class TileExtentController {
required this.extentMin,
required this.extentMax,
required this.spacing,
required this.horizontalPadding,
}) {
userPreferredExtent = settings.getTileExtent(settingsRouteKey);
settings.addListener(_onSettingsChanged);
@ -68,11 +69,11 @@ class TileExtentController {
return newExtent;
}
double _extentMax() => min(extentMax, (viewportSize.shortestSide - spacing * (columnCountMin - 1)) / columnCountMin);
double _extentMax() => min(extentMax, (viewportSize.shortestSide - (horizontalPadding * 2) - spacing * (columnCountMin - 1)) / columnCountMin);
double _columnCountForExtent(double extent) => (viewportSize.width + spacing) / (extent + spacing);
double _columnCountForExtent(double extent) => (viewportSize.width - (horizontalPadding * 2) + spacing) / (extent + spacing);
double _extentForColumnCount(int columnCount) => (viewportSize.width - spacing * (columnCount - 1)) / columnCount;
double _extentForColumnCount(int columnCount) => (viewportSize.width - (horizontalPadding * 2) - spacing * (columnCount - 1)) / columnCount;
int _effectiveColumnCountMin() => _columnCountForExtent(_extentMax()).ceil();
@ -94,7 +95,7 @@ class TileExtentController {
Duration getTileAnimationDelay(Duration pageTarget) {
final extent = extentNotifier.value;
final columnCount = ((viewportSize.width + spacing) / (extent + spacing)).round();
final columnCount = ((viewportSize.width - (horizontalPadding * 2) + spacing) / (extent + spacing)).round();
final rowCount = (viewportSize.height + spacing) ~/ (extent + spacing);
return pageTarget ~/ (columnCount + rowCount) * timeDilation;
}

View file

@ -164,6 +164,7 @@ class _FilterGridState<T extends CollectionFilter> extends State<FilterGrid<T>>
extentMin: 60,
extentMax: 300,
spacing: 8,
horizontalPadding: 2,
);
return TileExtentControllerProvider(
controller: _tileExtentController!,
@ -237,12 +238,13 @@ class _FilterGridContent<T extends CollectionFilter> extends StatelessWidget {
final sectionedListLayoutProvider = ValueListenableBuilder<double>(
valueListenable: context.select<TileExtentController, ValueNotifier<double>>((controller) => controller.extentNotifier),
builder: (context, thumbnailExtent, child) {
return Selector<TileExtentController, Tuple3<double, int, double>>(
selector: (context, c) => Tuple3(c.viewportSize.width, c.columnCount, c.spacing),
return Selector<TileExtentController, Tuple4<double, int, double, double>>(
selector: (context, c) => Tuple4(c.viewportSize.width, c.columnCount, c.spacing, c.horizontalPadding),
builder: (context, c, child) {
final scrollableWidth = c.item1;
final columnCount = c.item2;
final tileSpacing = c.item3;
final horizontalPadding = c.item4;
// do not listen for animation delay change
final target = context.read<DurationsData>().staggeredAnimationPageTarget;
final tileAnimationDelay = context.read<TileExtentController>().getTileAnimationDelay(target);
@ -265,6 +267,7 @@ class _FilterGridContent<T extends CollectionFilter> extends StatelessWidget {
scrollableWidth: scrollableWidth,
columnCount: columnCount,
spacing: tileSpacing,
horizontalPadding: horizontalPadding,
tileWidth: thumbnailExtent,
tileHeight: tileHeight,
tileBuilder: (gridItem) {
@ -430,8 +433,10 @@ class _FilterScaler<T extends CollectionFilter> extends StatelessWidget {
@override
Widget build(BuildContext context) {
final tileSpacing = context.select<TileExtentController, double>((controller) => controller.spacing);
final textScaleFactor = context.select<MediaQueryData, double>((mq) => mq.textScaleFactor);
final metrics = context.select<TileExtentController, Tuple2<double, double>>((v) => Tuple2(v.spacing, v.horizontalPadding));
final tileSpacing = metrics.item1;
final horizontalPadding = metrics.item2;
return GridScaleGestureDetector<FilterGridItem<T>>(
scrollableKey: scrollableKey,
tileLayout: tileLayout,
@ -442,6 +447,7 @@ class _FilterScaler<T extends CollectionFilter> extends StatelessWidget {
tileCenter: center,
tileSize: tileSize,
spacing: tileSpacing,
horizontalPadding: horizontalPadding,
borderWidth: AvesFilterChip.outlineWidth,
borderRadius: CoveredFilterChip.radius(tileSize.shortestSide),
color: Colors.grey.shade700,

View file

@ -15,6 +15,7 @@ class SectionedFilterListLayoutProvider<T extends CollectionFilter> extends Sect
required TileLayout tileLayout,
required int columnCount,
required double spacing,
required double horizontalPadding,
required double tileWidth,
required double tileHeight,
required Widget Function(FilterGridItem<T> gridItem) tileBuilder,
@ -26,6 +27,7 @@ class SectionedFilterListLayoutProvider<T extends CollectionFilter> extends Sect
tileLayout: tileLayout,
columnCount: columnCount,
spacing: spacing,
horizontalPadding: horizontalPadding,
tileWidth: tileWidth,
tileHeight: tileHeight,
tileBuilder: tileBuilder,