Commit bf289a2a authored by Matt Perry's avatar Matt Perry Committed by GitHub

Animate size of label text over text fields. (#7241)

* Animate size of label text over text fields.

Also fade in the hintText so it doesn't pop in immediately as the label
text slides up.

Fixes https://github.com/flutter/flutter/issues/7217

* rm annotations

* test

* rename
parent 37593328
......@@ -275,21 +275,31 @@ class _InputContainerState extends State<InputContainer> {
top: top,
duration: _kTransitionDuration,
curve: _kTransitionCurve,
child: new Text(config.labelText, style: labelStyle),
child: new _AnimatedLabel(
text: config.labelText,
style: labelStyle,
duration: _kTransitionDuration,
curve: _kTransitionCurve,
)
),
);
topPadding += topPaddingIncrement;
}
if (config.hintText != null && config.isEmpty && !hasInlineLabel) {
if (config.hintText != null) {
TextStyle hintStyle = textStyle.copyWith(color: themeData.hintColor);
stackChildren.add(
new Positioned(
left: 0.0,
top: topPadding + textStyle.fontSize - hintStyle.fontSize,
child: new IgnorePointer(
child: new Text(config.hintText, style: hintStyle),
child: new AnimatedOpacity(
opacity: (config.isEmpty && !hasInlineLabel) ? 1.0 : 0.0,
duration: _kTransitionDuration,
curve: _kTransitionCurve,
child: new IgnorePointer(
child: new Text(config.hintText, style: hintStyle),
),
),
),
);
......@@ -643,3 +653,59 @@ class InputFormField extends FormField<InputValue> {
},
);
}
// Helper widget to smoothly animate the labelText of an Input, as it
// transitions between inline and caption.
class _AnimatedLabel extends ImplicitlyAnimatedWidget {
_AnimatedLabel({
Key key,
this.text,
this.style,
Curve curve: Curves.linear,
Duration duration,
}) : super(key: key, curve: curve, duration: duration) {
assert(style != null);
}
final String text;
final TextStyle style;
@override
_AnimatedLabelState createState() => new _AnimatedLabelState();
@override
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
'$style'.split('\n').forEach(description.add);
}
}
class _AnimatedLabelState extends AnimatedWidgetBaseState<_AnimatedLabel> {
TextStyleTween _style;
@override
void forEachTween(TweenVisitor<dynamic> visitor) {
_style = visitor(_style, config.style, (dynamic value) => new TextStyleTween(begin: value));
}
@override
Widget build(BuildContext context) {
TextStyle style = _style.evaluate(animation);
double scale = 1.0;
if (style.fontSize != config.style.fontSize) {
// While the fontSize is transitioning, use a scaled Transform as a
// fraction of the original fontSize. That way we get a smooth scaling
// effect with no snapping between discrete font sizes.
scale = style.fontSize / config.style.fontSize;
style = style.copyWith(fontSize: config.style.fontSize);
}
return new Transform(
transform: new Matrix4.identity()..scale(scale),
child: new Text(
config.text,
style: style,
)
);
}
}
......@@ -30,6 +30,16 @@ class MockClipboard {
}
}
Widget overlay(Widget child) {
return new Overlay(
initialEntries: <OverlayEntry>[
new OverlayEntry(
builder: (BuildContext context) => child
)
]
);
}
void main() {
MockTextInput mockTextInput = new MockTextInput()..register();
MockClipboard mockClipboard = new MockClipboard();
......@@ -755,4 +765,49 @@ void main() {
checkText('Hello World');
});
testWidgets('Input label text animates', (WidgetTester tester) async {
GlobalKey inputKey = new GlobalKey();
GlobalKey focusKey = new GlobalKey();
Widget innerBuilder() {
return new Center(
child: new Material(
child: new Focus(
key: focusKey,
child: new Column(
children: <Widget>[
new Input(
labelText: 'First'
),
new Input(
key: inputKey,
labelText: 'Second'
),
]
)
)
)
);
}
Widget builder() => overlay(innerBuilder());
await tester.pumpWidget(builder());
Point pos = tester.getTopLeft(find.text('Second'));
// Focus the Input. The label should start animating upwards.
await tester.tap(find.byKey(inputKey));
await tester.pump();
await tester.pump(const Duration(milliseconds: 50));
Point newPos = tester.getTopLeft(find.text('Second'));
expect(newPos.y, lessThan(pos.y));
// Label should still be sliding upward.
await tester.pump(const Duration(milliseconds: 50));
pos = newPos;
newPos = tester.getTopLeft(find.text('Second'));
expect(newPos.y, lessThan(pos.y));
});
}
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