Skip to content

Instantly share code, notes, and snippets.

@takumakei
Last active June 23, 2021 13:30
Show Gist options
  • Save takumakei/468d6fe88d38c4c0f199739d6f9393f0 to your computer and use it in GitHub Desktop.
Save takumakei/468d6fe88d38c4c0f199739d6f9393f0 to your computer and use it in GitHub Desktop.
How to disable ripple effect of TextButton
import 'dart:math' as math;
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
textButtonTheme: _textButtonTheme,
primarySwatch: Colors.blue,
splashFactory: InkRipple.splashFactory,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
TextButtonThemeData get _textButtonTheme {
return TextButtonThemeData(
style: TextButton.styleFrom(
textStyle: const TextStyle(fontSize: 24.0),
),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
Widget build(BuildContext context) {
final noSplashStyle = Theme.of(context).textButtonTheme.style!.merge(
TextButton.styleFrom(
splashFactory: NoSplash.splashFactory,
),
);
final zeroInkSplashStyle = Theme.of(context).textButtonTheme.style!.merge(
TextButton.styleFrom(
splashFactory: ZeroInkSplash.splashFactory,
),
);
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
// デフォルト
TextButton(
child: const Text('TextButton 1'),
onPressed: showFeedback(context, 'TextButton 1'),
),
// TextButton一つだけRippleEffectを無効化する
TextButton(
child: const Text('TextButton 2'),
onPressed: showFeedback(context, 'TextButton 2'),
style: noSplashStyle,
),
// テーマを書き換えてツリーの下のRippleEffectを無効化する
// (TextButtonThemeだけ変更)
TextButtonTheme(
data: TextButtonThemeData(style: noSplashStyle),
child: Column(
children: [
TextButton(
child: const Text('TextButton C1'),
onPressed: showFeedback(context, 'TextButton C1'),
),
TextButton(
child: const Text('TextButton C2'),
onPressed: showFeedback(context, 'TextButton C2'),
),
],
),
),
// TextButton一つだけZeroInkSplashに変更する
TextButton(
child: const Text('TextButton 3'),
onPressed: showFeedback(context, 'TextButton 3'),
style: zeroInkSplashStyle,
),
// テーマを書き換えてツリーの下をZeroInkSplashに変更する
Theme(
data: Theme.of(context).copyWith(
textButtonTheme: TextButtonThemeData(style: zeroInkSplashStyle),
),
child: Column(
children: [
TextButton(
child: const Text('TextButton C3'),
onPressed: showFeedback(context, 'TextButton C3'),
),
TextButton(
child: const Text('TextButton C4'),
onPressed: showFeedback(context, 'TextButton C4'),
),
],
),
),
],
),
),
);
}
VoidCallback showFeedback(BuildContext context, String text) {
return () {
final snackBar = SnackBar(
content: Text(text),
duration: const Duration(milliseconds: 250),
);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
};
}
}
// Ripple effect を表示したくないけれどハイライトは表示したい。
// NoSplash を使うとハイライトがなくなってしまう。
//
// flutter/lib/src/material/ink_splash.dart をコピーして
// _kUnconfirmedSplashDuration を 0 に変更したもの
// 混乱を避けるために、クラス名も変更した。
//
// もっと賢い方法があれば教えて欲しい。
const Duration _kUnconfirmedSplashDuration = Duration(seconds: 0);
const Duration _kSplashFadeDuration = 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 () => 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 _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();
}
class _ZeroInkSplashFactory extends InteractiveInkFeatureFactory {
const _ZeroInkSplashFactory();
@override
InteractiveInkFeature create({
required MaterialInkController controller,
required RenderBox referenceBox,
required Offset position,
required Color color,
required TextDirection textDirection,
bool containedInkWell = false,
RectCallback? rectCallback,
BorderRadius? borderRadius,
ShapeBorder? customBorder,
double? radius,
VoidCallback? onRemoved,
}) {
return ZeroInkSplash(
controller: controller,
referenceBox: referenceBox,
position: position,
color: color,
containedInkWell: containedInkWell,
rectCallback: rectCallback,
borderRadius: borderRadius,
customBorder: customBorder,
radius: radius,
onRemoved: onRemoved,
textDirection: textDirection,
);
}
}
/// 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
/// directly, consider using an [InkResponse] or [InkWell] widget, which uses
/// gestures (such as tap and long-press) to trigger ink splashes.
///
/// 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
/// 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].
/// * [Ink], a convenience widget for drawing images and other decorations on
/// Material widgets.
class ZeroInkSplash extends InteractiveInkFeature {
/// 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.
ZeroInkSplash({
required MaterialInkController controller,
required RenderBox referenceBox,
required TextDirection textDirection,
Offset? position,
required Color color,
bool containedInkWell = false,
RectCallback? rectCallback,
BorderRadius? borderRadius,
ShapeBorder? customBorder,
double? radius,
VoidCallback? onRemoved,
}) : _position = position,
_borderRadius = borderRadius ?? BorderRadius.zero,
_customBorder = customBorder,
_targetRadius = radius ??
_getTargetRadius(
referenceBox, containedInkWell, rectCallback, position!),
_clipCallback =
_getClipCallback(referenceBox, containedInkWell, rectCallback),
_repositionToReferenceBox = !containedInkWell,
_textDirection = textDirection,
super(
controller: controller,
referenceBox: referenceBox,
color: color,
onRemoved: onRemoved) {
_radiusController = AnimationController(
duration: _kUnconfirmedSplashDuration, vsync: controller.vsync)
..addListener(controller.markNeedsPaint)
..forward();
_radius = _radiusController.drive(Tween<double>(
begin: _kSplashInitialSize,
end: _targetRadius,
));
_alphaController = AnimationController(
duration: _kSplashFadeDuration, vsync: controller.vsync)
..addListener(controller.markNeedsPaint)
..addStatusListener(_handleAlphaStatusChanged);
_alpha = _alphaController!.drive(IntTween(
begin: color.alpha,
end: 0,
));
controller.addInkFeature(this);
}
final Offset? _position;
final BorderRadius _borderRadius;
final ShapeBorder? _customBorder;
final double _targetRadius;
final RectCallback? _clipCallback;
final bool _repositionToReferenceBox;
final TextDirection _textDirection;
late Animation<double> _radius;
late AnimationController _radiusController;
late Animation<int> _alpha;
AnimationController? _alphaController;
/// Used to specify this type of ink splash for an [InkWell], [InkResponse],
/// material [Theme], or [ButtonStyle].
static const InteractiveInkFeatureFactory splashFactory =
_ZeroInkSplashFactory();
@override
void confirm() {
final int duration = (_targetRadius / _kSplashConfirmedVelocity).floor();
_radiusController
..duration = 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();
}
@override
void paintFeature(Canvas canvas, Matrix4 transform) {
final Paint paint = Paint()..color = color.withAlpha(_alpha.value);
Offset? center = _position;
if (_repositionToReferenceBox)
center = Offset.lerp(center, referenceBox.size.center(Offset.zero),
_radiusController.value);
paintInkCircle(
canvas: canvas,
transform: transform,
paint: paint,
center: center!,
textDirection: _textDirection,
radius: _radius.value,
customBorder: _customBorder,
borderRadius: _borderRadius,
clipCallback: _clipCallback,
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment