You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将PayPal Downshift式国家汇款自动补全代码转译为Flutter(Dart)?

Great question! Let's build that PayPal Downshift-style country autocomplete in Flutter step by step. I've put together a complete implementation that covers all the core functionality from your reference example, plus some handy optional enhancements.

Core Functionality We'll Implement
  • A text input field that filters countries in real-time as you type
  • A dropdown list showing matching country options
  • Auto-filling the input field and closing the dropdown when an option is selected
  • Closing the dropdown when clicking outside the input field
Step-by-Step Implementation

1. Define a Country Data Model

First, let's create a simple class to hold country data (name and ISO code) for cleaner code:

class Country {
  final String name;
  final String code;

  Country({required this.name, required this.code});
}

// Example country list – replace this with your full country dataset
final List<Country> countries = [
  Country(name: 'United States', code: 'US'),
  Country(name: 'Canada', code: 'CA'),
  Country(name: 'United Kingdom', code: 'GB'),
  Country(name: 'Australia', code: 'AU'),
  Country(name: 'Germany', code: 'DE'),
  Country(name: 'France', code: 'FR'),
];

2. Build the Autocomplete Widget

We'll use a StatefulWidget to manage the input state, filtered country list, and dropdown visibility:

import 'package:flutter/material.dart';

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

  @override
  State<CountryAutocomplete> createState() => _CountryAutocompleteState();
}

class _CountryAutocompleteState extends State<CountryAutocomplete> {
  final TextEditingController _inputController = TextEditingController();
  final FocusNode _inputFocusNode = FocusNode();
  List<Country> _filteredCountries = [];
  bool _isDropdownOpen = false;

  @override
  void initState() {
    super.initState();
    // Initialize with full country list
    _filteredCountries = countries;
    // Listen for text changes to filter countries
    _inputController.addListener(_filterCountries);
    // Listen for focus changes to toggle dropdown visibility
    _inputFocusNode.addListener(() {
      setState(() {
        _isDropdownOpen = _inputFocusNode.hasFocus;
      });
    });
  }

  @override
  void dispose() {
    // Clean up controllers to avoid memory leaks
    _inputController.dispose();
    _inputFocusNode.dispose();
    super.dispose();
  }

  // Filter countries by name or ISO code
  void _filterCountries() {
    final searchQuery = _inputController.text.toLowerCase();
    setState(() {
      _filteredCountries = countries.where((country) {
        return country.name.toLowerCase().contains(searchQuery) ||
            country.code.toLowerCase().contains(searchQuery);
      }).toList();
    });
  }

  // Handle country selection
  void _onCountrySelected(Country selectedCountry) {
    setState(() {
      _inputController.text = selectedCountry.name;
      _isDropdownOpen = false;
      _inputFocusNode.unfocus();
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        // Input Field
        TextField(
          controller: _inputController,
          focusNode: _inputFocusNode,
          decoration: const InputDecoration(
            labelText: 'Select a Country',
            border: OutlineInputBorder(),
            suffixIcon: Icon(Icons.arrow_drop_down),
            contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
          ),
        ),
        // Dropdown List
        if (_isDropdownOpen)
          Container(
            width: double.infinity,
            margin: const EdgeInsets.only(top: 4),
            decoration: BoxDecoration(
              border: Border.all(color: Colors.grey.shade300),
              borderRadius: const BorderRadius.only(
                bottomLeft: Radius.circular(8),
                bottomRight: Radius.circular(8),
              ),
              boxShadow: const [
                BoxShadow(
                  color: Colors.black12,
                  blurRadius: 4,
                  offset: Offset(0, 2),
                ),
              ],
            ),
            child: _filteredCountries.isNotEmpty
                ? ListView.builder(
                    shrinkWrap: true,
                    padding: EdgeInsets.zero,
                    itemCount: _filteredCountries.length,
                    itemBuilder: (context, index) {
                      final country = _filteredCountries[index];
                      return ListTile(
                        leading: Text(country.code, style: const TextStyle(fontWeight: FontWeight.bold)),
                        title: Text(country.name),
                        onTap: () => _onCountrySelected(country),
                        hoverColor: Colors.grey.shade100,
                      );
                    },
                  )
                : const Padding(
                    padding: EdgeInsets.all(16.0),
                    child: Text('No countries match your search'),
                  ),
          ),
      ],
    );
  }
}

3. Use the Widget in Your App

To use the autocomplete, just add it to any screen:

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Country Autocomplete')),
      body: Padding(
        padding: const EdgeInsets.all(24.0),
        child: const CountryAutocomplete(),
      ),
    );
  }
}
Optional Enhancements

Here are some ways to make this even closer to PayPal's Downshift:

  • Highlight Search Matches: Use RichText to highlight the parts of the country name/code that match the search query
  • Keyboard Navigation: Add support for arrow keys to navigate the dropdown list
  • Debounce Filtering: Add a small delay before filtering to avoid excessive setState calls (great for large country lists)
  • Scroll-to-Match: Auto-scroll to the first matching country in long lists
  • Internationalization: Load country names in the user's device language

内容的提问来源于stack exchange,提问作者kevinbrink

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 03:44:01