如何将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
RichTextto 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
setStatecalls (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
相关产品推荐
相关产品推荐

