Flutter技术问询:点击按钮基于HTTP响应动态构建卡片并固定搜索栏
问题解决:点击按钮触发请求生成卡片,搜索栏固定顶部
需求说明
需要实现:
- 点击搜索按钮后才发起HTTP请求,根据响应动态创建卡片
- 「Search Item」输入框和「Search Button」固定在页面顶部
- 卡片列表区域可独立滚动
之前使用FutureBuilder时,页面首次加载就自动触发了数据请求,不符合需求。
解决方案
核心调整点:
- 用状态变量存储请求Future,初始为
null,仅在点击按钮时才触发请求 - 重构布局结构,将搜索区域与列表区域分离,列表区域使用
Expanded实现滚动,保证顶部元素固定
修改后的完整代码
import 'dart:convert' as convert; import 'package:http/http.dart' as http; import 'package:flutter/material.dart'; class clientRecord { final String englishName; final String chineseName; const clientRecord({ required this.englishName, required this.chineseName, }); factory clientRecord.fromJson(Map<String, dynamic> json) { return clientRecord( englishName: json['Ename'] as String, chineseName: json['Cname'] as String, ); } } class homePage extends StatefulWidget { @override _homePageState createState() => _homePageState(); } class _homePageState extends State<homePage> { final _searchItemController = TextEditingController(); Future<List<clientRecord>>? _fetchFuture; // 存储请求Future的状态变量 List<clientRecord> parseJson(String responseBody) { final parsed = convert.jsonDecode(responseBody).cast<Map<String, dynamic>>(); return parsed .map<clientRecord>((json) => clientRecord.fromJson(json)) .toList(); } Future<List<clientRecord>> fetchData(http.Client client, String searchItem) async { // 建议将搜索参数拼接到请求URL中,示例: // final response = await client.get(Uri.parse('test.php?search=$searchItem')); final response = await client.get(Uri.parse('test.php')); return parseJson(response.body); } @override void dispose() { _searchItemController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Client List')), body: Column( children: [ // 顶部固定的搜索区域 Padding( padding: const EdgeInsets.fromLTRB(0, 30, 0, 20), child: Text( adminPassword(), // 假设该方法已实现 style: TextStyle( fontSize: 30, fontWeight: FontWeight.bold, color: Colors.orange[800]), ), ), Padding( padding: const EdgeInsets.all(12.0), child: TextField( decoration: const InputDecoration( border: OutlineInputBorder(), labelText: 'Search Item'), controller: _searchItemController, ), ), const SizedBox(height: 20), Container( height: 45, width: 250, decoration: BoxDecoration( color: Colors.teal, borderRadius: BorderRadius.circular(16)), child: TextButton( onPressed: () { // 点击按钮时触发请求,更新状态变量 setState(() { _fetchFuture = fetchData(http.Client(), _searchItemController.text); }); }, child: const Text( 'Search', style: TextStyle(color: Colors.white, fontSize: 20), ), ), ), // 可滚动的卡片列表区域 Expanded( child: _fetchFuture == null ? const Center(child: Text('点击搜索按钮获取数据')) : FutureBuilder<List<clientRecord>>( future: _fetchFuture, builder: (context, snapshot) { if (snapshot.hasError) { return const Center( child: Text('请求出错!'), ); } else if (snapshot.hasData) { return buildBody(dl: snapshot.data!); } else { return const Center( child: CircularProgressIndicator(), ); } }, ), ), ], ), ); } } class buildBody extends StatelessWidget { const buildBody({super.key, required this.dl}); final List<clientRecord> dl; @override Widget build(BuildContext context) { // 使用ListView实现高效滚动,替代原嵌套的SingleChildScrollView return ListView.builder( itemCount: dl.length, itemBuilder: (context, index) => dataCard(dl[index], context), ); } } Widget dataCard(clientRecord dataRecord, BuildContext c) { return GestureDetector( onTap: () { // Navigator.push( // c, // MaterialPageRoute(builder: (c) => newdevelopmentdetail(dataRecord.id)), // ); }, child: Card( color: Colors.lightGreen[100], child: SizedBox( height: 100, child: Row( children: [ Padding( padding: const EdgeInsets.all(16.0), child: Text(dataRecord.englishName), ), const SizedBox(width: 16), Text(dataRecord.chineseName), ], ), ), ), ); } // 示例实现adminPassword方法 String adminPassword() { return 'Admin'; }
关键修改说明
- 请求触发逻辑:新增
_fetchFuture状态变量,仅在点击搜索按钮时通过setState赋值,彻底避免页面初始化时自动请求 - 布局结构:将
body改为Column,顶部搜索区域直接放置,列表区域用Expanded包裹,确保顶部元素固定,列表占满剩余空间并可滚动 - 列表组件优化:将
buildBody中的SingleChildScrollView+Column替换为ListView.builder,更高效处理大量数据,同时解决原代码中_scrollController未定义的问题 - 初始状态提示:当
_fetchFuture为null时,显示友好提示文本而非加载指示器
内容的提问来源于stack exchange,提问作者Tony Lam
相关产品推荐
相关产品推荐

