diff --git a/examples/material_gallery/lib/main.dart b/examples/material_gallery/lib/main.dart index 6f5a81bec1d..d196bc08725 100644 --- a/examples/material_gallery/lib/main.dart +++ b/examples/material_gallery/lib/main.dart @@ -8,11 +8,13 @@ import 'chip_demo.dart'; import 'date_picker_demo.dart'; import 'drop_down_demo.dart'; import 'gallery_page.dart'; +import 'slider_demo.dart'; import 'time_picker_demo.dart'; import 'widget_demo.dart'; final List _kDemos = [ kChipDemo, + kSliderDemo, kDatePickerDemo, kTimePickerDemo, kDropDownDemo, diff --git a/examples/material_gallery/lib/slider_demo.dart b/examples/material_gallery/lib/slider_demo.dart new file mode 100644 index 00000000000..f582dcdeaa8 --- /dev/null +++ b/examples/material_gallery/lib/slider_demo.dart @@ -0,0 +1,45 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; + +import 'widget_demo.dart'; + +class SliderDemo extends StatefulComponent { + _SliderDemoState createState() => new _SliderDemoState(); +} + +class _SliderDemoState extends State { + double _value = 0.25; + + Widget build(BuildContext context) { + return new Block([ + new Container( + height: 100.0, + child: new Center( + child: new Row([ + new Slider( + value: _value, + onChanged: (double value) { + setState(() { + _value = value; + }); + } + ), + new Container( + padding: const EdgeDims.symmetric(horizontal: 16.0), + child: new Text(_value.toStringAsFixed(2)) + ), + ], justifyContent: FlexJustifyContent.collapse) + ) + ) + ]); + } +} + +final WidgetDemo kSliderDemo = new WidgetDemo( + title: 'Sliders', + routeName: '/sliders', + builder: (_) => new SliderDemo() +); diff --git a/packages/flutter/lib/material.dart b/packages/flutter/lib/material.dart index 0453f7c67da..9639d39ea3c 100644 --- a/packages/flutter/lib/material.dart +++ b/packages/flutter/lib/material.dart @@ -45,6 +45,7 @@ export 'src/material/raised_button.dart'; export 'src/material/scaffold.dart'; export 'src/material/scrollbar_painter.dart'; export 'src/material/shadows.dart'; +export 'src/material/slider.dart'; export 'src/material/snack_bar.dart'; export 'src/material/switch.dart'; export 'src/material/tabs.dart'; diff --git a/packages/flutter/lib/src/material/slider.dart b/packages/flutter/lib/src/material/slider.dart new file mode 100644 index 00000000000..5e0a5afaead --- /dev/null +++ b/packages/flutter/lib/src/material/slider.dart @@ -0,0 +1,156 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/gestures.dart'; +import 'package:flutter/painting.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/widgets.dart'; + +import 'colors.dart'; +import 'theme.dart'; + +const double _kThumbRadius = 6.0; +const double _kReactionRadius = 16.0; +const double _kTrackWidth = 144.0; +const int _kReactionAlpha = 0x33; + +class Slider extends StatelessComponent { + Slider({ Key key, this.value, this.onChanged }) + : super(key: key); + + final double value; + final ValueChanged onChanged; + + Widget build(BuildContext context) { + return new _SliderRenderObjectWidget( + value: value, + primaryColor: Theme.of(context).accentColor, + onChanged: onChanged + ); + } +} + +class _SliderRenderObjectWidget extends LeafRenderObjectWidget { + _SliderRenderObjectWidget({ Key key, this.value, this.primaryColor, this.onChanged }) + : super(key: key); + + final double value; + final Color primaryColor; + final ValueChanged onChanged; + + _RenderSlider createRenderObject() => new _RenderSlider( + value: value, + primaryColor: primaryColor, + onChanged: onChanged + ); + + void updateRenderObject(_RenderSlider renderObject, _SliderRenderObjectWidget oldWidget) { + renderObject.value = value; + renderObject.primaryColor = primaryColor; + renderObject.onChanged = onChanged; + } +} + +final Color _kInactiveTrackColor = Colors.grey[400]; +final Color _kActiveTrackColor = Colors.grey[500]; + +class _RenderSlider extends RenderConstrainedBox { + _RenderSlider({ + double value, + Color primaryColor, + this.onChanged + }) : _value = value, + _primaryColor = primaryColor, + super(additionalConstraints: const BoxConstraints.tightFor(width: _kTrackWidth + 2 * _kReactionRadius, height: 2 * _kReactionRadius)) { + assert(value != null && value >= 0.0 && value <= 1.0); + _drag = new HorizontalDragGestureRecognizer(router: FlutterBinding.instance.pointerRouter) + ..onStart = _handleDragStart + ..onUpdate = _handleDragUpdate + ..onEnd = _handleDragEnd; + } + + double get value => _value; + double _value; + void set value(double newValue) { + assert(newValue != null && newValue >= 0.0 && newValue <= 1.0); + if (newValue == _value) + return; + _value = newValue; + markNeedsPaint(); + } + + Color get primaryColor => _primaryColor; + Color _primaryColor; + void set primaryColor(Color value) { + if (value == _primaryColor) + return; + _primaryColor = value; + markNeedsPaint(); + } + + ValueChanged onChanged; + + double get _trackLength => size.width - 2.0 * _kReactionRadius; + + HorizontalDragGestureRecognizer _drag; + bool _active = false; + double _currentDragValue = 0.0; + + void _handleDragStart(Point globalPosition) { + if (onChanged != null) { + _active = true; + _currentDragValue = globalToLocal(globalPosition).x / _trackLength; + onChanged(_currentDragValue.clamp(0.0, 1.0)); + markNeedsPaint(); + } + } + + void _handleDragUpdate(double delta) { + if (onChanged != null) { + _currentDragValue += delta / _trackLength; + onChanged(_currentDragValue.clamp(0.0, 1.0)); + } + } + + void _handleDragEnd(Offset velocity) { + if (_active) { + _active = false; + _currentDragValue = 0.0; + markNeedsPaint(); + } + } + + bool hitTestSelf(Point position) => true; + + void handleEvent(InputEvent event, BoxHitTestEntry entry) { + if (event.type == 'pointerdown' && onChanged != null) + _drag.addPointer(event); + } + + void paint(PaintingContext context, Offset offset) { + final Canvas canvas = context.canvas; + + final double trackLength = _trackLength; + + double trackCenter = offset.dy + size.height / 2.0; + double trackLeft = offset.dx + _kReactionRadius; + double trackTop = trackCenter - 1.0; + double trackBottom = trackCenter + 1.0; + double trackRight = trackLeft + trackLength; + double trackActive = trackLeft + trackLength * value; + + Paint primaryPaint = new Paint()..color = _primaryColor; + Paint trackPaint = new Paint()..color = _active ? _kActiveTrackColor : _kInactiveTrackColor; + + canvas.drawRect(new Rect.fromLTRB(trackLeft, trackTop, trackRight, trackBottom), trackPaint); + canvas.drawRect(new Rect.fromLTRB(trackLeft, trackTop, trackActive, trackBottom), primaryPaint); + + Point activeLocation = new Point(trackActive, trackCenter); + if (_active) { + Paint reactionPaint = new Paint()..color = _primaryColor.withAlpha(_kReactionAlpha); + canvas.drawCircle(activeLocation, _kReactionRadius, reactionPaint); + } + canvas.drawCircle(activeLocation, _kThumbRadius, trackPaint); + } +}