How can I round the corners of a slider in Flutter

匆匆过客 提交于 2021-02-07 06:58:09

问题


I'm using the basic slider, and I found how to update just the parts of the slider theme data that I want to change like trackHeight, but unfortunately I'm not sure how to update the field for "trackShape". Here is what I do in the main app to update track height for example:

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

I did try using ClipRRect() around the slider widget and that had no effect.

Here is a simple page for one slider:

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

Here is what that slider looks like:

UPDATE: After getting the answer, I was able to create something like this easily by updating the tick mark shape, and the thumb shape:

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

There was a bit of a trick to the tick mark shape. If you make it too big it just skips painting it! Probably makes sense but I didn't know much about paint/rendering so it took me a while to learn how to get the tick marks (Rect) to show up properly


回答1:


Image of Slider with Rounded Corners I've copied the base code from RectangularSliderTrackShape into a new class called 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);
  }
}

The following is how the SliderTheme is setup.

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(),
),

What was added was two circle arcs to the side of the 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
);



回答2:


Answer by @Jun Xiang seems to work but I did a minor change in the following code:

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

Instead of using 11.0 I used sliderTheme.trackHeight * 1/2 and seems to work for all track heights entered (not only 22).

PS: I couldn't comment so I posted an answer.




回答3:


The default trackShape for slider is now a RoundedRectSliderTrackShape in master:

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

Does that work for you?

RectangularSliderTrackShape can be preserved by overriding the trackShape in the SliderThemeData




回答4:


enter image description here

I have a better solution

    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)



回答5:


Create custom shape like this, In this i am drawing 2 circles on thumb

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

Then use it in your widget tree like this

SliderTheme(
                                data: SliderThemeData(
                                  activeTrackColor: Colors.blue,
                                  inactiveTrackColor: Color(0xffd0d2d3),
                                  trackHeight: 2,
                                  thumbShape: SliderThumbShape(),
                                ),
                                child: Slider(
                                  onChanged: (value) {},
                                  value: 40.5,
                                  max: 100,
                                  min: 0,
                                ),
                              ),



来源:https://stackoverflow.com/questions/55987559/how-can-i-round-the-corners-of-a-slider-in-flutter

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!