blob: 7617855aaba4ea75784e82d9530e960b066a95bb [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math' as math;
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:meta/meta.dart';
const EdgeInsets _kPadding = const EdgeInsets.only(top: 32.0, bottom: 12.0);
const ListEquality<RawImage> _kImageListEquality =
const ListEquality<RawImage>();
// Shows a grid of horizontally scrolling images. Their scroll behavior is
// driven automatically using a ScrollController (see ImageGridModel).
class ImageGrid extends StatelessWidget {
/// Image data used for rendering this grid.
final List<RawImage> images;
// If false, just use empty Containers instead of painting the RawImages.
final bool drawImages;
/// If not null, this is used to scroll the grid.
final ScrollController scrollController;
/// Constructor.
const ImageGrid({
@required this.images,
@required this.drawImages,
this.scrollController,
});
@override
Widget build(BuildContext context) {
return new DefaultTextStyle(
style: const TextStyle(
color: Colors.black,
fontSize: 8.0,
height: 1.2,
),
child: new Container(
padding: _kPadding,
child: new Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
new Text('Image Grid Test (Flutter)'),
new Container(
height: 8.0,
),
new Expanded(
child: new CustomScrollView(
scrollDirection: Axis.horizontal,
controller: scrollController,
slivers: <Widget>[
new SliverPadding(
padding: const EdgeInsets.only(left: 32.0),
sliver: new SliverGrid(
gridDelegate: new _ImageGridDelegate(
images: images,
rowCount: 3,
),
delegate: new SliverChildBuilderDelegate(
(BuildContext context, int index) =>
_buildItem(images[index]),
childCount: images.length),
),
),
],
),
),
],
),
),
);
}
Widget _buildItem(RawImage image) {
return new Container(
padding: const EdgeInsets.only(
right: 16.0,
bottom: 16.0,
),
child: new PhysicalModel(
color: Colors.grey[200],
borderRadius: new BorderRadius.circular(4.0),
elevation: 2.0,
child: drawImages
? image
: new Container(
width: 500.0,
height: 500.0,
),
),
);
}
}
class _ImageGridDelegate extends SliverGridDelegate {
final List<RawImage> images;
final int rowCount;
SliverConstraints _lastConstraints;
_ImageGridLayout _lastLayout;
_ImageGridDelegate({
this.images,
this.rowCount,
});
@override
SliverGridLayout getLayout(SliverConstraints constraints) {
if (_newConstraints(constraints)) {
_lastConstraints = constraints;
_lastLayout = new _ImageGridLayout(
sliverGridGeometryList: _getSliverGeometryList(
constraints.crossAxisExtent,
),
rowCount: rowCount,
);
}
return _lastLayout;
}
bool _newConstraints(SliverConstraints constraints) {
return _lastConstraints == null ||
_lastConstraints.crossAxisExtent != constraints.crossAxisExtent;
}
@override
bool shouldRelayout(_ImageGridDelegate oldDelegate) {
return oldDelegate.rowCount != rowCount ||
!_kImageListEquality.equals(oldDelegate.images, images);
}
/// Generates the individual SliverGridGeometry elements that correspond to
/// each image.
List<SliverGridGeometry> _getSliverGeometryList(
double crossAxisExtent,
) {
List<SliverGridGeometry> sliverGridGeometryList = <SliverGridGeometry>[];
List<SliverGridGeometry> lastElementInRow = <SliverGridGeometry>[];
double rowHeight = crossAxisExtent / rowCount;
int getShortestRow() {
double smallestOffest = double.infinity;
int smallestOffestIndex = 0;
for (int i = 0; i < rowCount; i++) {
if (lastElementInRow.length < i + 1) {
smallestOffestIndex = i;
lastElementInRow.add(const SliverGridGeometry(
scrollOffset: 0.0,
crossAxisOffset: 0.0,
mainAxisExtent: 0.0,
crossAxisExtent: 0.0,
));
break;
}
double offset = lastElementInRow[i].scrollOffset +
lastElementInRow[i].mainAxisExtent;
if (offset < smallestOffest) {
smallestOffestIndex = i;
smallestOffest = offset;
}
}
return smallestOffestIndex;
}
for (RawImage image in images) {
int rowIndex = getShortestRow();
double scrollOffset = lastElementInRow[rowIndex].scrollOffset +
lastElementInRow[rowIndex].mainAxisExtent;
double crossAxisOffset = rowHeight * rowIndex;
double mainAxisExtent = _getScaledImageWidth(image, rowHeight);
double crossAxisExtent = rowHeight;
SliverGridGeometry gridGeometry = new SliverGridGeometry(
scrollOffset: scrollOffset,
crossAxisOffset: crossAxisOffset,
mainAxisExtent: mainAxisExtent,
crossAxisExtent: crossAxisExtent,
);
lastElementInRow[rowIndex] = gridGeometry;
sliverGridGeometryList.add(gridGeometry);
}
return sliverGridGeometryList;
}
/// Gets the scaled width of the image given the height.
/// This will maintain the aspect-ratio of the image as well.
double _getScaledImageWidth(RawImage image, double height) =>
image.width * height / image.height;
}
class _ImageGridLayout extends SliverGridLayout {
final List<SliverGridGeometry> sliverGridGeometryList;
final int rowCount;
const _ImageGridLayout({
this.sliverGridGeometryList,
this.rowCount,
});
/// The minimum child index that is visible at (or after) this scroll offset.
@override
int getMinChildIndexForScrollOffset(double scrollOffset) {
int index = 0;
for (int i = 0; i < sliverGridGeometryList.length; i++) {
if (sliverGridGeometryList[i].scrollOffset +
sliverGridGeometryList[i].mainAxisExtent >=
scrollOffset) {
index = i;
break;
}
}
return index;
}
/// The maximum child index that is visible at (or before) this scroll offset.
@override
int getMaxChildIndexForScrollOffset(double scrollOffset) {
int index = sliverGridGeometryList.length - 1;
for (int i = 0; i < sliverGridGeometryList.length; i++) {
if (sliverGridGeometryList[i].scrollOffset > scrollOffset) {
index = math.max(0, i - 1);
break;
}
}
return index;
}
@override
SliverGridGeometry getGeometryForChildIndex(int index) {
return sliverGridGeometryList[index];
}
@override
double computeMaxScrollOffset(int childCount) {
double maxScrollOffset = double.negativeInfinity;
/// Get the max of the last few elements based on rowCount
for (int i = childCount - 1; i >= 0 && i > i - rowCount; i--) {
double offset = sliverGridGeometryList[i].scrollOffset +
sliverGridGeometryList[i].mainAxisExtent;
if (offset > maxScrollOffset) {
maxScrollOffset = offset;
}
}
return maxScrollOffset;
}
}