Custom Shapes in Flutter Range Selector (SfRangeSelector)

18 Nov 201824 minutes to read

This section explains how to customize the shapes of the range selector elements.

Track shape

You can change the size and shape of the track using the trackShape property in the SfRangeSelector.

  • getPreferredSize() - Returns the size based on the values passed to it.
  • paint() - Used to change the track shape.

NOTE

You must use the startThumbCenter, endThumbCenter, and currentValues parameters of the paint override method for customizing the track of the Flutter Range Selector.

import 'dart:math' as math;

import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_charts/charts.dart';
import 'package:syncfusion_flutter_core/theme.dart';
import 'package:syncfusion_flutter_sliders/sliders.dart';

class TrackShapeSample extends StatefulWidget {
  @override
  _TrackShapeSampleState createState() => _TrackShapeSampleState();
}

class _TrackShapeSampleState extends State<TrackShapeSample> {
  final double _min = 2.0;
  final double _max = 10.0;
  SfRangeValues _values = SfRangeValues(3.0, 8.0);

  final List<Data> chartData = <Data>[
    Data(x: 2.0, y: 2.2),
    Data(x: 3.0, y: 3.4),
    Data(x: 4.0, y: 2.8),
    Data(x: 5.0, y: 1.6),
    Data(x: 6.0, y: 2.3),
    Data(x: 7.0, y: 2.5),
    Data(x: 8.0, y: 2.9),
    Data(x: 9.0, y: 3.8),
    Data(x: 10.0, y: 3.7),
  ];

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: SfRangeSelectorTheme(
            data: SfRangeSelectorThemeData(
              activeTrackHeight: 10,
              inactiveTrackHeight: 10,
            ),
            child: SfRangeSelector(
              min: _min,
              max: _max,
              initialValues: _values,
              trackShape: _TrackShape(),
              child: Container(
                height: 130,
                child: SfCartesianChart(
                  margin: const EdgeInsets.all(0),
                  primaryXAxis:
                      NumericAxis(minimum: _min, maximum: _max, isVisible: false),
                  primaryYAxis: NumericAxis(isVisible: false),
                  plotAreaBorderWidth: 0,
                  series: <SplineAreaSeries<Data, double>>[
                    SplineAreaSeries<Data, double>(
                        color: Color.fromARGB(255, 126, 184, 253),
                        dataSource: chartData,
                        xValueMapper: (Data sales, int index) => sales.x,
                        yValueMapper: (Data sales, int index) => sales.y)
                  ],
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

class Data {
  Data({required this.x, required this.y});
  final double x;
  final double y;
}

class _TrackShape extends SfTrackShape {
  @override
  void paint(PaintingContext context, Offset offset, Offset? thumbCenter,
      Offset? startThumbCenter, Offset? endThumbCenter,
      {required RenderBox parentBox,
      required SfSliderThemeData themeData,
      SfRangeValues? currentValues,
      dynamic currentValue,
      required Animation<double> enableAnimation,
      required Paint? inactivePaint,
      required Paint? activePaint,
      required TextDirection textDirection}) {
    Paint paint = Paint()
      ..color = themeData.activeTrackColor!
      ..style = PaintingStyle.stroke
      ..strokeWidth = 1;
    super.paint(context, offset, thumbCenter, startThumbCenter, endThumbCenter,
        parentBox: parentBox,
        themeData: themeData,
        enableAnimation: enableAnimation,
        inactivePaint: inactivePaint,
        activePaint: paint,
        textDirection: textDirection);
  }
}

Track shape

Thumb shape

You can change the size and shape of the thumb using the thumbShape property in the SfRangeSelector.

  • getPreferredSize() - Returns the size based on the values passed to it.
  • paint() - Used to change the thumb shape.

NOTE

You must use the currentValues parameter of the paint override method for customizing the thumbs of the Flutter Range Selector.

import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_charts/charts.dart';
import 'package:syncfusion_flutter_core/theme.dart';
import 'package:syncfusion_flutter_sliders/sliders.dart';

class ThumbShapeSample extends StatefulWidget {
  @override
  _ThumbShapeSampleState createState() => _ThumbShapeSampleState();
}

class _ThumbShapeSampleState extends State<ThumbShapeSample> {
  final double _min = 2.0;
  final double _max = 10.0;
  SfRangeValues _values = SfRangeValues(4.0, 8.0);

  final List<Data> chartData = <Data>[
    Data(x: 2.0, y: 2.2),
    Data(x: 3.0, y: 3.4),
    Data(x: 4.0, y: 2.8),
    Data(x: 5.0, y: 1.6),
    Data(x: 6.0, y: 2.3),
    Data(x: 7.0, y: 2.5),
    Data(x: 8.0, y: 2.9),
    Data(x: 9.0, y: 3.8),
    Data(x: 10.0, y: 3.7),
  ];

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: SfRangeSelectorTheme(
            data: SfRangeSelectorThemeData(overlayColor: Colors.transparent),
            child: SfRangeSelector(
              min: _min,
              max: _max,
              initialValues: _values,
              thumbShape: _SfThumbShape(),
              child: Container(
                height: 130,
                child: SfCartesianChart(
                  margin: const EdgeInsets.all(0),
                  primaryXAxis: NumericAxis(
                      minimum: _min, maximum: _max, isVisible: false),
                  primaryYAxis: NumericAxis(isVisible: false),
                  plotAreaBorderWidth: 0,
                  series: <SplineAreaSeries<Data, double>>[
                    SplineAreaSeries<Data, double>(
                        color: Color.fromARGB(255, 126, 184, 253),
                        dataSource: chartData,
                        xValueMapper: (Data sales, int index) => sales.x,
                        yValueMapper: (Data sales, int index) => sales.y)
                  ],
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

class Data {
  Data({required this.x, required this.y});
  final double x;
  final double y;
}

class _SfThumbShape extends SfThumbShape {
  @override
  void paint(PaintingContext context, Offset center,
      {required RenderBox parentBox,
      required RenderBox? child,
      required SfSliderThemeData themeData,
      SfRangeValues? currentValues,
      dynamic currentValue,
      required Paint? paint,
      required Animation<double> enableAnimation,
      required TextDirection textDirection,
      required SfThumb? thumb}) {
    final Path path = Path();

    path.moveTo(center.dx, center.dy);
    path.lineTo(center.dx + 10, center.dy - 15);
    path.lineTo(center.dx - 10, center.dy - 15);
    path.close();
    context.canvas.drawPath(
        path,
        Paint()
          ..color = themeData.activeTrackColor!
          ..style = PaintingStyle.fill
          ..strokeWidth = 2);
  }
}

Thumb shape

Divider shape

You can change the size and shape of the divider using the dividerShape property in the SfRangeSelector.

  • getPreferredSize() - Returns the size based on the values passed to it.
  • paint() - Used to change the divider shape.

NOTE

You must use the startThumbCenter, endThumbCenter, and currentValues parameters of the paint override method for customizing the divider of the Flutter Range Selector.

import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_charts/charts.dart';
import 'package:syncfusion_flutter_sliders/sliders.dart';

class DividerShapeSample extends StatefulWidget {
  @override
  _DividerShapeSampleState createState() => _DividerShapeSampleState();
}

class _DividerShapeSampleState extends State<DividerShapeSample> {
  final double _min = 2.0;
  final double _max = 10.0;
  SfRangeValues _values = SfRangeValues(4.0, 8.0);

  final List<Data> chartData = <Data>[
    Data(x: 2.0, y: 2.2),
    Data(x: 3.0, y: 3.4),
    Data(x: 4.0, y: 2.8),
    Data(x: 5.0, y: 1.6),
    Data(x: 6.0, y: 2.3),
    Data(x: 7.0, y: 2.5),
    Data(x: 8.0, y: 2.9),
    Data(x: 9.0, y: 3.8),
    Data(x: 10.0, y: 3.7),
  ];

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: SfRangeSelector(
            min: _min,
            max: _max,
            initialValues: _values,
            interval: 1,
            showDividers: true,
            dividerShape: _DividerShape(),
            child: Container(
              height: 130,
              child: SfCartesianChart(
                margin: const EdgeInsets.all(0),
                primaryXAxis:
                    NumericAxis(minimum: _min, maximum: _max, isVisible: false),
                primaryYAxis: NumericAxis(isVisible: false),
                plotAreaBorderWidth: 0,
                series: <SplineAreaSeries<Data, double>>[
                  SplineAreaSeries<Data, double>(
                      color: Color.fromARGB(255, 126, 184, 253),
                      dataSource: chartData,
                      xValueMapper: (Data sales, int index) => sales.x,
                      yValueMapper: (Data sales, int index) => sales.y)
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }
}

class Data {
  Data({required this.x, required this.y});
  final double x;
  final double y;
}

class _DividerShape extends SfDividerShape {
  @override
  void paint(PaintingContext context, Offset center, Offset? thumbCenter,
      Offset? startThumbCenter, Offset? endThumbCenter,
      {required RenderBox parentBox,
      required SfSliderThemeData themeData,
      SfRangeValues? currentValues,
      dynamic currentValue,
      required Paint? paint,
      required Animation<double> enableAnimation,
      required TextDirection textDirection}) {
    final bool isActive =
        center.dx >= startThumbCenter!.dx && center.dx <= endThumbCenter!.dx;
    context.canvas.drawRect(
        Rect.fromCenter(center: center, width: 5.0, height: 10.0),
        Paint()
          ..isAntiAlias = true
          ..style = PaintingStyle.fill
          ..color = isActive ? Colors.white : themeData.activeTrackColor!);
  }
}

Divider shape

Major and minor ticks shapes

You can change the size and shape of the major and minor ticks using the tickShape and minorTickShape properties in the SfRangeSelector.

  • getPreferredSize() - Returns the size based on the values passed to it.
  • paint() - Used to change the ticks shape.

NOTE

You must use the startThumbCenter, endThumbCenter, and currentValues parameters of the paint override method for customizing the ticks of the Range Selector.

import 'dart:math' as math;

import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_charts/charts.dart';
import 'package:syncfusion_flutter_sliders/sliders.dart';

class TicksShapeSample extends StatefulWidget {
  @override
  _TicksShapeSampleState createState() => _TicksShapeSampleState();
}

class _TicksShapeSampleState extends State<TicksShapeSample> {
  final double _min = 2.0;
  final double _max = 10.0;
  SfRangeValues _values = SfRangeValues(4.0, 8.0);

  final List<Data> chartData = <Data>[
    Data(x: 2.0, y: 2.2),
    Data(x: 3.0, y: 3.4),
    Data(x: 4.0, y: 2.8),
    Data(x: 5.0, y: 1.6),
    Data(x: 6.0, y: 2.3),
    Data(x: 7.0, y: 2.5),
    Data(x: 8.0, y: 2.9),
    Data(x: 9.0, y: 3.8),
    Data(x: 10.0, y: 3.7),
  ];

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: SfRangeSelector(
            min: _min,
            max: _max,
            initialValues: _values,
            interval: 1,
            showTicks: true,
            minorTicksPerInterval: 3,
            tickShape: _TickShape(),
            minorTickShape: _MinorTickShape(),
            child: Container(
              height: 130,
              child: SfCartesianChart(
                margin: const EdgeInsets.all(0),
                primaryXAxis:
                    NumericAxis(minimum: _min, maximum: _max, isVisible: false),
                primaryYAxis: NumericAxis(isVisible: false),
                plotAreaBorderWidth: 0,
                series: <SplineAreaSeries<Data, double>>[
                  SplineAreaSeries<Data, double>(
                      color: Color.fromARGB(255, 126, 184, 253),
                      dataSource: chartData,
                      xValueMapper: (Data sales, int index) => sales.x,
                      yValueMapper: (Data sales, int index) => sales.y)
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }
}

class Data {
  Data({required this.x, required this.y});
  final double x;
  final double y;
}

class _TickShape extends SfTickShape {
  @override
  void paint(PaintingContext context, Offset offset, Offset? thumbCenter,
      Offset? startThumbCenter, Offset? endThumbCenter,
      {required RenderBox parentBox,
      required SfSliderThemeData themeData,
      SfRangeValues? currentValues,
      dynamic currentValue,
      required Animation<double> enableAnimation,
      required TextDirection textDirection}) {
    final Size tickSize = getPreferredSize(themeData);
    final bool isTickRightOfThumb = endThumbCenter == null
        ? offset.dx > thumbCenter!.dx
        : offset.dx < startThumbCenter!.dx || offset.dx > endThumbCenter!.dx;
    final Color begin = isTickRightOfThumb
        ? themeData.disabledInactiveTickColor
        : themeData.disabledActiveTickColor;
    final Color end = isTickRightOfThumb
        ? themeData.inactiveTickColor
        : themeData.activeTickColor;
    final Paint paint = Paint()
      ..isAntiAlias = true
      ..strokeWidth = tickSize.width
      ..color = ColorTween(begin: begin, end: end).evaluate(enableAnimation)!;
    context.canvas.drawLine(
        offset, Offset(offset.dx, offset.dy + tickSize.height), paint);
    context.canvas.drawLine(
        Offset(
            offset.dx,
            offset.dy -
                2 -
                math.max(themeData.activeTrackHeight,
                    themeData.inactiveTrackHeight)),
        Offset(
            offset.dx,
            offset.dy -
                2 -
                math.max(themeData.activeTrackHeight,
                    themeData.inactiveTrackHeight) -
                tickSize.height),
        paint);
  }
}

class _MinorTickShape extends SfTickShape {
  @override
  void paint(PaintingContext context, Offset offset, Offset? thumbCenter,
      Offset? startThumbCenter, Offset? endThumbCenter,
      {required RenderBox parentBox,
      required SfSliderThemeData themeData,
      SfRangeValues? currentValues,
      dynamic currentValue,
      required Animation<double> enableAnimation,
      required TextDirection textDirection}) {
    final Size minorTickSize = getPreferredSize(themeData);
    final bool isMinorTickRightOfThumb = endThumbCenter == null
        ? offset.dx > thumbCenter!.dx
        : offset.dx < startThumbCenter!.dx || offset.dx > endThumbCenter!.dx;

    final Color begin = isMinorTickRightOfThumb
        ? themeData.disabledInactiveMinorTickColor
        : themeData.disabledActiveMinorTickColor;
    final Color end = isMinorTickRightOfThumb
        ? themeData.inactiveMinorTickColor
        : themeData.activeMinorTickColor;
    final Paint paint = Paint()
      ..isAntiAlias = true
      ..strokeWidth = minorTickSize.width
      ..color = ColorTween(begin: begin, end: end).evaluate(enableAnimation)!;
    context.canvas.drawLine(
        offset, Offset(offset.dx, offset.dy + minorTickSize.height), paint);
    context.canvas.drawLine(
        Offset(
            offset.dx,
            offset.dy -
                2 -
                math.max(themeData.activeTrackHeight,
                    themeData.inactiveTrackHeight)),
        Offset(
            offset.dx,
            offset.dy -
                2 -
                math.max(themeData.activeTrackHeight,
                    themeData.inactiveTrackHeight) -
                minorTickSize.height),
        paint);
  }
}

Ticks shape