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';
export 'src/material/icon_theme_data.dart';
export 'src/material/icons.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/input.dart';
export 'src/material/list.dart';
......
......@@ -136,7 +136,7 @@ class IconButton extends StatelessWidget {
maxHeight: size,
child: new ConstrainedBox(
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(
alignment: alignment,
......@@ -161,7 +161,7 @@ class IconButton extends StatelessWidget {
return new InkResponse(
onTap: onPressed,
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';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:meta/meta.dart';
import 'debug.dart';
import 'ink_highlight.dart';
import 'ink_splash.dart';
import 'material.dart';
import 'theme.dart';
......@@ -88,6 +91,7 @@ class InkResponse extends StatefulWidget {
/// specialize [InkResponse] for unusual cases. For example,
/// [TableRowInkWell] implements this method to verify that the widget is
/// in a table.
@mustCallSuper
bool debugCheckContext(BuildContext context) {
assert(debugCheckHasMaterial(context));
return true;
......@@ -108,9 +112,9 @@ class _InkResponseState<T extends InkResponse> extends State<T> {
return;
if (value) {
if (_lastHighlight == null) {
RenderBox referenceBox = context.findRenderObject();
assert(Material.of(context) != null);
_lastHighlight = Material.of(context).highlightAt(
final RenderBox referenceBox = context.findRenderObject();
_lastHighlight = new InkHighlight(
controller: Material.of(context),
referenceBox: referenceBox,
color: Theme.of(context).highlightColor,
shape: config.highlightShape,
......@@ -118,7 +122,7 @@ class _InkResponseState<T extends InkResponse> extends State<T> {
onRemoved: () {
assert(_lastHighlight != null);
_lastHighlight = null;
}
},
);
} else {
_lastHighlight.activate();
......@@ -132,11 +136,11 @@ class _InkResponseState<T extends InkResponse> extends State<T> {
}
void _handleTapDown(TapDownDetails details) {
RenderBox referenceBox = context.findRenderObject();
assert(Material.of(context) != null);
final RenderBox referenceBox = context.findRenderObject();
final RectCallback rectCallback = config.getRectCallback(referenceBox);
InkSplash splash;
RectCallback rectCallback = config.getRectCallback(referenceBox);
splash = Material.of(context).splashAt(
splash = new InkSplash(
controller: Material.of(context),
referenceBox: referenceBox,
position: referenceBox.globalToLocal(details.globalPosition),
color: Theme.of(context).splashColor,
......@@ -189,7 +193,7 @@ class _InkResponseState<T extends InkResponse> extends State<T> {
@override
void deactivate() {
if (_splashes != null) {
Set<InkSplash> splashes = _splashes;
final Set<InkSplash> splashes = _splashes;
_splashes = null;
for (InkSplash splash in splashes)
splash.dispose();
......@@ -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.
///
......
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