Flutter-教你一步步還原豆瓣 06遠程API接口調用和搜索頁面製作

遠程API接口調用和搜索頁面製作

源碼:https://github.com/mumushuiding/douban

1、搜索頁面基本框架

修改 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;
	}
}

到這裏可以運行程序,查看結果

3.搜索組件編寫

修改 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);
            },
          )
        ],
      ),
    );
  }

  // 省略.....

搜索電影

1、修改lib\http\API.dart

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;
                  });
                });

3、 http請求文件編寫

新建 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 '';
    }
  }
}

4、搜索結果展示

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))
            ],
          ),
        )
      ],
    );
  }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章