修改 lib\views\search\search.dart:
import 'package:douban/http/API.dart';
import 'package:douban/model/search_result.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class SearchPage extends StatefulWidget{
final String searchHintContent;
SearchPage({Key key, this.searchHintContent = '阿甘正传'}):super(key:key);
@override
State<StatefulWidget> createState() {
return _SearchPageState();
}
}
class _SearchPageState extends State<SearchPage>{
final API _api = API();
SearchResult _searchResult;
var imgW;
var imgH;
bool showLoading = false;
@override
Widget build(BuildContext context) {
// 图片长宽
if (imgW == null){
imgW = MediaQuery.of(context).size.width /7;
imgH = imgW /0.75;
}
// 查询结果排序
return Scaffold(
body: SafeArea(
child: showLoading ? Center(
child: CupertinoActivityIndicator(),
) : _searchResult == null ? getSearchWidget() : Column(
children: <Widget>[
getSearchWidget(),
// 搜索结果展示
Expanded(
child: Text('搜索结果'),
),
],
),
),
);
}
// 搜索组件
Widget getSearchWidget() {
return Container(
child: Text('这是搜索组件'),
);
}
}
### 2、搜索对象编写
新建 lib\model\search_result.dart,可以直接复制,跟布局没什么关系
class SearchResult {
int total;
List<SearchResultSubject> subjects;
int count;
int start;
String title;
SearchResult({this.total, this.subjects, this.count, this.start, this.title});
// json搜索结果转换成对象
SearchResult.fromJson(Map<String, dynamic> json){
total = json['total'];
if (json['subjects'] != null){
subjects = new List<SearchResultSubject>();
json['subjects'].forEach( (v) {
subjects.add(new SearchResultSubject.fromJson(v));
}
);
}
count = json['count'];
start = json['start'];
title = json['title'];
}
Map<String, dynamic> toJson(){
final Map<String, dynamic> data = new Map<String, dynamic>();
data['total'] = this.total;
if (this.subjects != null ) {
data['subjects'] = this.subjects.map((v) => v.toJson()).toList();
}
data['count'] = this.count;
data['start'] = this.start;
data['title'] = this.title;
return data;
}
}
class SearchResultSubject {
SearchResultSubjectsImages images;
String originalTitle;
String year;
List<SearchResultSubjectsDirector> directors;
SearchResultSubjectsRating rating;
String alt;
String title;
int collectCount;
bool hasVideo;
List<String> pubdates;
List<SearchResultSubjectsCast> casts;
String subtype;
List<String> genres;
List<String> durations;
String mainlandPubdate;
String id;
SearchResultSubject({this.images, this.originalTitle, this.year, this.directors, this.rating, this.alt, this.title, this.collectCount, this.hasVideo, this.pubdates, this.casts, this.subtype, this.genres, this.durations, this.mainlandPubdate, this.id});
SearchResultSubject.fromJson(Map<String, dynamic> json) {
images = json['images'] != null ? new SearchResultSubjectsImages.fromJson(json['images']) : null;
originalTitle = json['original_title'];
year = json['year'];
if (json['directors'] != null) {
directors = new List<SearchResultSubjectsDirector>();
json['directors'].forEach((v) { directors.add(new SearchResultSubjectsDirector.fromJson(v)); });
}
rating = json['rating'] != null ? new SearchResultSubjectsRating.fromJson(json['rating']) : null;
alt = json['alt'];
title = json['title'];
collectCount = json['collect_count'];
hasVideo = json['has_video'];
pubdates = json['pubdates'].cast<String>();
if (json['casts'] != null) {
casts = new List<SearchResultSubjectsCast>();
json['casts'].forEach((v) { casts.add(new SearchResultSubjectsCast.fromJson(v)); });
}
subtype = json['subtype'];
genres = json['genres'].cast<String>();
durations = json['durations'].cast<String>();
mainlandPubdate = json['mainland_pubdate'];
id = json['id'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
if (this.images != null) {
data['images'] = this.images.toJson();
}
data['original_title'] = this.originalTitle;
data['year'] = this.year;
if (this.directors != null) {
data['directors'] = this.directors.map((v) => v.toJson()).toList();
}
if (this.rating != null) {
data['rating'] = this.rating.toJson();
}
data['alt'] = this.alt;
data['title'] = this.title;
data['collect_count'] = this.collectCount;
data['has_video'] = this.hasVideo;
data['pubdates'] = this.pubdates;
if (this.casts != null) {
data['casts'] = this.casts.map((v) => v.toJson()).toList();
}
data['subtype'] = this.subtype;
data['genres'] = this.genres;
data['durations'] = this.durations;
data['mainland_pubdate'] = this.mainlandPubdate;
data['id'] = this.id;
return data;
}
}
class SearchResultSubjectsImages {
String small;
String large;
String medium;
SearchResultSubjectsImages({this.small, this.large, this.medium});
SearchResultSubjectsImages.fromJson(Map<String, dynamic> json) {
small = json['small'];
large = json['large'];
medium = json['medium'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['small'] = this.small;
data['large'] = this.large;
data['medium'] = this.medium;
return data;
}
}
class SearchResultSubjectsDirector {
var name;
var alt;
var id;
var avatars;
var nameEn;
SearchResultSubjectsDirector({this.name, this.alt, this.id, this.avatars, this.nameEn});
SearchResultSubjectsDirector.fromJson(Map<String, dynamic> json) {
name = json['name'];
alt = json['alt'];
id = json['id'];
avatars = json['avatars'];
nameEn = json['name_en'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['name'] = this.name;
data['alt'] = this.alt;
data['id'] = this.id;
data['avatars'] = this.avatars;
data['name_en'] = this.nameEn;
return data;
}
}
class SearchResultSubjectsRating {
var average;
var min;
var max;
SearchResultSubjectsRatingDetails details;
String stars;
SearchResultSubjectsRating({this.average, this.min, this.max, this.details, this.stars});
SearchResultSubjectsRating.fromJson(Map<String, dynamic> json) {
average = json['average'];
min = json['min'];
max = json['max'];
details = json['details'] != null ? new SearchResultSubjectsRatingDetails.fromJson(json['details']) : null;
stars = json['stars'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['average'] = this.average;
data['min'] = this.min;
data['max'] = this.max;
if (this.details != null) {
data['details'] = this.details.toJson();
}
data['stars'] = this.stars;
return data;
}
}
class SearchResultSubjectsRatingDetails {
var d1;
var d2;
var d3;
var d4;
var d5;
SearchResultSubjectsRatingDetails({this.d1, this.d2, this.d3, this.d4, this.d5});
SearchResultSubjectsRatingDetails.fromJson(Map<String, dynamic> json) {
d1 = json['1'];
d2 = json['2'];
d3 = json['3'];
d4 = json['4'];
d5 = json['5'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['1'] = this.d1;
data['2'] = this.d2;
data['3'] = this.d3;
data['4'] = this.d4;
data['5'] = this.d5;
return data;
}
}
class SearchResultSubjectsCast {
String name;
var alt;
var id;
var avatars;
String nameEn;
SearchResultSubjectsCast({this.name, this.alt, this.id, this.avatars, this.nameEn});
SearchResultSubjectsCast.fromJson(Map<String, dynamic> json) {
name = json['name'];
alt = json['alt'];
id = json['id'];
avatars = json['avatars'];
nameEn = json['name_en'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['name'] = this.name;
data['alt'] = this.alt;
data['id'] = this.id;
data['avatars'] = this.avatars;
data['name_en'] = this.nameEn;
return data;
}
}
到这里可以运行程序,查看结果
修改 search.dart 文件
// 省略.....
Widget getSearchWidget() {
return Padding(
padding: EdgeInsets.only(left: 10.0, right: 10.0, top: 10.0, bottom: 20.0),
child: Row(
children: <Widget>[
Expanded(
child: SearchTextFieldWidget(
hintText: widget.searchHintContent,
onSubmitted: (searchContent) {
showLoading = true;
// 搜索电影
},
),
),
// 检测点击拖动等交互操作
GestureDetector(
child: Padding(
padding: EdgeInsets.only(left: 10.0),
child: Text(
' 取消',
style: TextStyle(
color: Colors.green,
fontSize: 17.0,
fontWeight: FontWeight.bold
),
),
),
onTap: () {
Navigator.pop(context);
},
)
],
),
);
}
// 省略.....
import 'package:douban/model/search_result.dart';
import 'http_request.dart';
typedef RequestCallBack<T> = void Function(T value);
class API{
static const BASE_URL = 'https://api.douban.com';
///TOP250
static const String TOP_250 = '/v2/movie/top250';
///正在热映
static const String IN_THEATERS = '/v2/movie/in_theaters?apikey=0b2bdeda43b5688921839c8ecb20399b';
///即将上映
static const String COMING_SOON = '/v2/movie/coming_soon?apikey=0b2bdeda43b5688921839c8ecb20399b';
///一周口碑榜
static const String WEEKLY = '/v2/movie/weekly?apikey=0b2bdeda43b5688921839c8ecb20399b';
///影人条目信息
static const String CELEBRITY = '/v2/movie/celebrity/';
static const String REIVIEWS = '/v2/movie/subject/26266893/reviews';
var _request = HttpRequest(API.BASE_URL);
void searchMovie(
String searchContent, RequestCallBack requestCallBack) async {
// 访问远程数据
// final result= await _request.get(
// '/v2/movie/search?q=$searchContent&apikey=0b2bdeda43b5688921839c8ecb20399b');
// 若接口不能使用,使用模拟数据
var req = MockRequest();
var result = await req.get(API.COMING_SOON);
// 将json 转换成对象
SearchResult bean = SearchResult.fromJson(result);
requestCallBack(bean);
}
}
#### 2、修改 search.dart, 页面内搜索注释"搜索电影",在下方插入以下代码:
_api.searchMovie(searchContent, (searchResult) {
setState(() {
showLoading = false;
_searchResult = searchResult;
});
});
新建 lib\http\http_request.dart:
import 'dart:io';
import 'dart:convert' as Convert;
import 'package:http/http.dart' as http;
typedef RequestCallBack = void Function(Map data);
class HttpRequest{
static requestGET(
String authority, String unencodedPath, RequestCallBack callBack,
[Map<String,String> queryParameters]) async {
try {
var httpClient = new HttpClient();
var uri = new Uri.http(authority, unencodedPath, queryParameters);
var request = await httpClient.getUrl(uri);
var response = await request.close();
var responseBody = await response.transform(Convert.utf8.decoder).join();
Map data = Convert.jsonDecode(responseBody);
callBack(data);
} on Exception catch (e) {
print(e.toString());
}
}
final baseUrl;
HttpRequest(this.baseUrl);
Future<dynamic> get(String uri,{Map<String,String> headers}) async {
try {
http.Response response = await http.get(baseUrl+uri, headers: headers);
print(baseUrl+uri);
final statusCode = response.statusCode;
final body = response.body;
print('[uri=$uri][statusCode=$statusCode][response=$body]');
var result = Convert.jsonDecode(body);
return result;
} on Exception catch (e) {
print('[uri=$uri]exception e=${e.toString()}');
return '';
}
}
Future<dynamic> getResponseBody(String uri, {Map<String, String> headers}) async {
try {
http.Response response = await http.get(baseUrl + uri, headers: headers);
final statusCode = response.statusCode;
final body = response.body;
// var result = Convert.jsonDecode(body);
print('[uri=$uri][statusCode=$statusCode][response=$body]');
return body;
} on Exception catch (e) {
print('[uri=$uri]exception e=${e.toString()}');
return null;
}
}
Future<dynamic> post(String uri, dynamic body, {Map<String, String> headers}) async {
try {
http.Response response = await http.post(baseUrl + uri, body: body, headers: headers);
final statusCode = response.statusCode;
final responseBody = response.body;
var result = Convert.jsonDecode(responseBody);
print('[uri=$uri][statusCode=$statusCode][response=$responseBody]');
return result;
} on Exception catch (e) {
print('[uri=$uri]exception e=${e.toString()}');
return '';
}
}
}
1、打开 search.dart 搜索注释"搜索结果展示",在下方替换原来的Expanded:
_searchResult.subjects ==null ? Container(child: Text('搜索结果为空'),) : Expanded((
child: ListView.builder(
itemBuilder: (BuildContext context, int index) {
SearchResultSubject bean = _searchResult.subjects[index];
return Padding(
padding: EdgeInsets.all(10.0),
child: GestureDetector(
behavior: HitTestBehavior.translucent,
child: _getItem(bean, index),
onTap: (){
Application.router.navigateTo(context, '${Routes.detailPage}?id=${bean.id}');
},
),
);
},
itemCount: _searchResult.subjects.length,
),
),
2、 search.dart 中 _SearchPageState 类添加以下方法
String getType(String subtype) {
switch (subtype) {
case 'movie':
return '电影';
}
return '';
}
TextStyle getStyle(Color color, double fontSize, {bool bold = false}) {
return TextStyle(
color: color,
fontSize: fontSize,
fontWeight: bold ? FontWeight.bold : FontWeight.normal);
}
String listConvertString(List<String> genres) {
if (genres.isEmpty) {
return '';
} else {
String tmp = '';
for (String item in genres) {
tmp = tmp + item;
}
return tmp;
}
}
String listConvertString2(List<SearchResultSubjectsDirector> genres) {
if (genres.isEmpty) {
return '';
} else {
String tmp = '';
for (SearchResultSubjectsDirector item in genres) {
tmp = tmp + item.name;
}
return tmp;
}
}
Widget _getItem(SearchResultSubject bean, int index) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Card(
child: Image.network(
bean.images.medium,
fit: BoxFit.cover,
width: imgW,
height: imgH,
),
clipBehavior: Clip.antiAlias,
elevation: 5.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(5.0))),
),
Padding(
padding: EdgeInsets.all(5.0),
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
getType(bean.subtype),
style: getStyle(Colors.grey, 12.0),
),
Text(bean.title + '(${bean.year})',
style: getStyle(Colors.black, 15.0, bold: true)),
Text(
'${bean.rating.average} 分 / ${listConvertString(bean.pubdates)} / ${listConvertString(bean.genres)} / ${listConvertString2(bean.directors)}',
style: getStyle(Colors.grey, 13.0))
],
),
)
],
);
}
运行效果图