I am having trouble replicating a normal settings menu in Flutter. I am using an InkWell to try to create the splash effect that normally occurs when you tap on a settings option. The problem is that the splash effect appears way too fast compared to how it normally is. Basically, I just want to slow down the InkWell.
Asked
Active
Viewed 1.3k times
19
-
4I would change the title of the question, it's extremely misleading... – salihgueler Jun 30 '18 at 14:50
-
✚1 Inkwell is fading too fast, user might not noticed if they did a normal click. And my workaround is make `splashColor` more black. – nyconing Oct 05 '18 at 15:55
2 Answers
31
If you would like a slower ripple effect, then you have to change splashFactory
property in your MaterialApp
theme from InkSplash.splashFactory
(default) to InkRipple.splashFactory
. InkRipple's splash looks more like native.

Vadim Osovsky
- 838
- 9
- 14
-
2If that's correct, the Flutter team should replace the default. – SacWebDeveloper Feb 15 '20 at 01:32
-
1@SacWebDeveloper I think that's because the Flutter team aims towards replicating Material Design rather than native Android – Vadim Osovsky May 29 '20 at 18:06
-
@VadimOsovsky What about ios behaviour?, ios doesn't follow the material design right? So how can it behave on ios as it should according to "ios splash" (Which has no ripple just highlight)? Also, is there any more predefined splashFactories available? besides the InkSplash and InkRipple? – Moti Bartov Aug 05 '20 at 14:24
-
1@MotiBartov since flutter doesn't use native widgets but draws its own with Skia (flutter's graphics engine), this will behave and look identical on both ios and android unless you use a cupertino-specific widget. No, as far as I know there are only the 2 types. – Vadim Osovsky Aug 06 '20 at 09:00
-
@VadimOsovsky Thanks, you're right, but there are some places that Flutter do attempt to match the platform behaviour like lists scrolling. What i did is, I've created custom SplashFactory just like described here by salihguler but instead of InkSplash it inspired by the InkHighlight (https://api.flutter.dev/flutter/material/InkHighlight-class.html) which doesn't has ripple effect, just highlighting which is much more 'ios like'. – Moti Bartov Aug 06 '20 at 09:20
10
It's possible to create what you wanted but it requires a custom splashFactory
under InkWell
class.
As you see in the variables below, these are meant to be private values and they are not open to modification within classes.
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;
To answer your question, yes you can do it. I just copied and pasted everything from the source code and change the animation values. After the code below just use it in splashFactory
.
///Part to use within application
new InkWell(
onTap: () {},
splashFactory: CustomSplashFactory(),
child: Container(
padding: EdgeInsets.all(12.0),
child: Text('Flat Button'),
),
//Part to copy from the source code.
const Duration _kUnconfirmedSplashDuration = const Duration(seconds: 10);
const Duration _kSplashFadeDuration = const Duration(seconds: 2);
const double _kSplashInitialSize = 0.0; // logical pixels
const double _kSplashConfirmedVelocity = 0.1;
class CustomSplashFactory extends InteractiveInkFeatureFactory {
const CustomSplashFactory();
@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 CustomSplash(
controller: controller,
referenceBox: referenceBox,
position: position,
color: color,
containedInkWell: containedInkWell,
rectCallback: rectCallback,
borderRadius: borderRadius,
radius: radius,
onRemoved: onRemoved,
);
}
}
class CustomSplash extends InteractiveInkFeature {
/// Used to specify this type of ink splash for an [InkWell], [InkResponse]
/// or material [Theme].
static const InteractiveInkFeatureFactory splashFactory = const CustomSplashFactory();
/// 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.
CustomSplash({
@required MaterialInkController controller,
@required RenderBox referenceBox,
Offset position,
Color color,
bool containedInkWell = false,
RectCallback rectCallback,
BorderRadius borderRadius,
double radius,
VoidCallback onRemoved,
}) : _position = position,
_borderRadius = borderRadius ?? BorderRadius.zero,
_targetRadius = radius ?? _getTargetRadius(referenceBox, containedInkWell, rectCallback, position),
_clipCallback = _getClipCallback(referenceBox, containedInkWell, rectCallback),
_repositionToReferenceBox = !containedInkWell,
super(controller: controller, referenceBox: referenceBox, color: color, onRemoved: onRemoved) {
assert(_borderRadius != null);
_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 Offset _position;
final BorderRadius _borderRadius;
final double _targetRadius;
final RectCallback _clipCallback;
final bool _repositionToReferenceBox;
Animation<double> _radius;
AnimationController _radiusController;
Animation<int> _alpha;
AnimationController _alphaController;
@override
void confirm() {
final 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.dispose();
_alphaController.dispose();
_alphaController = null;
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 Paint paint = new Paint()..color = color.withAlpha(_alpha.value);
Offset center = _position;
if (_repositionToReferenceBox)
center = Offset.lerp(center, referenceBox.size.center(Offset.zero), _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();
}
}
}
double _getTargetRadius(RenderBox referenceBox, bool containedInkWell, RectCallback rectCallback, Offset position) {
if (containedInkWell) {
final Size size = rectCallback != null ? rectCallback().size : referenceBox.size;
return _getSplashRadiusForPositionInSize(size, position);
}
return Material.defaultSplashRadius;
}
double _getSplashRadiusForPositionInSize(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();
}
RectCallback _getClipCallback(RenderBox referenceBox, bool containedInkWell, RectCallback rectCallback) {
if (rectCallback != null) {
assert(containedInkWell);
return rectCallback;
}
if (containedInkWell)
return () => Offset.zero & referenceBox.size;
return null;
}

salihgueler
- 3,284
- 2
- 22
- 33
-
Is there a way to alter to speed at which the InkHighlight fades? – Cole Weinman Jul 01 '18 at 02:01
-
1If you're getting errors after trying to implement this, you need to first `import 'package:flutter/material.dart';` and `import 'dart:math' as math;` and then in your `InteractiveInkFeature create` override add `@required TextDirection textDirection` and `ShapeBorder customBorder` – Sludge Aug 09 '20 at 14:12