Commit 9f36737f authored by Adam Barth's avatar Adam Barth Committed by GitHub

InkSplash and InkHighlight shouldn't be special (#7531)

Previously, these classes where in material.dart, which meant they could
access private interfaces in that library. This patch moves them out
into their own files so that they need to play by the rules of the
public API.

Fixes #5969
parent 712eb969
...@@ -49,6 +49,8 @@ export 'src/material/icon_theme.dart'; ...@@ -49,6 +49,8 @@ export 'src/material/icon_theme.dart';
export 'src/material/icon_theme_data.dart'; export 'src/material/icon_theme_data.dart';
export 'src/material/icons.dart'; export 'src/material/icons.dart';
export 'src/material/image_icon.dart'; export 'src/material/image_icon.dart';
export 'src/material/ink_highlight.dart';
export 'src/material/ink_splash.dart';
export 'src/material/ink_well.dart'; export 'src/material/ink_well.dart';
export 'src/material/input.dart'; export 'src/material/input.dart';
export 'src/material/list.dart'; export 'src/material/list.dart';
......
...@@ -136,7 +136,7 @@ class IconButton extends StatelessWidget { ...@@ -136,7 +136,7 @@ class IconButton extends StatelessWidget {
maxHeight: size, maxHeight: size,
child: new ConstrainedBox( child: new ConstrainedBox(
constraints: new BoxConstraints.loose( constraints: new BoxConstraints.loose(
new Size.square(math.max(size, InkSplash.defaultRadius * 2.0)) new Size.square(math.max(size, Material.defaultSplashRadius * 2.0))
), ),
child: new Align( child: new Align(
alignment: alignment, alignment: alignment,
...@@ -161,7 +161,7 @@ class IconButton extends StatelessWidget { ...@@ -161,7 +161,7 @@ class IconButton extends StatelessWidget {
return new InkResponse( return new InkResponse(
onTap: onPressed, onTap: onPressed,
child: result, child: result,
radius: math.max(size, InkSplash.defaultRadius), radius: math.max(size, Material.defaultSplashRadius),
); );
} }
......
// Copyright 2017 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 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'material.dart';
const Duration _kHighlightFadeDuration = const Duration(milliseconds: 200);
/// A visual emphasis on a part of a [Material] receiving user interaction.
///
/// This object is rarely created directly. Instead of creating an ink highlight
/// directly, consider using an [InkResponse] or [InkWell] widget, which uses
/// gestures (such as tap and long-press) to trigger ink highlights.
///
/// See also:
///
/// * [InkResponse], which uses gestures to trigger ink highlights and ink
/// splashes in the parent [Material].
/// * [InkWell], which is a rectangular [InkResponse] (the most common type of
/// ink response).
/// * [Material], which is the widget on which the ink highlight is painted.
/// * [InkSplash], which is an ink feature that shows a reaction to user input
/// on a [Material].
class InkHighlight extends InkFeature {
/// Begin a highlight animation.
///
/// The [controller] argument is typically obtained via
/// `Material.of(context)`.
///
/// If a `rectCallback` is given, then it provides the highlight rectangle,
/// otherwise, the highlight rectangle is coincident with the [referenceBox].
///
/// When the highlight is removed, `onRemoved` will be called.
InkHighlight({
@required MaterialInkController controller,
@required RenderBox referenceBox,
@required Color color,
BoxShape shape: BoxShape.rectangle,
RectCallback rectCallback,
VoidCallback onRemoved,
}) : _color = color,
_shape = shape,
_rectCallback = rectCallback,
super(controller: controller, referenceBox: referenceBox, onRemoved: onRemoved) {
assert(color != null);
assert(shape != null);
_alphaController = new AnimationController(duration: _kHighlightFadeDuration, vsync: controller.vsync)
..addListener(controller.markNeedsPaint)
..addStatusListener(_handleAlphaStatusChanged)
..forward();
_alpha = new IntTween(
begin: 0,
end: color.alpha
).animate(_alphaController);
controller.addInkFeature(this);
}
final BoxShape _shape;
final RectCallback _rectCallback;
Animation<int> _alpha;
AnimationController _alphaController;
/// The color of the ink used to emphasize part of the material.
Color get color => _color;
Color _color;
set color(Color value) {
if (value == _color)
return;
_color = value;
controller.markNeedsPaint();
}
/// Whether this part of the material is being visually emphasized.
bool get active => _active;
bool _active = true;
/// Start visually emphasizing this part of the material.
void activate() {
_active = true;
_alphaController.forward();
}
/// Stop visually emphasizing this part of the material.
void deactivate() {
_active = false;
_alphaController.reverse();
}
void _handleAlphaStatusChanged(AnimationStatus status) {
if (status == AnimationStatus.dismissed && !_active)
dispose();
}
@override
void dispose() {
_alphaController.dispose();
super.dispose();
}
void _paintHighlight(Canvas canvas, Rect rect, Paint paint) {
assert(_shape != null);
switch (_shape) {
case BoxShape.circle:
canvas.drawCircle(rect.center, Material.defaultSplashRadius, paint);
break;
case BoxShape.rectangle:
canvas.drawRect(rect, paint);
break;
}
}
@override
void paintFeature(Canvas canvas, Matrix4 transform) {
final Paint paint = new Paint()..color = color.withAlpha(_alpha.value);
final Offset originOffset = MatrixUtils.getAsTranslation(transform);
final Rect rect = (_rectCallback != null ? _rectCallback() : Point.origin & referenceBox.size);
if (originOffset == null) {
canvas.save();
canvas.transform(transform.storage);
_paintHighlight(canvas, rect, paint);
canvas.restore();
} else {
_paintHighlight(canvas, rect.shift(originOffset), paint);
}
}
}
// Copyright 2017 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 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'material.dart';
const Duration _kUnconfirmedSplashDuration = const Duration(seconds: 1);
const Duration _kSplashFadeDuration = const Duration(milliseconds: 200);
const double _kSplashInitialSize = 0.0; // logical pixels
const double _kSplashConfirmedVelocity = 1.0; // logical pixels per millisecond
RectCallback _getClipCallback(RenderBox referenceBox, bool containedInkWell, RectCallback rectCallback) {
if (rectCallback != null) {
assert(containedInkWell);
return rectCallback;
}
if (containedInkWell)
return () => Point.origin & referenceBox.size;
return null;
}
double _getTargetRadius(RenderBox referenceBox, bool containedInkWell, RectCallback rectCallback, Point position) {
if (containedInkWell) {
final Size size = rectCallback != null ? rectCallback().size : referenceBox.size;
return _getSplashRadiusForPoistionInSize(size, position);
}
return Material.defaultSplashRadius;
}
double _getSplashRadiusForPoistionInSize(Size bounds, Point position) {
double d1 = (position - bounds.topLeft(Point.origin)).distance;
double d2 = (position - bounds.topRight(Point.origin)).distance;
double d3 = (position - bounds.bottomLeft(Point.origin)).distance;
double d4 = (position - bounds.bottomRight(Point.origin)).distance;
return math.max(math.max(d1, d2), math.max(d3, d4)).ceilToDouble();
}
/// A visual reaction on a piece of [Material] to user input.
///
/// This object is rarely created directly. Instead of creating an ink splash
/// directly, consider using an [InkResponse] or [InkWell] widget, which uses
/// gestures (such as tap and long-press) to trigger ink splashes.
///
/// See also:
///
/// * [InkResponse], which uses gestures to trigger ink highlights and ink
/// splashes in the parent [Material].
/// * [InkWell], which is a rectangular [InkResponse] (the most common type of
/// ink response).
/// * [Material], which is the widget on which the ink splash is painted.
/// * [InkHighlight], which is an ink feature that emphasizes a part of a
/// [Material].
class InkSplash extends InkFeature {
/// Begin a splash, centered at position relative to [referenceBox].
///
/// The [controller] argument is typically obtained via
/// `Material.of(context)`.
///
/// If `containedInkWell` is true, then the splash will be sized to fit
/// the well rectangle, then clipped to it when drawn. The well
/// rectangle is the box returned by `rectCallback`, if provided, or
/// otherwise is the bounds of the [referenceBox].
///
/// If `containedInkWell` is false, then `rectCallback` should be null.
/// The ink splash is clipped only to the edges of the [Material].
/// This is the default.
///
/// When the splash is removed, `onRemoved` will be called.
InkSplash({
@required MaterialInkController controller,
@required RenderBox referenceBox,
Point position,
Color color,
bool containedInkWell: false,
RectCallback rectCallback,
double radius,
VoidCallback onRemoved,
}) : _position = position,
_color = color,
_targetRadius = radius ?? _getTargetRadius(referenceBox, containedInkWell, rectCallback, position),
_clipCallback = _getClipCallback(referenceBox, containedInkWell, rectCallback),
_repositionToReferenceBox = !containedInkWell,
super(controller: controller, referenceBox: referenceBox, onRemoved: onRemoved) {
_radiusController = new AnimationController(duration: _kUnconfirmedSplashDuration, vsync: controller.vsync)
..addListener(controller.markNeedsPaint)
..forward();
_radius = new Tween<double>(
begin: _kSplashInitialSize,
end: _targetRadius
).animate(_radiusController);
_alphaController = new AnimationController(duration: _kSplashFadeDuration, vsync: controller.vsync)
..addListener(controller.markNeedsPaint)
..addStatusListener(_handleAlphaStatusChanged);
_alpha = new IntTween(
begin: color.alpha,
end: 0
).animate(_alphaController);
controller.addInkFeature(this);
}
final Point _position;
final Color _color;
final double _targetRadius;
final RectCallback _clipCallback;
final bool _repositionToReferenceBox;
Animation<double> _radius;
AnimationController _radiusController;
Animation<int> _alpha;
AnimationController _alphaController;
/// The user input is confirmed.
///
/// Causes the reaction to propagate faster across the material.
void confirm() {
final int duration = (_targetRadius / _kSplashConfirmedVelocity).floor();
_radiusController
..duration = new Duration(milliseconds: duration)
..forward();
_alphaController.forward();
}
/// The user input was canceled.
///
/// Causes the reaction to gradually disappear.
void cancel() {
_alphaController.forward();
}
void _handleAlphaStatusChanged(AnimationStatus status) {
if (status == AnimationStatus.completed)
dispose();
}
@override
void dispose() {
_radiusController.dispose();
_alphaController.dispose();
super.dispose();
}
@override
void paintFeature(Canvas canvas, Matrix4 transform) {
final Paint paint = new Paint()..color = _color.withAlpha(_alpha.value);
Point center = _position;
if (_repositionToReferenceBox)
center = Point.lerp(center, referenceBox.size.center(Point.origin), _radiusController.value);
final Offset originOffset = MatrixUtils.getAsTranslation(transform);
if (originOffset == null) {
canvas.save();
canvas.transform(transform.storage);
if (_clipCallback != null)
canvas.clipRect(_clipCallback());
canvas.drawCircle(center, _radius.value, paint);
canvas.restore();
} else {
if (_clipCallback != null) {
canvas.save();
canvas.clipRect(_clipCallback().shift(originOffset));
}
canvas.drawCircle(center + originOffset, _radius.value, paint);
if (_clipCallback != null)
canvas.restore();
}
}
}
...@@ -7,8 +7,11 @@ import 'dart:collection'; ...@@ -7,8 +7,11 @@ import 'dart:collection';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:meta/meta.dart';
import 'debug.dart'; import 'debug.dart';
import 'ink_highlight.dart';
import 'ink_splash.dart';
import 'material.dart'; import 'material.dart';
import 'theme.dart'; import 'theme.dart';
...@@ -88,6 +91,7 @@ class InkResponse extends StatefulWidget { ...@@ -88,6 +91,7 @@ class InkResponse extends StatefulWidget {
/// specialize [InkResponse] for unusual cases. For example, /// specialize [InkResponse] for unusual cases. For example,
/// [TableRowInkWell] implements this method to verify that the widget is /// [TableRowInkWell] implements this method to verify that the widget is
/// in a table. /// in a table.
@mustCallSuper
bool debugCheckContext(BuildContext context) { bool debugCheckContext(BuildContext context) {
assert(debugCheckHasMaterial(context)); assert(debugCheckHasMaterial(context));
return true; return true;
...@@ -108,9 +112,9 @@ class _InkResponseState<T extends InkResponse> extends State<T> { ...@@ -108,9 +112,9 @@ class _InkResponseState<T extends InkResponse> extends State<T> {
return; return;
if (value) { if (value) {
if (_lastHighlight == null) { if (_lastHighlight == null) {
RenderBox referenceBox = context.findRenderObject(); final RenderBox referenceBox = context.findRenderObject();
assert(Material.of(context) != null); _lastHighlight = new InkHighlight(
_lastHighlight = Material.of(context).highlightAt( controller: Material.of(context),
referenceBox: referenceBox, referenceBox: referenceBox,
color: Theme.of(context).highlightColor, color: Theme.of(context).highlightColor,
shape: config.highlightShape, shape: config.highlightShape,
...@@ -118,7 +122,7 @@ class _InkResponseState<T extends InkResponse> extends State<T> { ...@@ -118,7 +122,7 @@ class _InkResponseState<T extends InkResponse> extends State<T> {
onRemoved: () { onRemoved: () {
assert(_lastHighlight != null); assert(_lastHighlight != null);
_lastHighlight = null; _lastHighlight = null;
} },
); );
} else { } else {
_lastHighlight.activate(); _lastHighlight.activate();
...@@ -132,11 +136,11 @@ class _InkResponseState<T extends InkResponse> extends State<T> { ...@@ -132,11 +136,11 @@ class _InkResponseState<T extends InkResponse> extends State<T> {
} }
void _handleTapDown(TapDownDetails details) { void _handleTapDown(TapDownDetails details) {
RenderBox referenceBox = context.findRenderObject(); final RenderBox referenceBox = context.findRenderObject();
assert(Material.of(context) != null); final RectCallback rectCallback = config.getRectCallback(referenceBox);
InkSplash splash; InkSplash splash;
RectCallback rectCallback = config.getRectCallback(referenceBox); splash = new InkSplash(
splash = Material.of(context).splashAt( controller: Material.of(context),
referenceBox: referenceBox, referenceBox: referenceBox,
position: referenceBox.globalToLocal(details.globalPosition), position: referenceBox.globalToLocal(details.globalPosition),
color: Theme.of(context).splashColor, color: Theme.of(context).splashColor,
...@@ -189,7 +193,7 @@ class _InkResponseState<T extends InkResponse> extends State<T> { ...@@ -189,7 +193,7 @@ class _InkResponseState<T extends InkResponse> extends State<T> {
@override @override
void deactivate() { void deactivate() {
if (_splashes != null) { if (_splashes != null) {
Set<InkSplash> splashes = _splashes; final Set<InkSplash> splashes = _splashes;
_splashes = null; _splashes = null;
for (InkSplash splash in splashes) for (InkSplash splash in splashes)
splash.dispose(); splash.dispose();
...@@ -219,7 +223,7 @@ class _InkResponseState<T extends InkResponse> extends State<T> { ...@@ -219,7 +223,7 @@ class _InkResponseState<T extends InkResponse> extends State<T> {
} }
/// A rectangular area of a Material that responds to touch. /// A rectangular area of a [Material] that responds to touch.
/// ///
/// Must have an ancestor [Material] widget in which to cause ink reactions. /// Must have an ancestor [Material] widget in which to cause ink reactions.
/// ///
......
...@@ -2,8 +2,6 @@ ...@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:math' as math;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
...@@ -14,7 +12,7 @@ import 'theme.dart'; ...@@ -14,7 +12,7 @@ import 'theme.dart';
/// Signature for the callback used by ink effects to obtain the rectangle for the effect. /// Signature for the callback used by ink effects to obtain the rectangle for the effect.
/// ///
/// Used by [MaterialInkController.splashAt], for example. /// Used by [InkHighlight] and [InkSplash], for example.
typedef Rect RectCallback(); typedef Rect RectCallback();
/// The various kinds of material in material design. Used to /// The various kinds of material in material design. Used to
...@@ -55,48 +53,6 @@ final Map<MaterialType, BorderRadius> kMaterialEdges = <MaterialType, BorderRadi ...@@ -55,48 +53,6 @@ final Map<MaterialType, BorderRadius> kMaterialEdges = <MaterialType, BorderRadi
MaterialType.transparency: null, MaterialType.transparency: null,
}; };
/// A visual reaction on a piece of [Material] to user input.
///
/// Typically created by [MaterialInkController.splashAt].
abstract class InkSplash {
/// The user input is confirmed.
///
/// Causes the reaction to propagate faster across the material.
void confirm();
/// The user input was canceled.
///
/// Causes the reaction to gradually disappear.
void cancel();
/// Free up the resources associated with this reaction.
void dispose();
/// The default radius of an ink splash in logical pixels.
static const double defaultRadius = 35.0;
}
/// A visual emphasis on a part of a [Material] receiving user interaction.
///
/// Typically created by [MaterialInkController.highlightAt].
abstract class InkHighlight {
/// Start visually emphasizing this part of the material.
void activate();
/// Stop visually emphasizing this part of the material.
void deactivate();
/// Free up the resources associated with this highlight.
void dispose();
/// Whether this part of the material is being visually emphasized.
bool get active;
/// The color of the ink used to emphasize part of the material.
Color get color;
set color(Color value);
}
/// An interface for creating [InkSplash]s and [InkHighlight]s on a material. /// An interface for creating [InkSplash]s and [InkHighlight]s on a material.
/// ///
/// Typically obtained via [Material.of]. /// Typically obtained via [Material.of].
...@@ -104,41 +60,19 @@ abstract class MaterialInkController { ...@@ -104,41 +60,19 @@ abstract class MaterialInkController {
/// The color of the material. /// The color of the material.
Color get color; Color get color;
/// Begin a splash, centered at position relative to referenceBox. /// The ticker provider used by the controller.
///
/// If containedInkWell is true, then the splash will be sized to fit
/// the well rectangle, then clipped to it when drawn. The well
/// rectangle is the box returned by rectCallback, if provided, or
/// otherwise is the bounds of the referenceBox.
/// ///
/// If containedInkWell is false, then rectCallback should be null. /// Ink features that are added to this controller with [addInkFeature] should
/// The ink splash is clipped only to the edges of the [Material]. /// use this vsync to drive their animations.
/// This is the default. TickerProvider get vsync;
/// Add an [InkFeature], such as an [InkSplash] or an [InkHighlight].
/// ///
/// When the splash is removed, onRemoved will be called. /// The ink feature will paint as part of this controller.
InkSplash splashAt({
RenderBox referenceBox,
Point position,
Color color,
bool containedInkWell: false,
RectCallback rectCallback,
VoidCallback onRemoved,
double radius,
});
/// Begin a highlight animation. If a rectCallback is given, then it
/// provides the highlight rectangle, otherwise, the highlight
/// rectangle is coincident with the referenceBox.
InkHighlight highlightAt({
RenderBox referenceBox,
Color color,
BoxShape shape: BoxShape.rectangle,
RectCallback rectCallback,
VoidCallback onRemoved
});
/// Add an arbitrary InkFeature to this InkController.
void addInkFeature(InkFeature feature); void addInkFeature(InkFeature feature);
/// Notifies the controller that one of its ink features needs to repaint.
void markNeedsPaint();
} }
/// A piece of material. /// A piece of material.
...@@ -247,6 +181,9 @@ class Material extends StatefulWidget { ...@@ -247,6 +181,9 @@ class Material extends StatefulWidget {
if (borderRadius != null) if (borderRadius != null)
description.add('borderRadius: $borderRadius'); description.add('borderRadius: $borderRadius');
} }
/// The default radius of an ink splash in logical pixels.
static const double defaultSplashRadius = 35.0;
} }
class _MaterialState extends State<Material> with TickerProviderStateMixin { class _MaterialState extends State<Material> with TickerProviderStateMixin {
...@@ -321,10 +258,6 @@ class _MaterialState extends State<Material> with TickerProviderStateMixin { ...@@ -321,10 +258,6 @@ class _MaterialState extends State<Material> with TickerProviderStateMixin {
} }
const Duration _kHighlightFadeDuration = const Duration(milliseconds: 200); const Duration _kHighlightFadeDuration = const Duration(milliseconds: 200);
const Duration _kUnconfirmedSplashDuration = const Duration(seconds: 1);
const double _kSplashConfirmedVelocity = 1.0; // logical pixels per millisecond
const double _kSplashInitialSize = 0.0; // logical pixels
class _RenderInkFeatures extends RenderProxyBox implements MaterialInkController { class _RenderInkFeatures extends RenderProxyBox implements MaterialInkController {
_RenderInkFeatures({ RenderBox child, @required this.vsync, this.color }) : super(child) { _RenderInkFeatures({ RenderBox child, @required this.vsync, this.color }) : super(child) {
...@@ -334,6 +267,7 @@ class _RenderInkFeatures extends RenderProxyBox implements MaterialInkController ...@@ -334,6 +267,7 @@ class _RenderInkFeatures extends RenderProxyBox implements MaterialInkController
// This class should exist in a 1:1 relationship with a MaterialState object, // This class should exist in a 1:1 relationship with a MaterialState object,
// since there's no current support for dynamically changing the ticker // since there's no current support for dynamically changing the ticker
// provider. // provider.
@override
final TickerProvider vsync; final TickerProvider vsync;
// This is here to satisfy the MaterialInkController contract. // This is here to satisfy the MaterialInkController contract.
...@@ -344,75 +278,6 @@ class _RenderInkFeatures extends RenderProxyBox implements MaterialInkController ...@@ -344,75 +278,6 @@ class _RenderInkFeatures extends RenderProxyBox implements MaterialInkController
final List<InkFeature> _inkFeatures = <InkFeature>[]; final List<InkFeature> _inkFeatures = <InkFeature>[];
@override
InkSplash splashAt({
RenderBox referenceBox,
Point position,
Color color,
bool containedInkWell: false,
RectCallback rectCallback,
VoidCallback onRemoved,
double radius,
}) {
RectCallback clipCallback;
if (containedInkWell) {
Size size;
if (rectCallback != null) {
size = rectCallback().size;
clipCallback = rectCallback;
} else {
size = referenceBox.size;
clipCallback = () => Point.origin & referenceBox.size;
}
radius ??= _getSplashTargetSize(size, position);
} else {
assert(rectCallback == null);
radius ??= InkSplash.defaultRadius;
}
_InkSplash splash = new _InkSplash(
controller: this,
referenceBox: referenceBox,
position: position,
color: color,
targetRadius: radius,
clipCallback: clipCallback,
repositionToReferenceBox: !containedInkWell,
onRemoved: onRemoved,
vsync: vsync,
);
addInkFeature(splash);
return splash;
}
double _getSplashTargetSize(Size bounds, Point position) {
double d1 = (position - bounds.topLeft(Point.origin)).distance;
double d2 = (position - bounds.topRight(Point.origin)).distance;
double d3 = (position - bounds.bottomLeft(Point.origin)).distance;
double d4 = (position - bounds.bottomRight(Point.origin)).distance;
return math.max(math.max(d1, d2), math.max(d3, d4)).ceilToDouble();
}
@override
InkHighlight highlightAt({
RenderBox referenceBox,
Color color,
BoxShape shape: BoxShape.rectangle,
RectCallback rectCallback,
VoidCallback onRemoved
}) {
_InkHighlight highlight = new _InkHighlight(
controller: this,
referenceBox: referenceBox,
color: color,
shape: shape,
rectCallback: rectCallback,
onRemoved: onRemoved,
vsync: vsync,
);
addInkFeature(highlight);
return highlight;
}
@override @override
void addInkFeature(InkFeature feature) { void addInkFeature(InkFeature feature) {
assert(!feature._debugDisposed); assert(!feature._debugDisposed);
...@@ -478,11 +343,15 @@ class _InkFeatures extends SingleChildRenderObjectWidget { ...@@ -478,11 +343,15 @@ class _InkFeatures extends SingleChildRenderObjectWidget {
abstract class InkFeature { abstract class InkFeature {
/// Initializes fields for subclasses. /// Initializes fields for subclasses.
InkFeature({ InkFeature({
MaterialInkController controller, @required MaterialInkController controller,
this.referenceBox, @required this.referenceBox,
this.onRemoved this.onRemoved
}) : _controller = controller; }) : _controller = controller {
assert(_controller != null);
assert(referenceBox != null);
}
MaterialInkController get controller => _controller;
_RenderInkFeatures _controller; _RenderInkFeatures _controller;
/// The render box whose visual position defines the frame of reference for this ink feature. /// The render box whose visual position defines the frame of reference for this ink feature.
...@@ -494,6 +363,7 @@ abstract class InkFeature { ...@@ -494,6 +363,7 @@ abstract class InkFeature {
bool _debugDisposed = false; bool _debugDisposed = false;
/// Free up the resources associated with this ink feature. /// Free up the resources associated with this ink feature.
@mustCallSuper
void dispose() { void dispose() {
assert(!_debugDisposed); assert(!_debugDisposed);
assert(() { _debugDisposed = true; return true; }); assert(() { _debugDisposed = true; return true; });
...@@ -506,7 +376,7 @@ abstract class InkFeature { ...@@ -506,7 +376,7 @@ abstract class InkFeature {
assert(referenceBox.attached); assert(referenceBox.attached);
assert(!_debugDisposed); assert(!_debugDisposed);
// find the chain of renderers from us to the feature's referenceBox // find the chain of renderers from us to the feature's referenceBox
List<RenderBox> descendants = <RenderBox>[referenceBox]; final List<RenderBox> descendants = <RenderBox>[referenceBox];
RenderBox node = referenceBox; RenderBox node = referenceBox;
while (node != _controller) { while (node != _controller) {
node = node.parent; node = node.parent;
...@@ -514,7 +384,7 @@ abstract class InkFeature { ...@@ -514,7 +384,7 @@ abstract class InkFeature {
descendants.add(node); descendants.add(node);
} }
// determine the transform that gets our coordinate system to be like theirs // determine the transform that gets our coordinate system to be like theirs
Matrix4 transform = new Matrix4.identity(); final Matrix4 transform = new Matrix4.identity();
assert(descendants.length >= 2); assert(descendants.length >= 2);
for (int index = descendants.length - 1; index > 0; index -= 1) for (int index = descendants.length - 1; index > 0; index -= 1)
descendants[index].applyPaintTransform(descendants[index - 1], transform); descendants[index].applyPaintTransform(descendants[index - 1], transform);
...@@ -525,190 +395,9 @@ abstract class InkFeature { ...@@ -525,190 +395,9 @@ abstract class InkFeature {
/// ///
/// The transform argument gives the coordinate conversion from the coordinate /// The transform argument gives the coordinate conversion from the coordinate
/// system of the canvas to the coodinate system of the [referenceBox]. /// system of the canvas to the coodinate system of the [referenceBox].
@protected
void paintFeature(Canvas canvas, Matrix4 transform); void paintFeature(Canvas canvas, Matrix4 transform);
@override @override
String toString() => "$runtimeType@$hashCode"; String toString() => '$runtimeType@$hashCode';
}
class _InkSplash extends InkFeature implements InkSplash {
_InkSplash({
_RenderInkFeatures controller,
RenderBox referenceBox,
this.position,
this.color,
this.targetRadius,
this.clipCallback,
this.repositionToReferenceBox,
VoidCallback onRemoved,
@required TickerProvider vsync,
}) : super(controller: controller, referenceBox: referenceBox, onRemoved: onRemoved) {
_radiusController = new AnimationController(duration: _kUnconfirmedSplashDuration, vsync: vsync)
..addListener(controller.markNeedsPaint)
..forward();
_radius = new Tween<double>(
begin: _kSplashInitialSize,
end: targetRadius
).animate(_radiusController);
_alphaController = new AnimationController(duration: _kHighlightFadeDuration, vsync: vsync)
..addListener(controller.markNeedsPaint)
..addStatusListener(_handleAlphaStatusChanged);
_alpha = new IntTween(
begin: color.alpha,
end: 0
).animate(_alphaController);
}
final Point position;
final Color color;
final double targetRadius;
final RectCallback clipCallback;
final bool repositionToReferenceBox;
Animation<double> _radius;
AnimationController _radiusController;
Animation<int> _alpha;
AnimationController _alphaController;
@override
void confirm() {
int duration = (targetRadius / _kSplashConfirmedVelocity).floor();
_radiusController
..duration = new Duration(milliseconds: duration)
..forward();
_alphaController.forward();
}
@override
void cancel() {
_alphaController.forward();
}
void _handleAlphaStatusChanged(AnimationStatus status) {
if (status == AnimationStatus.completed)
dispose();
}
@override
void dispose() {
_radiusController.stop();
_alphaController.stop();
super.dispose();
}
@override
void paintFeature(Canvas canvas, Matrix4 transform) {
Paint paint = new Paint()..color = color.withAlpha(_alpha.value);
Point center = position;
if (repositionToReferenceBox)
center = Point.lerp(center, referenceBox.size.center(Point.origin), _radiusController.value);
Offset originOffset = MatrixUtils.getAsTranslation(transform);
if (originOffset == null) {
canvas.save();
canvas.transform(transform.storage);
if (clipCallback != null)
canvas.clipRect(clipCallback());
canvas.drawCircle(center, _radius.value, paint);
canvas.restore();
} else {
if (clipCallback != null) {
canvas.save();
canvas.clipRect(clipCallback().shift(originOffset));
}
canvas.drawCircle(center + originOffset, _radius.value, paint);
if (clipCallback != null)
canvas.restore();
}
}
}
class _InkHighlight extends InkFeature implements InkHighlight {
_InkHighlight({
_RenderInkFeatures controller,
RenderBox referenceBox,
this.rectCallback,
Color color,
this.shape,
VoidCallback onRemoved,
@required TickerProvider vsync,
}) : _color = color,
super(controller: controller, referenceBox: referenceBox, onRemoved: onRemoved) {
_alphaController = new AnimationController(duration: _kHighlightFadeDuration, vsync: vsync)
..addListener(controller.markNeedsPaint)
..addStatusListener(_handleAlphaStatusChanged)
..forward();
_alpha = new IntTween(
begin: 0,
end: color.alpha
).animate(_alphaController);
}
final RectCallback rectCallback;
@override
Color get color => _color;
Color _color;
@override
set color(Color value) {
if (value == _color)
return;
_color = value;
_controller.markNeedsPaint();
}
final BoxShape shape;
@override
bool get active => _active;
bool _active = true;
Animation<int> _alpha;
AnimationController _alphaController;
@override
void activate() {
_active = true;
_alphaController.forward();
}
@override
void deactivate() {
_active = false;
_alphaController.reverse();
}
void _handleAlphaStatusChanged(AnimationStatus status) {
if (status == AnimationStatus.dismissed && !_active)
dispose();
}
@override
void dispose() {
_alphaController.stop();
super.dispose();
}
void _paintHighlight(Canvas canvas, Rect rect, Paint paint) {
if (shape == BoxShape.rectangle)
canvas.drawRect(rect, paint);
else
canvas.drawCircle(rect.center, InkSplash.defaultRadius, paint);
}
@override
void paintFeature(Canvas canvas, Matrix4 transform) {
Paint paint = new Paint()..color = color.withAlpha(_alpha.value);
Offset originOffset = MatrixUtils.getAsTranslation(transform);
final Rect rect = (rectCallback != null ? rectCallback() : Point.origin & referenceBox.size);
if (originOffset == null) {
canvas.save();
canvas.transform(transform.storage);
_paintHighlight(canvas, rect, paint);
canvas.restore();
} else {
_paintHighlight(canvas, rect.shift(originOffset), paint);
}
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment