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 HAND
part 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,
),
);
}
}
完毕!