如何在Flutter中圆角滑块的角落

9

我正在使用基本滑块,已经知道如何更新我想要改变的滑块主题数据的部分,例如trackHeight,但不幸的是我不知道该如何更新“trackShape”字段。以下是我在主应用程序中更新track height的示例:

final SliderThemeData tweakedSliderTheme = Theme.of(context).sliderTheme.copyWith(
    trackHeight: 22.0,
     //trackShape: RectangularSliderTrackShape(),  // How do I update this??
);

我尝试在滑块小部件周围使用ClipRRect(),但没有效果。

这是一个简单的页面,只有一个滑块:

import 'package:flutter/material.dart';

class RoomControl extends StatefulWidget {
  @override
  _RoomControlState createState() => _RoomControlState();
}

class _RoomControlState extends State<RoomControl> {
  double _value = 0.0;
  void _setvalue(double value) => setState(() => _value = value);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Name here'),
      ),
      //hit Ctrl+space in intellij to know what are the options you can use in flutter widgets
      body: Container(
        padding: EdgeInsets.all(32.0),
        child: Center(
          child: Column(
            children: <Widget>[
              Text('Value: ${(_value * 100).round()}'),
              ClipRRect(
                borderRadius: BorderRadius.circular(5.0),
              child:Slider(
                  value: _value,
                  onChanged: _setvalue,
                  divisions: 10,
              )
    )
            ],
          ),
        ),
      ),
    );
  }
}

以下是滑块的外观:

输入图片描述

更新: 得到答案后,我可以通过更新刻度标记形状和拇指形状轻松创建类似于下面这样的内容:

输入图片描述

final SliderThemeData tweakedSliderTheme = Theme.of(context).sliderTheme.copyWith(
  trackHeight: 20.0,
  thumbShape: MyRoundSliderThumbShape(enabledThumbRadius: 13.0, disabledThumbRadius: 13.0),
  trackShape: MyRoundSliderTrackShape(),  // TODO: this is hard coded right now for 20 track height
  inactiveTrackColor: lightGreySliderColor,
  activeTickMarkColor: Color(blackPrimaryValue),
  inactiveTickMarkColor: colorizedMenuColor,
  tickMarkShape: MyRectSliderTickMarkShape(tickMarkRadius: 4.0),
);

在打勾标记的形状上有一些技巧。如果它太大了,它就会跳过绘制!这可能是有道理的,但我不太了解绘画/渲染,所以花了一段时间才学会如何正确地显示打勾标记(Rect)。


1
你有MyRectSliderTickMarkShape的代码示例吗? - Erwin de Gier
1
@ErwindeGier 好久不见了,但我是这样创建的:"class MyRectSliderTickMarkShape extends SliderTickMarkShape { // code here }"。如果你能找到原始的SliderTickMarkShape,你可以使用那段代码或者像我一样调整它的外观。 - Eradicatore
6个回答

5

带圆角的滑块图像 我将RectangularSliderTrackShape的基础代码复制到一个名为RoundSliderTrackShape的新类中。

round_slider_track_shape.dart

import 'dart:math';
import 'package:flutter/material.dart';

class RoundSliderTrackShape extends SliderTrackShape {
  /// Create a slider track that draws 2 rectangles.
  const RoundSliderTrackShape({ this.disabledThumbGapWidth = 2.0 });

  /// Horizontal spacing, or gap, between the disabled thumb and the track.
  ///
  /// This is only used when the slider is disabled. There is no gap around
  /// the thumb and any part of the track when the slider is enabled. The
  /// Material spec defaults this gap width 2, which is half of the disabled
  /// thumb radius.
  final double disabledThumbGapWidth;

  @override
  Rect getPreferredRect({
    RenderBox parentBox,
    Offset offset = Offset.zero,
    SliderThemeData sliderTheme,
    bool isEnabled,
    bool isDiscrete,
  }) {
    final double overlayWidth = sliderTheme.overlayShape.getPreferredSize(isEnabled, isDiscrete).width;
    final double trackHeight = sliderTheme.trackHeight;
    assert(overlayWidth >= 0);
    assert(trackHeight >= 0);
    assert(parentBox.size.width >= overlayWidth);
    assert(parentBox.size.height >= trackHeight);

    final double trackLeft = offset.dx + overlayWidth / 2;
    final double trackTop = offset.dy + (parentBox.size.height - trackHeight) / 2;
    // TODO(clocksmith): Although this works for a material, perhaps the default
    // rectangular track should be padded not just by the overlay, but by the
    // max of the thumb and the overlay, in case there is no overlay.
    final double trackWidth = parentBox.size.width - overlayWidth;
    return Rect.fromLTWH(trackLeft, trackTop, trackWidth, trackHeight);
  }


  @override
  void paint(
    PaintingContext context,
    Offset offset, {
    RenderBox parentBox,
    SliderThemeData sliderTheme,
    Animation<double> enableAnimation,
    TextDirection textDirection,
    Offset thumbCenter,
    bool isDiscrete,
    bool isEnabled,
  }) {
    // If the slider track height is 0, then it makes no difference whether the
    // track is painted or not, therefore the painting can be a no-op.
    if (sliderTheme.trackHeight == 0) {
      return;
    }

    // Assign the track segment paints, which are left: active, right: inactive,
    // but reversed for right to left text.
    final ColorTween activeTrackColorTween = ColorTween(begin: sliderTheme.disabledActiveTrackColor , end: sliderTheme.activeTrackColor);
    final ColorTween inactiveTrackColorTween = ColorTween(begin: sliderTheme.disabledInactiveTrackColor , end: sliderTheme.inactiveTrackColor);
    final Paint activePaint = Paint()..color = activeTrackColorTween.evaluate(enableAnimation);
    final Paint inactivePaint = Paint()..color = inactiveTrackColorTween.evaluate(enableAnimation);
    Paint leftTrackPaint;
    Paint rightTrackPaint;
    switch (textDirection) {
      case TextDirection.ltr:
        leftTrackPaint = activePaint;
        rightTrackPaint = inactivePaint;
        break;
      case TextDirection.rtl:
        leftTrackPaint = inactivePaint;
        rightTrackPaint = activePaint;
        break;
    }

    // Used to create a gap around the thumb iff the slider is disabled.
    // If the slider is enabled, the track can be drawn beneath the thumb
    // without a gap. But when the slider is disabled, the track is shortened
    // and this gap helps determine how much shorter it should be.
    // TODO(clocksmith): The new Material spec has a gray circle in place of this gap.
    double horizontalAdjustment = 0.0;
    if (!isEnabled) {
      final double disabledThumbRadius = sliderTheme.thumbShape.getPreferredSize(false, isDiscrete).width / 2.0;
      final double gap = disabledThumbGapWidth * (1.0 - enableAnimation.value);
      horizontalAdjustment = disabledThumbRadius + gap;
    }

    final Rect trackRect = getPreferredRect(
        parentBox: parentBox,
        offset: offset,
        sliderTheme: sliderTheme,
        isEnabled: isEnabled,
        isDiscrete: isDiscrete,
    );
    final Rect leftTrackSegment = Rect.fromLTRB(trackRect.left, trackRect.top, thumbCenter.dx - horizontalAdjustment, trackRect.bottom);

    // Left Arc
    context.canvas.drawArc(
      Rect.fromCircle(center: Offset(trackRect.left, trackRect.top + 11.0), radius: 11.0),
      -pi * 3 / 2, // -270 degrees
      pi, // 180 degrees
      false,
      trackRect.left - thumbCenter.dx == 0.0 ? rightTrackPaint : leftTrackPaint
    );

    // Right Arc
    context.canvas.drawArc(
      Rect.fromCircle(center: Offset(trackRect.right, trackRect.top + 11.0), radius: 11.0),
      -pi / 2, // -90 degrees
      pi, // 180 degrees
      false,
      trackRect.right - thumbCenter.dx == 0.0 ? leftTrackPaint : rightTrackPaint
    );

    context.canvas.drawRect(leftTrackSegment, leftTrackPaint);
    final Rect rightTrackSegment = Rect.fromLTRB(thumbCenter.dx + horizontalAdjustment, trackRect.top, trackRect.right, trackRect.bottom);
    context.canvas.drawRect(rightTrackSegment, rightTrackPaint);
  }
}

以下是 SliderTheme 的设置方式。
import 'package:flutter_stackoverflow/round_slider_track_shape.dart';
...
sliderTheme: Theme.of(context).sliderTheme.copyWith(
  trackHeight: 22.0,
  trackShape: RoundSliderTrackShape(),
  activeTrackColor: Colors.green,
  // trackShape: RectangularSliderTrackShape(),
),

新增的是两个圆弧,位于SliderTrack的侧面。
// Left Arc
context.canvas.drawArc(
  Rect.fromCircle(center: Offset(trackRect.left, trackRect.top + 11.0), radius: 11.0),
  -pi * 3 / 2, // -270 degrees
  pi, // 180 degrees
  false,
  trackRect.left - thumbCenter.dx == 0.0 ? rightTrackPaint : leftTrackPaint
);

// Right Arc
context.canvas.drawArc(
  Rect.fromCircle(center: Offset(trackRect.right, trackRect.top + 11.0), radius: 11.0),
  -pi / 2, // -90 degrees
  pi, // 180 degrees
  false,
  trackRect.right - thumbCenter.dx == 0.0 ? leftTrackPaint : rightTrackPaint
);

通过您的答案,我成功地解锁了更新另一幅画作的关键,例如勾号形状和拇指形状。谢谢!!!(请参见问题中的更新图像) - Eradicatore
在这个设计中,您可能还想使用NoThumbShape - clocksmith

5

@Jun Xiang 的回答似乎可行,但我在以下代码中进行了小修改:

// Left Arc

context.canvas.drawArc(
  Rect.fromCircle(center: Offset(trackRect.left, trackRect.top + 11.0), radius: 11.0),
  -pi * 3 / 2, // -270 degrees
  pi, // 180 degrees
  false,
  trackRect.left - thumbCenter.dx == 0.0 ? rightTrackPaint : leftTrackPaint
);

// Right Arc

context.canvas.drawArc(
  Rect.fromCircle(center: Offset(trackRect.right, trackRect.top + 11.0), radius: 11.0),
  -pi / 2, // -90 degrees
  pi, // 180 degrees
  false,
  trackRect.right - thumbCenter.dx == 0.0 ? leftTrackPaint : rightTrackPaint
);

我使用了 sliderTheme.trackHeight * 1/2,而不是使用 11.0,这看起来对于所有输入的轨道高度都有效(而不仅仅是22)。

附注:由于我无法发表评论,所以我发布了一个回答。


好的,说得对。我确实在使用这个解决方案时很快就注意到了这一点,所以最好将其变成通用形式。这个解决方案对我来说非常有帮助,让我理解如何进入这些材料小部件的核心并“绘制”我需要的任何内容。 - Eradicatore

3

创建类似于这样的自定义形状,在这里我在拇指上绘制了两个圆圈

    class SliderThumbShape extends SliderComponentShape {
  /// Create a slider thumb that draws a circle.

  const SliderThumbShape({
    this.enabledThumbRadius = 10.0,
    this.disabledThumbRadius,
    this.elevation = 1.0,
    this.pressedElevation = 6.0,
  });

  /// The preferred radius of the round thumb shape when the slider is enabled.
  ///
  /// If it is not provided, then the material default of 10 is used.
  final double enabledThumbRadius;

  /// The preferred radius of the round thumb shape when the slider is disabled.
  ///
  /// If no disabledRadius is provided, then it is equal to the
  /// [enabledThumbRadius]
  final double disabledThumbRadius;
  double get _disabledThumbRadius => disabledThumbRadius ?? enabledThumbRadius;

  /// The resting elevation adds shadow to the unpressed thumb.
  ///
  /// The default is 1.
  ///
  /// Use 0 for no shadow. The higher the value, the larger the shadow. For
  /// example, a value of 12 will create a very large shadow.
  ///
  final double elevation;

  /// The pressed elevation adds shadow to the pressed thumb.
  ///
  /// The default is 6.
  ///
  /// Use 0 for no shadow. The higher the value, the larger the shadow. For
  /// example, a value of 12 will create a very large shadow.
  final double pressedElevation;

  @override
  Size getPreferredSize(bool isEnabled, bool isDiscrete) {
    return Size.fromRadius(isEnabled == true ? enabledThumbRadius : _disabledThumbRadius);
  }

  @override
  void paint(
      PaintingContext context,
      Offset center, {
        Animation<double> activationAnimation,
        @required Animation<double> enableAnimation,
        bool isDiscrete,
        TextPainter labelPainter,
        RenderBox parentBox,
        @required SliderThemeData sliderTheme,
        TextDirection textDirection,
        double value,
        double textScaleFactor,
        Size sizeWithOverflow,
      }) {
    assert(context != null);
    assert(center != null);
    assert(enableAnimation != null);
    assert(sliderTheme != null);
    assert(sliderTheme.disabledThumbColor != null);
    assert(sliderTheme.thumbColor != null);
    assert(!sizeWithOverflow.isEmpty);

    final Canvas canvas = context.canvas;
    final Tween<double> radiusTween = Tween<double>(
      begin: _disabledThumbRadius,
      end: enabledThumbRadius,
    );

    final double radius = radiusTween.evaluate(enableAnimation);

    final Tween<double> elevationTween = Tween<double>(
      begin: elevation,
      end: pressedElevation,
    );

    final double evaluatedElevation = elevationTween.evaluate(activationAnimation);

    {
      final Path path = Path()
        ..addArc(Rect.fromCenter(center: center, width: 1 * radius, height: 1 * radius), 0, math.pi * 2);

      Paint paint = Paint()..color = Colors.black;
      paint.strokeWidth = 15;
      paint.style = PaintingStyle.stroke;
      canvas.drawCircle(
        center,
        radius,
        paint,
      );
      {
        Paint paint = Paint()..color = Colors.white;
        paint.style = PaintingStyle.fill;
        canvas.drawCircle(
          center,
          radius,
          paint,
        );
      }
    }
  }
}

然后在您的小部件树中像这样使用它。
SliderTheme(
                                data: SliderThemeData(
                                  activeTrackColor: Colors.blue,
                                  inactiveTrackColor: Color(0xffd0d2d3),
                                  trackHeight: 2,
                                  thumbShape: SliderThumbShape(),
                                ),
                                child: Slider(
                                  onChanged: (value) {},
                                  value: 40.5,
                                  max: 100,
                                  min: 0,
                                ),
                              ),

enter image description here


2

谢谢!是的,那也可以起作用!我一直在YouTube上观看Google IO 19,并看到他们发布了一个新的Flutter和类似上面的更新,这太棒了!不过我还必须说,原始答案对我学习如何控制自己的小部件绘制非常完美。我需要它来绘制勾号形状和拇指形状! - Eradicatore

2
最初的回答

在这里输入图片描述

我有一个更好的解决方案。

    import 'package:flutter/material.dart';

    class RoundSliderTrackShape extends SliderTrackShape {

      const RoundSliderTrackShape({this.disabledThumbGapWidth = 2.0, this.radius = 0});

      final double disabledThumbGapWidth;
      final double radius;

      @override
      Rect getPreferredRect({
        RenderBox parentBox,
        Offset offset = Offset.zero,
        SliderThemeData sliderTheme,
        bool isEnabled,
        bool isDiscrete,
      }) {
        final double overlayWidth = sliderTheme.overlayShape.getPreferredSize(isEnabled, isDiscrete).width;
        final double trackHeight = sliderTheme.trackHeight;
        assert(overlayWidth >= 0);
        assert(trackHeight >= 0);
        assert(parentBox.size.width >= overlayWidth);
        assert(parentBox.size.height >= trackHeight);

        final double trackLeft = offset.dx + overlayWidth / 2;
        final double trackTop = offset.dy + (parentBox.size.height - trackHeight) / 2;

        final double trackWidth = parentBox.size.width - overlayWidth;
        return Rect.fromLTWH(trackLeft, trackTop, trackWidth, trackHeight);
      }

      @override
      void paint(
        PaintingContext context,
        Offset offset, {
        RenderBox parentBox,
        SliderThemeData sliderTheme,
        Animation<double> enableAnimation,
        TextDirection textDirection,
        Offset thumbCenter,
        bool isDiscrete,
        bool isEnabled,
      }) {
        if (sliderTheme.trackHeight == 0) {
          return;
        }

        final ColorTween activeTrackColorTween =
            ColorTween(begin: sliderTheme.disabledActiveTrackColor, end: sliderTheme.activeTrackColor);
        final ColorTween inactiveTrackColorTween =
            ColorTween(begin: sliderTheme.disabledInactiveTrackColor, end: sliderTheme.inactiveTrackColor);
        final Paint activePaint = Paint()..color = activeTrackColorTween.evaluate(enableAnimation);
        final Paint inactivePaint = Paint()..color = inactiveTrackColorTween.evaluate(enableAnimation);
        Paint leftTrackPaint;
        Paint rightTrackPaint;
        switch (textDirection) {
          case TextDirection.ltr:
            leftTrackPaint = activePaint;
            rightTrackPaint = inactivePaint;
            break;
          case TextDirection.rtl:
            leftTrackPaint = inactivePaint;
            rightTrackPaint = activePaint;
            break;
        }

        double horizontalAdjustment = 0.0;
        if (!isEnabled) {
          final double disabledThumbRadius =
              sliderTheme.thumbShape.getPreferredSize(false, isDiscrete).width / 2.0;
          final double gap = disabledThumbGapWidth * (1.0 - enableAnimation.value);
          horizontalAdjustment = disabledThumbRadius + gap;
        }

        final Rect trackRect = getPreferredRect(
          parentBox: parentBox,
          offset: offset,
          sliderTheme: sliderTheme,
          isEnabled: isEnabled,
          isDiscrete: isDiscrete,
        );
        //Modify this side
        final RRect leftTrackSegment = RRect.fromLTRBR(trackRect.left, trackRect.top,
            thumbCenter.dx - horizontalAdjustment, trackRect.bottom, Radius.circular(radius));
        context.canvas.drawRRect(leftTrackSegment, leftTrackPaint);
        final RRect rightTrackSegment = RRect.fromLTRBR(thumbCenter.dx + horizontalAdjustment, trackRect.top,
            trackRect.right, trackRect.bottom, Radius.circular(radius));
        context.canvas.drawRRect(rightTrackSegment, rightTrackPaint);
      }
    }

use:


trackShape: RoundSliderTrackShape(radius: 8)

除了提供代码外,还应该在代码中添加一些解释,以说明代码的作用。 - Cray

0
另一个选项是使用圆形StrokeCap绘制粗线。两端的帽子半径加上了线宽。
  @override
  void paint(PaintingContext context, Offset offset,
      {required RenderBox parentBox,
      required SliderThemeData sliderTheme,
      required Animation<double> enableAnimation,
      required Offset thumbCenter,
      bool isEnabled = true,
      bool isDiscrete = true,
      required TextDirection textDirection}) {
    final canvas = context.canvas;
    final width = 200;
    final height = 16;

    canvas.drawLine(
        Offset(0, height / 2),
        Offset(width, height / 2),
        Paint()
          ..strokeCap = StrokeCap.round
          ..strokeWidth = height);
  }

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