Home > Software design >  Flutter 2: How to create a Gradient Range Slider
Flutter 2: How to create a Gradient Range Slider

Time:03-02

I am trying to create a Range Slider with a gradient body.

I have based my code on a Gradient Slider code from this response: Flutter slider with gradient

I adjusted the Paint override depending on the Interface of the object.

So this is the situation:

Error

======== Exception caught by rendering library =====================================================
The following _CastError was thrown during paint():
Null check operator used on a null value

The relevant error-causing widget was: 
  RangeSlider RangeSlider:file:///[PATH]flutter/lib/source/shared_components/common/slider_range/slider_range.item.dart:19:14
When the exception was thrown, this was the stack: 
#0      BaseSliderTrackShape.getPreferredRect (package:flutter/src/material/slider_theme.dart:1484:53)
#1      _RenderRangeSlider.paint (package:flutter/src/material/range_slider.dart:1267:58)
#2      RenderObject._paintWithContext (package:flutter/src/rendering/object.dart:2451:7)
#3      PaintingContext.paintChild (package:flutter/src/rendering/object.dart:187:13)
#4      RenderProxyBoxMixin.paint (package:flutter/src/rendering/proxy_box.dart:140:15)
#5      PaintingContext.pushLayer (package:flutter/src/rendering/object.dart:387:12)
#6      RenderLeaderLayer.paint (package:flutter/src/rendering/proxy_box.dart:5138:13)
#7      RenderObject._paintWithContext (package:flutter/src/rendering/object.dart:2451:7)
#8      PaintingContext.paintChild (package:flutter/src/rendering/object.dart:187:13)
#9      RenderBoxContainerDefaultsMixin.defaultPaint (package:flutter/src/rendering/box.dart:2844:15)
#10     RenderFlex.paint (package:flutter/src/rendering/flex.dart:1078:7)
#11     RenderObject._paintWithContext (package:flutter/src/rendering/object.dart:2451:7)
#12     PaintingContext.paintChild (package:flutter/src/rendering/object.dart:187:13)
#13     RenderShiftedBox.paint (package:flutter/src/rendering/shifted_box.dart:79:15)
#14     RenderObject._paintWithContext (package:flutter/src/rendering/object.dart:2451:7)
#15     PaintingContext.paintChild (package:flutter/src/rendering/object.dart:187:13)
#16     RenderProxyBoxMixin.paint (package:flutter/src/rendering/proxy_box.dart:140:15)
#17     RenderDecoratedBox.paint (package:flutter/src/rendering/proxy_box.dart:2169:11)
#18     RenderObject._paintWithContext (package:flutter/src/rendering/object.dart:2451:7)
#19     PaintingContext.paintChild (package:flutter/src/rendering/object.dart:187:13)
#20     RenderShiftedBox.paint (package:flutter/src/rendering/shifted_box.dart:79:15)
#21     RenderObject._paintWithContext (package:flutter/src/rendering/object.dart:2451:7)
#22     PaintingContext.paintChild (package:flutter/src/rendering/object.dart:187:13)
#23     RenderProxyBoxMixin.paint (package:flutter/src/rendering/proxy_box.dart:140:15)
#24     RenderObject._paintWithContext (package:flutter/src/rendering/object.dart:2451:7)
#25     PaintingContext.paintChild (package:flutter/src/rendering/object.dart:187:13)
#26     RenderProxyBoxMixin.paint (package:flutter/src/rendering/proxy_box.dart:140:15)
#27     RenderObject._paintWithContext (package:flutter/src/rendering/object.dart:2451:7)
#28     PaintingContext.paintChild (package:flutter/src/rendering/object.dart:187:13)
#29     RenderProxyBoxMixin.paint (package:flutter/src/rendering/proxy_box.dart:140:15)
#30     RenderObject._paintWithContext (package:flutter/src/rendering/object.dart:2451:7)
#31     PaintingContext.paintChild (package:flutter/src/rendering/object.dart:187:13)
#32     RenderProxyBoxMixin.paint (package:flutter/src/rendering/proxy_box.dart:140:15)
#33     RenderObject._paintWithContext (package:flutter/src/rendering/object.dart:2451:7)
#34     PaintingContext.paintChild (package:flutter/src/rendering/object.dart:187:13)
#35     RenderBoxContainerDefaultsMixin.defaultPaint (package:flutter/src/rendering/box.dart:2844:15)
#36     RenderFlex.paint (package:flutter/src/rendering/flex.dart:1078:7)
#37     RenderObject._paintWithContext (package:flutter/src/rendering/object.dart:2451:7)
#38     PaintingContext.paintChild (package:flutter/src/rendering/object.dart:187:13)
#39     RenderShiftedBox.paint (package:flutter/src/rendering/shifted_box.dart:79:15)
#40     RenderObject._paintWithContext (package:flutter/src/rendering/object.dart:2451:7)
#41     PaintingContext.paintChild (package:flutter/src/rendering/object.dart:187:13)
#42     _RenderSingleChildViewport.paint.paintContents (package:flutter/src/widgets/single_child_scroll_view.dart:542:17)
#43     _RenderSingleChildViewport.paint (package:flutter/src/widgets/single_child_scroll_view.dart:556:9)
#44     RenderObject._paintWithContext (package:flutter/src/rendering/object.dart:2451:7)
#45     PaintingContext._repaintCompositedChild (package:flutter/src/rendering/object.dart:141:11)
#46     PaintingContext.repaintCompositedChild (package:flutter/src/rendering/object.dart:100:5)
#47     PipelineOwner.flushPaint (package:flutter/src/rendering/object.dart:995:29)
#48     RendererBinding.drawFrame (package:flutter/src/rendering/binding.dart:499:19)
#49     WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:883:13)
#50     RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:363:5)
#51     SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1144:15)
#52     SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1081:9)
#53     SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:995:5)
#57     _invoke (dart:ui/hooks.dart:151:10)
#58     PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:308:5)
#59     _drawFrame (dart:ui/hooks.dart:115:31)
(elided 3 frames from dart:async)
The following RenderObject was being processed when the exception was fired: _RenderRangeSlider#59316
...  parentData: <none> (can use size)
...  constraints: BoxConstraints(0.0<=w<=367.4, 0.0<=h<=Infinity)
...  semantic boundary
...  size: Size(367.4, 48.0)
RenderObject: _RenderRangeSlider#59316
  parentData: <none> (can use size)
  constraints: BoxConstraints(0.0<=w<=367.4, 0.0<=h<=Infinity)
  semantic boundary
  size: Size(367.4, 48.0)
====================================================================================================

Track

import 'package:flutter/material.dart';

class GradientRectRangeSliderTrackShape extends RangeSliderTrackShape
    with BaseSliderTrackShape {
  const GradientRectRangeSliderTrackShape({
    this.gradient = const LinearGradient(
      colors: [
        Colors.red,
        Colors.yellow,
      ],
    ),
    this.darkenInactive = true,
  });

  final LinearGradient gradient;
  final bool darkenInactive;

  @override
  void paint(
      PaintingContext context,
      Offset offset,
      {
        required RenderBox parentBox,
        required SliderThemeData sliderTheme,
        required Animation<double> enableAnimation,
        required TextDirection textDirection,
        required Offset startThumbCenter,
        required Offset endThumbCenter,
        bool isDiscrete = false,
        bool isEnabled = false,
        double additionalActiveTrackHeight = 2,
      }
    ) {
    assert(
      sliderTheme.disabledActiveTrackColor != null,
      'sliderTheme.disabledActiveTrackColor is required'
    );
    assert(
      sliderTheme.disabledInactiveTrackColor != null,
      'sliderTheme.disabledInactiveTrackColor is required'
    );
    assert(
      sliderTheme.activeTrackColor != null,
      'sliderTheme.activeTrackColor is required'
    );
    assert(
      sliderTheme.inactiveTrackColor != null,
      'sliderTheme.inactiveTrackColor'
    );
    assert(
      sliderTheme.thumbShape != null,
      'sliderTheme.thumbShape is required'
    );
    assert(
      sliderTheme.trackHeight != null && sliderTheme.trackHeight! > 0,
      'sliderTheme.trackHeight != null and sliderTheme.trackHeight! > 0'
      'are required'
    );

    final Rect trackRect = getPreferredRect(
      parentBox: parentBox,
      offset: offset,
      sliderTheme: sliderTheme,
      isEnabled: isEnabled,
      isDiscrete: isDiscrete,
    );

    final activeGradientRect = Rect.fromLTRB(
      startThumbCenter.dx,
      (textDirection == TextDirection.ltr)
          ? trackRect.top - (additionalActiveTrackHeight / 2)
          : trackRect.top,
      endThumbCenter.dx,
      (textDirection == TextDirection.ltr)
          ? trackRect.bottom   (additionalActiveTrackHeight / 2)
          : trackRect.bottom,
    );

    // Assign the track segment paints, which are leading: active and
    // trailing: inactive.
    final ColorTween activeTrackColorTween = ColorTween(
        begin: sliderTheme.disabledActiveTrackColor,
        end: sliderTheme.activeTrackColor);
    final ColorTween inactiveTrackColorTween = darkenInactive
        ? ColorTween(
        begin: sliderTheme.disabledInactiveTrackColor,
        end: sliderTheme.inactiveTrackColor
    )
        : activeTrackColorTween;
    final Paint activePaint = Paint()
      ..shader = gradient.createShader(activeGradientRect)
      ..color = activeTrackColorTween.evaluate(enableAnimation)!;
    final Paint inactivePaint = Paint()
      ..color = inactiveTrackColorTween.evaluate(enableAnimation)!;
    final Paint leftTrackPaint;
    final Paint rightTrackPaint;
    switch (textDirection) {
      case TextDirection.ltr:
        leftTrackPaint = activePaint;
        rightTrackPaint = inactivePaint;
        break;
      case TextDirection.rtl:
        leftTrackPaint = inactivePaint;
        rightTrackPaint = activePaint;
        break;
    }

    final Radius trackRadius = Radius.circular(trackRect.height / 2);
    final Radius activeTrackRadius = Radius.circular(trackRect.height / 2   1);

    context.canvas.drawRRect(
      RRect.fromLTRBAndCorners(
        startThumbCenter.dx,
        (textDirection == TextDirection.ltr)
            ? trackRect.top - (additionalActiveTrackHeight / 2)
            : trackRect.top,
        endThumbCenter.dx,
        (textDirection == TextDirection.ltr)
            ? trackRect.bottom   (additionalActiveTrackHeight / 2)
            : trackRect.bottom,
        topLeft: (textDirection == TextDirection.ltr)
            ? activeTrackRadius
            : trackRadius,
        bottomLeft: (textDirection == TextDirection.ltr)
            ? activeTrackRadius
            : trackRadius,
      ),
      leftTrackPaint,
    );
    context.canvas.drawRRect(
      RRect.fromLTRBAndCorners(
        startThumbCenter.dx,
        (textDirection == TextDirection.rtl)
            ? trackRect.top - (additionalActiveTrackHeight / 2)
            : trackRect.top,
        endThumbCenter.dx,
        (textDirection == TextDirection.rtl)
            ? trackRect.bottom   (additionalActiveTrackHeight / 2)
            : trackRect.bottom,
        topRight: (textDirection == TextDirection.rtl)
            ? activeTrackRadius
            : trackRadius,
        bottomRight: (textDirection == TextDirection.rtl)
            ? activeTrackRadius
            : trackRadius,
      ),
      rightTrackPaint,
    );
  }
}

Widget

SliderTheme(
  data: SliderThemeData(
    rangeTrackShape: GradientRectRangeSliderTrackShape()
  ),
  child: RangeSlider(
    onChanged: onChanged,
    values: data.values,
    min: data.min,
    max: data.max,
    divisions: data.labels.length
  )
)

CodePudding user response:

This is the code to create a gradient slider range

Track It can be put directly in the Theme of your app

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

class GradientRectRangeSliderTrackShape extends RangeSliderTrackShape {
  const GradientRectRangeSliderTrackShape({
    this.gradient = const LinearGradient(
      colors: [
        Colors.red,
        Colors.yellow,
      ],
    ),
    this.darkenInactive = true,
  });

  final LinearGradient gradient;
  final bool darkenInactive;

  @override
  Rect getPreferredRect({
    required RenderBox parentBox,
    Offset offset = Offset.zero,
    required SliderThemeData sliderTheme,
    bool isEnabled = false,
    bool isDiscrete = false,
  }) {
    assert(
      sliderTheme.overlayShape != null,
      'sliderTheme.overlayShape is required'
    );
    assert(
      sliderTheme.trackHeight != null,
      'sliderTheme.trackHeight is required'
    );

    final double overlayWidth = sliderTheme.overlayShape!
      .getPreferredSize(isEnabled, isDiscrete).width;
    final double trackHeight = sliderTheme.trackHeight!;
    assert(overlayWidth >= 0);
    assert(trackHeight >= 0);

    final double trackLeft = offset.dx   overlayWidth / 2;
    final double trackTop = offset.dy
          (parentBox.size.height - trackHeight) / 2;
    final double trackRight = trackLeft   parentBox.size.width - overlayWidth;
    final double trackBottom = trackTop   trackHeight;
    return Rect.fromLTRB(
      math.min(
        trackLeft,
        trackRight
      ),
      trackTop,
      math.max(
        trackLeft,
        trackRight
      ),
      trackBottom
    );
  }

  @override
  void paint(
      PaintingContext context,
      Offset offset, {
        required RenderBox parentBox,
        required SliderThemeData sliderTheme,
        required Animation<double> enableAnimation,
        required Offset startThumbCenter,
        required Offset endThumbCenter,
        bool isEnabled = false,
        bool isDiscrete = false,
        required TextDirection textDirection,
        double additionalActiveTrackHeight = 2,
      }) {
    assert(
      sliderTheme.disabledActiveTrackColor != null,
      'sliderTheme.disabledActiveTrackColor is required'
    );
    assert(
      sliderTheme.disabledInactiveTrackColor != null,
      'sliderTheme.disabledInactiveTrackColor is required'
    );
    assert(
      sliderTheme.activeTrackColor != null,
      'sliderTheme.activeTrackColor is required'
    );
    assert(
      sliderTheme.inactiveTrackColor != null,
      'sliderTheme.inactiveTrackColor is required'
    );
    assert(
      sliderTheme.rangeThumbShape != null,
      'sliderTheme.rangeThumbShape iss required'
    );
    assert(
      sliderTheme.trackHeight != null && sliderTheme.trackHeight! > 0,
      'sliderTheme.trackHeight != null and sliderTheme.trackHeight! > 0'
        'are required'
    );

    final Rect trackRect = getPreferredRect(
      parentBox: parentBox,
      offset: offset,
      sliderTheme: sliderTheme,
      isEnabled: isEnabled,
      isDiscrete: isDiscrete,
    );

    final ColorTween activeTrackColorTween = ColorTween(
      begin: sliderTheme.disabledActiveTrackColor,
      end: sliderTheme.activeTrackColor,
    );
    final ColorTween inactiveTrackColorTween = darkenInactive
        ? ColorTween(
          begin: sliderTheme.disabledInactiveTrackColor,
          end: sliderTheme.inactiveTrackColor,
        )
        : activeTrackColorTween;
    final Paint activePaint = Paint()
      ..shader = gradient.createShader(trackRect)
      ..color = activeTrackColorTween.evaluate(enableAnimation)!;
    final Paint inactivePaint = Paint()
      ..color = inactiveTrackColorTween.evaluate(enableAnimation)!;

    final Offset leftThumbOffset;
    final Offset rightThumbOffset;
    switch (textDirection) {
      case TextDirection.ltr:
        leftThumbOffset = startThumbCenter;
        rightThumbOffset = endThumbCenter;
        break;
      case TextDirection.rtl:
        leftThumbOffset = endThumbCenter;
        rightThumbOffset = startThumbCenter;
        break;
    }
    final Size thumbSize = sliderTheme.rangeThumbShape!
      .getPreferredSize(
        isEnabled,
        isDiscrete
      );
    final double thumbRadius = thumbSize.width / 2;
    assert(thumbRadius > 0);

    final Radius trackRadius = Radius.circular(trackRect.height / 2);

    context.canvas.drawRRect(
      RRect.fromLTRBAndCorners(
        trackRect.left,
        trackRect.top,
        leftThumbOffset.dx,
        trackRect.bottom,
        topLeft: trackRadius,
        bottomLeft: trackRadius,
      ),
      inactivePaint,
    );
    context.canvas.drawRect(
      Rect.fromLTRB(
        leftThumbOffset.dx,
        trackRect.top - (additionalActiveTrackHeight / 2),
        rightThumbOffset.dx,
        trackRect.bottom   (additionalActiveTrackHeight / 2),
      ),
      activePaint,
    );
    context.canvas.drawRRect(
      RRect.fromLTRBAndCorners(
        rightThumbOffset.dx,
        trackRect.top,
        trackRect.right,
        trackRect.bottom,
        topRight: trackRadius,
        bottomRight: trackRadius,
      ),
      inactivePaint,
    );
  }
}

Widget

SliderTheme(
  data: SliderThemeData(
    rangeTrackShape: GradientRectRangeSliderTrackShape()
  ),
  child: RangeSlider(
    onChanged: onChanged,
    values: data.values,
    min: data.min,
    max: data.max,
    divisions: data.labels.length
  )
)

CodePudding user response:

This happens when you use a operator ! on a nullable instance which is not initialized.

That happens on:

final Paint activePaint = Paint()
      ..shader = gradient.createShader(activeGradientRect)
      ..color = activeTrackColorTween.evaluate(enableAnimation)!;
    final Paint inactivePaint = Paint()
      ..color = inactiveTrackColorTween.evaluate(enableAnimation)!;

You can control it with if (variable != null) or set a default value with ??

  • Related