Last active
June 23, 2021 13:30
-
-
Save takumakei/468d6fe88d38c4c0f199739d6f9393f0 to your computer and use it in GitHub Desktop.
How to disable ripple effect of TextButton
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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