mirror of
https://github.com/flutter/flutter
synced 2024-10-13 19:52:53 +00:00
Add support for a new kind of InkSplash: the "ripple" (#13986)
This commit is contained in:
parent
c5c63dfd47
commit
03e8ab1f53
|
@ -53,6 +53,7 @@ export 'src/material/grid_tile_bar.dart';
|
||||||
export 'src/material/icon_button.dart';
|
export 'src/material/icon_button.dart';
|
||||||
export 'src/material/icons.dart';
|
export 'src/material/icons.dart';
|
||||||
export 'src/material/ink_highlight.dart';
|
export 'src/material/ink_highlight.dart';
|
||||||
|
export 'src/material/ink_ripple.dart';
|
||||||
export 'src/material/ink_splash.dart';
|
export 'src/material/ink_splash.dart';
|
||||||
export 'src/material/ink_well.dart';
|
export 'src/material/ink_well.dart';
|
||||||
export 'src/material/input_border.dart';
|
export 'src/material/input_border.dart';
|
||||||
|
|
|
@ -6,6 +6,7 @@ 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';
|
||||||
|
|
||||||
|
import 'ink_well.dart' show InteractiveInkFeature;
|
||||||
import 'material.dart';
|
import 'material.dart';
|
||||||
|
|
||||||
const Duration _kHighlightFadeDuration = const Duration(milliseconds: 200);
|
const Duration _kHighlightFadeDuration = const Duration(milliseconds: 200);
|
||||||
|
@ -25,7 +26,7 @@ const Duration _kHighlightFadeDuration = const Duration(milliseconds: 200);
|
||||||
/// * [Material], which is the widget on which the ink highlight is painted.
|
/// * [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
|
/// * [InkSplash], which is an ink feature that shows a reaction to user input
|
||||||
/// on a [Material].
|
/// on a [Material].
|
||||||
class InkHighlight extends InkFeature {
|
class InkHighlight extends InteractiveInkFeature {
|
||||||
/// Begin a highlight animation.
|
/// Begin a highlight animation.
|
||||||
///
|
///
|
||||||
/// The [controller] argument is typically obtained via
|
/// The [controller] argument is typically obtained via
|
||||||
|
@ -45,11 +46,10 @@ class InkHighlight extends InkFeature {
|
||||||
VoidCallback onRemoved,
|
VoidCallback onRemoved,
|
||||||
}) : assert(color != null),
|
}) : assert(color != null),
|
||||||
assert(shape != null),
|
assert(shape != null),
|
||||||
_color = color,
|
|
||||||
_shape = shape,
|
_shape = shape,
|
||||||
_borderRadius = borderRadius ?? BorderRadius.zero,
|
_borderRadius = borderRadius ?? BorderRadius.zero,
|
||||||
_rectCallback = rectCallback,
|
_rectCallback = rectCallback,
|
||||||
super(controller: controller, referenceBox: referenceBox, onRemoved: onRemoved) {
|
super(controller: controller, referenceBox: referenceBox, color: color, onRemoved: onRemoved) {
|
||||||
_alphaController = new AnimationController(duration: _kHighlightFadeDuration, vsync: controller.vsync)
|
_alphaController = new AnimationController(duration: _kHighlightFadeDuration, vsync: controller.vsync)
|
||||||
..addListener(controller.markNeedsPaint)
|
..addListener(controller.markNeedsPaint)
|
||||||
..addStatusListener(_handleAlphaStatusChanged)
|
..addStatusListener(_handleAlphaStatusChanged)
|
||||||
|
@ -69,16 +69,6 @@ class InkHighlight extends InkFeature {
|
||||||
Animation<int> _alpha;
|
Animation<int> _alpha;
|
||||||
AnimationController _alphaController;
|
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.
|
/// Whether this part of the material is being visually emphasized.
|
||||||
bool get active => _active;
|
bool get active => _active;
|
||||||
bool _active = true;
|
bool _active = true;
|
||||||
|
|
275
packages/flutter/lib/src/material/ink_ripple.dart
Normal file
275
packages/flutter/lib/src/material/ink_ripple.dart
Normal file
|
@ -0,0 +1,275 @@
|
||||||
|
// 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 'ink_well.dart';
|
||||||
|
import 'material.dart';
|
||||||
|
|
||||||
|
const Duration _kUnconfirmedRippleDuration = const Duration(seconds: 1);
|
||||||
|
const Duration _kFadeInDuration = const Duration(milliseconds: 75);
|
||||||
|
const Duration _kRadiusDuration = const Duration(milliseconds: 225);
|
||||||
|
const Duration _kFadeOutDuration = const Duration(milliseconds: 450);
|
||||||
|
const Duration _kCancelDuration = const Duration(milliseconds: 75);
|
||||||
|
|
||||||
|
// The fade out begins 300ms after the _fadeOutController starts. See confirm().
|
||||||
|
const double _kFadeOutIntervalStart = 300.0 / 450.0;
|
||||||
|
|
||||||
|
const double _kRippleConfirmedVelocity = 1.0; // logical pixels per millisecond
|
||||||
|
|
||||||
|
RectCallback _getClipCallback(RenderBox referenceBox, bool containedInkWell, RectCallback rectCallback) {
|
||||||
|
if (rectCallback != null) {
|
||||||
|
assert(containedInkWell);
|
||||||
|
return rectCallback;
|
||||||
|
}
|
||||||
|
if (containedInkWell)
|
||||||
|
return () => Offset.zero & referenceBox.size;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
double _getTargetRadius(RenderBox referenceBox, bool containedInkWell, RectCallback rectCallback, Offset position) {
|
||||||
|
if (containedInkWell) {
|
||||||
|
final Size size = rectCallback != null ? rectCallback().size : referenceBox.size;
|
||||||
|
return _getRippleRadiusForPositionInSize(size, position);
|
||||||
|
}
|
||||||
|
return Material.defaultSplashRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
double _getRippleRadiusForPositionInSize(Size bounds, Offset position) {
|
||||||
|
final double d1 = (position - bounds.topLeft(Offset.zero)).distance;
|
||||||
|
final double d2 = (position - bounds.topRight(Offset.zero)).distance;
|
||||||
|
final double d3 = (position - bounds.bottomLeft(Offset.zero)).distance;
|
||||||
|
final double d4 = (position - bounds.bottomRight(Offset.zero)).distance;
|
||||||
|
return math.max(math.max(d1, d2), math.max(d3, d4)).ceilToDouble();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _InkRippleFactory extends InteractiveInkFeatureFactory {
|
||||||
|
const _InkRippleFactory();
|
||||||
|
|
||||||
|
@override
|
||||||
|
InteractiveInkFeature create({
|
||||||
|
@required MaterialInkController controller,
|
||||||
|
@required RenderBox referenceBox,
|
||||||
|
@required Offset position,
|
||||||
|
@required Color color,
|
||||||
|
bool containedInkWell: false,
|
||||||
|
RectCallback rectCallback,
|
||||||
|
BorderRadius borderRadius,
|
||||||
|
double radius,
|
||||||
|
VoidCallback onRemoved,
|
||||||
|
}) {
|
||||||
|
return new InkRipple(
|
||||||
|
controller: controller,
|
||||||
|
referenceBox: referenceBox,
|
||||||
|
position: position,
|
||||||
|
color: color,
|
||||||
|
containedInkWell: containedInkWell,
|
||||||
|
rectCallback: rectCallback,
|
||||||
|
borderRadius: borderRadius,
|
||||||
|
radius: radius,
|
||||||
|
onRemoved: onRemoved,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A visual reaction on a piece of [Material] to user input.
|
||||||
|
///
|
||||||
|
/// A circular ink feature whose origin starts at the input touch point and
|
||||||
|
/// whose radius expands from 60% of the final radius. The splash origin
|
||||||
|
/// animates to the center of its [referenceBox].
|
||||||
|
///
|
||||||
|
/// This object is rarely created directly. Instead of creating an ink ripple,
|
||||||
|
/// consider using an [InkResponse] or [InkWell] widget, which uses
|
||||||
|
/// gestures (such as tap and long-press) to trigger ink splashes. This class
|
||||||
|
/// is used when the [Theme]'s [ThemeData.splashType] is [InkSplashType.ripple].
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [InkSplash], which is an ink splash feature that expands less
|
||||||
|
/// aggressively than the ripple.
|
||||||
|
/// * [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 InkRipple extends InteractiveInkFeature {
|
||||||
|
/// Used to specify this type of ink splash for an [InkWell], [InkResponse]
|
||||||
|
/// or material [Theme].
|
||||||
|
static const InteractiveInkFeatureFactory splashFactory = const _InkRippleFactory();
|
||||||
|
|
||||||
|
/// Begin a ripple, centered at [position] relative to [referenceBox].
|
||||||
|
///
|
||||||
|
/// The [controller] argument is typically obtained via
|
||||||
|
/// `Material.of(context)`.
|
||||||
|
///
|
||||||
|
/// If [containedInkWell] is true, then the ripple 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 ripple is clipped only to the edges of the [Material].
|
||||||
|
/// This is the default.
|
||||||
|
///
|
||||||
|
/// When the ripple is removed, [onRemoved] will be called.
|
||||||
|
InkRipple({
|
||||||
|
@required MaterialInkController controller,
|
||||||
|
@required RenderBox referenceBox,
|
||||||
|
@required Offset position,
|
||||||
|
@required Color color,
|
||||||
|
bool containedInkWell: false,
|
||||||
|
RectCallback rectCallback,
|
||||||
|
BorderRadius borderRadius,
|
||||||
|
double radius,
|
||||||
|
VoidCallback onRemoved,
|
||||||
|
}) : assert(color != null),
|
||||||
|
assert(position != null),
|
||||||
|
_position = position,
|
||||||
|
_borderRadius = borderRadius ?? BorderRadius.zero,
|
||||||
|
_targetRadius = radius ?? _getTargetRadius(referenceBox, containedInkWell, rectCallback, position),
|
||||||
|
_clipCallback = _getClipCallback(referenceBox, containedInkWell, rectCallback),
|
||||||
|
super(controller: controller, referenceBox: referenceBox, color: color, onRemoved: onRemoved)
|
||||||
|
{
|
||||||
|
assert(_borderRadius != null);
|
||||||
|
|
||||||
|
// Immediately begin fading-in the initial splash.
|
||||||
|
_fadeInController = new AnimationController(duration: _kFadeInDuration, vsync: controller.vsync)
|
||||||
|
..addListener(controller.markNeedsPaint)
|
||||||
|
..forward();
|
||||||
|
_fadeIn = new IntTween(
|
||||||
|
begin: 0,
|
||||||
|
end: color.alpha,
|
||||||
|
).animate(_fadeInController);
|
||||||
|
|
||||||
|
// Controls the splash radius and its center. Starts upon confirm.
|
||||||
|
_radiusController = new AnimationController(duration: _kUnconfirmedRippleDuration, vsync: controller.vsync)
|
||||||
|
..addListener(controller.markNeedsPaint)
|
||||||
|
..forward();
|
||||||
|
// Initial splash diamater is 60% of the target diameter, final
|
||||||
|
// diameter is 10dps larger than the target diameter.
|
||||||
|
_radius = new Tween<double>(
|
||||||
|
begin: _targetRadius * 0.30,
|
||||||
|
end: _targetRadius + 5.0,
|
||||||
|
).animate(
|
||||||
|
new CurvedAnimation(
|
||||||
|
parent: _radiusController,
|
||||||
|
curve: Curves.ease,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Controls the splash radius and its center. Starts upon confirm however its
|
||||||
|
// Interval delays changes until the radius expansion has completed.
|
||||||
|
_fadeOutController = new AnimationController(duration: _kFadeOutDuration, vsync: controller.vsync)
|
||||||
|
..addListener(controller.markNeedsPaint)
|
||||||
|
..addStatusListener(_handleAlphaStatusChanged);
|
||||||
|
_fadeOut = new IntTween(
|
||||||
|
begin: color.alpha,
|
||||||
|
end: 0,
|
||||||
|
).animate(
|
||||||
|
new CurvedAnimation(
|
||||||
|
parent: _fadeOutController,
|
||||||
|
curve: const Interval(_kFadeOutIntervalStart, 1.0)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
controller.addInkFeature(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Offset _position;
|
||||||
|
final BorderRadius _borderRadius;
|
||||||
|
final double _targetRadius;
|
||||||
|
final RectCallback _clipCallback;
|
||||||
|
|
||||||
|
Animation<double> _radius;
|
||||||
|
AnimationController _radiusController;
|
||||||
|
|
||||||
|
Animation<int> _fadeIn;
|
||||||
|
AnimationController _fadeInController;
|
||||||
|
|
||||||
|
Animation<int> _fadeOut;
|
||||||
|
AnimationController _fadeOutController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void confirm() {
|
||||||
|
_radiusController
|
||||||
|
..duration = _kRadiusDuration
|
||||||
|
..forward();
|
||||||
|
_fadeOutController.forward();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void cancel() {
|
||||||
|
_fadeInController.stop();
|
||||||
|
_fadeOutController.animateTo(1.0, duration: _kCancelDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleAlphaStatusChanged(AnimationStatus status) {
|
||||||
|
if (status == AnimationStatus.completed)
|
||||||
|
dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_radiusController.dispose();
|
||||||
|
_fadeInController.dispose();
|
||||||
|
_fadeOutController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
RRect _clipRRectFromRect(Rect rect) {
|
||||||
|
return new RRect.fromRectAndCorners(
|
||||||
|
rect,
|
||||||
|
topLeft: _borderRadius.topLeft, topRight: _borderRadius.topRight,
|
||||||
|
bottomLeft: _borderRadius.bottomLeft, bottomRight: _borderRadius.bottomRight,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _clipCanvasWithRect(Canvas canvas, Rect rect, {Offset offset}) {
|
||||||
|
Rect clipRect = rect;
|
||||||
|
if (offset != null) {
|
||||||
|
clipRect = clipRect.shift(offset);
|
||||||
|
}
|
||||||
|
if (_borderRadius != BorderRadius.zero) {
|
||||||
|
canvas.clipRRect(_clipRRectFromRect(clipRect));
|
||||||
|
} else {
|
||||||
|
canvas.clipRect(clipRect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paintFeature(Canvas canvas, Matrix4 transform) {
|
||||||
|
final int alpha = _fadeInController.isAnimating ? _fadeIn.value : _fadeOut.value;
|
||||||
|
final Paint paint = new Paint()..color = color.withAlpha(alpha);
|
||||||
|
// Splash moves to the center of the reference box.
|
||||||
|
final Offset center = Offset.lerp(
|
||||||
|
_position,
|
||||||
|
referenceBox.size.center(Offset.zero),
|
||||||
|
Curves.ease.transform(_radiusController.value),
|
||||||
|
);
|
||||||
|
final Offset originOffset = MatrixUtils.getAsTranslation(transform);
|
||||||
|
if (originOffset == null) {
|
||||||
|
canvas.save();
|
||||||
|
canvas.transform(transform.storage);
|
||||||
|
if (_clipCallback != null) {
|
||||||
|
_clipCanvasWithRect(canvas, _clipCallback());
|
||||||
|
}
|
||||||
|
canvas.drawCircle(center, _radius.value, paint);
|
||||||
|
canvas.restore();
|
||||||
|
} else {
|
||||||
|
if (_clipCallback != null) {
|
||||||
|
canvas.save();
|
||||||
|
_clipCanvasWithRect(canvas, _clipCallback(), offset: originOffset);
|
||||||
|
}
|
||||||
|
canvas.drawCircle(center + originOffset, _radius.value, paint);
|
||||||
|
if (_clipCallback != null)
|
||||||
|
canvas.restore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ 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';
|
||||||
|
|
||||||
|
import 'ink_well.dart';
|
||||||
import 'material.dart';
|
import 'material.dart';
|
||||||
|
|
||||||
const Duration _kUnconfirmedSplashDuration = const Duration(seconds: 1);
|
const Duration _kUnconfirmedSplashDuration = const Duration(seconds: 1);
|
||||||
|
@ -42,14 +43,48 @@ double _getSplashRadiusForPositionInSize(Size bounds, Offset position) {
|
||||||
return math.max(math.max(d1, d2), math.max(d3, d4)).ceilToDouble();
|
return math.max(math.max(d1, d2), math.max(d3, d4)).ceilToDouble();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _InkSplashFactory extends InteractiveInkFeatureFactory {
|
||||||
|
const _InkSplashFactory();
|
||||||
|
|
||||||
|
@override
|
||||||
|
InteractiveInkFeature create({
|
||||||
|
@required MaterialInkController controller,
|
||||||
|
@required RenderBox referenceBox,
|
||||||
|
@required Offset position,
|
||||||
|
@required Color color,
|
||||||
|
bool containedInkWell: false,
|
||||||
|
RectCallback rectCallback,
|
||||||
|
BorderRadius borderRadius,
|
||||||
|
double radius,
|
||||||
|
VoidCallback onRemoved,
|
||||||
|
}) {
|
||||||
|
return new InkSplash(
|
||||||
|
controller: controller,
|
||||||
|
referenceBox: referenceBox,
|
||||||
|
position: position,
|
||||||
|
color: color,
|
||||||
|
containedInkWell: containedInkWell,
|
||||||
|
rectCallback: rectCallback,
|
||||||
|
borderRadius: borderRadius,
|
||||||
|
radius: radius,
|
||||||
|
onRemoved: onRemoved,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A visual reaction on a piece of [Material] to user input.
|
/// A visual reaction on a piece of [Material] to user input.
|
||||||
///
|
///
|
||||||
|
/// A circular ink feature whose origin starts at the input touch point
|
||||||
|
/// and whose radius expands from zero.
|
||||||
|
///
|
||||||
/// This object is rarely created directly. Instead of creating an ink splash
|
/// This object is rarely created directly. Instead of creating an ink splash
|
||||||
/// directly, consider using an [InkResponse] or [InkWell] widget, which uses
|
/// directly, consider using an [InkResponse] or [InkWell] widget, which uses
|
||||||
/// gestures (such as tap and long-press) to trigger ink splashes.
|
/// gestures (such as tap and long-press) to trigger ink splashes.
|
||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
|
/// * [InkRipple], which is an ink splash feature that expands more
|
||||||
|
/// aggressively than this class does.
|
||||||
/// * [InkResponse], which uses gestures to trigger ink highlights and ink
|
/// * [InkResponse], which uses gestures to trigger ink highlights and ink
|
||||||
/// splashes in the parent [Material].
|
/// splashes in the parent [Material].
|
||||||
/// * [InkWell], which is a rectangular [InkResponse] (the most common type of
|
/// * [InkWell], which is a rectangular [InkResponse] (the most common type of
|
||||||
|
@ -57,7 +92,11 @@ double _getSplashRadiusForPositionInSize(Size bounds, Offset position) {
|
||||||
/// * [Material], which is the widget on which the ink splash is painted.
|
/// * [Material], which is the widget on which the ink splash is painted.
|
||||||
/// * [InkHighlight], which is an ink feature that emphasizes a part of a
|
/// * [InkHighlight], which is an ink feature that emphasizes a part of a
|
||||||
/// [Material].
|
/// [Material].
|
||||||
class InkSplash extends InkFeature {
|
class InkSplash extends InteractiveInkFeature {
|
||||||
|
/// Used to specify this type of ink splash for an [InkWell], [InkResponse]
|
||||||
|
/// or material [Theme].
|
||||||
|
static const InteractiveInkFeatureFactory splashFactory = const _InkSplashFactory();
|
||||||
|
|
||||||
/// Begin a splash, centered at position relative to [referenceBox].
|
/// Begin a splash, centered at position relative to [referenceBox].
|
||||||
///
|
///
|
||||||
/// The [controller] argument is typically obtained via
|
/// The [controller] argument is typically obtained via
|
||||||
|
@ -84,12 +123,11 @@ class InkSplash extends InkFeature {
|
||||||
double radius,
|
double radius,
|
||||||
VoidCallback onRemoved,
|
VoidCallback onRemoved,
|
||||||
}) : _position = position,
|
}) : _position = position,
|
||||||
_color = color,
|
|
||||||
_borderRadius = borderRadius,
|
_borderRadius = borderRadius,
|
||||||
_targetRadius = radius ?? _getTargetRadius(referenceBox, containedInkWell, rectCallback, position),
|
_targetRadius = radius ?? _getTargetRadius(referenceBox, containedInkWell, rectCallback, position),
|
||||||
_clipCallback = _getClipCallback(referenceBox, containedInkWell, rectCallback),
|
_clipCallback = _getClipCallback(referenceBox, containedInkWell, rectCallback),
|
||||||
_repositionToReferenceBox = !containedInkWell,
|
_repositionToReferenceBox = !containedInkWell,
|
||||||
super(controller: controller, referenceBox: referenceBox, onRemoved: onRemoved) {
|
super(controller: controller, referenceBox: referenceBox, color: color, onRemoved: onRemoved) {
|
||||||
assert(_borderRadius != null);
|
assert(_borderRadius != null);
|
||||||
_radiusController = new AnimationController(duration: _kUnconfirmedSplashDuration, vsync: controller.vsync)
|
_radiusController = new AnimationController(duration: _kUnconfirmedSplashDuration, vsync: controller.vsync)
|
||||||
..addListener(controller.markNeedsPaint)
|
..addListener(controller.markNeedsPaint)
|
||||||
|
@ -121,20 +159,7 @@ class InkSplash extends InkFeature {
|
||||||
Animation<int> _alpha;
|
Animation<int> _alpha;
|
||||||
AnimationController _alphaController;
|
AnimationController _alphaController;
|
||||||
|
|
||||||
/// The color of the splash.
|
@override
|
||||||
Color get color => _color;
|
|
||||||
Color _color;
|
|
||||||
set color(Color value) {
|
|
||||||
if (value == _color)
|
|
||||||
return;
|
|
||||||
_color = value;
|
|
||||||
controller.markNeedsPaint();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// The user input is confirmed.
|
|
||||||
///
|
|
||||||
/// Causes the reaction to propagate faster across the material.
|
|
||||||
void confirm() {
|
void confirm() {
|
||||||
final int duration = (_targetRadius / _kSplashConfirmedVelocity).floor();
|
final int duration = (_targetRadius / _kSplashConfirmedVelocity).floor();
|
||||||
_radiusController
|
_radiusController
|
||||||
|
@ -143,9 +168,7 @@ class InkSplash extends InkFeature {
|
||||||
_alphaController.forward();
|
_alphaController.forward();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The user input was canceled.
|
@override
|
||||||
///
|
|
||||||
/// Causes the reaction to gradually disappear.
|
|
||||||
void cancel() {
|
void cancel() {
|
||||||
_alphaController.forward();
|
_alphaController.forward();
|
||||||
}
|
}
|
||||||
|
@ -184,7 +207,7 @@ class InkSplash extends InkFeature {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void paintFeature(Canvas canvas, Matrix4 transform) {
|
void paintFeature(Canvas canvas, Matrix4 transform) {
|
||||||
final Paint paint = new Paint()..color = _color.withAlpha(_alpha.value);
|
final Paint paint = new Paint()..color = color.withAlpha(_alpha.value);
|
||||||
Offset center = _position;
|
Offset center = _position;
|
||||||
if (_repositionToReferenceBox)
|
if (_repositionToReferenceBox)
|
||||||
center = Offset.lerp(center, referenceBox.size.center(Offset.zero), _radiusController.value);
|
center = Offset.lerp(center, referenceBox.size.center(Offset.zero), _radiusController.value);
|
||||||
|
|
|
@ -12,10 +12,94 @@ import 'package:flutter/widgets.dart';
|
||||||
import 'debug.dart';
|
import 'debug.dart';
|
||||||
import 'feedback.dart';
|
import 'feedback.dart';
|
||||||
import 'ink_highlight.dart';
|
import 'ink_highlight.dart';
|
||||||
import 'ink_splash.dart';
|
|
||||||
import 'material.dart';
|
import 'material.dart';
|
||||||
import 'theme.dart';
|
import 'theme.dart';
|
||||||
|
|
||||||
|
/// An ink feature that displays a [color] "splash" in response to a user
|
||||||
|
/// gesture that can be confirmed or canceled.
|
||||||
|
///
|
||||||
|
/// Subclasses call [confirm] when an input gesture is recognized. For
|
||||||
|
/// example a press event might trigger an ink feature that's confirmed
|
||||||
|
/// when the corresponding up event is seen.
|
||||||
|
///
|
||||||
|
/// Subclasses call [cancel] when an input gesture is aborted before it
|
||||||
|
/// is recognized. For example a press event might trigger an ink feature
|
||||||
|
/// that's cancelled when the pointer is dragged out of the reference
|
||||||
|
/// box.
|
||||||
|
///
|
||||||
|
/// The [InkWell] and [InkResponse] widgets generate instances of this
|
||||||
|
/// class.
|
||||||
|
abstract class InteractiveInkFeature extends InkFeature {
|
||||||
|
/// Creates an InteractiveInkFeature.
|
||||||
|
///
|
||||||
|
/// The [controller] and [referenceBox] arguments must not be null.
|
||||||
|
InteractiveInkFeature({
|
||||||
|
@required MaterialInkController controller,
|
||||||
|
@required RenderBox referenceBox,
|
||||||
|
Color color,
|
||||||
|
VoidCallback onRemoved,
|
||||||
|
}) : assert(controller != null),
|
||||||
|
assert(referenceBox != null),
|
||||||
|
_color = color,
|
||||||
|
super(controller: controller, referenceBox: referenceBox, onRemoved: onRemoved);
|
||||||
|
|
||||||
|
/// Called when the user input that triggered this feature's appearance was confirmed.
|
||||||
|
///
|
||||||
|
/// Typically causes the ink to propagate faster across the material. By default this
|
||||||
|
/// method does nothing.
|
||||||
|
void confirm() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when the user input that triggered this feature's appearance was canceled.
|
||||||
|
///
|
||||||
|
/// Typically causes the ink to gradually disappear. By default this method does
|
||||||
|
/// nothing.
|
||||||
|
void cancel() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The ink's color.
|
||||||
|
Color get color => _color;
|
||||||
|
Color _color;
|
||||||
|
set color(Color value) {
|
||||||
|
if (value == _color)
|
||||||
|
return;
|
||||||
|
_color = value;
|
||||||
|
controller.markNeedsPaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An encapsulation of an [InteractiveInkFeature] constructor used by [InkWell]
|
||||||
|
/// [InkResponse] and [ThemeData].
|
||||||
|
///
|
||||||
|
/// Interactive ink feature implementations should provide a static const
|
||||||
|
/// `splashFactory` value that's an instance of this class. The `splashFactory`
|
||||||
|
/// can be used to configure an [InkWell], [InkResponse] or [ThemeData].
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [InkSplash.splashFactory]
|
||||||
|
/// * [InkRipple.splashFactory]
|
||||||
|
abstract class InteractiveInkFeatureFactory {
|
||||||
|
/// Subclasses should provide a const constructor.
|
||||||
|
const InteractiveInkFeatureFactory();
|
||||||
|
|
||||||
|
/// The factory method.
|
||||||
|
///
|
||||||
|
/// Subclasses should override this method to return a new instance of an
|
||||||
|
/// [InteractiveInkFeature].
|
||||||
|
InteractiveInkFeature create({
|
||||||
|
@required MaterialInkController controller,
|
||||||
|
@required RenderBox referenceBox,
|
||||||
|
@required Offset position,
|
||||||
|
@required Color color,
|
||||||
|
bool containedInkWell: false,
|
||||||
|
RectCallback rectCallback,
|
||||||
|
BorderRadius borderRadius,
|
||||||
|
double radius,
|
||||||
|
VoidCallback onRemoved,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// An area of a [Material] that responds to touch. Has a configurable shape and
|
/// An area of a [Material] that responds to touch. Has a configurable shape and
|
||||||
/// can be configured to clip splashes that extend outside its bounds or not.
|
/// can be configured to clip splashes that extend outside its bounds or not.
|
||||||
///
|
///
|
||||||
|
@ -95,6 +179,7 @@ class InkResponse extends StatefulWidget {
|
||||||
this.borderRadius: BorderRadius.zero,
|
this.borderRadius: BorderRadius.zero,
|
||||||
this.highlightColor,
|
this.highlightColor,
|
||||||
this.splashColor,
|
this.splashColor,
|
||||||
|
this.splashFactory,
|
||||||
this.enableFeedback: true,
|
this.enableFeedback: true,
|
||||||
this.excludeFromSemantics: false,
|
this.excludeFromSemantics: false,
|
||||||
}) : assert(enableFeedback != null), super(key: key);
|
}) : assert(enableFeedback != null), super(key: key);
|
||||||
|
@ -162,6 +247,7 @@ class InkResponse extends StatefulWidget {
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
/// * [splashColor], the color of the splash.
|
/// * [splashColor], the color of the splash.
|
||||||
|
/// * [splashFactory], which defines the appearance of the splash.
|
||||||
final double radius;
|
final double radius;
|
||||||
|
|
||||||
/// The clipping radius of the containing rect.
|
/// The clipping radius of the containing rect.
|
||||||
|
@ -174,6 +260,7 @@ class InkResponse extends StatefulWidget {
|
||||||
///
|
///
|
||||||
/// * [highlightShape], the shape of the highlight.
|
/// * [highlightShape], the shape of the highlight.
|
||||||
/// * [splashColor], the color of the splash.
|
/// * [splashColor], the color of the splash.
|
||||||
|
/// * [splashFactory], which defines the appearance of the splash.
|
||||||
final Color highlightColor;
|
final Color highlightColor;
|
||||||
|
|
||||||
/// The splash color of the ink response. If this property is null then the
|
/// The splash color of the ink response. If this property is null then the
|
||||||
|
@ -181,10 +268,25 @@ class InkResponse extends StatefulWidget {
|
||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
|
/// * [splashFactory], which defines the appearance of the splash.
|
||||||
/// * [radius], the (maximum) size of the ink splash.
|
/// * [radius], the (maximum) size of the ink splash.
|
||||||
/// * [highlightColor], the color of the highlight.
|
/// * [highlightColor], the color of the highlight.
|
||||||
final Color splashColor;
|
final Color splashColor;
|
||||||
|
|
||||||
|
/// Defines the appearance of the splash.
|
||||||
|
///
|
||||||
|
/// Defaults to the value of the theme's splash factory: [ThemeData.splashFactory].
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [radius], the (maximum) size of the ink splash.
|
||||||
|
/// * [splashColor], the color of the splash.
|
||||||
|
/// * [highlightColor], the color of the highlight.
|
||||||
|
/// * [InkSplash.splashFactory], which defines the default splash.
|
||||||
|
/// * [InkRipple.splashFactory], which defines a splash that spreads out
|
||||||
|
/// more aggresively than the default.
|
||||||
|
final InteractiveInkFeatureFactory splashFactory;
|
||||||
|
|
||||||
/// Whether detected gestures should provide acoustic and/or haptic feedback.
|
/// Whether detected gestures should provide acoustic and/or haptic feedback.
|
||||||
///
|
///
|
||||||
/// For example, on Android a tap will produce a clicking sound and a
|
/// For example, on Android a tap will produce a clicking sound and a
|
||||||
|
@ -255,8 +357,8 @@ class InkResponse extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _InkResponseState<T extends InkResponse> extends State<T> with AutomaticKeepAliveClientMixin {
|
class _InkResponseState<T extends InkResponse> extends State<T> with AutomaticKeepAliveClientMixin {
|
||||||
Set<InkSplash> _splashes;
|
Set<InteractiveInkFeature> _splashes;
|
||||||
InkSplash _currentSplash;
|
InteractiveInkFeature _currentSplash;
|
||||||
InkHighlight _lastHighlight;
|
InkHighlight _lastHighlight;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -295,20 +397,16 @@ class _InkResponseState<T extends InkResponse> extends State<T> with AutomaticKe
|
||||||
updateKeepAlive();
|
updateKeepAlive();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleTapDown(TapDownDetails details) {
|
InteractiveInkFeature _createInkFeature(TapDownDetails details) {
|
||||||
|
final MaterialInkController inkController = Material.of(context);
|
||||||
final RenderBox referenceBox = context.findRenderObject();
|
final RenderBox referenceBox = context.findRenderObject();
|
||||||
final RectCallback rectCallback = widget.getRectCallback(referenceBox);
|
final Offset position = referenceBox.globalToLocal(details.globalPosition);
|
||||||
InkSplash splash;
|
final Color color = widget.splashColor ?? Theme.of(context).splashColor;
|
||||||
splash = new InkSplash(
|
final RectCallback rectCallback = widget.containedInkWell ? widget.getRectCallback(referenceBox) : null;
|
||||||
controller: Material.of(context),
|
final BorderRadius borderRadius = widget.borderRadius ?? BorderRadius.zero;
|
||||||
referenceBox: referenceBox,
|
|
||||||
position: referenceBox.globalToLocal(details.globalPosition),
|
InteractiveInkFeature splash;
|
||||||
color: widget.splashColor ?? Theme.of(context).splashColor,
|
void onRemoved() {
|
||||||
containedInkWell: widget.containedInkWell,
|
|
||||||
rectCallback: widget.containedInkWell ? rectCallback : null,
|
|
||||||
radius: widget.radius,
|
|
||||||
borderRadius: widget.borderRadius ?? BorderRadius.zero,
|
|
||||||
onRemoved: () {
|
|
||||||
if (_splashes != null) {
|
if (_splashes != null) {
|
||||||
assert(_splashes.contains(splash));
|
assert(_splashes.contains(splash));
|
||||||
_splashes.remove(splash);
|
_splashes.remove(splash);
|
||||||
|
@ -317,8 +415,25 @@ class _InkResponseState<T extends InkResponse> extends State<T> with AutomaticKe
|
||||||
updateKeepAlive();
|
updateKeepAlive();
|
||||||
} // else we're probably in deactivate()
|
} // else we're probably in deactivate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
splash = (widget.splashFactory ?? Theme.of(context).splashFactory).create(
|
||||||
|
controller: inkController,
|
||||||
|
referenceBox: referenceBox,
|
||||||
|
position: position,
|
||||||
|
color: color,
|
||||||
|
containedInkWell: widget.containedInkWell,
|
||||||
|
rectCallback: rectCallback,
|
||||||
|
radius: widget.radius,
|
||||||
|
borderRadius: borderRadius,
|
||||||
|
onRemoved: onRemoved,
|
||||||
);
|
);
|
||||||
_splashes ??= new HashSet<InkSplash>();
|
|
||||||
|
return splash;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleTapDown(TapDownDetails details) {
|
||||||
|
final InteractiveInkFeature splash = _createInkFeature(details);
|
||||||
|
_splashes ??= new HashSet<InteractiveInkFeature>();
|
||||||
_splashes.add(splash);
|
_splashes.add(splash);
|
||||||
_currentSplash = splash;
|
_currentSplash = splash;
|
||||||
updateKeepAlive();
|
updateKeepAlive();
|
||||||
|
@ -362,9 +477,9 @@ class _InkResponseState<T extends InkResponse> extends State<T> with AutomaticKe
|
||||||
@override
|
@override
|
||||||
void deactivate() {
|
void deactivate() {
|
||||||
if (_splashes != null) {
|
if (_splashes != null) {
|
||||||
final Set<InkSplash> splashes = _splashes;
|
final Set<InteractiveInkFeature> splashes = _splashes;
|
||||||
_splashes = null;
|
_splashes = null;
|
||||||
for (InkSplash splash in splashes)
|
for (InteractiveInkFeature splash in splashes)
|
||||||
splash.dispose();
|
splash.dispose();
|
||||||
_currentSplash = null;
|
_currentSplash = null;
|
||||||
}
|
}
|
||||||
|
@ -436,6 +551,8 @@ class InkWell extends InkResponse {
|
||||||
ValueChanged<bool> onHighlightChanged,
|
ValueChanged<bool> onHighlightChanged,
|
||||||
Color highlightColor,
|
Color highlightColor,
|
||||||
Color splashColor,
|
Color splashColor,
|
||||||
|
InteractiveInkFeatureFactory splashFactory,
|
||||||
|
double radius,
|
||||||
BorderRadius borderRadius,
|
BorderRadius borderRadius,
|
||||||
bool enableFeedback: true,
|
bool enableFeedback: true,
|
||||||
bool excludeFromSemantics: false,
|
bool excludeFromSemantics: false,
|
||||||
|
@ -450,6 +567,8 @@ class InkWell extends InkResponse {
|
||||||
highlightShape: BoxShape.rectangle,
|
highlightShape: BoxShape.rectangle,
|
||||||
highlightColor: highlightColor,
|
highlightColor: highlightColor,
|
||||||
splashColor: splashColor,
|
splashColor: splashColor,
|
||||||
|
splashFactory: splashFactory,
|
||||||
|
radius: radius,
|
||||||
borderRadius: borderRadius,
|
borderRadius: borderRadius,
|
||||||
enableFeedback: enableFeedback,
|
enableFeedback: enableFeedback,
|
||||||
excludeFromSemantics: excludeFromSemantics,
|
excludeFromSemantics: excludeFromSemantics,
|
||||||
|
|
|
@ -8,6 +8,8 @@ import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
import 'colors.dart';
|
import 'colors.dart';
|
||||||
|
import 'ink_splash.dart';
|
||||||
|
import 'ink_well.dart' show InteractiveInkFeatureFactory;
|
||||||
import 'typography.dart';
|
import 'typography.dart';
|
||||||
|
|
||||||
/// Describes the contrast needs of a color.
|
/// Describes the contrast needs of a color.
|
||||||
|
@ -82,6 +84,7 @@ class ThemeData {
|
||||||
Color dividerColor,
|
Color dividerColor,
|
||||||
Color highlightColor,
|
Color highlightColor,
|
||||||
Color splashColor,
|
Color splashColor,
|
||||||
|
InteractiveInkFeatureFactory splashFactory,
|
||||||
Color selectedRowColor,
|
Color selectedRowColor,
|
||||||
Color unselectedWidgetColor,
|
Color unselectedWidgetColor,
|
||||||
Color disabledColor,
|
Color disabledColor,
|
||||||
|
@ -118,6 +121,7 @@ class ThemeData {
|
||||||
dividerColor ??= isDark ? const Color(0x1FFFFFFF) : const Color(0x1F000000);
|
dividerColor ??= isDark ? const Color(0x1FFFFFFF) : const Color(0x1F000000);
|
||||||
highlightColor ??= isDark ? _kDarkThemeHighlightColor : _kLightThemeHighlightColor;
|
highlightColor ??= isDark ? _kDarkThemeHighlightColor : _kLightThemeHighlightColor;
|
||||||
splashColor ??= isDark ? _kDarkThemeSplashColor : _kLightThemeSplashColor;
|
splashColor ??= isDark ? _kDarkThemeSplashColor : _kLightThemeSplashColor;
|
||||||
|
splashFactory ??= InkSplash.splashFactory;
|
||||||
selectedRowColor ??= Colors.grey[100];
|
selectedRowColor ??= Colors.grey[100];
|
||||||
unselectedWidgetColor ??= isDark ? Colors.white70 : Colors.black54;
|
unselectedWidgetColor ??= isDark ? Colors.white70 : Colors.black54;
|
||||||
disabledColor ??= isDark ? Colors.white30 : Colors.black26;
|
disabledColor ??= isDark ? Colors.white30 : Colors.black26;
|
||||||
|
@ -156,6 +160,7 @@ class ThemeData {
|
||||||
dividerColor: dividerColor,
|
dividerColor: dividerColor,
|
||||||
highlightColor: highlightColor,
|
highlightColor: highlightColor,
|
||||||
splashColor: splashColor,
|
splashColor: splashColor,
|
||||||
|
splashFactory: splashFactory,
|
||||||
selectedRowColor: selectedRowColor,
|
selectedRowColor: selectedRowColor,
|
||||||
unselectedWidgetColor: unselectedWidgetColor,
|
unselectedWidgetColor: unselectedWidgetColor,
|
||||||
disabledColor: disabledColor,
|
disabledColor: disabledColor,
|
||||||
|
@ -196,6 +201,7 @@ class ThemeData {
|
||||||
@required this.dividerColor,
|
@required this.dividerColor,
|
||||||
@required this.highlightColor,
|
@required this.highlightColor,
|
||||||
@required this.splashColor,
|
@required this.splashColor,
|
||||||
|
@required this.splashFactory,
|
||||||
@required this.selectedRowColor,
|
@required this.selectedRowColor,
|
||||||
@required this.unselectedWidgetColor,
|
@required this.unselectedWidgetColor,
|
||||||
@required this.disabledColor,
|
@required this.disabledColor,
|
||||||
|
@ -226,6 +232,7 @@ class ThemeData {
|
||||||
assert(dividerColor != null),
|
assert(dividerColor != null),
|
||||||
assert(highlightColor != null),
|
assert(highlightColor != null),
|
||||||
assert(splashColor != null),
|
assert(splashColor != null),
|
||||||
|
assert(splashFactory != null),
|
||||||
assert(selectedRowColor != null),
|
assert(selectedRowColor != null),
|
||||||
assert(unselectedWidgetColor != null),
|
assert(unselectedWidgetColor != null),
|
||||||
assert(disabledColor != null),
|
assert(disabledColor != null),
|
||||||
|
@ -317,6 +324,16 @@ class ThemeData {
|
||||||
/// The color of ink splashes. See [InkWell].
|
/// The color of ink splashes. See [InkWell].
|
||||||
final Color splashColor;
|
final Color splashColor;
|
||||||
|
|
||||||
|
/// Defines the appearance of ink splashes produces by [InkWell]
|
||||||
|
/// and [InkResponse].
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [InkSplash.splashFactory], which defines the default splash.
|
||||||
|
/// * [InkRipple.splashFactory], which defines a splash that spreads out
|
||||||
|
/// more aggresively than the default.
|
||||||
|
final InteractiveInkFeatureFactory splashFactory;
|
||||||
|
|
||||||
/// The color used to highlight selected rows.
|
/// The color used to highlight selected rows.
|
||||||
final Color selectedRowColor;
|
final Color selectedRowColor;
|
||||||
|
|
||||||
|
@ -398,6 +415,7 @@ class ThemeData {
|
||||||
Color dividerColor,
|
Color dividerColor,
|
||||||
Color highlightColor,
|
Color highlightColor,
|
||||||
Color splashColor,
|
Color splashColor,
|
||||||
|
InteractiveInkFeatureFactory splashFactory,
|
||||||
Color selectedRowColor,
|
Color selectedRowColor,
|
||||||
Color unselectedWidgetColor,
|
Color unselectedWidgetColor,
|
||||||
Color disabledColor,
|
Color disabledColor,
|
||||||
|
@ -430,6 +448,7 @@ class ThemeData {
|
||||||
dividerColor: dividerColor ?? this.dividerColor,
|
dividerColor: dividerColor ?? this.dividerColor,
|
||||||
highlightColor: highlightColor ?? this.highlightColor,
|
highlightColor: highlightColor ?? this.highlightColor,
|
||||||
splashColor: splashColor ?? this.splashColor,
|
splashColor: splashColor ?? this.splashColor,
|
||||||
|
splashFactory: splashFactory ?? this.splashFactory,
|
||||||
selectedRowColor: selectedRowColor ?? this.selectedRowColor,
|
selectedRowColor: selectedRowColor ?? this.selectedRowColor,
|
||||||
unselectedWidgetColor: unselectedWidgetColor ?? this.unselectedWidgetColor,
|
unselectedWidgetColor: unselectedWidgetColor ?? this.unselectedWidgetColor,
|
||||||
disabledColor: disabledColor ?? this.disabledColor,
|
disabledColor: disabledColor ?? this.disabledColor,
|
||||||
|
@ -545,6 +564,7 @@ class ThemeData {
|
||||||
dividerColor: Color.lerp(a.dividerColor, b.dividerColor, t),
|
dividerColor: Color.lerp(a.dividerColor, b.dividerColor, t),
|
||||||
highlightColor: Color.lerp(a.highlightColor, b.highlightColor, t),
|
highlightColor: Color.lerp(a.highlightColor, b.highlightColor, t),
|
||||||
splashColor: Color.lerp(a.splashColor, b.splashColor, t),
|
splashColor: Color.lerp(a.splashColor, b.splashColor, t),
|
||||||
|
splashFactory: t < 0.5 ? a.splashFactory : b.splashFactory,
|
||||||
selectedRowColor: Color.lerp(a.selectedRowColor, b.selectedRowColor, t),
|
selectedRowColor: Color.lerp(a.selectedRowColor, b.selectedRowColor, t),
|
||||||
unselectedWidgetColor: Color.lerp(a.unselectedWidgetColor, b.unselectedWidgetColor, t),
|
unselectedWidgetColor: Color.lerp(a.unselectedWidgetColor, b.unselectedWidgetColor, t),
|
||||||
disabledColor: Color.lerp(a.disabledColor, b.disabledColor, t),
|
disabledColor: Color.lerp(a.disabledColor, b.disabledColor, t),
|
||||||
|
@ -583,6 +603,7 @@ class ThemeData {
|
||||||
(otherData.dividerColor == dividerColor) &&
|
(otherData.dividerColor == dividerColor) &&
|
||||||
(otherData.highlightColor == highlightColor) &&
|
(otherData.highlightColor == highlightColor) &&
|
||||||
(otherData.splashColor == splashColor) &&
|
(otherData.splashColor == splashColor) &&
|
||||||
|
(otherData.splashFactory == splashFactory) &&
|
||||||
(otherData.selectedRowColor == selectedRowColor) &&
|
(otherData.selectedRowColor == selectedRowColor) &&
|
||||||
(otherData.unselectedWidgetColor == unselectedWidgetColor) &&
|
(otherData.unselectedWidgetColor == unselectedWidgetColor) &&
|
||||||
(otherData.disabledColor == disabledColor) &&
|
(otherData.disabledColor == disabledColor) &&
|
||||||
|
@ -618,6 +639,7 @@ class ThemeData {
|
||||||
dividerColor,
|
dividerColor,
|
||||||
highlightColor,
|
highlightColor,
|
||||||
splashColor,
|
splashColor,
|
||||||
|
splashFactory,
|
||||||
selectedRowColor,
|
selectedRowColor,
|
||||||
unselectedWidgetColor,
|
unselectedWidgetColor,
|
||||||
disabledColor,
|
disabledColor,
|
||||||
|
@ -627,8 +649,8 @@ class ThemeData {
|
||||||
textSelectionHandleColor,
|
textSelectionHandleColor,
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
accentColor,
|
accentColor,
|
||||||
accentColorBrightness,
|
|
||||||
hashValues( // Too many values.
|
hashValues( // Too many values.
|
||||||
|
accentColorBrightness,
|
||||||
indicatorColor,
|
indicatorColor,
|
||||||
dialogBackgroundColor,
|
dialogBackgroundColor,
|
||||||
hintColor,
|
hintColor,
|
||||||
|
|
|
@ -9,7 +9,7 @@ import 'package:flutter_test/flutter_test.dart';
|
||||||
import '../rendering/mock_canvas.dart';
|
import '../rendering/mock_canvas.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('Does the ink widget render a border radius', (WidgetTester tester) async {
|
testWidgets('The inkwell widget renders an ink splash', (WidgetTester tester) async {
|
||||||
final Color highlightColor = const Color(0xAAFF0000);
|
final Color highlightColor = const Color(0xAAFF0000);
|
||||||
final Color splashColor = const Color(0xAA0000FF);
|
final Color splashColor = const Color(0xAA0000FF);
|
||||||
final BorderRadius borderRadius = new BorderRadius.circular(6.0);
|
final BorderRadius borderRadius = new BorderRadius.circular(6.0);
|
||||||
|
@ -50,4 +50,128 @@ void main() {
|
||||||
|
|
||||||
await gesture.up();
|
await gesture.up();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('The inkwell widget renders an ink ripple', (WidgetTester tester) async {
|
||||||
|
final Color highlightColor = const Color(0xAAFF0000);
|
||||||
|
final Color splashColor = const Color(0xB40000FF);
|
||||||
|
final BorderRadius borderRadius = new BorderRadius.circular(6.0);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
new Material(
|
||||||
|
child: new Center(
|
||||||
|
child: new Container(
|
||||||
|
width: 100.0,
|
||||||
|
height: 100.0,
|
||||||
|
child: new InkWell(
|
||||||
|
borderRadius: borderRadius,
|
||||||
|
highlightColor: highlightColor,
|
||||||
|
splashColor: splashColor,
|
||||||
|
onTap: () { },
|
||||||
|
radius: 100.0,
|
||||||
|
splashFactory: InkRipple.splashFactory,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final Offset tapDownOffset = tester.getTopLeft(find.byType(InkWell));
|
||||||
|
final Offset inkWellCenter = tester.getCenter(find.byType(InkWell));
|
||||||
|
//final TestGesture gesture = await tester.startGesture(tapDownOffset);
|
||||||
|
await tester.tapAt(tapDownOffset);
|
||||||
|
await tester.pump(); // start gesture
|
||||||
|
|
||||||
|
final RenderBox box = Material.of(tester.element(find.byType(InkWell))) as dynamic;
|
||||||
|
|
||||||
|
bool offsetsAreClose(Offset a, Offset b) => (a - b).distance < 1.0;
|
||||||
|
bool radiiAreClose(double a, double b) => (a - b).abs() < 1.0;
|
||||||
|
|
||||||
|
// Initially the ripple's center is where the tap occurred,
|
||||||
|
expect(box, paints..something((Symbol method, List<dynamic> arguments) {
|
||||||
|
if (method != #drawCircle)
|
||||||
|
return false;
|
||||||
|
final Offset center = arguments[0];
|
||||||
|
final double radius = arguments[1];
|
||||||
|
final Paint paint = arguments[2];
|
||||||
|
if (offsetsAreClose(center, tapDownOffset) && radius == 30.0 && paint.color.alpha == 0)
|
||||||
|
return true;
|
||||||
|
throw '''
|
||||||
|
Expected: center == $tapDownOffset, radius == 30.0, alpha == 0
|
||||||
|
Found: center == $center radius == $radius alpha == ${paint.color.alpha}''';
|
||||||
|
}));
|
||||||
|
|
||||||
|
// The ripple fades in for 75ms. During that time its alpha is eased from
|
||||||
|
// 0 to the splashColor's alpha value and its center moves towards the
|
||||||
|
// center of the ink well.
|
||||||
|
await tester.pump(const Duration(milliseconds: 50));
|
||||||
|
expect(box, paints..something((Symbol method, List<dynamic> arguments) {
|
||||||
|
if (method != #drawCircle)
|
||||||
|
return false;
|
||||||
|
final Offset center = arguments[0];
|
||||||
|
final double radius = arguments[1];
|
||||||
|
final Paint paint = arguments[2];
|
||||||
|
final Offset expectedCenter = tapDownOffset + const Offset(17.0, 17.0);
|
||||||
|
final double expectedRadius = 56.0;
|
||||||
|
if (offsetsAreClose(center, expectedCenter) && radiiAreClose(radius, expectedRadius) && paint.color.alpha == 120)
|
||||||
|
return true;
|
||||||
|
throw '''
|
||||||
|
Expected: center == $expectedCenter, radius == $expectedRadius, alpha == 120
|
||||||
|
Found: center == $center radius == $radius alpha == ${paint.color.alpha}''';
|
||||||
|
}));
|
||||||
|
|
||||||
|
// At 75ms the ripple has fade in: it's alpha matches the splashColor's
|
||||||
|
// alpha and its center has moved closer to the ink well's center.
|
||||||
|
await tester.pump(const Duration(milliseconds: 25));
|
||||||
|
expect(box, paints..something((Symbol method, List<dynamic> arguments) {
|
||||||
|
if (method != #drawCircle)
|
||||||
|
return false;
|
||||||
|
final Offset center = arguments[0];
|
||||||
|
final double radius = arguments[1];
|
||||||
|
final Paint paint = arguments[2];
|
||||||
|
final Offset expectedCenter = tapDownOffset + const Offset(29.0, 29.0);
|
||||||
|
final double expectedRadius = 73.0;
|
||||||
|
if (offsetsAreClose(center, expectedCenter) && radiiAreClose(radius, expectedRadius) && paint.color.alpha == 180)
|
||||||
|
return true;
|
||||||
|
throw '''
|
||||||
|
Expected: center == $expectedCenter, radius == $expectedRadius, alpha == 180
|
||||||
|
Found: center == $center radius == $radius alpha == ${paint.color.alpha}''';
|
||||||
|
}));
|
||||||
|
|
||||||
|
// At this point the splash radius has expanded to its limit: 5 past the
|
||||||
|
// ink well's radius parameter. The splash center has moved to its final
|
||||||
|
// location at the inkwell's center and the fade-out is about to start.
|
||||||
|
await tester.pump(const Duration(milliseconds: 225));
|
||||||
|
expect(box, paints..something((Symbol method, List<dynamic> arguments) {
|
||||||
|
if (method != #drawCircle)
|
||||||
|
return false;
|
||||||
|
final Offset center = arguments[0];
|
||||||
|
final double radius = arguments[1];
|
||||||
|
final Paint paint = arguments[2];
|
||||||
|
final Offset expectedCenter = inkWellCenter;
|
||||||
|
final double expectedRadius = 105.0;
|
||||||
|
if (offsetsAreClose(center, expectedCenter) && radiiAreClose(radius, expectedRadius) && paint.color.alpha == 180)
|
||||||
|
return true;
|
||||||
|
throw '''
|
||||||
|
Expected: center == $expectedCenter, radius == $expectedRadius, alpha == 180
|
||||||
|
Found: center == $center radius == $radius alpha == ${paint.color.alpha}''';
|
||||||
|
}));
|
||||||
|
|
||||||
|
// After another 150ms the fade-out is complete.
|
||||||
|
await tester.pump(const Duration(milliseconds: 150));
|
||||||
|
expect(box, paints..something((Symbol method, List<dynamic> arguments) {
|
||||||
|
if (method != #drawCircle)
|
||||||
|
return false;
|
||||||
|
final Offset center = arguments[0];
|
||||||
|
final double radius = arguments[1];
|
||||||
|
final Paint paint = arguments[2];
|
||||||
|
final Offset expectedCenter = inkWellCenter;
|
||||||
|
final double expectedRadius = 105.0;
|
||||||
|
if (offsetsAreClose(center, expectedCenter) && radiiAreClose(radius, expectedRadius) && paint.color.alpha == 0)
|
||||||
|
return true;
|
||||||
|
throw '''
|
||||||
|
Expected: center == $expectedCenter, radius == $expectedRadius, alpha == 0
|
||||||
|
Found: center == $center radius == $radius alpha == ${paint.color.alpha}''';
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue