在 Flutter 中建立文字輸入的格式化器:提升使用者體驗的有效方法


摘要

在 Flutter 開發中,建立有效的文字輸入格式化器不僅能提升使用者體驗,更是提高應用程式效率的重要手段。 歸納要點:

  • IbanFormatter 擴充套件提升輸入效能,確保國際銀行帳號格式的相容性與擴展支援。
  • 利用 TextEditingController 和 SelectionController 精準控制文字編輯器遊標,動態調整偏移量以保持一致的使用者體驗。
  • 正則表達式用於定義靈活的格式化規則,適應各種場景,如電話號碼和郵政編碼的格式化需求。
透過這些技巧與工具,開發者可以創造出更友好的輸入介面,有效簡化使用者操作流程。


你是否曾在 Flutter 中遇到需要一個能自動插入空格的 TextInputFormatter,但又希望文字選擇偏移量自然地考慮到這些空格的情況?如果是的話,這篇文章將對你有所幫助。我們希望以 IBAN 格式化器為例,提供一個可能的解決方案。


我們建立一個文字欄位。此欄位設定的最大長度為27個字元,代表德國IBAN的預設格式(DE12 3456 7890 1234 5678 90)。IBAN本身由22個字元組成,其中包含5個空格。我們提供了一個名為IbanFormatter的自定義擴充套件類,作為輸入格式化器,其具體內容將在下文中詳細解釋。

TextField(   maxLines: 1,   // 22 (IBAN length) + 5 (Space length)   maxLength: 27,   inputFormatters: const [     IbanFormatter(),   ],   decoration: InputDecoration(     labelText: 'IBAN',     border: OutlineInputBorder(       borderRadius: BorderRadius.circular(12.0),     ),   ), )

IbanFormatter 擴充套件:提升效能、確保相容性與擴充支援

IbanFormatter 擴充套件了 TextInputFormatter 類別。在此基礎上,我們定義了一些靜態變數,文字長度設定為 22,空格數量設定為 5,而總的最大長度則是它們的總和。接著,我們重寫了 formatEditUpdate() 函式,該函式接收舊的 TextEditingValue 和新的 TextEditingValue 作為引數。新的值代表使用者輸入變更後的狀態,而舊的值則代表之前的狀態。在 formatEditUpdate() 函式內部,會呼叫 formattingIban() 方法,此方法接收新的值文字作為字串並將其格式化為 IBAN 字串。這個方法如何運作的細節將在下一部分進行描述。

值得注意的是,目前程式碼示例中使用 formatEditUpdate() 函式進行 IBAN 格式化,但這可能導致效能瓶頸,尤其是在使用者快速輸入或文字較長時。我們可以考慮將 formattingIban() 方法的實現改用正規表示式來最佳化。正規表示式能夠更快速地匹配和替換字元,以提升格式化效率。例如,我們可以利用正規表示式來定位每個空格的位置,同時確保使用者輸入的字元符合 IBAN 格式規範。

有關 IBAN 格式的國際標準化工作一直在持續推進,因此我們應當隨時關注最新標準變動,以確保 IbanFormatter 能夠相容最新的 IBAN 格式規範。目前一些國家已經採用了新的 IBAN 格式,IbanFormatter 需要具備識別這些新格式並執行相應格式化功能。也可考慮增加對其他國際銀行賬戶格式支援,例如 SWIFT 程式碼,以滿足更多使用者需求。

針對 IbanFormatter 的相關查詢意圖,一方面是效能最佳化:如何提升 IbanFormatter 在大文字量或快速輸入情境下的格式化效率?另一方面是相容性問題:如何確保 IbanFormatter 能夠與最新發布的 IBAN 格式標準保持相容?再者是擴充套件性需求:如何擴充套件 IbanFormatter 以支援更多國際銀行賬戶格式?

若想更全面了解 IbanFormatter 的內部運作機制,可以參考以下資源:IBAN 格式標準規範,以了解最新 IBAN 標準;正規表示式教程,用於學習使用正規表示式進行字串匹配和替換,以最佳化格式化效率;以及 Flutter 文件,深入了解 Flutter 中 TextEditingValue 和 TextInputFormatter 類別及其應用場景。

class IbanFormatter extends TextInputFormatter {   static const textLength = 22;   static const spaceLength = 5;   static const maxLength = textLength + spaceLength;    const IbanFormatter();    @override   TextEditingValue formatEditUpdate(       TextEditingValue oldValue, TextEditingValue newValue) {     final newFormattedText = formattingIban(newValue.text);     final calculatedTextSelection = calculateSelectionOffset(       oldValue: oldValue,       newValue: newValue,       newText: newFormattedText,       maxFormattedLength: maxLength,     );      return newValue.copyWith(       text: newFormattedText,       selection: calculatedTextSelection,     );   } }

在格式化 IBAN 之後,我們使用 copyWith() 方法來更新 newValue 引數,將文字設定為新格式的文字。我們還使用一個自定義的 calculateSelectionOffset() 函式來更新 selection 引數。這個函式將包含所有必要的邏輯,以便在文字輸入欄位中進行變更後,自動調整偏移量。該函式將在最後一部分中描述。在本節中,我們希望簡要解釋字串是如何被格式化為 IBAN 的。

String formattingIban(String input) {   return input       .replaceAll(' ', '')       .toUpperCase()       .replaceAllMapped(RegExp(r'.{1,4}'), (match) => '${match.group(0)} ')       .trim(); }

精準控制文字編輯器遊標:調整偏移量以確保一致的使用者體驗

我們清除字串中的所有空格,然後將所有字母轉換為大寫,接著使用正規表示式找到四個字元組成的一組並在每組之後插入一個空格。我們會修剪結尾的任何多餘空格。現在我們來到這篇部落格的關鍵部分:如何調整偏移量,以便從使用者的角度來看,空格不會影響游標位置?因此,我們實現了 calculateSelectionOffset() 方法。該函式接受舊的和新的 TextEditingValue、重新格式化的文字(作為字串)以及最大長度(作為整數)作為引數,並返回 null 或一個 TextSelection。在這個函式內部,我們獲取各自值中選擇範圍和基礎偏移量的舊偏移量和新偏移量。

深入探討 calculateSelectionOffset() 方法的重要性與設計考量,可以幫助開發者理解如何精確地管理文字編輯器中的游標位置,而不受格式化過程中引入的新空格影響。結合最新趨勢,我們也可以研究如何將上述方法應用於支援多種語言的文字格式化,以確保不同語言環境下都能保持一致的使用者體驗。在當今跨文化交流日益頻繁的背景下,此類技術無疑具有重要意義。

TextSelection? calculateSelectionOffset({   required TextEditingValue oldValue,   required TextEditingValue newValue,   required String newText,   required int maxFormattedLength, }) {   final oldOffset = oldValue.selection.baseOffset;   final newOffset = newValue.selection.baseOffset;    // Prevent the "range start is out of text of length" error   if (newOffset > newText.length || oldOffset > newText.length) {     return TextSelection.collapsed(       offset: newText.length,     );   }    // If the old offset equals the length of the old text, it shifts the offset to the end of the new text   if (oldValue.text.length == oldOffset) {     return TextSelection.collapsed(       offset: newText.length,     );   }    final newTextUntilOldOffset = newText.substring(0, oldOffset);   final newTextUntilNewOffset = newText.substring(0, newOffset);   final spaceDifference =       countSpaceDif(newTextUntilNewOffset, newTextUntilOldOffset);   // Adjust the offset by increasing it based on the difference in spaces   if (spaceDifference > 0) {     return TextSelection.collapsed(       offset: newOffset + spaceDifference,     );   }    // Reduce the selection offset by one if a digit following a space is removed   if (newOffset != 0 && newValue.text[newOffset - 1] == ' ') {     return TextSelection.collapsed(       offset: newOffset - 1,     );   }    // Return null and use default selection behavior   return null; }

現在讓我們深入探討 calculateSelectionOffset() 的不同情況。我們檢查新的偏移量是否大於新的文字長度,或是舊的偏移量是否大於新的文字長度。如果任一條件成立,我們將偏移量設定為新文字的末尾,以避免出現超出範圍的錯誤。

  // Prevent the "range start is out of text of length" error   if (newOffset > newText.length || oldOffset > newText.length) {     return TextSelection.collapsed(       offset: newText.length,     );   }

接下來,我們檢查舊文字的長度是否與舊偏移量相符,這表示遊標位於欄位的末尾。如果確實如此,我們將偏移量設定為新文字的末尾。這種行為是必要的,例如在複製和貼上完整的 IBAN 時。

  // If the old offset equals the length of the old text, it shifts the offset to the end of the new text   if (oldValue.text.length == oldOffset) {     return TextSelection.collapsed(       offset: newText.length,     );   }

隨後,我們根據在插入數字或複製和貼上多個數字時所增加的空格數量來增加偏移量。為此,我們從新文字中提取出從起始位置到舊偏移量的子字串,以及從舊偏移量到新偏移量的子字串。

final newTextUntilOldOffset = newText.substring(0, oldOffset); final newTextUntilNewOffset = newText.substring(0, newOffset); final spaceDifference =countSpaceDif(newTextUntilNewOffset, newTextUntilOldOffset);    // Adjust the offset by increasing it based on the difference in spaces if (spaceDifference > 0) {   return TextSelection.collapsed(     offset: newOffset + spaceDifference,   ); }

我們利用輔助函式 countSpaceDif() 計算空格數量的差異。如果這個差異大於 0,意味著增加了空格,我們將返回新的偏移量加上這個差異。

int countSpaceDif(String newTextUntilNewOffset, String newTextUntilOldOffset) {   return ' '.allMatches(newTextUntilNewOffset).length -       ' '.allMatches(newTextUntilOldOffset).length; }

如果在空格後移除了一個數字,我們需要透過減去 1 來調整新的偏移量,以便游標回到前一組。因此,我們檢查新文字中位於偏移量 -1 的字元是否為空格,並作為保障,我們還需確保新的偏移量不為 0。

// Reduce the selection offset by one if a digit following a space is removed if (newOffset != 0 && newValue.text[newOffset - 1] == ' ') {   return TextSelection.collapsed(     offset: newOffset - 1,   ); }

如果上述條件都不符合,我們將簡單地返回 null。這確保了偏移量的預設行為被應用。而這就是全部了。透過將 IbanFormatter 作為 inputFormatter 新增到 TextField 中,格式化器會在文字框中的每次變更時自動應用。


為了進一步驗證輸入,我們需要一個驗證器,或者我們可以限制輸入以符合 IBAN 格式的正規表示式——但這是另一篇部落格文章的主題。我們希望這篇文章對您有所幫助。請在下方留言告訴我們您的想法!(作者:Marius Jost)請檢視我們 Flutter 的 GitHub 倉庫中的完整範例:


MD

專家

相關討論

❖ 相關專欄