From 4e83a5ccdb99d143746f075f8bda9a10585a9e3e Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Fri, 22 Jan 2016 13:47:03 -0800 Subject: [PATCH] Add the ability to lose focus Fixes #1308 --- packages/flutter/lib/src/material/input.dart | 1 + .../flutter/lib/src/widgets/editable.dart | 1 + packages/flutter/lib/src/widgets/focus.dart | 27 ++++++--- packages/flutter/test/widget/focus_test.dart | 55 +++++++++++++++++-- 4 files changed, 73 insertions(+), 11 deletions(-) diff --git a/packages/flutter/lib/src/material/input.dart b/packages/flutter/lib/src/material/input.dart index d9e7200f4a2..b55f74646c1 100644 --- a/packages/flutter/lib/src/material/input.dart +++ b/packages/flutter/lib/src/material/input.dart @@ -90,6 +90,7 @@ class InputState extends ScrollableState { } void _handleTextSubmitted() { + Focus.clear(context); if (config.onSubmitted != null) config.onSubmitted(_value); } diff --git a/packages/flutter/lib/src/widgets/editable.dart b/packages/flutter/lib/src/widgets/editable.dart index 043aea224f5..40362b5c889 100644 --- a/packages/flutter/lib/src/widgets/editable.dart +++ b/packages/flutter/lib/src/widgets/editable.dart @@ -136,6 +136,7 @@ class EditableString implements KeyboardClient { } void submit(SubmitAction action) { + composing = const TextRange.empty(); onSubmitted(); } } diff --git a/packages/flutter/lib/src/widgets/focus.dart b/packages/flutter/lib/src/widgets/focus.dart index 2a1e05cb41a..760df06b881 100644 --- a/packages/flutter/lib/src/widgets/focus.dart +++ b/packages/flutter/lib/src/widgets/focus.dart @@ -25,7 +25,7 @@ class _FocusScope extends InheritedWidget { Widget child }) : super(key: key, child: child); - final FocusState focusState; + final _FocusState focusState; final bool scopeFocused; // These are mutable because we implicitly change them when they're null in @@ -144,6 +144,12 @@ class Focus extends StatefulComponent { } } + static void clear(BuildContext context) { + _FocusScope focusScope = context.ancestorWidgetOfExactType(_FocusScope); + if (focusScope != null) + focusScope.focusState._clearFocusedWidget(); + } + /// Focuses a particular focus scope, identified by its GlobalKey. The widget /// must be in the widget tree. /// @@ -157,10 +163,10 @@ class Focus extends StatefulComponent { focusScope.focusState._setFocusedScope(key); } - FocusState createState() => new FocusState(); + _FocusState createState() => new _FocusState(); } -class FocusState extends State { +class _FocusState extends State { GlobalKey _focusedWidget; // when null, the first component to ask if it's focused will get the focus GlobalKey _currentlyRegisteredWidgetRemovalListenerKey; @@ -181,12 +187,19 @@ class FocusState extends State { } } + void _clearFocusedWidget() { + if (_focusedWidget != null) { + _updateWidgetRemovalListener(null); + setState(() { + _focusedWidget = null; + }); + } + } + void _handleWidgetRemoved(GlobalKey key) { + assert(key != null); assert(_focusedWidget == key); - _updateWidgetRemovalListener(null); - setState(() { - _focusedWidget = null; - }); + _clearFocusedWidget(); } void _updateWidgetRemovalListener(GlobalKey key) { diff --git a/packages/flutter/test/widget/focus_test.dart b/packages/flutter/test/widget/focus_test.dart index 8dbe0155be4..d0b31111ed0 100644 --- a/packages/flutter/test/widget/focus_test.dart +++ b/packages/flutter/test/widget/focus_test.dart @@ -7,11 +7,19 @@ import 'package:flutter/widgets.dart'; import 'package:test/test.dart'; class TestFocusable extends StatelessComponent { - TestFocusable(this.no, this.yes, GlobalKey key) : super(key: key); + TestFocusable({ + GlobalKey key, + this.no, + this.yes, + this.autofocus: true + }) : super(key: key); + final String no; final String yes; + final bool autofocus; + Widget build(BuildContext context) { - bool focused = Focus.at(context, autofocus: true); + bool focused = Focus.at(context, autofocus: autofocus); return new GestureDetector( onTap: () { Focus.moveTo(key); }, child: new Text(focused ? yes : no) @@ -29,8 +37,16 @@ void main() { child: new Column( children: [ // reverse these when you fix https://github.com/flutter/engine/issues/1495 - new TestFocusable('b', 'B FOCUSED', keyB), - new TestFocusable('a', 'A FOCUSED', keyA), + new TestFocusable( + key: keyB, + no: 'b', + yes: 'B FOCUSED' + ), + new TestFocusable( + key: keyA, + no: 'a', + yes: 'A FOCUSED' + ), ] ) ) @@ -65,4 +81,35 @@ void main() { expect(tester.findText('B FOCUSED'), isNull); }); }); + + test('Can blur', () { + testWidgets((WidgetTester tester) { + GlobalKey keyA = new GlobalKey(); + tester.pumpWidget( + new Focus( + child: new TestFocusable( + key: keyA, + no: 'a', + yes: 'A FOCUSED', + autofocus: false + ) + ) + ); + + expect(tester.findText('a'), isNotNull); + expect(tester.findText('A FOCUSED'), isNull); + + Focus.moveTo(keyA); + tester.pump(); + + expect(tester.findText('a'), isNull); + expect(tester.findText('A FOCUSED'), isNotNull); + + Focus.clear(keyA.currentContext); + tester.pump(); + + expect(tester.findText('a'), isNotNull); + expect(tester.findText('A FOCUSED'), isNull); + }); + }); }