🎢

独自デザインSliderを頑張りたい人へ【SliderThemeData】

に公開

直近、デザインシステムに基づいた独自デザインのSliderを実装しました
標準Widgetとは地味に異なるポイントが多々あるデザインで実装が大変でしたが、ゴリ押しで自作すれば結局なんとかなるのがFlutterのいいところです
Sliderのカスタムで実現できることとその工夫を共有します

前提と先に共有

今回実装したカスタムSliderはFlutter 3.32系のものになります
3.35系以上では内部のレイアウトが変わっている可能性があり、期待するレイアウトにならない場合があります

サンプルコードもFlutter 3.32系で用意しています↓

https://github.com/kentomiya89/slider_sample

独自デザインSliderのデザイン要件

Material Design3のSliderのtick mark(目盛り)の始点と終点の位置はtrack(スライダーのライン)の両端になります

divisionsというパラメータで区切りの数から間隔を計算しtick markを描画します

https://www.youtube.com/watch?v=ufb4gIPDmEs

実装したSliderのデザイン要件は以下でした

  • tick markの始点と終点をtrackの両端から何pxかずらして描画する
  • 標準のtick markを表示せず、trackの下に独自のラベルつきのtick markを表示する

また、trackやthumbも独自デザインで実装する必要がありました

標準のSliderでは実現が難しいです
また、3rdパーティパッケージで解決する方法もあります
例えばsyncfusion_flutter_slidersのようなリッチなUIパッケージがありますが、商用パッケージのため金銭的なコストがかかってしまいます

他のパッケージ群もメンテナンスが活発なものがあまりなく、今回は自作の方向で検討しました

Sliderのカスタム

Slider Widgetはデザインレベルで基本的に設定できるパラメータとしては、色やちょっとしたpaddingくらいです(RangeSliderも同様)

https://api.flutter.dev/flutter/material/Slider/Slider.html

https://api.flutter.dev/flutter/material/RangeSlider-class.html

カスタムする場合は、SliderThemeDataで独自デザインを定義し、SliderThemeを使ってSlider Widgetに適用する形になります

https://api.flutter.dev/flutter/material/SliderTheme-class.html

https://api.flutter.dev/flutter/material/SliderThemeData-class.html

SliderThemeDataのコンストラクタでは、色や高さ、thumb・track・tick markなどの各パーツを独自デザインで設定できます

https://api.flutter.dev/flutter/material/SliderThemeData/SliderThemeData.html

独自デザインを実装する場合は、各Sliderのパーツに対する抽象クラスが用意されています
CustomPainterと同じ要領で、その抽象クラスを継承して独自デザインを実装していく形になります

SliderComponentShape, which can be used to create custom shapes for the Slider's thumb, overlay, and value indicator and the RangeSlider's overlay.
SliderTrackShape, which can be used to create custom shapes for the Slider's track.
SliderTickMarkShape, which can be used to create custom shapes for the Slider's tick marks.
RangeSliderThumbShape, which can be used to create custom shapes for the RangeSlider's thumb.
RangeSliderValueIndicatorShape, which can be used to create custom shapes for the RangeSlider's value indicator.
RangeSliderTrackShape, which can be used to create custom shapes for the RangeSlider's track.
RangeSliderTickMarkShape, which can be used to create custom shapes for the RangeSlider's tick marks.

また、ある程度デザインされた具象クラスも用意されているので、デザイン要件を満たせる場合はそちらを使うのも手です

The thumb, track, tick marks, value indicator, and overlay can be customized by creating subclasses of SliderTrackShape, SliderComponentShape, and/or SliderTickMarkShape. See RoundSliderThumbShape, RectangularSliderTrackShape, RoundSliderTickMarkShape, RectangularSliderValueIndicatorShape, and RoundSliderOverlayShape for examples.

自作までの道のり

今回はRangeSliderをカスタムした例になります

  1. tick markの始点をずらし標準のtick markを非表示にする

trackの端からずらしたいpx分をSlider Widgetにpaddingとして設定します
そしてrangeTrackShapeを使って、ずらした分のpxを両端のtrackに描画します

↓ Sliderにpaddingを設定

https://github.com/kentomiya89/slider_sample/blob/e1cf7684758b5e347af75a212fad93b81fa9b5d4/lib/custom_range_slider.dart#L59-L82

↓ ずらした分のpxだけ両端のtrackを延長して描画
左端はずらした分だけ左の座標を引いて変更、右端は足して右の座標を伸ばしている

https://github.com/kentomiya89/slider_sample/blob/e1cf7684758b5e347af75a212fad93b81fa9b5d4/lib/custom_range_slider.dart#L253-L308

標準のtick markを非表示にするには、RoundRangeSliderTickMarkShapeを使ってradiusを0に設定します

https://github.com/kentomiya89/slider_sample/blob/e1cf7684758b5e347af75a212fad93b81fa9b5d4/lib/custom_range_slider.dart#L78-L80

SliderThemeDataのtickMarkShapeにSliderTickMarkShape.noTickMarkを設定してもRangeSliderのtick markは非表示にならないので注意です

tickMarkShapeはSlider Widget用で、SliderとRange Sliderではそれぞれ設定するプロパティが異なります

  1. 外側tick mark の配置計算

正直ここが一番困りました

ただ、FlutterフレームワークのいいところはOSSです
SliderやRange Sliderの内部実装にあるtick markの計算ロジックを模倣すればいけるのでは?と気づきました

じっくり読んでみるのも面白いですが、限られた業務時間の中で数千行あるOSSコードを読み解くのは大変です
そのためAIエージェント(自分はClaude Code)を使ってtick markの計算ロジックを探索してもらい、素早く実装できました

たとえば 2025/12/11時点でのRange Sliderの tick markのロジックは以下の通りです

https://github.com/flutter/flutter/blob/f159b58f96e4bc4f97666b6c76127015268373e3/packages/flutter/lib/src/material/range_slider.dart#L1678-L1706

isDiscreteでdivisionsに0以上が与えられているかで、tick markの計算ロジックを実行するか分岐しています

この式でpaddingを引いた実際のtrack幅を算出しています

final double discreteTrackPadding = trackRect.height;
final double adjustedTrackWidth = trackRect.width - discreteTrackPadding;

divisionsの数に応じて、track幅に対するtick markごとの位置の比率を計算し、trackのpaddingを両端で割った分を足してx座標を求め、ループで描画しています

        final double dy = trackRect.center.dy;
        for (var i = 0; i <= divisions!; i++) {
          final double value = i / divisions!;
          // The ticks are mapped to be within the track, so the tick mark width
          // must be subtracted from the track width.
          final double dx = trackRect.left + value * adjustedTrackWidth + discreteTrackPadding / 2;
          final tickMarkOffset = Offset(dx, dy);
          _sliderTheme.rangeTickMarkShape!.paint(
            context,
            tickMarkOffset,
            parentBox: this,
            sliderTheme: _sliderTheme,
            enableAnimation: _enableAnimation,
            textDirection: _textDirection,
            startThumbCenter: _startThumbCenter,
            endThumbCenter: _endThumbCenter,
            isEnabled: isEnabled,
          );
        }

※ フレームワーク側にはdivisionsを多く設定してtick markが密集した時の対策として、以下の条件式が実装に含まれています

      // If the tick marks would be too dense, don't bother painting them.
      if (adjustedTrackWidth / divisions! >= 3.0 * tickMarkWidth) {

上記のロジックを参考に実装したコードが以下です

https://github.com/kentomiya89/slider_sample/blob/e1cf7684758b5e347af75a212fad93b81fa9b5d4/lib/custom_range_slider.dart#L92-L159

Sliderに対してずらしたpaddingを設定しているので、自作tick markとラベルにも同様のpadding値を設定します

同様の方法でtick markの間隔の計算ロジックを基に、自作tick markとラベルを表示しています

サンプルコード例ではラベルが短いですが、実際の業務では文字列が長かったり複数行になるパターンがあり、Stack・Positionedを使った力技で配置を調整しています(ここはもう少し改善の余地があるかもしれません)

標準のtick markと比較しても、適切な位置に表示できています

まとめ

標準のSlider Widgetでは実現できない独自デザイン要件に対して、以下のアプローチで実装できました

  • SliderThemeDataとカスタムShapeクラスを活用
  • Flutterフレームワークの内部実装を参考にtick markの配置ロジックを模倣

もちろん、安定した3rdパーティパッケージがあればそれを使うのが最善ですが、要件を満たすパッケージがない場合でも、Flutterのデザインカスタマイズの柔軟性を改めて実感しました

特にAIエージェントの活用により、数千行のフレームワークコードから必要なロジックを効率的に見つけ出すことができ、実装のハードルが大きく下がりました

この記事が、独自デザインのSlider実装に取り組む方の参考になれば幸いです

GitHubで編集を提案

Discussion