moved scroll function out of scaling gesture detector
This commit is contained in:
parent
81f72d8322
commit
8b06e6c86c
3 changed files with 76 additions and 38 deletions
|
@ -89,11 +89,14 @@ class SectionedListLayoutProvider extends StatelessWidget {
|
||||||
|
|
||||||
final minEntryIndex = sectionChildIndex * columnCount;
|
final minEntryIndex = sectionChildIndex * columnCount;
|
||||||
final maxEntryIndex = min(sectionEntryCount, minEntryIndex + columnCount);
|
final maxEntryIndex = min(sectionEntryCount, minEntryIndex + columnCount);
|
||||||
|
final ids = <int>[];
|
||||||
final children = <Widget>[];
|
final children = <Widget>[];
|
||||||
for (var i = minEntryIndex; i < maxEntryIndex; i++) {
|
for (var i = minEntryIndex; i < maxEntryIndex; i++) {
|
||||||
final entry = section[i];
|
final entry = section[i];
|
||||||
|
final id = entry.contentId;
|
||||||
|
ids.add(id);
|
||||||
children.add(GridThumbnail(
|
children.add(GridThumbnail(
|
||||||
key: ValueKey(entry.contentId),
|
key: ValueKey(id),
|
||||||
collection: collection,
|
collection: collection,
|
||||||
index: i,
|
index: i,
|
||||||
entry: entry,
|
entry: entry,
|
||||||
|
@ -101,6 +104,7 @@ class SectionedListLayoutProvider extends StatelessWidget {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
return Row(
|
return Row(
|
||||||
|
key: ValueKey(ids.join('-')),
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: children,
|
children: children,
|
||||||
);
|
);
|
||||||
|
@ -136,7 +140,6 @@ class SectionedListLayout {
|
||||||
|
|
||||||
final left = tileExtent * column;
|
final left = tileExtent * column;
|
||||||
final top = sectionLayout.indexToLayoutOffset(listIndex);
|
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);
|
return Rect.fromLTWH(left, top, tileExtent, tileExtent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,29 +2,27 @@ import 'dart:math';
|
||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
import 'package:aves/model/image_entry.dart';
|
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/list_sliver.dart';
|
||||||
import 'package:aves/widgets/album/grid/tile_extent_manager.dart';
|
import 'package:aves/widgets/album/grid/tile_extent_manager.dart';
|
||||||
import 'package:aves/widgets/album/thumbnail/decorated.dart';
|
import 'package:aves/widgets/album/thumbnail/decorated.dart';
|
||||||
import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart';
|
import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
class GridScaleGestureDetector extends StatefulWidget {
|
class GridScaleGestureDetector extends StatefulWidget {
|
||||||
final GlobalKey scrollableKey;
|
final GlobalKey scrollableKey;
|
||||||
final ValueNotifier<double> appBarHeightNotifier;
|
|
||||||
final ValueNotifier<double> extentNotifier;
|
final ValueNotifier<double> extentNotifier;
|
||||||
final Size mqSize;
|
final Size mqSize;
|
||||||
final double mqHorizontalPadding;
|
final double mqHorizontalPadding;
|
||||||
|
final void Function(ImageEntry entry) onScaled;
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
|
||||||
const GridScaleGestureDetector({
|
const GridScaleGestureDetector({
|
||||||
this.scrollableKey,
|
this.scrollableKey,
|
||||||
@required this.appBarHeightNotifier,
|
|
||||||
@required this.extentNotifier,
|
@required this.extentNotifier,
|
||||||
@required this.mqSize,
|
@required this.mqSize,
|
||||||
@required this.mqHorizontalPadding,
|
@required this.mqHorizontalPadding,
|
||||||
|
@required this.onScaled,
|
||||||
@required this.child,
|
@required this.child,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -38,7 +36,6 @@ class _GridScaleGestureDetectorState extends State<GridScaleGestureDetector> {
|
||||||
ValueNotifier<double> _scaledExtentNotifier;
|
ValueNotifier<double> _scaledExtentNotifier;
|
||||||
OverlayEntry _overlayEntry;
|
OverlayEntry _overlayEntry;
|
||||||
ThumbnailMetadata _metadata;
|
ThumbnailMetadata _metadata;
|
||||||
RenderViewport _renderViewport;
|
|
||||||
|
|
||||||
ValueNotifier<double> get tileExtentNotifier => widget.extentNotifier;
|
ValueNotifier<double> get tileExtentNotifier => widget.extentNotifier;
|
||||||
|
|
||||||
|
@ -64,7 +61,6 @@ class _GridScaleGestureDetectorState extends State<GridScaleGestureDetector> {
|
||||||
final renderMetaData = firstOf<RenderMetaData>(result);
|
final renderMetaData = firstOf<RenderMetaData>(result);
|
||||||
// abort if we cannot find an image to show on overlay
|
// abort if we cannot find an image to show on overlay
|
||||||
if (renderMetaData == null) return;
|
if (renderMetaData == null) return;
|
||||||
_renderViewport = firstOf<RenderViewport>(result);
|
|
||||||
_metadata = renderMetaData.metaData;
|
_metadata = renderMetaData.metaData;
|
||||||
_startExtent = tileExtentNotifier.value;
|
_startExtent = tileExtentNotifier.value;
|
||||||
_scaledExtentNotifier = ValueNotifier(_startExtent);
|
_scaledExtentNotifier = ValueNotifier(_startExtent);
|
||||||
|
@ -113,21 +109,8 @@ class _GridScaleGestureDetectorState extends State<GridScaleGestureDetector> {
|
||||||
_applyingScale = false;
|
_applyingScale = false;
|
||||||
} else {
|
} else {
|
||||||
// scroll to show the focal point thumbnail at its new position
|
// scroll to show the focal point thumbnail at its new position
|
||||||
final viewportClosure = _renderViewport;
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
// about scrolling & offset retrieval:
|
widget.onScaled(_metadata.entry);
|
||||||
// `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));
|
|
||||||
_applyingScale = false;
|
_applyingScale = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:aves/model/collection_lens.dart';
|
import 'package:aves/model/collection_lens.dart';
|
||||||
import 'package:aves/model/filters/favourite.dart';
|
import 'package:aves/model/filters/favourite.dart';
|
||||||
import 'package:aves/model/filters/mime.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/model/mime_types.dart';
|
||||||
import 'package:aves/widgets/album/app_bar.dart';
|
import 'package:aves/widgets/album/app_bar.dart';
|
||||||
import 'package:aves/widgets/album/empty.dart';
|
import 'package:aves/widgets/album/empty.dart';
|
||||||
|
@ -18,7 +21,6 @@ import 'package:tuple/tuple.dart';
|
||||||
class ThumbnailCollection extends StatelessWidget {
|
class ThumbnailCollection extends StatelessWidget {
|
||||||
final ValueNotifier<double> _appBarHeightNotifier = ValueNotifier(0);
|
final ValueNotifier<double> _appBarHeightNotifier = ValueNotifier(0);
|
||||||
final ValueNotifier<double> _tileExtentNotifier = ValueNotifier(0);
|
final ValueNotifier<double> _tileExtentNotifier = ValueNotifier(0);
|
||||||
final GlobalKey _scrollableKey = GlobalKey();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -34,23 +36,25 @@ class ThumbnailCollection extends StatelessWidget {
|
||||||
// so that view updates on collection filter changes
|
// so that view updates on collection filter changes
|
||||||
return Consumer<CollectionLens>(
|
return Consumer<CollectionLens>(
|
||||||
builder: (context, collection, child) {
|
builder: (context, collection, child) {
|
||||||
final scrollView = _buildScrollView(collection);
|
final appBar = CollectionAppBar(
|
||||||
final draggable = _buildDraggableScrollView(scrollView);
|
|
||||||
final scaler = GridScaleGestureDetector(
|
|
||||||
scrollableKey: _scrollableKey,
|
|
||||||
appBarHeightNotifier: _appBarHeightNotifier,
|
appBarHeightNotifier: _appBarHeightNotifier,
|
||||||
extentNotifier: _tileExtentNotifier,
|
collection: collection,
|
||||||
mqSize: mqSize,
|
|
||||||
mqHorizontalPadding: mqHorizontalPadding,
|
|
||||||
child: draggable,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final sectionedListLayoutProvider = ValueListenableBuilder<double>(
|
final sectionedListLayoutProvider = ValueListenableBuilder<double>(
|
||||||
valueListenable: _tileExtentNotifier,
|
valueListenable: _tileExtentNotifier,
|
||||||
builder: (context, tileExtent, child) => SectionedListLayoutProvider(
|
builder: (context, tileExtent, child) => SectionedListLayoutProvider(
|
||||||
collection: collection,
|
collection: collection,
|
||||||
scrollableWidth: mqSize.width - mqHorizontalPadding,
|
scrollableWidth: mqSize.width - mqHorizontalPadding,
|
||||||
tileExtent: tileExtent,
|
tileExtent: tileExtent,
|
||||||
child: scaler,
|
child: _ScalableThumbnailCollection(
|
||||||
|
appBarHeightNotifier: _appBarHeightNotifier,
|
||||||
|
tileExtentNotifier: _tileExtentNotifier,
|
||||||
|
collection: collection,
|
||||||
|
mqSize: mqSize,
|
||||||
|
mqHorizontalPadding: mqHorizontalPadding,
|
||||||
|
appBar: appBar,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return sectionedListLayoutProvider;
|
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(
|
return CustomScrollView(
|
||||||
key: _scrollableKey,
|
key: _scrollableKey,
|
||||||
primary: true,
|
primary: true,
|
||||||
|
@ -69,10 +107,7 @@ class ThumbnailCollection extends StatelessWidget {
|
||||||
// when there is no content and we use `SliverFillRemaining`
|
// when there is no content and we use `SliverFillRemaining`
|
||||||
physics: collection.isEmpty ? const NeverScrollableScrollPhysics() : null,
|
physics: collection.isEmpty ? const NeverScrollableScrollPhysics() : null,
|
||||||
slivers: [
|
slivers: [
|
||||||
CollectionAppBar(
|
appBar,
|
||||||
appBarHeightNotifier: _appBarHeightNotifier,
|
|
||||||
collection: collection,
|
|
||||||
),
|
|
||||||
collection.isEmpty
|
collection.isEmpty
|
||||||
? SliverFillRemaining(
|
? SliverFillRemaining(
|
||||||
child: _buildEmptyCollectionPlaceholder(collection),
|
child: _buildEmptyCollectionPlaceholder(collection),
|
||||||
|
@ -93,7 +128,7 @@ class ThumbnailCollection extends StatelessWidget {
|
||||||
|
|
||||||
Widget _buildDraggableScrollView(ScrollView scrollView) {
|
Widget _buildDraggableScrollView(ScrollView scrollView) {
|
||||||
return ValueListenableBuilder<double>(
|
return ValueListenableBuilder<double>(
|
||||||
valueListenable: _appBarHeightNotifier,
|
valueListenable: appBarHeightNotifier,
|
||||||
builder: (context, appBarHeight, child) => Selector<MediaQueryData, double>(
|
builder: (context, appBarHeight, child) => Selector<MediaQueryData, double>(
|
||||||
selector: (context, mq) => mq.viewInsets.bottom,
|
selector: (context, mq) => mq.viewInsets.bottom,
|
||||||
builder: (context, mqViewInsetsBottom, child) => DraggableScrollbar(
|
builder: (context, mqViewInsetsBottom, child) => DraggableScrollbar(
|
||||||
|
@ -128,4 +163,21 @@ class ThumbnailCollection extends StatelessWidget {
|
||||||
)
|
)
|
||||||
: const EmptyContent();
|
: 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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue