![](/img/trans.png)
[英]How to implement undo/redo and preserve the value on orientation change in android EditText?
[英]Preserve TextField value on orientation change in Flutter
我在 Flutter 的OrientationBuilder
中有一個TextField
。
現在會發生什么,當我改變我的應用程序的方向時,TextField 的當前值(例如用戶輸入的文本)丟失了,但我想保留 TextField 的值。
我需要重建TextField
,因為橫向模式下的布局不同。
這能做到嗎?
編輯:
這是我迄今為止嘗試過的:
兩次我都使用了以下模型(位於 -> 模型 -> markdownModel.dart):
import 'package:scoped_model/scoped_model.dart';
class Code extends Model{
String _markdown = '# Markdown Preview \n Your Markdown will be rendered here, once you start typing in the editor pane.';
String get markdown => _markdown;
void changeMarkdown(String markdown){
this._markdown = markdown;
notifyListeners();
}
}
現在,在我第一次嘗試時,我執行了以下操作(但是請注意,TextField 的值不會在 OrientationChange 上保留):
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import 'models/markdownModel.dart';
void main() => runApp(HomeScreen(
markdown: Code(),
));
class HomeScreen extends StatelessWidget {
final Code markdown;
const HomeScreen({Key key, this.markdown}) : super(key: key);
@override
Widget build(BuildContext context) {
return ScopedModel<Code>(
model: markdown,
child: MaterialApp(
home: OrientationBuilder(
builder: (context, orientation) {
return orientation == Orientation.portrait
? DefaultTabController(
initialIndex: 1,
length: 2,
child: Scaffold(
appBar: AppBar(
bottom: TabBar(
tabs: <Widget>[
Tab(text: "Editor", /*icon: Icon(Icons.edit),*/),
Tab(text:"Preview", /*icon: Icon(Icons.chrome_reader_mode),*/),
],
),
title: Text("Markdown - Editor"),
centerTitle: true,
),
body: TabBarView(
children: <Widget>[
TextEditorWidget(),
MarkdownPreviewWidget()
],
),
),
)
: Scaffold(
appBar: AppBar(
title: Text("Markdown - Editor"),
centerTitle: true,
),
body: Container(
child: Row(
children: <Widget>[
Expanded(
child: TextEditorWidget(),
),
Expanded(
child: MarkdownPreviewWidget(),
),
],
),
),
);
},
),
),
);
}
}
class TextEditorWidget extends StatefulWidget {
TextEditorWidget({Key key,}) : super(key: key,);
@override
_TextEditorWidgetState createState() => _TextEditorWidgetState();
}
class _TextEditorWidgetState extends State<TextEditorWidget> {
@override
Widget build(BuildContext context) {
return ScopedModelDescendant<Code>(builder: (context, child, model) {
return Container(
padding: EdgeInsets.all(7.0),
child: ConstrainedBox(
constraints: BoxConstraints(
minWidth: 30, maxWidth: 40, minHeight: 50, maxHeight: 1400),
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
reverse: true,
child: TextField(
keyboardType: TextInputType.multiline,
onChanged: (text){
model.changeMarkdown(text);
},
maxLines: null,
decoration:
InputDecoration.collapsed(hintText: "Write Markdown here."),
),
),
),
);
});
}
}
class MarkdownPreviewWidget extends StatefulWidget {
MarkdownPreviewWidget({Key key,}) : super(key: key,);
@override
_MarkdownPreviewWidgetState createState() => _MarkdownPreviewWidgetState();
}
class _MarkdownPreviewWidgetState extends State<MarkdownPreviewWidget> {
@override
Widget build(BuildContext context) {
return ScopedModelDescendant<Code>(builder: (context, child, model) {
return Container(
child: new Markdown(
data: model.markdown,
),
);
});
}
}
然后我在我的OrientationBuilder
的構建方法之外使用了一個TextEditingController
,正如 George 所指出的。 這有效(在我的情況下有一些額外的擺弄),但是應用程序現在非常慢:
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import 'models/markdownModel.dart';
void main() => runApp(HomeScreen(
markdown: Code(),
));
class HomeScreen extends StatelessWidget {
final Code markdown;
const HomeScreen({Key key, this.markdown}) : super(key: key);
@override
Widget build(BuildContext context) {
return ScopedModel<Code>(
model: markdown,
child: MaterialApp(
home: MainView(),
),
);
}
}
class MainView extends StatefulWidget {
MainView({Key key,}) : super(key: key,);
@override
_MainViewState createState() => _MainViewState();
}
class _MainViewState extends State<MainView> {
final _textController = TextEditingController();
@override
void initState() {
super.initState();
}
@override
void dispose() {
// Clean up the controller when the Widget is removed from the Widget tree
// This also removes the _printLatestValue listener
_textController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return OrientationBuilder(
builder: (context, orientation) {
return orientation == Orientation.portrait
? DefaultTabController(
initialIndex: 1,
length: 2,
child: Scaffold(
appBar: AppBar(
bottom: TabBar(
tabs: <Widget>[
Tab(text: "Editor", /*icon: Icon(Icons.edit),*/),
Tab(text:"Preview", /*icon: Icon(Icons.chrome_reader_mode),*/),
],
),
title: Text("Markdown - Editor"),
centerTitle: true,
),
body: TabBarView(
children: <Widget>[
TextEditorWidget(textController: _textController,),
MarkdownPreviewWidget()
],
),
),
)
: Scaffold(
appBar: AppBar(
title: Text("Markdown - Editor"),
centerTitle: true,
),
body: Container(
child: Row(
children: <Widget>[
Expanded(
child: TextEditorWidget(textController: _textController,),
),
Expanded(
child: MarkdownPreviewWidget(),
),
],
),
),
);
});
}
}
class TextEditorWidget extends StatefulWidget {
final textController;
TextEditorWidget({Key key, @required this.textController }) : super(key: key,);
@override
_TextEditorWidgetState createState() => _TextEditorWidgetState();
}
class _TextEditorWidgetState extends State<TextEditorWidget> {
@override
Widget build(BuildContext context) {
return ScopedModelDescendant<Code>(builder: (context, child, model) {
widget.textController.addListener(
model.changeMarkdown(widget.textController.text)
);
return Container(
padding: EdgeInsets.all(7.0),
child: ConstrainedBox(
constraints: BoxConstraints(
minWidth: 30, maxWidth: 40, minHeight: 50, maxHeight: 1400),
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
reverse: true,
child: TextField(
keyboardType: TextInputType.multiline,
controller: widget.textController,
maxLines: null,
decoration:
InputDecoration.collapsed(hintText: "Write Markdown here."),
),
),
),
);
});
}
}
class MarkdownPreviewWidget extends StatefulWidget {
MarkdownPreviewWidget({Key key,}) : super(key: key,);
@override
_MarkdownPreviewWidgetState createState() => _MarkdownPreviewWidgetState();
}
class _MarkdownPreviewWidgetState extends State<MarkdownPreviewWidget> {
@override
Widget build(BuildContext context) {
return ScopedModelDescendant<Code>(builder: (context, child, model) {
return Container(
child: new Markdown(
data: model.markdown,
),
);
});
}
}
編輯2:
我認為上面的例子很慢,因為反復拋出以下異常:
I/flutter ( 5375): ══╡ EXCEPTION CAUGHT BY FOUNDATION LIBRARY
╞════════════════════════════════════════════════════════
I/flutter ( 5375): The following NoSuchMethodError was thrown while dispatching notifications for
I/flutter ( 5375): TextEditingController:
I/flutter ( 5375): The method 'call' was called on null.
I/flutter ( 5375): Receiver: null
I/flutter ( 5375): Tried calling: call()
I/flutter ( 5375):
I/flutter ( 5375): When the exception was thrown, this was the stack:
I/flutter ( 5375): #0 Object.noSuchMethod (dart:core-patch/object_patch.dart:50:5)
I/flutter ( 5375): #1 ChangeNotifier.notifyListeners
package:flutter/…/foundation/change_notifier.dart:206
I/flutter ( 5375): #2 ValueNotifier.value=
package:flutter/…/foundation/change_notifier.dart:270
I/flutter ( 5375): #3 TextEditingController.selection=
package:flutter/…/widgets/editable_text.dart:166
I/flutter ( 5375): #4 EditableTextState._handleSelectionChanged
package:flutter/…/widgets/editable_text.dart:1118
I/flutter ( 5375): #5 RenderEditable.selectPositionAt
package:flutter/…/rendering/editable.dart:1403
I/flutter ( 5375): #6 RenderEditable.selectPosition
package:flutter/…/rendering/editable.dart:1375
I/flutter ( 5375): #7 _TextFieldState._handleSingleTapUp
package:flutter/…/material/text_field.dart:686
I/flutter ( 5375): #8 _TextSelectionGestureDetectorState._handleTapUp
package:flutter/…/widgets/text_selection.dart:806
I/flutter ( 5375): #9 TapGestureRecognizer._checkUp.<anonymous closure>
package:flutter/…/gestures/tap.dart:238
I/flutter ( 5375): #10 GestureRecognizer.invokeCallback
package:flutter/…/gestures/recognizer.dart:166
I/flutter ( 5375): #11 TapGestureRecognizer._checkUp
package:flutter/…/gestures/tap.dart:238
I/flutter ( 5375): #12 TapGestureRecognizer.acceptGesture
package:flutter/…/gestures/tap.dart:211
I/flutter ( 5375): #13 GestureArenaManager.sweep
package:flutter/…/gestures/arena.dart:156
I/flutter ( 5375): #14 _WidgetsFlutterBinding&BindingBase&GestureBinding.handleEvent
package:flutter/…/gestures/binding.dart:225
I/flutter ( 5375): #15 _WidgetsFlutterBinding&BindingBase&GestureBinding.dispatchEvent
package:flutter/…/gestures/binding.dart:199
I/flutter ( 5375): #16 _WidgetsFlutterBinding&BindingBase&GestureBinding._handlePointerEvent
package:flutter/…/gestures/binding.dart:156
I/flutter ( 5375): #17 _WidgetsFlutterBinding&BindingBase&GestureBinding._flushPointerEventQueue
package:flutter/…/gestures/binding.dart:102
I/flutter ( 5375): #18 _WidgetsFlutterBinding&BindingBase&GestureBinding._handlePointerDataPacket
package:flutter/…/gestures/binding.dart:86
I/flutter ( 5375): #22 _invoke1 (dart:ui/hooks.dart:233:10)
I/flutter ( 5375): #23 _dispatchPointerDataPacket (dart:ui/hooks.dart:154:5)
I/flutter ( 5375): (elided 3 frames from package dart:async)
I/flutter ( 5375):
I/flutter ( 5375): The TextEditingController sending notification was:
I/flutter ( 5375): TextEditingController#05f71(TextEditingValue(text: ┤├, selection: TextSelection(baseOffset: 0,
I/flutter ( 5375): extentOffset: 0, affinity: TextAffinity.downstream, isDirectional: false), composing:
I/flutter ( 5375): TextRange(start: -1, end: -1)))
I/flutter ( 5375): ════════════════════════════════════════════════════════════════════════════════════════════════════
I/flutter ( 5375): Another exception was thrown: NoSuchMethodError: The method 'call' was called on null.
I/chatty ( 5375): uid=10098(com.example.markdown_editor) Thread-2 identical 42 lines
I/flutter ( 5375): Another exception was thrown: NoSuchMethodError: The method 'call' was called on null.
I/flutter ( 5375): Another exception was thrown: NoSuchMethodError: The method 'call' was called on null.
I/chatty ( 5375): uid=10098(com.example.markdown_editor) Thread-2 identical 7 lines
順便提一句。 我正在嘗試構建一個帶有實時預覽的簡單降價編輯器...
抱歉,@George 方法是正確的,但簡單的方法是通過在小部件中使用鍵來實現這一點,只需在TextField
小部件中添加關鍵字段並添加final _inputKey = GlobalKey(debugLabel: 'inputText');
在 State Class 中,它將在方向更改時保留文本字段值
為什么會發生這種情況: OrientationBuilder
在方向更改時重建自身,可能會重置其中所有小部件的狀態。
解決方案:將TextEditingController
分配給您的TextField
,控制器在OrientationBuilder
上下文之外聲明。 這將防止文本字段丟失其值。
例如
final _controller = TextEditingController();
Widget build(BuildContext context) {
// ...
TextField(
controller: _controller,
)
// ...
}
編輯感謝包含的代碼。
首先,改變這個:
class _TextEditorWidgetState extends State<TextEditorWidget> {
@override
Widget build(BuildContext context) {
return ScopedModelDescendant<Code>(builder: (context, child, model) {
widget.textController.addListener(
model.changeMarkdown(widget.textController.text)
);
// ...
對此:
class _TextEditorWidgetState extends State<TextEditorWidget> {
@override
void initState() {
super.initState();
widget.textController.addListener(controllerListener);
}
@override
void dispose() {
widget.textController.removeListener(controllerListener);
super.dispose();
}
void controllerListener() {
// I'm not entirely sure what `changeMarkdown` method does exactly in your code.
// If this does not work - ask a separate question about scoped models.
ScopedModel.of<Code>(context).changeMarkdown(widget.textController.text);
}
@override
Widget build(BuildContext context) {
return ScopedModelDescendant<Code>(builder: (context, child, model) {
// ...
}
此外,還有一件小事 - 在Row
考慮用Flexible
替換Expanded
。
希望這可以幫助。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.