1
0
mirror of https://github.com/flutter/samples.git synced 2025-11-12 15:58:32 +00:00

[Gallery] Fix directory structure (#312)

This commit is contained in:
Pierre-Louis
2020-02-05 20:11:54 +01:00
committed by GitHub
parent 082592e9a9
commit cee267cf88
762 changed files with 12 additions and 12 deletions

View File

@@ -0,0 +1,298 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:gallery/studies/shrine/model/product.dart';
import 'package:gallery/studies/shrine/supplemental/desktop_product_columns.dart';
import 'package:gallery/studies/shrine/supplemental/layout_cache.dart';
/// A placeholder id for an empty element. See [_iterateUntilBalanced]
/// for more information.
const _emptyElement = -1;
/// To avoid infinite loops, improvements to the layout are only performed
/// when a column's height changes by more than
/// [_deviationImprovementThreshold] pixels.
const _deviationImprovementThreshold = 10;
/// Height of a product image, paired with the product's id.
class _TaggedHeightData {
const _TaggedHeightData({
@required this.index,
@required this.height,
});
/// The id of the corresponding product.
final int index;
/// The height of the product image.
final double height;
}
/// Converts a set of [_TaggedHeightData] elements to a list,
/// and add an empty element.
/// Used for iteration.
List<_TaggedHeightData> toListAndAddEmpty(Set<_TaggedHeightData> set) {
List<_TaggedHeightData> result = List<_TaggedHeightData>.from(set);
result.add(_TaggedHeightData(index: _emptyElement, height: 0));
return result;
}
/// Encode parameters for caching.
String _encodeParameters({
@required int columnCount,
@required List<Product> products,
@required double largeImageWidth,
@required double smallImageWidth,
}) {
final String productString =
[for (final product in products) product.id.toString()].join(',');
return '$columnCount;$productString,$largeImageWidth,$smallImageWidth';
}
/// Given a layout, replace integers by their corresponding products.
List<List<Product>> _generateLayout({
@required List<Product> products,
@required List<List<int>> layout,
}) {
return [
for (final column in layout)
[
for (final index in column) products[index],
]
];
}
/// Returns the size of an [Image] widget.
Size _imageSize(Image imageWidget) {
Size result;
imageWidget.image.resolve(ImageConfiguration()).addListener(
ImageStreamListener(
(info, synchronousCall) {
final finalImage = info.image;
result = Size(
finalImage.width.toDouble(),
finalImage.height.toDouble(),
);
},
),
);
return result;
}
/// Given [columnObjects], list of the set of objects in each column,
/// and [columnHeights], list of heights of each column,
/// [_iterateUntilBalanced] moves and swaps objects between columns
/// until their heights are sufficiently close to each other.
/// This prevents the layout having significant, avoidable gaps at the bottom.
void _iterateUntilBalanced(
List<Set<_TaggedHeightData>> columnObjects,
List<double> columnHeights,
) {
int failedMoves = 0;
final int columnCount = columnObjects.length;
// No need to rearrange a 1-column layout.
if (columnCount == 1) {
return;
}
while (true) {
// Loop through all possible 2-combinations of columns.
for (int source = 0; source < columnCount; ++source) {
for (int target = source + 1; target < columnCount; ++target) {
// Tries to find an object A from source column
// and an object B from target column, such that switching them
// causes the height of the two columns to be closer.
// A or B can be empty; in this case, moving an object from one
// column to the other is the best choice.
bool success = false;
final double bestHeight =
(columnHeights[source] + columnHeights[target]) / 2;
final double scoreLimit = (columnHeights[source] - bestHeight).abs();
final List<_TaggedHeightData> sourceObjects =
toListAndAddEmpty(columnObjects[source]);
final List<_TaggedHeightData> targetObjects =
toListAndAddEmpty(columnObjects[target]);
_TaggedHeightData bestA, bestB;
double bestScore;
for (final a in sourceObjects) {
for (final b in targetObjects) {
if (a.index == _emptyElement && b.index == _emptyElement) {
continue;
} else {
final double score =
(columnHeights[source] - a.height + b.height - bestHeight)
.abs();
if (score < scoreLimit - _deviationImprovementThreshold) {
success = true;
if (bestScore == null || score < bestScore) {
bestScore = score;
bestA = a;
bestB = b;
}
}
}
}
}
if (!success) {
++failedMoves;
} else {
failedMoves = 0;
// Switch A and B.
if (bestA.index != _emptyElement) {
columnObjects[source].remove(bestA);
columnObjects[target].add(bestA);
}
if (bestB.index != _emptyElement) {
columnObjects[target].remove(bestB);
columnObjects[source].add(bestB);
}
columnHeights[source] += bestB.height - bestA.height;
columnHeights[target] += bestA.height - bestB.height;
}
// If no two columns' heights can be made closer by switching
// elements, the layout is sufficiently balanced.
if (failedMoves >= columnCount * (columnCount - 1) ~/ 2) {
return;
}
}
}
}
}
/// Given a list of numbers [data], representing the heights of each image,
/// and a list of numbers [biases], representing the heights of the space
/// above each column, [_balancedDistribution] returns a layout of [data]
/// so that the height of each column is sufficiently close to each other,
/// represented as a list of lists of integers, each integer being an ID
/// for a product.
List<List<int>> _balancedDistribution({
int columnCount,
List<double> data,
List<double> biases,
}) {
assert(biases.length == columnCount);
List<Set<_TaggedHeightData>> columnObjects =
List<Set<_TaggedHeightData>>.generate(columnCount, (column) => Set());
List<double> columnHeights = List<double>.from(biases);
for (var i = 0; i < data.length; ++i) {
final int column = i % columnCount;
columnHeights[column] += data[i];
columnObjects[column].add(_TaggedHeightData(index: i, height: data[i]));
}
_iterateUntilBalanced(columnObjects, columnHeights);
return [
for (final column in columnObjects)
[for (final object in column) object.index]..sort(),
];
}
/// Generates a balanced layout for [columnCount] columns,
/// with products specified by the list [products],
/// where the larger images have width [largeImageWidth]
/// and the smaller images have width [smallImageWidth].
/// The current [context] is also given to allow caching.
List<List<Product>> balancedLayout({
BuildContext context,
int columnCount,
List<Product> products,
double largeImageWidth,
double smallImageWidth,
}) {
final String encodedParameters = _encodeParameters(
columnCount: columnCount,
products: products,
largeImageWidth: largeImageWidth,
smallImageWidth: smallImageWidth,
);
// Check if this layout is cached.
if (LayoutCache.of(context).containsKey(encodedParameters)) {
return _generateLayout(
products: products,
layout: LayoutCache.of(context)[encodedParameters],
);
}
final List<Size> productSizes = [
for (var product in products)
_imageSize(
Image.asset(
product.assetName,
package: product.assetPackage,
),
),
];
bool hasNullSize = false;
for (final productSize in productSizes) {
if (productSize == null) {
hasNullSize = true;
break;
}
}
if (hasNullSize) {
// If some image sizes are not read, return default layout.
// Default layout is not cached.
List<List<Product>> result =
List<List<Product>>.generate(columnCount, (columnIndex) => []);
for (var index = 0; index < products.length; ++index) {
result[index % columnCount].add(products[index]);
}
return result;
}
// All images have sizes. Use tailored layout.
final List<double> productHeights = [
for (final productSize in productSizes)
productSize.height /
productSize.width *
(largeImageWidth + smallImageWidth) /
2 +
productCardAdditionalHeight,
];
final List<List<int>> layout = _balancedDistribution(
columnCount: columnCount,
data: productHeights,
biases: List<double>.generate(
columnCount,
(column) => (column % 2 == 0 ? 0 : columnTopSpace),
),
);
// Add tailored layout to cache.
LayoutCache.of(context)[encodedParameters] = layout;
final List<List<Product>> result = _generateLayout(
products: products,
layout: layout,
);
return result;
}