1202|1202 年了,还不了解 Flutter 解析数据的来康康

2021年了, Flutter 2.0版本已经发布,来康康Flutter 的Model 是怎么解析json 数据的吧!
如果你请求到豆瓣电影的高分电影排行榜json数据格式入下, 让你用flutter做成一个电影排行榜的列表, 你该如何下手呢?

[ { "rating": ["9.7", "50"], "rank": 1, "cover_url": "https://img2.doubanio.com\/view\/photo\/s_ratio_poster\/public\/p480747492.jpg", "is_playable": true, "id": "1292052", "types": ["犯罪", "剧情"], "regions": ["美国"], "title": "肖申克的救赎", "url": "https:\/\/movie.douban.com\/subject\/1292052\/", "release_date": "1994-09-10", "actor_count": 25, "vote_count": 2290289, "score": "9.7", "actors": ["蒂姆·罗宾斯", "摩根·弗里曼", "鲍勃·冈顿", "威廉姆·赛德勒", "克兰西·布朗", "吉尔·贝罗斯", "马克·罗斯顿", "詹姆斯·惠特摩", "杰弗里·德曼", "拉里·布兰登伯格", "尼尔·吉恩托利", "布赖恩·利比", "大卫·普罗瓦尔", "约瑟夫·劳格诺", "祖德·塞克利拉", "保罗·麦克兰尼", "芮妮·布莱恩", "阿方索·弗里曼", "V·J·福斯特", "弗兰克·梅德拉诺", "马克·迈尔斯", "尼尔·萨默斯", "耐德·巴拉米", "布赖恩·戴拉特", "唐·麦克马纳斯"], "is_watched": false }, { "rating": ["9.6", "50"], "rank": 2, "cover_url": "https://img3.doubanio.com\/view\/photo\/s_ratio_poster\/public\/p2561716440.jpg", "is_playable": true, "id": "1291546", "types": ["剧情", "爱情", "同性"], "regions": ["中国大陆", "中国香港"], "title": "霸王别姬", "url": "https:\/\/movie.douban.com\/subject\/1291546\/", "release_date": "1993-07-26", "actor_count": 26, "vote_count": 1698938, "score": "9.6", "actors": ["张国荣", "张丰毅", "巩俐", "葛优", "英达", "蒋雯丽", "吴大维", "吕齐", "雷汉", "尹治", "马明威", "费振翔", "智一桐", "李春", "赵海龙", "李丹", "童弟", "沈慧芬", "黄斐", "徐杰", "黄磊", "冯远征", "杨立新", "方征", "周璞", "隋永清"], "is_watched": false } ]

首先登场的是 json_serializable 真心好用的一个json 解析框架, 项目的pubspec.yaml 中添加 json_serializable:
dev_dependencies: flutter_test: sdk: flutter json_serializable: ^3.5.1

然后, 观察这个josn 的数据结构,发现是数组套字典;
那么model 的数据结构为:
"rating": ["9.6", "50"], "rank": 2, "cover_url": "https://img3.doubanio.com\/view\/photo\/s_ratio_poster\/public\/p2561716440.jpg", "is_playable": true, "id": "1291546", "types": ["剧情", "爱情", "同性"], "regions": ["中国大陆", "中国香港"], "title": "霸王别姬", "url": "https:\/\/movie.douban.com\/subject\/1291546\/", "release_date": "1993-07-26", "actor_count": 26, "vote_count": 1698938, "score": "9.6", "actors": ["张国荣", "张丰毅", "巩俐", "葛优", "英达", "蒋雯丽", "吴大维", "吕齐", "雷汉", "尹治", "马明威", "费振翔", "智一桐", "李春", "赵海龙", "李丹", "童弟", "沈慧芬", "黄斐", "徐杰", "黄磊", "冯远征", "杨立新", "方征", "周璞", "隋永清"], "is_watched": false,

使用这个 json_serializable 自动转model 工具:
https://caijinglong.github.io/json2dart/index_ch.html
然后把这个工具生成的model 代码copy 到项目的douban.dart model 中,
import 'package:json_annotation/json_annotation.dart'; part 'douban.g.dart'; @JsonSerializable() class Douban extends Object {@JsonKey(name: 'rating') List rating; @JsonKey(name: 'rank') int rank; @JsonKey(name: 'cover_url') String coverUrl; @JsonKey(name: 'is_playable') bool isPlayable; @JsonKey(name: 'id') String id; @JsonKey(name: 'types') List types; @JsonKey(name: 'regions') List regions; @JsonKey(name: 'title') String title; @JsonKey(name: 'url') String url; @JsonKey(name: 'release_date') String releaseDate; @JsonKey(name: 'actor_count') int actorCount; @JsonKey(name: 'vote_count') int voteCount; @JsonKey(name: 'score') String score; @JsonKey(name: 'actors') List actors; @JsonKey(name: 'is_watched') bool isWatched; Douban(this.rating, this.rank, this.coverUrl, this.isPlayable, this.id, this.types, this.regions, this.title, this.url, this.releaseDate, this.actorCount, this.voteCount, this.score, this.actors, this.isWatched, ); factory Douban.fromJson(Map srcJson) => _$DoubanFromJson(srcJson); Map toJson() => _$DoubanToJson(this); }

项目的根目录中,执行:
flutter packages pub run build_runner build

如果报错:
flutter packages pub run build_runner clean flutter packages pub run build_runner build --delete-conflicting-outputs

此时,项目中出现 douban.g.dart 的文件, 此时项目报错消失.
// GENERATED CODE - DO NOT MODIFY BY HANDpart of 'douban.dart'; // ************************************************************************** // JsonSerializableGenerator // **************************************************************************Douban _$DoubanFromJson(Map json) { return Douban( (json['rating'] as List)?.map((e) => e as String)?.toList(), json['rank'] as int, json['cover_url'] as String, json['is_playable'] as bool, json['id'] as String, (json['types'] as List)?.map((e) => e as String)?.toList(), (json['regions'] as List)?.map((e) => e as String)?.toList(), json['title'] as String, json['url'] as String, json['release_date'] as String, json['actor_count'] as int, json['vote_count'] as int, json['score'] as String, (json['actors'] as List)?.map((e) => e as String)?.toList(), json['is_watched'] as bool, ); }Map _$DoubanToJson(Douban instance) => { 'rating': instance.rating, 'rank': instance.rank, 'cover_url': instance.coverUrl, 'is_playable': instance.isPlayable, 'id': instance.id, 'types': instance.types, 'regions': instance.regions, 'title': instance.title, 'url': instance.url, 'release_date': instance.releaseDate, 'actor_count': instance.actorCount, 'vote_count': instance.voteCount, 'score': instance.score, 'actors': instance.actors, 'is_watched': instance.isWatched, };

这个文件禁止手写.
OK,准备工作已经搞完了.
创建一个douban_listview.dart 的文件,写入如下代码:
import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_navigation_demo/json_resource/douban_json.dart'; import 'package:flutter_navigation_demo/widgets/desc_widget.dart'; import 'package:flutter_navigation_demo/model/douban.dart'; class DouBanListView extends StatefulWidget { @override State createState() { return _DouBanState(); } }class _DouBanState extends State {List subjects = []; double itemHeight = 180.0; Douban douban; @override void initState() { setState(() { subjects = movieDataList; }); }@override Widget build(BuildContext context) { return Container( child: _getListViewContainer(), ); }Widget _getListViewContainer() { if (subjects.length == null || subjects.length == 0) { // loading return Center(child: Text('null'),); } return ListView.builder( //item 的数量 itemCount: subjects.length, itemBuilder: (BuildContext context, int index) { return GestureDetector(//Flutter 手势处理 child: Container( color: Colors.transparent, child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ // rank _movieRankWidget(index + 1),_getItemContainerView(subjects[index]),//下面的灰色分割线 Container( height: 0.5, margin: EdgeInsets.fromLTRB(5, 0, 5, 5), color: Color.fromARGB(255, 234, 233, 234), ),], ), ), onTap: () { //监听点击事件 print("click item index=$index"); }, ); }); }// 肖申克的救赎(1993) View Widget _getTitleView(subject) { var title = douban.title; var releaseDate =douban.releaseDate; return Container( child: Row( children: [ Icon( Icons.play_circle_outline, color: Colors.redAccent, ), Text( title, style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.black), ), Text('($releaseDate)', style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: Colors.grey)) ], ), ); }// 电影图片 介绍 Widget _getItemContainerView(Map subject) { douban = Douban.fromJson(subject); return Container( width: double.infinity, padding: EdgeInsets.all(5.0), child: Row( children: [ // 图片加载 _getNetworkImage(douban.coverUrl), Expanded( child: // 电影信息 _getMovieInfoView(), flex: 1, ) ], ), ); }// 圆角图片 _getNetworkImage(String imageUrl) { // CachedNetworkImage(); return Container( decoration: BoxDecoration( image: DecorationImage(image: NetworkImage(imageUrl), fit: BoxFit.cover), borderRadius: BorderRadius.all(Radius.circular(5.0))), margin: EdgeInsets.only(left: 8, top: 2, right: 4, bottom: 4), height: itemHeight, width: 100.0, ); }// 电影标题,星标评分,演员简介 Widget _getMovieInfoView() { return Container( height: itemHeight, alignment: Alignment.topLeft, child: Column( children: [ // 电影名字 年份 _getTitleView(douban), // 星星评分 RatingBar(double.parse(douban.score)), // 演员名字介绍 DescWidget(douban), ], ), ); }// 电影排行榜(列表排行) Widget _movieRankWidget(int rank) { return Container( child: Text( 'No.$rank', style: TextStyle(color: Color.fromARGB(255, 133, 66, 0)), ), // 装饰(金色) decoration: BoxDecoration( color: Color.fromARGB(255, 255, 201, 129), borderRadius: BorderRadius.all(Radius.circular(5.0))), padding: EdgeInsets.fromLTRB(8, 4, 8, 4), margin: EdgeInsets.only(left: 12, top: 10), ); } }

封装的组件类:
desc_widget.dart
import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_navigation_demo/model/douban.dart'; // 类别、演员、介绍 class DescWidget extends StatelessWidget {Douban douban; DescWidget(this.douban); @override Widget build(BuildContext context) {List actors = douban.actors; var stringBuffer = StringBuffer(); // 种类 List types = douban.types; if(types.length != 0) { for (var i = 0; i < types.length; i++) { stringBuffer.write('${types[i]}'); stringBuffer.write('/'); } }for (var i = 0; i < actors.length; i++) { stringBuffer.write('${actors[i]}'); stringBuffer.write('/'); }String movieInfo = stringBuffer.toString(); return Flexible(child: Container( alignment: Alignment.topLeft, child: Text( movieInfo, softWrap: true, textDirection: TextDirection.ltr, style: TextStyle(fontSize: 16, color: Color.fromARGB(255, 118, 117, 118)), ), ) ); } }// 电影星星评分/等级 // ignore: must_be_immutable class RatingBar extends StatelessWidget {double star; RatingBar(this.star); @override Widget build(BuildContext context) { List _startList = []; // 实心星星(整除,余数部分舍弃取整) var _startNumber = star ~/ 2; // 半实心星星 var _startHalf = 0; if (star.toString().contains('.')) { int tmp = int.parse((star.toString().split('.')[1])); if (tmp >= 5) { _startHalf = 1; } } // 空心星星 var _startEmpty = 5 - _startNumber - _startHalf; for (var i = 0; i < _startNumber; i++) { _startList.add(Icon( Icons.star, color: Colors.amberAccent, size: 18, )); }if (_startHalf > 0) { _startList.add(Icon( Icons.star_half, color: Colors.amberAccent, size: 18, )); }for (var i = 0; i < _startEmpty; i++) { _startList.add(Icon( Icons.star_border, color: Colors.grey, size: 18, )); }_startList.add(Text( '$star', style: TextStyle( color: Colors.grey, ), )); return Container( alignment: Alignment.topLeft, padding: const EdgeInsets.only(left: 0, top: 8, right: 0, bottom: 5), child: Row( children: _startList, ), ); } }

【1202|1202 年了,还不了解 Flutter 解析数据的来康康】完毕!

    推荐阅读