album: scaling overlay grid
This commit is contained in:
parent
0f00846ddf
commit
d46fb09c07
2 changed files with 101 additions and 20 deletions
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:ui';
|
import 'dart:math';
|
||||||
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
import 'package:aves/model/image_entry.dart';
|
import 'package:aves/model/image_entry.dart';
|
||||||
import 'package:aves/widgets/album/collection_section.dart';
|
import 'package:aves/widgets/album/collection_section.dart';
|
||||||
|
@ -63,7 +64,7 @@ class _GridScaleGestureDetectorState extends State<GridScaleGestureDetector> {
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return ScaleOverlay(
|
return ScaleOverlay(
|
||||||
imageEntry: _metadata.entry,
|
imageEntry: _metadata.entry,
|
||||||
thumbnailCenter: thumbnailCenter,
|
center: thumbnailCenter,
|
||||||
gridWidth: gridWidth,
|
gridWidth: gridWidth,
|
||||||
scaledCountNotifier: _scaledCountNotifier,
|
scaledCountNotifier: _scaledCountNotifier,
|
||||||
);
|
);
|
||||||
|
@ -74,7 +75,7 @@ class _GridScaleGestureDetectorState extends State<GridScaleGestureDetector> {
|
||||||
onScaleUpdate: (details) {
|
onScaleUpdate: (details) {
|
||||||
if (_scaledCountNotifier == null) return;
|
if (_scaledCountNotifier == null) return;
|
||||||
final s = details.scale;
|
final s = details.scale;
|
||||||
_scaledCountNotifier.value = (s <= 1 ? lerpDouble(_start * 2, _start, s) : lerpDouble(_start, _start / 2, s / 6)).clamp(columnCountMin, columnCountMax);
|
_scaledCountNotifier.value = (s <= 1 ? ui.lerpDouble(_start * 2, _start, s) : ui.lerpDouble(_start, _start / 2, s / 6)).clamp(columnCountMin, columnCountMax);
|
||||||
},
|
},
|
||||||
onScaleEnd: (details) {
|
onScaleEnd: (details) {
|
||||||
if (_overlayEntry != null) {
|
if (_overlayEntry != null) {
|
||||||
|
@ -102,7 +103,7 @@ class _GridScaleGestureDetectorState extends State<GridScaleGestureDetector> {
|
||||||
// `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);
|
final scrollOffset = viewportClosure.scrollOffsetOf(sliverClosure, (row + 1) * newExtent - gridSize.height / 2);
|
||||||
viewportClosure.offset.jumpTo(scrollOffset);
|
viewportClosure.offset.jumpTo(scrollOffset.clamp(0, double.infinity));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
child: widget.child,
|
child: widget.child,
|
||||||
|
@ -112,13 +113,13 @@ class _GridScaleGestureDetectorState extends State<GridScaleGestureDetector> {
|
||||||
|
|
||||||
class ScaleOverlay extends StatefulWidget {
|
class ScaleOverlay extends StatefulWidget {
|
||||||
final ImageEntry imageEntry;
|
final ImageEntry imageEntry;
|
||||||
final Offset thumbnailCenter;
|
final Offset center;
|
||||||
final double gridWidth;
|
final double gridWidth;
|
||||||
final ValueNotifier<double> scaledCountNotifier;
|
final ValueNotifier<double> scaledCountNotifier;
|
||||||
|
|
||||||
const ScaleOverlay({
|
const ScaleOverlay({
|
||||||
@required this.imageEntry,
|
@required this.imageEntry,
|
||||||
@required this.thumbnailCenter,
|
@required this.center,
|
||||||
@required this.gridWidth,
|
@required this.gridWidth,
|
||||||
@required this.scaledCountNotifier,
|
@required this.scaledCountNotifier,
|
||||||
});
|
});
|
||||||
|
@ -130,6 +131,10 @@ class ScaleOverlay extends StatefulWidget {
|
||||||
class _ScaleOverlayState extends State<ScaleOverlay> {
|
class _ScaleOverlayState extends State<ScaleOverlay> {
|
||||||
bool _init = false;
|
bool _init = false;
|
||||||
|
|
||||||
|
Offset get center => widget.center;
|
||||||
|
|
||||||
|
double get gridWidth => widget.gridWidth;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
@ -141,23 +146,58 @@ class _ScaleOverlayState extends State<ScaleOverlay> {
|
||||||
return MediaQueryDataProvider(
|
return MediaQueryDataProvider(
|
||||||
child: IgnorePointer(
|
child: IgnorePointer(
|
||||||
child: AnimatedContainer(
|
child: AnimatedContainer(
|
||||||
color: _init ? Colors.black54 : Colors.transparent,
|
decoration: _init
|
||||||
|
? BoxDecoration(
|
||||||
|
gradient: RadialGradient(
|
||||||
|
center: FractionalOffset.fromOffsetAndSize(center, MediaQuery.of(context).size),
|
||||||
|
radius: 1,
|
||||||
|
colors: [
|
||||||
|
Colors.black,
|
||||||
|
Colors.black54,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const BoxDecoration(
|
||||||
|
// provide dummy gradient to lerp to the other one during animation
|
||||||
|
gradient: RadialGradient(
|
||||||
|
colors: [
|
||||||
|
Colors.transparent,
|
||||||
|
Colors.transparent,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
child: ValueListenableBuilder(
|
child: ValueListenableBuilder(
|
||||||
valueListenable: widget.scaledCountNotifier,
|
valueListenable: widget.scaledCountNotifier,
|
||||||
builder: (context, columnCount, child) {
|
builder: (context, columnCount, child) {
|
||||||
final extent = widget.gridWidth / columnCount;
|
final extent = gridWidth / columnCount;
|
||||||
return Stack(
|
|
||||||
children: [
|
// keep scaled thumbnail within the screen
|
||||||
Positioned(
|
var dx = .0;
|
||||||
left: widget.thumbnailCenter.dx - extent / 2,
|
if (center.dx - extent / 2 < 0) {
|
||||||
top: widget.thumbnailCenter.dy - extent / 2,
|
dx = extent / 2 - center.dx;
|
||||||
child: Thumbnail(
|
} else if (center.dx + extent / 2 > gridWidth) {
|
||||||
entry: widget.imageEntry,
|
dx = gridWidth - (center.dx + extent / 2);
|
||||||
extent: extent,
|
}
|
||||||
|
final clampedCenter = center.translate(dx, 0);
|
||||||
|
|
||||||
|
return CustomPaint(
|
||||||
|
painter: GridPainter(
|
||||||
|
center: clampedCenter,
|
||||||
|
extent: extent,
|
||||||
|
),
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Positioned(
|
||||||
|
left: clampedCenter.dx - extent / 2,
|
||||||
|
top: clampedCenter.dy - extent / 2,
|
||||||
|
child: Thumbnail(
|
||||||
|
entry: widget.imageEntry,
|
||||||
|
extent: extent,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -166,3 +206,39 @@ class _ScaleOverlayState extends State<ScaleOverlay> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class GridPainter extends CustomPainter {
|
||||||
|
final Offset center;
|
||||||
|
final double extent;
|
||||||
|
|
||||||
|
const GridPainter({
|
||||||
|
@required this.center,
|
||||||
|
@required this.extent,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
final paint = Paint()
|
||||||
|
..strokeWidth = Thumbnail.borderWidth
|
||||||
|
..shader = ui.Gradient.radial(
|
||||||
|
center,
|
||||||
|
size.width / 2,
|
||||||
|
[
|
||||||
|
Thumbnail.borderColor,
|
||||||
|
Colors.transparent,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
min(.5, 2 * extent / size.width),
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
final topLeft = center.translate(-extent / 2, -extent / 2);
|
||||||
|
for (int i = -1; i <= 2; i++) {
|
||||||
|
canvas.drawLine(Offset(0, topLeft.dy + extent * i), Offset(size.width, topLeft.dy + extent * i), paint);
|
||||||
|
canvas.drawLine(Offset(topLeft.dx + extent * i, 0), Offset(topLeft.dx + extent * i, size.height), paint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(CustomPainter oldDelegate) => true;
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,9 @@ class Thumbnail extends StatelessWidget {
|
||||||
final ImageEntry entry;
|
final ImageEntry entry;
|
||||||
final double extent;
|
final double extent;
|
||||||
|
|
||||||
|
static final Color borderColor = Colors.grey.shade700;
|
||||||
|
static const double borderWidth = .5;
|
||||||
|
|
||||||
const Thumbnail({
|
const Thumbnail({
|
||||||
Key key,
|
Key key,
|
||||||
@required this.entry,
|
@required this.entry,
|
||||||
|
@ -55,10 +58,12 @@ class Thumbnail extends StatelessWidget {
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: Colors.grey.shade700,
|
color: borderColor,
|
||||||
width: 0.5,
|
width: borderWidth,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
width: extent,
|
||||||
|
height: extent,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
alignment: AlignmentDirectional.bottomStart,
|
alignment: AlignmentDirectional.bottomStart,
|
||||||
children: [
|
children: [
|
||||||
|
|
Loading…
Reference in a new issue