點燈坊

失くすものさえない今が強くなるチャンスよ

TextField 實現驗證碼輸入1

Sam Xiao's Avatar 2024-12-02

TextField 可實現驗證碼輸入,包括顯示 數字鍵盤,與一次只能輸入 一個數字。本範例使用 FocusNode

Version

Flutter 3.24.5

Flutter

code01

  • Android 與 iOS 都成功使用 TextField 實現輸入驗證碼

TextField

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class Home extends StatefulWidget {
  const Home({super.key});

  
  State<Home> createState() => _Home();
}

class _Home extends State<Home> {
  final List<String> _codes = List.generate(
    6,
    (_) => '',
  );
  final List<FocusNode> _focusNodes = List.generate(
    6,
    (_) => FocusNode(),
  );

  
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback(
      (_) => _focusNodes[0].requestFocus(),
    );
  }

  
  Widget build(BuildContext context) {
    var appBar = AppBar(
      title: const Text('Verification Code'),
    );

    var codes = List.generate(
      6,
      (index) {
        return Container(
          margin: const EdgeInsets.symmetric(
            horizontal: 4,
          ),
          width: 40,
          child: TextField(
            focusNode: _focusNodes[index],
            keyboardType: TextInputType.number,
            maxLength: 1,
            textAlign: TextAlign.center,
            inputFormatters: [
              FilteringTextInputFormatter.digitsOnly,
            ],
            decoration: const InputDecoration(
              counterText: '',
              border: OutlineInputBorder(),
            ),
            onChanged: (value) {
              if (value.isEmpty) {
                return;
              }

              _codes[index] = value;

              if (index < 5) {
                _focusNodes[index + 1].requestFocus();
              } else {
                _focusNodes[index].unfocus();
              }
            },
            onTapOutside: (event) {
              _focusNodes[index].unfocus();
            },
          ),
        );
      },
    );

    var body = Center(
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: codes,
      ),
    );

    return Scaffold(
      appBar: appBar,
      body: body,
    );
  }

  
  void dispose() {
    for (var focusNode in _focusNodes) {
      focusNode.dispose();
    }
    super.dispose();
  }
}

Line 12

final List<String> _verificationCode = List.generate(
  6,
  (_) => '',
);
  • _codes:以 array 儲存所輸入的驗證碼,初始值皆為 empty string
  • (_) => ''List.generate() 的 callback 的型別原本為 (int index) => {},因為 index 沒用到,所以必須以 _ 代替

Line 16

final List<FocusNode> _focusNodes = List.generate(
  6,
  (_) => FocusNode(),
);
  • _focusNodes:建立每個 TextFieldFocusNode

Line 21


void initState() {
  super.initState();
  WidgetsBinding.instance.addPostFrameCallback(
    (_) => _focusNodes[0].requestFocus(),
  );
}
  • WidgetsBinding.instance.addPostFrameCallback():在 Widget Tree render 完時觸發,對第一個 TextField 進行 focus

Line 35

var codes = List.generate(
  6,
  (index) {
    return Container(
      margin: const EdgeInsets.symmetric(
        horizontal: 4,
      ),
      width: 40,
      child: TextField(
        focusNode: _focusNodes[index],
        keyboardType: TextInputType.number,
        maxLength: 1,
        textAlign: TextAlign.center,
        inputFormatters: [
          FilteringTextInputFormatter.digitsOnly,
        ],
        decoration: const InputDecoration(
          counterText: '',
          border: OutlineInputBorder(),
        ),
        onChanged: (value) {
          if (value.isEmpty) {
            return;
          }

          _codes[index] = value;

          if (index < 5) {
            _focusNodes[index + 1].requestFocus();
          } else {
            _focusNodes[index].unfocus();
          }
        },
        onTapOutside: (event) {
          _focusNodes[index].unfocus();
        },
      ),
    );
  },
);
  • focusNode: _focusNodes[index]:每個 TextField 綁定 FocusNode
  • keyboardType: TextInputType.number:自動顯示 數字鍵盤
  • maxLength: 1:一次只能輸入一個字,適合輸入驗證碼
  • textAlign: TextAlign.center:輸入的驗證碼水平置中
  • inputFormatters:設定 TextField 能輸入的限制
    • FilteringTextInputFormatter.digitsOnly:只能輸入數字
  • decoration
    • counterText: '':當搭配 maxLength 時,counterText 會顯示 當前字數 / 最大字數 的統計,但用於驗證碼輸入時則不需要,因此設定為 empty string
    • border: OutlineInputBorder():驗證輸入為圓框

Line 47

onChanged: (value) {
  if (value.isEmpty) {
    return;
  }

  _codes[index] = value;

  if (index < 5) {
    _focusNodes[index + 1].requestFocus();
  } else {
    _focusNodes[index].unfocus();
  }
},
  • 當輸入時,會將值寫入 _codes array
  • 當不是 最後一個 驗證碼時,會自動跳到下一個驗證碼
  • 當輸入完最一個驗證碼時,自動隱藏 數字鍵盤

會在 輸入後 才取得 index,而不是 輸入前 取得 index

Line 60

onTapOutside: (event) {
  _focusNodes[index].unfocus();
},
  • 離開 TextField 自動隱藏 數字鍵盤

Conclusion

  • FocusNode 實現較為繁瑣,實務上建議使用 FocusTraversalGroup 較精簡