簡體   English   中英

在 Flutter 中保留方向變化時的 TextField 值

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM