mirror of
https://github.com/flutter/samples.git
synced 2026-03-30 08:11:40 +00:00
Add flutter_web samples (#75)
This commit is contained in:
committed by
Andrew Brogdon
parent
42f2dce01b
commit
3fe927cb29
322
web/slide_puzzle/lib/src/widgets/decoration_image_plus.dart
Normal file
322
web/slide_puzzle/lib/src/widgets/decoration_image_plus.dart
Normal file
@@ -0,0 +1,322 @@
|
||||
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// ignore_for_file: omit_local_variable_types, annotate_overrides
|
||||
|
||||
import 'package:flutter_web_ui/ui.dart' as ui show Image;
|
||||
|
||||
import '../flutter.dart';
|
||||
|
||||
// A model on top of DecorationImage that supports slicing up the source image
|
||||
// efficiently to draw it as tiles in the puzzle game
|
||||
@immutable
|
||||
class DecorationImagePlus implements DecorationImage {
|
||||
final int puzzleWidth, puzzleHeight, pieceIndex;
|
||||
|
||||
/// Creates an image to show in a [BoxDecoration].
|
||||
///
|
||||
/// The [image], [alignment], [repeat], and [matchTextDirection] arguments
|
||||
/// must not be null.
|
||||
const DecorationImagePlus({
|
||||
@required this.image,
|
||||
@required this.puzzleWidth,
|
||||
@required this.puzzleHeight,
|
||||
@required this.pieceIndex,
|
||||
this.colorFilter,
|
||||
this.fit,
|
||||
this.alignment = Alignment.center,
|
||||
this.centerSlice,
|
||||
this.repeat = ImageRepeat.noRepeat,
|
||||
this.matchTextDirection = false,
|
||||
}) : assert(image != null),
|
||||
assert(alignment != null),
|
||||
assert(repeat != null),
|
||||
assert(matchTextDirection != null),
|
||||
assert(puzzleHeight > 1 &&
|
||||
puzzleHeight > 1 &&
|
||||
pieceIndex >= 0 &&
|
||||
pieceIndex < (puzzleHeight * puzzleWidth));
|
||||
|
||||
/// The image to be painted into the decoration.
|
||||
///
|
||||
/// Typically this will be an [AssetImage] (for an image shipped with the
|
||||
/// application) or a [NetworkImage] (for an image obtained from the network).
|
||||
final ImageProvider image;
|
||||
|
||||
/// A color filter to apply to the image before painting it.
|
||||
final ColorFilter colorFilter;
|
||||
|
||||
/// How the image should be inscribed into the box.
|
||||
///
|
||||
/// The default is [BoxFit.scaleDown] if [centerSlice] is null, and
|
||||
/// [BoxFit.fill] if [centerSlice] is not null.
|
||||
///
|
||||
/// See the discussion at [_paintImage] for more details.
|
||||
final BoxFit fit;
|
||||
|
||||
/// How to align the image within its bounds.
|
||||
///
|
||||
/// The alignment aligns the given position in the image to the given position
|
||||
/// in the layout bounds. For example, an [Alignment] alignment of (-1.0,
|
||||
/// -1.0) aligns the image to the top-left corner of its layout bounds, while a
|
||||
/// [Alignment] alignment of (1.0, 1.0) aligns the bottom right of the
|
||||
/// image with the bottom right corner of its layout bounds. Similarly, an
|
||||
/// alignment of (0.0, 1.0) aligns the bottom middle of the image with the
|
||||
/// middle of the bottom edge of its layout bounds.
|
||||
///
|
||||
/// To display a subpart of an image, consider using a [CustomPainter] and
|
||||
/// [Canvas.drawImageRect].
|
||||
///
|
||||
/// If the [alignment] is [TextDirection]-dependent (i.e. if it is a
|
||||
/// [AlignmentDirectional]), then a [TextDirection] must be available
|
||||
/// when the image is painted.
|
||||
///
|
||||
/// Defaults to [Alignment.center].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [Alignment], a class with convenient constants typically used to
|
||||
/// specify an [AlignmentGeometry].
|
||||
/// * [AlignmentDirectional], like [Alignment] for specifying alignments
|
||||
/// relative to text direction.
|
||||
final AlignmentGeometry alignment;
|
||||
|
||||
/// The center slice for a nine-patch image.
|
||||
///
|
||||
/// The region of the image inside the center slice will be stretched both
|
||||
/// horizontally and vertically to fit the image into its destination. The
|
||||
/// region of the image above and below the center slice will be stretched
|
||||
/// only horizontally and the region of the image to the left and right of
|
||||
/// the center slice will be stretched only vertically.
|
||||
///
|
||||
/// The stretching will be applied in order to make the image fit into the box
|
||||
/// specified by [fit]. When [centerSlice] is not null, [fit] defaults to
|
||||
/// [BoxFit.fill], which distorts the destination image size relative to the
|
||||
/// image's original aspect ratio. Values of [BoxFit] which do not distort the
|
||||
/// destination image size will result in [centerSlice] having no effect
|
||||
/// (since the nine regions of the image will be rendered with the same
|
||||
/// scaling, as if it wasn't specified).
|
||||
final Rect centerSlice;
|
||||
|
||||
/// How to paint any portions of the box that would not otherwise be covered
|
||||
/// by the image.
|
||||
final ImageRepeat repeat;
|
||||
|
||||
/// Whether to paint the image in the direction of the [TextDirection].
|
||||
///
|
||||
/// If this is true, then in [TextDirection.ltr] contexts, the image will be
|
||||
/// drawn with its origin in the top left (the "normal" painting direction for
|
||||
/// images); and in [TextDirection.rtl] contexts, the image will be drawn with
|
||||
/// a scaling factor of -1 in the horizontal direction so that the origin is
|
||||
/// in the top right.
|
||||
final bool matchTextDirection;
|
||||
|
||||
/// Creates a [DecorationImagePainterPlus] for this [DecorationImagePlus].
|
||||
///
|
||||
/// The `onChanged` argument must not be null. It will be called whenever the
|
||||
/// image needs to be repainted, e.g. because it is loading incrementally or
|
||||
/// because it is animated.
|
||||
DecorationImagePainterPlus createPainter(VoidCallback onChanged) {
|
||||
assert(onChanged != null);
|
||||
return DecorationImagePainterPlus._(this, onChanged);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
if (identical(this, other)) return true;
|
||||
return other is DecorationImagePlus &&
|
||||
other.runtimeType == runtimeType &&
|
||||
image == other.image &&
|
||||
colorFilter == other.colorFilter &&
|
||||
fit == other.fit &&
|
||||
alignment == other.alignment &&
|
||||
centerSlice == other.centerSlice &&
|
||||
repeat == other.repeat &&
|
||||
matchTextDirection == other.matchTextDirection;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => hashValues(image, colorFilter, fit, alignment,
|
||||
centerSlice, repeat, matchTextDirection);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
final List<String> properties = <String>['$image'];
|
||||
if (colorFilter != null) properties.add('$colorFilter');
|
||||
if (fit != null &&
|
||||
!(fit == BoxFit.fill && centerSlice != null) &&
|
||||
!(fit == BoxFit.scaleDown && centerSlice == null)) {
|
||||
properties.add('$fit');
|
||||
}
|
||||
properties.add('$alignment');
|
||||
if (centerSlice != null) properties.add('centerSlice: $centerSlice');
|
||||
if (repeat != ImageRepeat.noRepeat) properties.add('$repeat');
|
||||
if (matchTextDirection) properties.add('match text direction');
|
||||
return '$runtimeType(${properties.join(", ")})';
|
||||
}
|
||||
}
|
||||
|
||||
/// The painter for a [DecorationImagePlus].
|
||||
///
|
||||
/// To obtain a painter, call [DecorationImagePlus.createPainter].
|
||||
///
|
||||
/// To paint, call [paint]. The `onChanged` callback passed to
|
||||
/// [DecorationImagePlus.createPainter] will be called if the image needs to paint
|
||||
/// again (e.g. because it is animated or because it had not yet loaded the
|
||||
/// first time the [paint] method was called).
|
||||
///
|
||||
/// This object should be disposed using the [dispose] method when it is no
|
||||
/// longer needed.
|
||||
class DecorationImagePainterPlus implements DecorationImagePainter {
|
||||
DecorationImagePainterPlus._(this._details, this._onChanged)
|
||||
: assert(_details != null);
|
||||
|
||||
final DecorationImagePlus _details;
|
||||
final VoidCallback _onChanged;
|
||||
|
||||
ImageStream _imageStream;
|
||||
ImageInfo _image;
|
||||
|
||||
/// Draw the image onto the given canvas.
|
||||
///
|
||||
/// The image is drawn at the position and size given by the `rect` argument.
|
||||
///
|
||||
/// The image is clipped to the given `clipPath`, if any.
|
||||
///
|
||||
/// The `configuration` object is used to resolve the image (e.g. to pick
|
||||
/// resolution-specific assets), and to implement the
|
||||
/// [DecorationImagePlus.matchTextDirection] feature.
|
||||
///
|
||||
/// If the image needs to be painted again, e.g. because it is animated or
|
||||
/// because it had not yet been loaded the first time this method was called,
|
||||
/// then the `onChanged` callback passed to [DecorationImagePlus.createPainter]
|
||||
/// will be called.
|
||||
void paint(Canvas canvas, Rect rect, Path clipPath,
|
||||
ImageConfiguration configuration) {
|
||||
assert(canvas != null);
|
||||
assert(rect != null);
|
||||
assert(configuration != null);
|
||||
|
||||
if (_details.matchTextDirection) {
|
||||
assert(() {
|
||||
// We check this first so that the assert will fire immediately, not just
|
||||
// when the image is ready.
|
||||
if (configuration.textDirection == null) {
|
||||
throw FlutterError(
|
||||
'ImageDecoration.matchTextDirection can only be used when a TextDirection is available.\n'
|
||||
'When DecorationImagePainter.paint() was called, there was no text direction provided '
|
||||
'in the ImageConfiguration object to match.\n'
|
||||
'The DecorationImage was:\n'
|
||||
' $_details\n'
|
||||
'The ImageConfiguration was:\n'
|
||||
' $configuration');
|
||||
}
|
||||
return true;
|
||||
}());
|
||||
}
|
||||
|
||||
final ImageStream newImageStream = _details.image.resolve(configuration);
|
||||
if (newImageStream.key != _imageStream?.key) {
|
||||
_imageStream?.removeListener(_imageListener);
|
||||
_imageStream = newImageStream..addListener(_imageListener);
|
||||
}
|
||||
if (_image == null) return;
|
||||
|
||||
if (clipPath != null) {
|
||||
canvas
|
||||
..save()
|
||||
..clipPath(clipPath);
|
||||
}
|
||||
|
||||
_paintImage(
|
||||
canvas: canvas,
|
||||
puzzleWidth: _details.puzzleWidth,
|
||||
puzzleHeight: _details.puzzleHeight,
|
||||
pieceIndex: _details.pieceIndex,
|
||||
rect: rect,
|
||||
image: _image.image,
|
||||
scale: _image.scale,
|
||||
colorFilter: _details.colorFilter,
|
||||
fit: _details.fit,
|
||||
alignment: _details.alignment.resolve(configuration.textDirection),
|
||||
);
|
||||
|
||||
if (clipPath != null) canvas.restore();
|
||||
}
|
||||
|
||||
void _imageListener(ImageInfo value, bool synchronousCall) {
|
||||
if (_image == value) return;
|
||||
_image = value;
|
||||
assert(_onChanged != null);
|
||||
if (!synchronousCall) _onChanged();
|
||||
}
|
||||
|
||||
/// Releases the resources used by this painter.
|
||||
///
|
||||
/// This should be called whenever the painter is no longer needed.
|
||||
///
|
||||
/// After this method has been called, the object is no longer usable.
|
||||
@mustCallSuper
|
||||
void dispose() {
|
||||
_imageStream?.removeListener(_imageListener);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '$runtimeType(stream: $_imageStream, image: $_image) for $_details';
|
||||
}
|
||||
}
|
||||
|
||||
void _paintImage(
|
||||
{@required Canvas canvas,
|
||||
@required Rect rect,
|
||||
@required ui.Image image,
|
||||
@required int puzzleWidth,
|
||||
@required int puzzleHeight,
|
||||
@required int pieceIndex,
|
||||
double scale = 1.0,
|
||||
ColorFilter colorFilter,
|
||||
BoxFit fit,
|
||||
Alignment alignment = Alignment.center}) {
|
||||
assert(canvas != null);
|
||||
assert(image != null);
|
||||
assert(alignment != null);
|
||||
|
||||
if (rect.isEmpty) return;
|
||||
final outputSize = rect.size;
|
||||
final inputSize = Size(image.width.toDouble(), image.height.toDouble());
|
||||
fit ??= BoxFit.scaleDown;
|
||||
final FittedSizes fittedSizes =
|
||||
applyBoxFit(fit, inputSize / scale, outputSize);
|
||||
final Size sourceSize = fittedSizes.source * scale;
|
||||
final destinationSize = fittedSizes.destination;
|
||||
final Paint paint = Paint()
|
||||
..isAntiAlias = false
|
||||
..filterQuality = FilterQuality.medium;
|
||||
if (colorFilter != null) paint.colorFilter = colorFilter;
|
||||
final double halfWidthDelta =
|
||||
(outputSize.width - destinationSize.width) / 2.0;
|
||||
final double halfHeightDelta =
|
||||
(outputSize.height - destinationSize.height) / 2.0;
|
||||
final double dx = halfWidthDelta + (alignment.x) * halfWidthDelta;
|
||||
final double dy = halfHeightDelta + alignment.y * halfHeightDelta;
|
||||
final Offset destinationPosition = rect.topLeft.translate(dx, dy);
|
||||
final Rect destinationRect = destinationPosition & destinationSize;
|
||||
final Rect sourceRect =
|
||||
alignment.inscribe(sourceSize, Offset.zero & inputSize);
|
||||
|
||||
final sliceSize =
|
||||
Size(sourceRect.width / puzzleWidth, sourceRect.height / puzzleHeight);
|
||||
|
||||
final col = pieceIndex % puzzleWidth;
|
||||
final row = pieceIndex ~/ puzzleWidth;
|
||||
|
||||
final sliceRect = Rect.fromLTWH(
|
||||
sourceRect.left + col * sliceSize.width,
|
||||
sourceRect.top + row * sliceSize.height,
|
||||
sliceSize.width,
|
||||
sliceSize.height);
|
||||
|
||||
canvas.drawImageRect(image, sliceRect, destinationRect, paint);
|
||||
}
|
||||
106
web/slide_puzzle/lib/src/widgets/material_interior_alt.dart
Normal file
106
web/slide_puzzle/lib/src/widgets/material_interior_alt.dart
Normal file
@@ -0,0 +1,106 @@
|
||||
// Copyright 2019 The Chromium 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 '../flutter.dart';
|
||||
|
||||
// Copied from
|
||||
// https://github.com/flutter/flutter/blob/f5b02e3c05ed1ab31e890add84fb56e35de2d392/packages/flutter/lib/src/material/material.dart#L593-L715
|
||||
// So I could have animated color!
|
||||
// TODO(kevmoo): file a feature request for this?
|
||||
class MaterialInterior extends ImplicitlyAnimatedWidget {
|
||||
const MaterialInterior({
|
||||
Key key,
|
||||
@required this.child,
|
||||
@required this.shape,
|
||||
@required this.color,
|
||||
Curve curve = Curves.linear,
|
||||
@required Duration duration,
|
||||
}) : assert(child != null),
|
||||
assert(shape != null),
|
||||
assert(color != null),
|
||||
super(key: key, curve: curve, duration: duration);
|
||||
|
||||
/// The widget below this widget in the tree.
|
||||
///
|
||||
/// {@macro flutter.widgets.child}
|
||||
final Widget child;
|
||||
|
||||
/// The border of the widget.
|
||||
///
|
||||
/// This border will be painted, and in addition the outer path of the border
|
||||
/// determines the physical shape.
|
||||
final ShapeBorder shape;
|
||||
|
||||
/// The target background color.
|
||||
final Color color;
|
||||
|
||||
@override
|
||||
_MaterialInteriorState createState() => _MaterialInteriorState();
|
||||
}
|
||||
|
||||
class _MaterialInteriorState extends AnimatedWidgetBaseState<MaterialInterior> {
|
||||
ShapeBorderTween _border;
|
||||
ColorTween _color;
|
||||
|
||||
@override
|
||||
void forEachTween(TweenVisitor<dynamic> visitor) {
|
||||
_border = visitor(_border, widget.shape,
|
||||
(value) => ShapeBorderTween(begin: value as ShapeBorder))
|
||||
as ShapeBorderTween;
|
||||
_color = visitor(
|
||||
_color, widget.color, (value) => ColorTween(begin: value as Color))
|
||||
as ColorTween;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final shape = _border.evaluate(animation);
|
||||
return PhysicalShape(
|
||||
child: _ShapeBorderPaint(
|
||||
child: widget.child,
|
||||
shape: shape,
|
||||
),
|
||||
clipper: ShapeBorderClipper(
|
||||
shape: shape,
|
||||
textDirection: Directionality.of(context),
|
||||
),
|
||||
color: _color.evaluate(animation),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ShapeBorderPaint extends StatelessWidget {
|
||||
const _ShapeBorderPaint({
|
||||
@required this.child,
|
||||
@required this.shape,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
final ShapeBorder shape;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CustomPaint(
|
||||
child: child,
|
||||
foregroundPainter: _ShapeBorderPainter(shape, Directionality.of(context)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ShapeBorderPainter extends CustomPainter {
|
||||
_ShapeBorderPainter(this.border, this.textDirection);
|
||||
|
||||
final ShapeBorder border;
|
||||
final TextDirection textDirection;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
border.paint(canvas, Offset.zero & size, textDirection: textDirection);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_ShapeBorderPainter oldDelegate) {
|
||||
return oldDelegate.border != border;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user