scrolling to entry with custom sliver list

This commit is contained in:
Thibault Deckers 2020-04-14 13:20:51 +09:00
parent 4edc2bf5d4
commit 6a5603a116
3 changed files with 54 additions and 21 deletions

View file

@ -1,18 +1,18 @@
import 'dart:math'; import 'dart:math';
import 'package:aves/model/collection_lens.dart'; import 'package:aves/model/collection_lens.dart';
import 'package:aves/model/image_entry.dart';
import 'package:aves/widgets/album/grid/header_generic.dart'; import 'package:aves/widgets/album/grid/header_generic.dart';
import 'package:aves/widgets/album/grid/list_sliver.dart'; import 'package:aves/widgets/album/grid/list_sliver.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class SectionedListLayoutProvider extends StatelessWidget { class SectionedListLayoutProvider extends StatelessWidget {
final Widget child;
final CollectionLens collection; final CollectionLens collection;
final int columnCount; final int columnCount;
final double scrollableWidth; final double scrollableWidth;
final double tileExtent; final double tileExtent;
final Widget child;
SectionedListLayoutProvider({ SectionedListLayoutProvider({
@required this.collection, @required this.collection,
@ -64,7 +64,12 @@ class SectionedListLayoutProvider extends StatelessWidget {
), ),
); );
}); });
return SectionedListLayout(sectionLayouts); return SectionedListLayout(
collection: collection,
columnCount: columnCount,
tileExtent: tileExtent,
sectionLayouts: sectionLayouts,
);
} }
Widget _buildInSection(int sectionChildIndex, CollectionLens collection, dynamic sectionKey) { Widget _buildInSection(int sectionChildIndex, CollectionLens collection, dynamic sectionKey) {
@ -100,9 +105,37 @@ class SectionedListLayoutProvider extends StatelessWidget {
} }
class SectionedListLayout { class SectionedListLayout {
final CollectionLens collection;
final int columnCount;
final double tileExtent;
final List<SectionLayout> sectionLayouts; final List<SectionLayout> sectionLayouts;
const SectionedListLayout(this.sectionLayouts); const SectionedListLayout({
@required this.collection,
@required this.columnCount,
@required this.tileExtent,
@required this.sectionLayouts,
});
Rect getTileRect(ImageEntry entry) {
final section = collection.sections.entries.firstWhere((kv) => kv.value.contains(entry), orElse: () => null);
if (section == null) return null;
final sectionKey = section.key;
final sectionLayout = sectionLayouts.firstWhere((sl) => sl.sectionKey == sectionKey, orElse: () => null);
if (sectionLayout == null) return null;
final showHeaders = collection.showHeaders;
final sectionEntryIndex = section.value.indexOf(entry);
final column = sectionEntryIndex % columnCount;
final row = (sectionEntryIndex / columnCount).floor();
final listIndex = sectionLayout.firstIndex + (showHeaders ? 1 : 0) + row;
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);
}
} }
class SectionLayout { class SectionLayout {

View file

@ -2,12 +2,14 @@ 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.dart'; import 'package:aves/widgets/album/thumbnail.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;
@ -33,7 +35,6 @@ class _GridScaleGestureDetectorState extends State<GridScaleGestureDetector> {
ValueNotifier<double> _scaledExtentNotifier; ValueNotifier<double> _scaledExtentNotifier;
OverlayEntry _overlayEntry; OverlayEntry _overlayEntry;
ThumbnailMetadata _metadata; ThumbnailMetadata _metadata;
RenderSliver _renderSliver;
RenderViewport _renderViewport; RenderViewport _renderViewport;
ValueNotifier<double> get tileExtentNotifier => widget.extentNotifier; ValueNotifier<double> get tileExtentNotifier => widget.extentNotifier;
@ -56,7 +57,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;
_renderSliver = firstOf<RenderSliver>(result);
_renderViewport = firstOf<RenderViewport>(result); _renderViewport = firstOf<RenderViewport>(result);
_metadata = renderMetaData.metaData; _metadata = renderMetaData.metaData;
_startExtent = tileExtentNotifier.value; _startExtent = tileExtentNotifier.value;
@ -105,18 +105,18 @@ class _GridScaleGestureDetectorState extends State<GridScaleGestureDetector> {
// TODO TLAD fix scroll to specific thumbnail with custom SliverList // TODO TLAD fix scroll to specific thumbnail with custom SliverList
// scroll to show the focal point thumbnail at its new position // scroll to show the focal point thumbnail at its new position
final sliverClosure = _renderSliver;
final viewportClosure = _renderViewport; final viewportClosure = _renderViewport;
final index = _metadata.index;
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
final scrollableContext = widget.scrollableKey.currentContext; final scrollableContext = widget.scrollableKey.currentContext;
final gridSize = (scrollableContext.findRenderObject() as RenderBox).size; final gridSize = (scrollableContext.findRenderObject() as RenderBox).size;
final newColumnCount = gridSize.width / newExtent; final sectionLayout = Provider.of<SectionedListLayout>(context, listen: false);
final row = index ~/ newColumnCount; final tileRect = sectionLayout.getTileRect(_metadata.entry);
final scrollOffset = (tileRect?.top ?? 0) - gridSize.height / 2;
viewportClosure.offset.jumpTo(scrollOffset.clamp(.0, double.infinity));
// about scrolling & offset retrieval:
// `Scrollable.ensureVisible` only works on already rendered objects // `Scrollable.ensureVisible` only works on already rendered objects
// `RenderViewport.showOnScreen` can find any `RenderSliver`, but not always a `RenderMetadata` // `RenderViewport.showOnScreen` can find any `RenderSliver`, but not always a `RenderMetadata`
final scrollOffset = viewportClosure.scrollOffsetOf(sliverClosure, (row + 1) * newExtent - gridSize.height / 2); // `RenderViewport.scrollOffsetOf` is a good alternative
viewportClosure.offset.jumpTo(scrollOffset.clamp(.0, double.infinity));
}); });
}, },
child: widget.child, child: widget.child,

View file

@ -44,23 +44,23 @@ class ThumbnailCollection extends StatelessWidget {
builder: (context, collection, child) { builder: (context, collection, child) {
final scrollView = _buildScrollView(collection); final scrollView = _buildScrollView(collection);
final draggable = _buildDraggableScrollView(scrollView); final draggable = _buildDraggableScrollView(scrollView);
final scaler = GridScaleGestureDetector(
scrollableKey: _scrollableKey,
extentNotifier: _tileExtentNotifier,
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: draggable, child: scaler,
), ),
); );
final scaler = GridScaleGestureDetector( return sectionedListLayoutProvider;
scrollableKey: _scrollableKey,
extentNotifier: _tileExtentNotifier,
mqSize: mqSize,
mqHorizontalPadding: mqHorizontalPadding,
child: sectionedListLayoutProvider,
);
return scaler;
}, },
); );
}, },