如何在Flutter中修整滑块的各个角

时间:2019-05-04 23:10:45

标签: flutter

我正在使用基本的滑块,并且发现了如何仅更新想要更改的滑块主题数据的部分(例如trackHeight),但是不幸的是,我不确定如何更新“ trackShape”的字段。例如,这是我在主应用程序中执行的以更新轨道高度的操作:

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,
              )
    )
            ],
          ),
        ),
      ),
    );
  }
}

以下是该滑块的外观:

enter image description here

更新: 得到答案后,我可以通过更新刻度线形状和拇指形状来轻松创建如下内容:

enter image description here

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)正确显示

6 个答案:

答案 0 :(得分:2)

Image of Slider with Rounded Corners 我已将基础代码从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
);

答案 1 :(得分:1)

滑块的默认trackShape现在是母版中的RoundedRectSliderTrackShape

https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/material/slider_theme.dart#L972

对您有用吗?

可以通过覆盖SliderThemeData中的RectangularSliderTrackShape来保留

trackShape

答案 2 :(得分:1)

@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)。

PS:我无法发表评论,所以我发布了答案。

答案 3 :(得分:0)

enter image description here

我有一个更好的解决方案

    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);
      }
    }

使用:


trackShape: RoundSliderTrackShape(radius: 8)

答案 4 :(得分:0)

创建这样的自定义形状,在这个过程中,我在拇指上绘制了2个圆圈

    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

答案 5 :(得分: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);
  }