iT邦幫忙

2025 iThome 鐵人賽

DAY 11
0
Mobile Development

《30 天 Flutter:跨平台 AI 行程規劃 App》系列 第 11

Day 11 - 從填空題到選擇題:Flutter 輸入元件技術拆解

  • 分享至 

  • xImage
  •  

在 Flutter 開發中,表單輸入幾乎是每個 App 都會遇到的需求,但不同的輸入欄位如果用同一種元件,很容易造成操作不便或樣式混亂。今天目標是實作「新增行程頁」,這個頁面需要填寫多種資訊:單行文字、多行文字、下拉選單和日期選擇,下面將說明如何選擇合適的元件、統一樣式與驗證邏輯,同時分享一些封裝小技巧,讓程式碼更容易維護,也更方便重複使用~


專案與元件架構

所有元件都集中在同一個資料夾,方便管理與維護:

lib/
├─ components/
│  └─ text_fields/
│     ├─ custom_text_field.dart       # 單行文字輸入 + 多行輸入 factory
│     ├─ custom_dropdown_field.dart   # 下拉選單
│     └─ custom_datetime_field.dart   # 日期時間選擇
└─ views/
   └─ activity_editor_view.dart        # 行程編輯頁面

說明:

  • custom_text_field.dart:單行文字輸入元件,並透過 factory constructor 提供多行文字輸入(TextArea)
  • custom_dropdown_field.dart:下拉選單元件
  • custom_datetime_field.dart:日期時間選擇元件

單行文字輸入框:CustomTextField

單行文字欄位常用於輸入行程名稱或地點。相比原生 TextFieldTextFormField 支援 Form 驗證,更適合表單使用。

CustomTextField(
  controller: _titleController,
  labelText: '行程名稱',
  validator: (value) {
    if (value == null || value.isEmpty) return '請輸入行程名稱';
    return null;
  },
)

特性

  • 支援 prefix/suffix Icon
  • 可設定 validator
  • 使用 Theme 統一顏色與間距
  • 集中管理樣式,方便維護

多行文字輸入框:TextArea vs factory constructor

備註或留言欄通常需要多行文字輸入,有兩種設計方式:

  1. 獨立 Widget (CustomTextArea)

    • 優點:邏輯獨立、文件化清楚
    • 缺點:多一個檔案需維護
  2. 使用 factory constructor (CustomTextField.textArea)

    • 優點:集中邏輯、使用方式簡單
    • 缺點:可擴展性有限
factory CustomTextField.textArea({
  required TextEditingController controller,
  String? hintText,
  String? labelText,
  String? Function(String?)? validator,
  bool enabled = true,
  FocusNode? focusNode,
  VoidCallback? onEditingComplete,
  ValueChanged<String>? onChanged,
}) {
  return CustomTextField(
    controller: controller,
    hintText: hintText,
    labelText: labelText,
    validator: validator,
    enabled: enabled,
    focusNode: focusNode,
    onEditingComplete: onEditingComplete,
    onChanged: onChanged,
    maxLines: 4,
    minLines: 4,
    keyboardType: TextInputType.multiline,
    textInputAction: TextInputAction.newline,
  );
}

使用方式:

CustomTextField.textArea(
  controller: _noteController,
  labelText: '備註',
)

建議
若多行輸入只是樣式或行數差異,使用 factory constructor 即可,無需額外 Widget。


下拉選單:CustomDropdownField

當欄位有固定選項時,使用文字輸入容易錯誤,建議改用下拉選單,這樣使用者只需要「點選」,不用再打字,體驗順暢很多。

CustomDropdownField<String>(
  value: selectedOption,
  items: ['選項一', '選項二'],
  onChanged: (val) => setState(() => selectedOption = val!),
)

特性
DropdownButtonFormField 封裝成 CustomDropdownField,並加上:

  • 支援 prefix/suffix Icon
  • 自動管理填充顏色與是否可編輯
  • 點擊選項時取消焦點避免鍵盤彈出
  • 建議封裝驗證與樣式

搭配 Enum 使用

  • 適合 固定集合、語意明確 的選項,例如活動類型
  • 避免 magic string 或數字代碼,降低錯誤
  • 可擴充 icon 或其他屬性
enum ActivityType {
  sightseeing, restaurant, shopping, accommodation, freeTime, transport, other
}

extension ActivityTypeIcon on ActivityType {
  String get label {
    switch (this) {
      case ActivityType.sightseeing: return 'Sightseeing';
      case ActivityType.restaurant: return 'Restaurant';
      case ActivityType.shopping: return 'Shopping';
      case ActivityType.accommodation: return 'Accommodation';
      case ActivityType.freeTime: return 'Free Time';
      case ActivityType.transport: return 'Transport';
      default: return 'Other';
    }
  }
}

使用範例:

CustomDropdownField<ActivityType>(
    hintText: 'Type',
    value: _selectedActivityType,
    items: ActivityType.values,

    // 顯示文字,這裡使用 enum extension 提供的 label
    itemLabelBuilder: (item) => item.label,

    onChanged: (ActivityType? newVal) {
      setState(() { _selectedActivityType = newVal; });
    }
)

日期時間選擇:CustomDateTimeField

手動輸入日期繁瑣且容易錯誤覺,尤其在行動裝置上,輸入日期是很繁瑣的操作,使用系統日期選擇器更直覺,這樣一來,使用者只需要滑動或點選日期,體驗比手動輸入好得多。

CustomDateTimeField(
  controller: _dateController,
  labelText: '行程日期',
  editingEnabled: true,
  onPicked: (picked) => print('使用者選擇: $picked'),
)

特性
TextFormField + showDatePicker 封裝成 CustomDateTimeField,提供:

  • 自動處理 controller、填充顏色與 prefix / suffix Icon
  • 支援 validator 與回調事件
  • 點擊自動呼叫日期選擇器
  • 樣式統一,符合 App Theme

輸入框樣式統一:使用 Theme 管理

若每個輸入欄位都自行設定 InputDecoration,維護成本會很高,容易導致邊框樣式、顏色不一致。建議透過 Theme 統一管理。

class AppInputDecoration {
  static InputDecorationTheme theme(AppColorExtension colors, TextTheme textTheme) {
    return InputDecorationTheme(
      enabledBorder: OutlineInputBorder(
        borderRadius: BorderRadius.circular(8),
        borderSide: BorderSide.none,
      ),
      filled: true,
      fillColor: colors.gray100,
    );
  }
}

透過 Theme 管理,可以一次調整全 App 的輸入框樣式,提高維護性。


實際效果示意

多行文字 下拉選單 時間選擇

AI 觀察簡單筆記:
只要指令夠精準,例如:「幫我評估是否把 components/text_fields 底下檔案的 decoration 拉到 theme 共用」,AI 不但能準確完成,最後還會提醒我是否要跑 flutter analyze。看來它不只是修 bug 的工具,也能協助重構,幫助我提高效率。


上一篇
Day 10 – 畫面刻畫與介面實作:結合 AI 工具的效率觀察
下一篇
Day 12 - 從靜態畫面到功能實現:核心行程 UI 宣告完成
系列文
《30 天 Flutter:跨平台 AI 行程規劃 App》20
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言