moved scroll function out of scaling gesture detector

This commit is contained in:
Thibault Deckers 2020-05-07 15:38:27 +09:00
parent 81f72d8322
commit 8b06e6c86c
3 changed files with 76 additions and 38 deletions

View file

@ -89,11 +89,14 @@ class SectionedListLayoutProvider extends StatelessWidget {
final minEntryIndex = sectionChildIndex * columnCount;
final maxEntryIndex = min(sectionEntryCount, minEntryIndex + columnCount);
final ids = <int>[];
final children = <Widget>[];
for (var i = minEntryIndex; i < maxEntryIndex; i++) {
final entry = section[i];
final id = entry.contentId;
ids.add(id);
children.add(GridThumbnail(
key: ValueKey(entry.contentId),
key: ValueKey(id),
collection: collection,
index: i,
entry: entry,
@ -101,6 +104,7 @@ class SectionedListLayoutProvider extends StatelessWidget {
));
}
return Row(
key: ValueKey(ids.join('-')),
mainAxisSize: MainAxisSize.min,
children: children,
);
@ -136,7 +140,6 @@ class SectionedListLayout {
final left = tileExtent * column;
final top = sectionLayout.indexToLayoutOffset(listIndex);
debugPrint('TLAD getTileRect sectionKey=$sectionKey sectionOffset=${sectionLayout.minOffset} top=$top row=$row column=$column for title=${entry.bestTitle}');
return Rect.fromLTWH(left, top, tileExtent, tileExtent);
}
}

View file

@ -2,29 +2,27 @@ import 'dart:math';
import 'dart:ui' as ui;
import 'package:aves/model/image_entry.dart';
import 'package:aves/widgets/album/grid/list_section_layout.dart';
import 'package:aves/widgets/album/grid/list_sliver.dart';
import 'package:aves/widgets/album/grid/tile_extent_manager.dart';
import 'package:aves/widgets/album/thumbnail/decorated.dart';
import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:provider/provider.dart';
class GridScaleGestureDetector extends StatefulWidget {
final GlobalKey scrollableKey;
final ValueNotifier<double> appBarHeightNotifier;
final ValueNotifier<double> extentNotifier;
final Size mqSize;
final double mqHorizontalPadding;
final void Function(ImageEntry entry) onScaled;
final Widget child;
const GridScaleGestureDetector({
this.scrollableKey,
@required this.appBarHeightNotifier,
@required this.extentNotifier,
@required this.mqSize,
@required this.mqHorizontalPadding,
@required this.onScaled,
@required this.child,
});
@ -38,7 +36,6 @@ class _GridScaleGestureDetectorState extends State<GridScaleGestureDetector> {
ValueNotifier<double> _scaledExtentNotifier;
OverlayEntry _overlayEntry;
ThumbnailMetadata _metadata;
RenderViewport _renderViewport;
ValueNotifier<double> get tileExtentNotifier => widget.extentNotifier;
@ -64,7 +61,6 @@ class _GridScaleGestureDetectorState extends State<GridScaleGestureDetector> {
final renderMetaData = firstOf<RenderMetaData>(result);
// abort if we cannot find an image to show on overlay
if (renderMetaData == null) return;
_renderViewport = firstOf<RenderViewport>(result);
_metadata = renderMetaData.metaData;
_startExtent = tileExtentNotifier.value;
_scaledExtentNotifier = ValueNotifier(_startExtent);
@ -113,21 +109,8 @@ class _GridScaleGestureDetectorState extends State<GridScaleGestureDetector> {
_applyingScale = false;
} else {
// scroll to show the focal point thumbnail at its new position
final viewportClosure = _renderViewport;
WidgetsBinding.instance.addPostFrameCallback((_) {
// about scrolling & offset retrieval:
// `Scrollable.ensureVisible` only works on already rendered objects
// `RenderViewport.showOnScreen` can find any `RenderSliver`, but not always a `RenderMetadata`
// `RenderViewport.scrollOffsetOf` is a good alternative
final scrollableContext = widget.scrollableKey.currentContext;
final gridSize = (scrollableContext.findRenderObject() as RenderBox).size;
final sectionLayout = Provider.of<SectionedListLayout>(context, listen: false);
final tileRect = sectionLayout.getTileRect(_metadata.entry) ?? Rect.zero;
// most of the time the app bar will be scrolled away after scaling,
// so we compensate for it to center the focal point thumbnail
final appBarHeight = widget.appBarHeightNotifier.value;
final scrollOffset = tileRect.top + (tileRect.height - gridSize.height) / 2 + appBarHeight;
viewportClosure.offset.jumpTo(max(.0, scrollOffset));
widget.onScaled(_metadata.entry);
_applyingScale = false;
});
}

View file

@ -1,6 +1,9 @@
import 'dart:math';
import 'package:aves/model/collection_lens.dart';
import 'package:aves/model/filters/favourite.dart';
import 'package:aves/model/filters/mime.dart';
import 'package:aves/model/image_entry.dart';
import 'package:aves/model/mime_types.dart';
import 'package:aves/widgets/album/app_bar.dart';
import 'package:aves/widgets/album/empty.dart';
@ -18,7 +21,6 @@ import 'package:tuple/tuple.dart';
class ThumbnailCollection extends StatelessWidget {
final ValueNotifier<double> _appBarHeightNotifier = ValueNotifier(0);
final ValueNotifier<double> _tileExtentNotifier = ValueNotifier(0);
final GlobalKey _scrollableKey = GlobalKey();
@override
Widget build(BuildContext context) {
@ -34,23 +36,25 @@ class ThumbnailCollection extends StatelessWidget {
// so that view updates on collection filter changes
return Consumer<CollectionLens>(
builder: (context, collection, child) {
final scrollView = _buildScrollView(collection);
final draggable = _buildDraggableScrollView(scrollView);
final scaler = GridScaleGestureDetector(
scrollableKey: _scrollableKey,
final appBar = CollectionAppBar(
appBarHeightNotifier: _appBarHeightNotifier,
extentNotifier: _tileExtentNotifier,
mqSize: mqSize,
mqHorizontalPadding: mqHorizontalPadding,
child: draggable,
collection: collection,
);
final sectionedListLayoutProvider = ValueListenableBuilder<double>(
valueListenable: _tileExtentNotifier,
builder: (context, tileExtent, child) => SectionedListLayoutProvider(
collection: collection,
scrollableWidth: mqSize.width - mqHorizontalPadding,
tileExtent: tileExtent,
child: scaler,
child: _ScalableThumbnailCollection(
appBarHeightNotifier: _appBarHeightNotifier,
tileExtentNotifier: _tileExtentNotifier,
collection: collection,
mqSize: mqSize,
mqHorizontalPadding: mqHorizontalPadding,
appBar: appBar,
),
),
);
return sectionedListLayoutProvider;
@ -60,8 +64,42 @@ class ThumbnailCollection extends StatelessWidget {
),
);
}
}
ScrollView _buildScrollView(CollectionLens collection) {
class _ScalableThumbnailCollection extends StatelessWidget {
final CollectionLens collection;
final ValueNotifier<double> appBarHeightNotifier;
final ValueNotifier<double> tileExtentNotifier;
final Size mqSize;
final double mqHorizontalPadding;
final Widget appBar;
final GlobalKey _scrollableKey = GlobalKey();
_ScalableThumbnailCollection({
@required this.appBarHeightNotifier,
@required this.tileExtentNotifier,
@required this.collection,
@required this.mqSize,
@required this.mqHorizontalPadding,
@required this.appBar,
});
@override
Widget build(BuildContext context) {
final scrollView = _buildScrollView(appBar, collection);
final draggable = _buildDraggableScrollView(scrollView);
return GridScaleGestureDetector(
scrollableKey: _scrollableKey,
extentNotifier: tileExtentNotifier,
mqSize: mqSize,
mqHorizontalPadding: mqHorizontalPadding,
onScaled: (entry) => _scrollToEntry(context, entry),
child: draggable,
);
}
ScrollView _buildScrollView(Widget appBar, CollectionLens collection) {
return CustomScrollView(
key: _scrollableKey,
primary: true,
@ -69,10 +107,7 @@ class ThumbnailCollection extends StatelessWidget {
// when there is no content and we use `SliverFillRemaining`
physics: collection.isEmpty ? const NeverScrollableScrollPhysics() : null,
slivers: [
CollectionAppBar(
appBarHeightNotifier: _appBarHeightNotifier,
collection: collection,
),
appBar,
collection.isEmpty
? SliverFillRemaining(
child: _buildEmptyCollectionPlaceholder(collection),
@ -93,7 +128,7 @@ class ThumbnailCollection extends StatelessWidget {
Widget _buildDraggableScrollView(ScrollView scrollView) {
return ValueListenableBuilder<double>(
valueListenable: _appBarHeightNotifier,
valueListenable: appBarHeightNotifier,
builder: (context, appBarHeight, child) => Selector<MediaQueryData, double>(
selector: (context, mq) => mq.viewInsets.bottom,
builder: (context, mqViewInsetsBottom, child) => DraggableScrollbar(
@ -128,4 +163,21 @@ class ThumbnailCollection extends StatelessWidget {
)
: const EmptyContent();
}
// about scrolling & offset retrieval:
// `Scrollable.ensureVisible` only works on already rendered objects
// `RenderViewport.showOnScreen` can find any `RenderSliver`, but not always a `RenderMetadata`
// `RenderViewport.scrollOffsetOf` is a good alternative
void _scrollToEntry(BuildContext context, ImageEntry entry) {
final scrollableContext = _scrollableKey.currentContext;
final scrollableHeight = (scrollableContext.findRenderObject() as RenderBox).size.height;
final sectionLayout = Provider.of<SectionedListLayout>(context, listen: false);
final tileRect = sectionLayout.getTileRect(entry) ?? Rect.zero;
// most of the time the app bar will be scrolled away after scaling,
// so we compensate for it to center the focal point thumbnail
final appBarHeight = appBarHeightNotifier.value;
final scrollOffset = tileRect.top + (tileRect.height - scrollableHeight) / 2 + appBarHeight;
PrimaryScrollController.of(context)?.jumpTo(max(.0, scrollOffset));
}
}