Commit 767ce826 authored by Adam Barth's avatar Adam Barth

Add support for discrete material sliders

Fixes #1541
parent daa0d2df
...@@ -11,53 +11,44 @@ class SliderDemo extends StatefulWidget { ...@@ -11,53 +11,44 @@ class SliderDemo extends StatefulWidget {
class _SliderDemoState extends State<SliderDemo> { class _SliderDemoState extends State<SliderDemo> {
double _value = 25.0; double _value = 25.0;
double _discreteValue = 20.0;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new Scaffold( return new Scaffold(
appBar: new AppBar(title: new Text("Sliders")), appBar: new AppBar(title: new Text('Sliders')),
body: new Block(children: <Widget>[ body: new Column(
new Container( mainAxisAlignment: MainAxisAlignment.spaceAround,
height: 100.0, children: <Widget>[
child: new Center( new Center(
child: new Row( child: new Slider(
children: <Widget>[ value: _value,
new Slider( min: 0.0,
value: _value, max: 100.0,
min: 0.0, onChanged: (double value) {
max: 100.0, setState(() {
onChanged: (double value) { _value = value;
setState(() { });
_value = value; }
});
}
),
new Container(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: new Text(_value.round().toString().padLeft(3, '0'))
),
],
mainAxisAlignment: MainAxisAlignment.collapse
) )
) ),
), new Center(child: new Slider(value: _value / 100.0)),
new Container( new Center(
height: 100.0, child: new Slider(
child: new Center( value: _discreteValue,
child: new Row( min: 0.0,
children: <Widget>[ max: 100.0,
// Disabled, but tracking the slider above. divisions: 5,
new Slider(value: _value / 100.0), label: '${_discreteValue.round()}',
new Container( onChanged: (double value) {
padding: const EdgeInsets.symmetric(horizontal: 16.0), setState(() {
child: new Text((_value / 100.0).toStringAsFixed(2)) _discreteValue = value;
), });
], }
mainAxisAlignment: MainAxisAlignment.collapse
) )
) ),
) ]
]) )
); );
} }
} }
...@@ -259,16 +259,9 @@ List<TextPainter> _initPainters(List<String> labels) { ...@@ -259,16 +259,9 @@ List<TextPainter> _initPainters(List<String> labels) {
List<TextPainter> painters = new List<TextPainter>(labels.length); List<TextPainter> painters = new List<TextPainter>(labels.length);
for (int i = 0; i < painters.length; ++i) { for (int i = 0; i < painters.length; ++i) {
String label = labels[i]; String label = labels[i];
TextPainter painter = new TextPainter( painters[i] = new TextPainter(
new TextSpan(style: style, text: label) new TextSpan(style: style, text: label)
); )..layoutToMaxIntrinsicWidth();
painter
..maxWidth = double.INFINITY
..maxHeight = double.INFINITY
..layout()
..maxWidth = painter.maxIntrinsicWidth
..layout();
painters[i] = painter;
} }
return painters; return painters;
} }
......
...@@ -243,7 +243,7 @@ class TextPainter { ...@@ -243,7 +243,7 @@ class TextPainter {
} }
ui.Paragraph _paragraph; ui.Paragraph _paragraph;
bool _needsLayout = true; bool _needsLayout = false;
TextSpan _text; TextSpan _text;
/// The (potentially styled) text to paint. /// The (potentially styled) text to paint.
...@@ -253,10 +253,15 @@ class TextPainter { ...@@ -253,10 +253,15 @@ class TextPainter {
if (_text == value) if (_text == value)
return; return;
_text = value; _text = value;
ui.ParagraphBuilder builder = new ui.ParagraphBuilder(); if (_text != null) {
_text.build(builder); ui.ParagraphBuilder builder = new ui.ParagraphBuilder();
_paragraph = builder.build(_text.style?.paragraphStyle ?? new ui.ParagraphStyle()); _text.build(builder);
_needsLayout = true; _paragraph = builder.build(_text.style?.paragraphStyle ?? new ui.ParagraphStyle());
_needsLayout = true;
} else {
_paragraph = null;
_needsLayout = false;
}
} }
/// The minimum width at which to layout the text. /// The minimum width at which to layout the text.
...@@ -343,12 +348,33 @@ class TextPainter { ...@@ -343,12 +348,33 @@ class TextPainter {
} }
} }
bool _lastLayoutWasToMaxIntrinsicWidth = false;
/// Computes the visual position of the glyphs for painting the text. /// Computes the visual position of the glyphs for painting the text.
void layout() { void layout() {
if (!_needsLayout) if (!_needsLayout)
return; return;
_paragraph.layout(); _paragraph.layout();
_needsLayout = false; _needsLayout = false;
_lastLayoutWasToMaxIntrinsicWidth = false;
}
/// Computes the visual position of the glyphs using the unconstrainted max intrinsic width.
void layoutToMaxIntrinsicWidth() {
if (!_needsLayout && _lastLayoutWasToMaxIntrinsicWidth && width == maxIntrinsicWidth)
return;
_needsLayout = false;
_lastLayoutWasToMaxIntrinsicWidth = true;
_paragraph
..minWidth = 0.0
..maxWidth = double.INFINITY
..layout();
final double newMaxIntrinsicWidth = maxIntrinsicWidth;
_paragraph
..minWidth = newMaxIntrinsicWidth
..maxWidth = newMaxIntrinsicWidth
..layout();
assert(width == maxIntrinsicWidth);
} }
/// Paints the text onto the given canvas at the given offset. /// Paints the text onto the given canvas at the given offset.
......
// Copyright 2016 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 'package:flutter/scheduler.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:test/test.dart';
void main() {
test('Slider can move when tapped', () {
testWidgets((WidgetTester tester) {
Key sliderKey = new UniqueKey();
double value = 0.0;
tester.pumpWidget(
new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return new Material(
child: new Center(
child: new Slider(
key: sliderKey,
value: value,
onChanged: (double newValue) {
setState(() {
value = newValue;
});
}
)
)
);
}
)
);
expect(value, equals(0.0));
tester.tap(tester.findElementByKey(sliderKey));
expect(value, equals(0.5));
tester.pump(); // No animation should start.
expect(Scheduler.instance.transientCallbackCount, equals(0));
});
});
test('Slider take on discrete values', () {
testWidgets((WidgetTester tester) {
Key sliderKey = new UniqueKey();
double value = 0.0;
tester.pumpWidget(
new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return new Material(
child: new Center(
child: new Slider(
key: sliderKey,
min: 0.0,
max: 100.0,
divisions: 10,
value: value,
onChanged: (double newValue) {
setState(() {
value = newValue;
});
}
)
)
);
}
)
);
expect(value, equals(0.0));
tester.tap(tester.findElementByKey(sliderKey));
expect(value, equals(50.0));
tester.scroll(tester.findElementByKey(sliderKey), const Offset(5.0, 0.0));
expect(value, equals(50.0));
tester.scroll(tester.findElementByKey(sliderKey), const Offset(40.0, 0.0));
expect(value, equals(80.0));
tester.pump(); // Starts animation.
expect(Scheduler.instance.transientCallbackCount, greaterThan(0));
tester.pump(const Duration(milliseconds: 200));
tester.pump(const Duration(milliseconds: 200));
tester.pump(const Duration(milliseconds: 200));
tester.pump(const Duration(milliseconds: 200));
// Animation complete.
expect(Scheduler.instance.transientCallbackCount, equals(0));
});
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment