skip to Main Content

Flutter (Channel stable, 3.13.9, on Microsoft Windows [版本 10.0.22621.4], locale zh-CN)

HUAWEI Mate 30 Pro 5G , HarmonyOS 4.0

The page has two TextFields, one for ordinary text and one for password. When the cursor jumps from the ordinary TextField to the password TextField, the password TextField will lose focus and the keyboard will switch to the secure keyboard. You need to click the password TextField again to gain focus.

I tried the following method but it still doesn’t work

 final _accountFocus = FocusNode();
 final _passwordFocus = FocusNode();

Column(
      children: [
        Listener(
          onPointerDown: (e) => FocusScope.of(context).requestFocus(_accountFocus),
          child: TextField(
            focusNode: _accountFocus
          ),
        ),
        Listener(
          onPointerDown: (e) => FocusScope.of(context).requestFocus(_passwordFocus),
          child: TextField(
            focusNode: _passwordFocus,
            obscureText: true,
          ),
       ),
    ],
)

2

Answers


  1. Chosen as BEST ANSWER

    I solved it indirectly using the following method.

    By customizing TextInputFormatter, replace the input content with * and save the original content.

    1. Customize a ObscureTextEditingController

      class ObscureTextEditingController {
        final TextEditingController controller;
        final bool compatibleModel;
        final List<String> values;
      
        ObscureTextEditingController({
          this.compatibleModel = false,
        })  : values = <String>[],
              controller = TextEditingController();
      
        String get text {
          return compatibleModel ? values.join() : controller.text;
        }
      
        bool get isCompatibleModel => compatibleModel == true;
      
        set text(String newText) {
          controller.text = newText;
        }
      
        TextSelection get selection => controller.selection;
      
        set selection(TextSelection newSelection) {
          controller.selection = newSelection;
        }
      
        addListener(VoidCallback listener) {
          controller.addListener(listener);
        }
      
        dispose() {
          controller.dispose();
        }
      
        clear() {
          values.clear();
          controller.clear();
        }
      
        recoverText() {
          final cursor = controller.selection.base.offset;
          controller.text = values.join();
          controller.selection = TextSelection.collapsed(offset: cursor);
        }
      
        obscureText() {
          if (values.isNotEmpty) {
            final cursor = controller.selection.base.offset;
            StringBuffer buffer = StringBuffer();
            for (int i = 0; i < values.length; i++) {
              buffer.write("*");
            }
            controller.text = buffer.toString();
            controller.selection = TextSelection.collapsed(offset: cursor);
          }
        }
      }
      
      
    2. Customize a ObscureTextInputFormatter

      class ObscureTextInputFormatter extends TextInputFormatter {
        final List<String> values;
        final bool obscureText;
      
        ObscureTextInputFormatter(this.values, {required this.obscureText});
      
        @override
        TextEditingValue formatEditUpdate(
          TextEditingValue oldValue,
          TextEditingValue newValue,
        ) {
          //----------oldValue:---newValue:1-
          //----------oldValue:*---newValue:*2-
          //----------oldValue:**---newValue:**3-
          //----------oldValue:***---newValue:***4-
          final newStart = newValue.selection.start;
          final oldStart = oldValue.selection.start;
          final newEnd = newValue.selection.end;
          final oldEnd = oldValue.selection.end;
          print('----------oldValue:${oldValue.text}---newValue:${newValue.text}');
          print('new start:$newStart  end:$newEnd');
          print('old start:$oldStart  end:$oldEnd');
          if (newStart == newEnd && oldStart == oldEnd && newStart == oldStart) {
            return newValue;
          }
          if (newStart < oldStart) {
            // 删除
            values.removeAt(newStart);
          } else {
            // 新增
            print('新增开始位置:$oldStart  ${newValue.text.length}');
            final char = newValue.text.substring(oldStart, oldStart + 1);
            if (newStart == newValue.text.length) {
              print('末尾追加:$char');
              values.add(char);
            } else {
              values.insert(oldStart, char);
              print('插入:$char');
            }
          }
          // // 将输入框中每个字符替换为指定字符'*'
          String newText = newValue.text.replaceAll(RegExp(r'.'), '*');
          print('obscureText: $obscureText  实际值:$values');
      
          return TextEditingValue(
            text: obscureText ? newText : values.join(""),
            selection: newValue.selection,
            // selection: TextSelection.collapsed(offset: newText.length),
          );
        }
      }
      
      
    3. How to use

          final controller = ObscureTextEditingController(
             compatibleModel: true,
          );
      
          TextField(
              controller: controller.controller,
              // obscureText: _obscureText,
              inputFormatters: [
                ObscureTextInputFormatter(
                  controller.values,
                  obscureText: _obscureText,
                )
              ],
            )
      
    4. text/obscure Text

      void toggleEyes() {
          setState(() {
            _obscureText = !_obscureText;
            //  解决华为在切换密码输入框弹窗安全键盘丢失焦点bug
            if (controller.isCompatibleModel) {
              if (!_obscureText) {
                controller.recoverText();
              } else {
                controller.obscureText();
              }
            }
          });
        }
      

  2. Did you define these variables under the build() method?

    final _accountFocus = FocusNode();
    final _passwordFocus = FocusNode();
    

    you should define these variables outside of build() method like this:

    class _MyHomePageState extends State<MyHomePage> {
      final _accountFocus = FocusNode();
      final _passwordFocus = FocusNode();
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Center(
            child: Column(
              children: [
                Listener(
                  onPointerDown: (e) =>
                      FocusScope.of(context).requestFocus(_accountFocus),
                  child: TextField(focusNode: _accountFocus),
                ),
                Listener(
                  onPointerDown: (e) =>
                      FocusScope.of(context).requestFocus(_passwordFocus),
                  child: TextField(
                    focusNode: _passwordFocus,
                    obscureText: true,
                  ),
                ),
              ],
            ),
          ),
        );
      }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search