2021年了, Flutter 2.0版本已經(jīng)發(fā)布,來(lái)康康Flutter 的Model 是怎么解析json 數(shù)據(jù)的吧!
如果你請(qǐng)求到豆瓣電影的高分電影排行榜json數(shù)據(jù)格式入下, 讓你用flutter做成一個(gè)電影排行榜的列表, 你該如何下手呢?
[
{
"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": ["美國(guó)"],
"title": "肖申克的救贖",
"url": "https:\/\/movie.douban.com\/subject\/1292052\/",
"release_date": "1994-09-10",
"actor_count": 25,
"vote_count": 2290289,
"score": "9.7",
"actors": ["蒂姆·羅賓斯", "摩根·弗里曼", "鮑勃·岡頓", "威廉姆·賽德勒", "克蘭西·布朗", "吉爾·貝羅斯", "馬克·羅斯頓", "詹姆斯·惠特摩", "杰弗里·德曼", "拉里·布蘭登伯格", "尼爾·吉恩托利", "布賴恩·利比", "大衛(wèi)·普羅瓦爾", "約瑟夫·勞格諾", "祖德·塞克利拉", "保羅·麥克蘭尼", "芮妮·布萊恩", "阿方索·弗里曼", "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": ["劇情", "愛(ài)情", "同性"],
"regions": ["中國(guó)大陸", "中國(guó)香港"],
"title": "霸王別姬",
"url": "https:\/\/movie.douban.com\/subject\/1291546\/",
"release_date": "1993-07-26",
"actor_count": 26,
"vote_count": 1698938,
"score": "9.6",
"actors": ["張國(guó)榮", "張豐毅", "鞏俐", "葛優(yōu)", "英達(dá)", "蔣雯麗", "吳大維", "呂齊", "雷漢", "尹治", "馬明威", "費(fèi)振翔", "智一桐", "李春", "趙海龍", "李丹", "童弟", "沈慧芬", "黃斐", "徐杰", "黃磊", "馮遠(yuǎn)征", "楊立新", "方征", "周璞", "隋永清"],
"is_watched": false
}
]
首先登場(chǎng)的是 json_serializable 真心好用的一個(gè)json 解析框架, 項(xiàng)目的pubspec.yaml 中添加 json_serializable:
dev_dependencies:
flutter_test:
sdk: flutter
json_serializable: ^3.5.1
然后, 觀察這個(gè)josn 的數(shù)據(jù)結(jié)構(gòu),發(fā)現(xiàn)是數(shù)組套字典;
那么model 的數(shù)據(jù)結(jié)構(gòu)為:
"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": ["劇情", "愛(ài)情", "同性"],
"regions": ["中國(guó)大陸", "中國(guó)香港"],
"title": "霸王別姬",
"url": "https:\/\/movie.douban.com\/subject\/1291546\/",
"release_date": "1993-07-26",
"actor_count": 26,
"vote_count": 1698938,
"score": "9.6",
"actors": ["張國(guó)榮", "張豐毅", "鞏俐", "葛優(yōu)", "英達(dá)", "蔣雯麗", "吳大維", "呂齊", "雷漢", "尹治", "馬明威", "費(fèi)振翔", "智一桐", "李春", "趙海龍", "李丹", "童弟", "沈慧芬", "黃斐", "徐杰", "黃磊", "馮遠(yuǎn)征", "楊立新", "方征", "周璞", "隋永清"],
"is_watched": false,
使用這個(gè) json_serializable 自動(dòng)轉(zhuǎn)model 工具:
https://caijinglong.github.io/json2dart/index_ch.html
然后把這個(gè)工具生成的model 代碼copy 到項(xiàng)目的douban.dart model 中,
import 'package:json_annotation/json_annotation.dart';
part 'douban.g.dart';
@JsonSerializable()
class Douban extends Object {
@JsonKey(name: 'rating')
List<String> 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<String> types;
@JsonKey(name: 'regions')
List<String> 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<String> 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<String, dynamic> srcJson) => _$DoubanFromJson(srcJson);
Map<String, dynamic> toJson() => _$DoubanToJson(this);
}
項(xiàng)目的根目錄中,執(zhí)行:
flutter packages pub run build_runner build
如果報(bào)錯(cuò):
flutter packages pub run build_runner clean
flutter packages pub run build_runner build --delete-conflicting-outputs
此時(shí),項(xiàng)目中出現(xiàn) douban.g.dart 的文件, 此時(shí)項(xiàng)目報(bào)錯(cuò)消失.
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'douban.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Douban _$DoubanFromJson(Map<String, dynamic> 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<String, dynamic> _$DoubanToJson(Douban instance) => <String, dynamic>{
'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,
};
這個(gè)文件禁止手寫.
OK,準(zhǔn)備工作已經(jīng)搞完了.
創(chuàng)建一個(gè)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<StatefulWidget> createState() {
return _DouBanState();
}
}
class _DouBanState extends State<DouBanListView> {
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 的數(shù)量
itemCount: subjects.length,
itemBuilder: (BuildContext context, int index) {
return GestureDetector(//Flutter 手勢(shì)處理
child: Container(
color: Colors.transparent,
child:
Column(crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// 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: () {
//監(jiān)聽(tīng)點(diǎn)擊事件
print("click item index=$index");
},
);
});
}
// 肖申克的救贖(1993) View
Widget _getTitleView(subject) {
var title = douban.title;
var releaseDate =douban.releaseDate;
return Container(
child: Row(
children: <Widget>[
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: <Widget>[
// 圖片加載
_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,
);
}
// 電影標(biāo)題,星標(biāo)評(píng)分展哭,演員簡(jiǎn)介
Widget _getMovieInfoView() {
return Container(
height: itemHeight,
alignment: Alignment.topLeft,
child: Column(
children: <Widget>[
// 電影名字 年份
_getTitleView(douban),
// 星星評(píng)分
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<String> actors = douban.actors;
var stringBuffer = StringBuffer();
// 種類
List<String> 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)),
),
)
);
}
}
// 電影星星評(píng)分/等級(jí)
// ignore: must_be_immutable
class RatingBar extends StatelessWidget {
double star;
RatingBar(this.star);
@override
Widget build(BuildContext context) {
List<Widget> _startList = [];
// 實(shí)心星星(整除挺据,余數(shù)部分舍棄取整)
var _startNumber = star ~/ 2;
// 半實(shí)心星星
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,
),
);
}
}
完畢!