flutter第九篇:列表页与详情页、接入高德地图、使用一些常见组件
列表页与详情页:
列表页:
1、在xxxState类中定义一个空的List,如_list
2、定义一个获取数据的方法,如getData(),在其中调用列表接口获取数据,获取数据后,把列表数据加到_list中,并用setState包裹,以重新渲染页面,把数据展示出来。
3、在initState()方法中调用getData()方法,获取第一页数据。
4、xxxState类的builder()方法中,Scaffold的body属性赋值为一个三目表达式,如果_list为空,则为一个CircularProgressIndicator,可在外面包一个Center,否则为一个ListView,children属性赋值为由_list构造的一个内部元素是ListTile的List。
这样,进入页面时,执行initState()方法,异步拉取数据。执行build()方法渲染页面,由于此时_list还是空,故页面上只有一个加载框。若干时间之后,拉取到数据,填充到_list中,setState()会导致重新执行build()方法重新加载页面,由于此时_iist不为空,故数据会被渲染进Listiew,展示出来。
下拉刷新:
利用RefreshIndicator组件可以实现ListView的下拉刷新。onRefresh属性是一个返回值类型为Future<void>的方法,如果我们获取数据的方法可以不用传参数,则可以直接赋值给onRefresh,否则需要赋值为一个Future<void>方法,可匿名,也可以不匿名,在此方法体中调用我们获取数据的方法,传的参数是第一页。
上拉分页:
见https://www.cnblogs.com/koushr/p/18551920
点击跳转详情页:
利用ListTile的onTap属性。在onTap对应的函数中执行Navigator.pushNamed()。若要带参,则需要利用pushNamed()方法的第三个参数。如Navigator.pushNamed(context, "/detail", arguments: {"id": 1}),或者Navigator.pushNamed(context, "/detail", arguments: <String, String>{"id": 1})。
在详情页获取参数,以在initState()方法中使用:
1、在列表页StatefulWidget子类和xxxState类外面,添加如下固定代码
Map<String, Function> routes = { "/detail": (context, {arguments}) => NewsContentPage(arguments: arguments), }; var onGenerateRoute = (RouteSettings settings) { String? name = settings.name; Function? builder = routes[name]; if (builder != null) { if (settings.arguments != null) { return MaterialPageRoute( builder: (context) => builder(context, arguments: settings.arguments)); } else { return MaterialPageRoute(builder: (context) => builder(context)); } } return null; };
其中,detail是详情页的路由名。NewsContentPage是详情页的StatefulWidget子类。
如果要不带参跳到另一个页面,如pageA,则需要在routes中再加一行配置: "/pageA": (context) => PageA()。
2、在列表页,把StatefulWidget子类中MaterialApp的onGenerateRoute属性赋值为上面声明的onGenerateRoute函数。
3、在详情页的StatefulWidget子类中定义一个Map类型的变量,如Map pm,同时定义一个带参的构造器 const NewsContentPage({super.key, required this.pm});
变量的类型,要和列表页中pushNamed()方法的arguments属性对应上,可能是个Map,也可能是个实体类。我们以Map为例。
这样,如果在列表页中调用Navigator.pushNamed(context, "/detail", arguments: {"id": 1}),那么在详情页的xxxState中的initState()方法和build()方法中,就可以用widget.pm拿到参数了。
如果详情接口返回的是html源码,那么可以用flutter_html解析,要使用3.0.0-beta.2版本,使用2.2.1的话,有时候会编译失败。
如果详情接口返回的是html链接,那么可以用flutter_inappwebview加载html。 示例如下:
import 'dart:convert'; import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; class NewsContentPage extends StatefulWidget { final Map<String, String> pm; const NewsContentPage({super.key, required this.pm}); @override State<NewsContentPage> createState() => _NewsContentPageState(); } class _NewsContentPageState extends State<NewsContentPage> { final List _list = []; bool hasLoading = false; @override void initState() { super.initState(); //String? id = widget.pm["id"]; //if (id != null) { // getData(id); //} } //Future getData(String id) async { // String url = "https://www.phonegap100.com/appapi.php?a=getPortalArticle&aid=$id"; // var rsp = await Dio().get(url); // Map data = json.decode(rsp.data); // var result = data["result"] as List; // setState(() { // _list.addAll(result); // }); //} @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('title'), ), body: Column( children: [ !hasLoading ? const LinearProgressIndicator() : Text(""), Expanded( child: InAppWebView( initialUrlRequest: URLRequest( url: WebUri( "https://www.phonegap100.com/newscontent.php?aid=${widget.pm["id"]}")), onProgressChanged: (controller, progress) { print(progress); if (progress == 100) { setState(() { hasLoading = true; }); } }, )) ], )); } }
其中,WebUri中的链接必须是https协议,http协议的会加载不出来。如果非得用http,那么
在ios侧,需要修改ios/Runner/Info.plist文件,在dict块中添加:
<key>NSAppTransportSecurity</key> <dict> <key>NSExceptionDomains</key> <dict> <key>xxx.com</key> <dict> <key>NSIncludesSubdomains</key> <true/> <key>NSExceptionRequiresForwardSecrecy</key> <false/> <key>NSExceptionAllowsInsecureHTTPLoads</key> <true/> </dict> </dict> </dict>
在android侧,需要
progress是进度,值会从0增加到100。Column里面的InAppWebView外面套了一个Expanded,否则会溢出。
可以通过dart.io中的Platform获取当前设备的一些信息,如当前设备的操作系统是android还是ios,当前设备的处理器是多少核等,可通过对Platform打点看有哪些属性。
要想看设备的制造商(如Xiaomi),设备的品牌(如Redmi),设备的序列号(如),设备的认证型号(如23124RN87C),设备的操作系统的版本(如Android 14),设备操作系统对应的API Level(如34),可以利用device_info_plus插件实现。https://pub.dev/packages/device_info_plus,参考官方文档使用即可。如果我们想在错误上报时带上设备信息,就可以用这个插件。
要想看设备连的是wifi,还是流量,还是没联网,可以用connectivity_plus插件实现。https://pub.dev/packages/connectivity_plus,参考官方文档使用即可。
要想让我们开发的应用可以打开默认浏览器,跳到电话页面打电话(电话号码摁完,不拨出),跳到短信页面发短信(收件号码填写好,内容没填),打开其他App(如淘宝、京东等近乎所有的App,具体App的跳转schema,google即可),可以利用url_launcher插件实现。https://pub.dev/packages/url_launcher,参考官方文档使用即可。
要想播放在线视频(如https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4),可以用chewie插件实现。https://pub.dev/packages/chewie,参考官方文档使用即可。ios可以播放http协议的视频,android不可以,需要修改android/app/src/main/AndroidManifest.xml文件,给application添加属性android:usesCleartextTraffic="true",android:usesCleartextTraffic和android:name同级。
Chewie可以直接作为Center的child,也可以放在AspectRatio中,作为ListView的一个元素。
额外的,如果想调整播放页中视频右上角的更多按钮(三个点竖着排列的那个图标)、全屏按钮、播放进度条,这三个与视频的距离,可以用AspectRatio把Chewie包起来,设置AspectRatio的aspectRatio即可,如设置为1/1,3/4等。默认情况下,点击右上角的更多按钮,弹出的底部弹框中的文案是英文,如果想改成中文,或者想自定义弹框中各菜单点击时的行为,需要使用ChewieController的optionsBuilder属性,设置为如下示例:
optionsBuilder: (context, defaultOptions) async { await showModalBottomSheet( context: context, builder: (context) { return ListView(children: [ ListTile( title: const Text("播放速度"), onTap: () { defaultOptions[0].onTap!(); }, ), ListTile( title: const Text("取消"), onTap: () { Navigator.pop(context); }, ) ]); }); },
需要注意的是,ChewieController的optionsBuilder属性的优先级比其additionalOptions属性的优先级要高。
想要拍照、录视频、从相册选取图片/视频,可以利用image_picker插件实现。https://pub.dev/packages/image_picker,参考官方文档使用即可。
android开箱即用,ios需要额外配置ios/Runner/Info.plist,在dict块中添加:
<key>NSPhotoLibraryUsageDescription</key> <string>应用需要访问相册读取文件</string> <key>NSCameraUsageDescription</key> <string>应用需要访问相机拍照</string> <key>NSMicrophoneUsageDescription</key> <string>应用需要访问麦克风录制视频</string>
想要在一个页面设置数据,另一个页面读取数据,可以利用shared_preferences插件实现。https://pub.dev/packages/shared_preferences,参考官方文档使用即可。
想要扫描二维码、条形码,可以利用barcode_scan2插件实现。https://pub.dev/packages/barcode_scan2,参考官方文档使用即可。只是不需要再按文档配置两个build.gradle文件了。
先要集成定位功能,可以利用高德地图的amap_flutter_location插件实现,https://pub.dev/packages/amap_flutter_location。参考高德的文档https://developer.amap.com/api/flutter/gettingstarted。
android侧,引入插件后,还需要:
1、在高德平台创建android apiKey
2、配置android/app/build.gradle文件
在android块中添加
signingConfigs { release {//发布版本的签名配置 storeFile file('flutter_learn.jks') keyAlias "flutter_learn_key" storePassword "123456" keyPassword "123456" } debug {//调试版本的签名配置 storeFile file('flutter_learn.jks') keyAlias "flutter_learn_key" storePassword "123456" keyPassword "123456" } } buildTypes { release { signingConfig = signingConfigs.release } debug { signingConfig = signingConfigs.debug } }
signingConfigs、buildTypes与defaultConfig同级。
在文件末尾添加
dependencies { implementation('com.amap.api:location:6.4.7') }
dependencies与android同级。com.amap.api:location最新版本可在https://mvnrepository.com/artifact/com.amap.api/location查看。
3、配置android/app/src/main/AndroidManifest.xml文件
在application块外,比如在application块上面添加:
<!--访问网络--> <uses-permission android:name="android.permission.INTERNET" /> <!--粗略定位--> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <!--精确定位--> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <!--申请调用 A-GPS 模块--> <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" /> <!--用于获取运营商信息,用于支持提供运营商信息相关的接口--> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <!--用于访问 wifi 网络信息,wifi 信息会用于进行网络定位--> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <!--用于获取 wifi 的获取权限,wifi 信息会用来进行网络定位--> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <!--用于读取手机当前的状态--> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <!--用于写入缓存数据到扩展存储卡--> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
在application块里面,比如在activity块上面添加:
<service android:name="com.amap.api.location.APSService" />
4、配置android/build.gradle文件:
在allprojects块内部添加:
subprojects { afterEvaluate { project -> if (project.hasProperty('android')) { project.android { if (namespace == null) { namespace project.group } } } } }
subprojects与repositories同级,在引入amap_flutter_location插件时必须添加,否则会报
Namespace not specified. Specify a namespace in the module's build file: C:\Users\xndm\AppData\Local\Pub\Cache\hosted\pub.flutter-io.cn\amap_flutter_location-3.0.0\android\build.gradle. See https://d.android.com/r/tools/upgrade-assistant/set-namespace for information about setting the namespace.
If you've specified the package attribute in the source AndroidManifest.xml, you can use the AGP Upgrade Assistant to migrate to the namespace value in the build file. Refer to https://d.android.com/r/tools/upgrade-assistant/agp-upgrade-assistant for general information about using the AGP Upgrade Assistant.
5、修改amap_flutter_location-3.0.0\android\src\main\AndroidManifest.xml文件,去掉package配置,否则会报
Incorrect package="com.amap.flutter.location" found in source AndroidManifest.xml: C:\Users\xndm\AppData\Local\Pub\Cache\hosted\pub.flutter-io.cn\amap_flutter_location-3.0.0\android\src\main\AndroidManifest.xml.
Setting the namespace via the package attribute in the source AndroidManifest.xml is no longer supported.
Recommendation: remove package="com.amap.flutter.location" from the source AndroidManifest.xml: C:\Users\xndm\AppData\Local\Pub\Cache\hosted\pub.flutter-io.cn\amap_flutter_location-3.0.0\android\src\main\AndroidManifest.xml.
配置完后,去https://developer.amap.com/api/flutter/download下载示例代码,参考开发即可。
ios侧,引入插件后,还需要配置:
1、在高德平台创建ios apiKey
2、在ios/Runner/Info.plist文件的dict块中添加
<key>UIBackgroundModes</key> <array> <string>location</string> </array> <key>NSLocationWhenInUseUsageDescription</key> <string>应用需要访问定位</string> <key>NSLocationAlwaysAndWhenInUseUsageDescription</key> <string>应用需要访问定位</string> <key>allowsBackgroundLocationUpdates</key> <string>应用需要访问后台定位</string>
3、参考http://bbs.itying.com/topic/631987c16275cc0bec05aaa1,修改ios/Podfile文件,以解决应用第一次打开时不弹定位请求窗的问题。
把最下面的
post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end end
替换为
post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| flutter_additional_ios_build_settings(target) # You can enable the permissions needed here. For example to enable camera # permission, just remove the `#` character in front so it looks like this: # # ## dart: PermissionGroup.camera # 'PERMISSION_CAMERA=1' # # Preprocessor definitions can be found in: https://github.com/Baseflow/flutter-permission-handler/blob/master/permission_handler/ios/Classes/PermissionHandlerEnums.h config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ '$(inherited)', ## dart: PermissionGroup.calendar # 'PERMISSION_EVENTS=1', ## dart: PermissionGroup.reminders # 'PERMISSION_REMINDERS=1', ## dart: PermissionGroup.contacts # 'PERMISSION_CONTACTS=1', ## dart: PermissionGroup.camera 'PERMISSION_CAMERA=1', ## dart: PermissionGroup.microphone # 'PERMISSION_MICROPHONE=1', ## dart: PermissionGroup.speech # 'PERMISSION_SPEECH_RECOGNIZER=1', ## dart: PermissionGroup.photos 'PERMISSION_PHOTOS=1', ## dart: [PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse] 'PERMISSION_LOCATION=1', ## dart: PermissionGroup.notification 'PERMISSION_NOTIFICATIONS=1', ## dart: PermissionGroup.mediaLibrary # 'PERMISSION_MEDIA_LIBRARY=1', ## dart: PermissionGroup.sensors # 'PERMISSION_SENSORS=1', ## dart: PermissionGroup.bluetooth # 'PERMISSION_BLUETOOTH=1', ## dart: PermissionGroup.appTrackingTransparency # 'PERMISSION_APP_TRACKING_TRANSPARENCY=1', ## dart: PermissionGroup.criticalAlerts # 'PERMISSION_CRITICAL_ALERTS=1' ] end end end
想要检测应用的版本号,可以使用package_info_plus插件实现,https://pub.dev/packages/package_info_plus。
想要下载文件,可以使用dio插件实现。
想要把文件存到本地,可以用path_provider插件实现,https://pub.dev/packages/path_provider。
想要打开文件,可以使用open_file插件实现,https://pub.dev/packages/open_file。
想要检测权限、向用户申请权限,可以使用permission_handler插件实现,https://pub.dev/packages/permission_handler。
利用上面这些插件,可以实现应用自动升级。流程是拿服务端最新的App版本和自己做比较,如果服务端版本比较新,则下载下来,然后安装。
想要实现验证码布局,可以使用pin_code_fields插件,https://pub.dev/packages/pin_code_fields。使用可以见Readme里的The pin code text field widget example部分。PinCodeTextField的pinTheme属性用于设置填入验证码的单元格的样式,PinTheme实例的selectedColor、activeColor、inactiveColor属性用于设置单元格边框的颜色,分别表示单元格被选中时边框的颜色、输入验证码后边框的颜色、还没选中没输入时边框的颜色。selectedFillColor、activeFillColor、inactiveFillColor属性用于设置单元格的背景色,分别表示单元格被选中时的背景色、输入验证码后的背景色、还没选中没输入时的背景色。PinCodeTextField的dialogConfig属性用于设置粘贴验证码时弹出的提示框,DialogConfig实例的dialogTitle用于配置提示标题,dialogContent用于配置提示内容,negativeText用于配置取消按钮的文案,affirmativeText用于配置确定按钮的文案。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效