改变InkWell的速度

19

我在Flutter中复制一个正常的设置菜单时遇到了问题。 我正在使用InkWell尝试创建点击设置选项时通常出现的水波纹效果。 问题是与通常情况下相比,水波纹效果出现得太快了。 基本上,我只想减慢InkWell的速度。

我在Flutter中遇到问题的GIF

我想要的GIF


4
我会更改问题的标题,因为它非常误导。 - salihgueler
✚1 Inkwell 水波纹消失太快,用户可能没有注意到正常单击。我的解决方法是使 splashColor 更黑。 - nyconing
2个回答

31
如果您想要一个更慢的涟漪效果,则需将 MaterialApp 主题中的 splashFactory 属性从默认值 InkSplash.splashFactory 更改为 InkRipple.splashFactory。使用 InkRipple 的涟漪看起来更像本地效果。

2
如果这是正确的,Flutter团队应该替换默认设置。 - SacWebDeveloper
1
@SacWebDeveloper 我认为这是因为Flutter团队的目标是复制Material Design而不是本机Android。 - Vadim Osovsky
@VadimOsovsky,iOS的行为怎么样?iOS不遵循材料设计,对吧?那么如何让它在iOS上表现得像“iOS闪屏”一样(没有涟漪只有高亮)?此外,除了InkSplash和InkRipple之外,还有其他预定义的splashFactories可用吗? - Moti Bartov
1
@MotiBartov,由于Flutter不使用本地小部件,而是使用Skia(Flutter的图形引擎)绘制自己的小部件,因此除非您使用Cupertino特定的小部件,否则它在iOS和Android上的行为和外观将完全相同。不,据我所知,只有这两种类型。 - Vadim Osovsky
@VadimOsovsky 谢谢,你说得对,但是Flutter确实会尝试匹配平台的行为,比如列表滚动。我所做的是,我创建了一个自定义的SplashFactory,就像salihguler在这里描述的那样,但是我使用的不是InkSplash,而是受到InkHighlight(https://api.flutter.dev/flutter/material/InkHighlight-class.html)启发的东西,它没有涟漪效果,只有突出显示,更符合“iOS风格”。 - Moti Bartov

10

你可以创建需要的内容,但需要在 InkWell 类下使用自定义 splashFactory

正如您在下面的变量中所看到的,这些变量是私有的,并且不能在类中进行修改。

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; 

回答你的问题,可以做到。我只是从源代码中复制并粘贴了所有内容,并更改了动画值。在下面的代码后,只需在 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;
}

有没有办法改变InkHighlight淡出的速度? - Cole Weinman
1
如果您在尝试实现此操作后遇到错误,则需要首先导入import 'package:flutter/material.dart';import 'dart:math' as math;,然后在您的InteractiveInkFeature create覆盖中添加@required TextDirection textDirectionShapeBorder customBorder - Sludge

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接