網(wǎng)頁(yè)數(shù)據(jù)訪問
接口準(zhǔn)備json數(shù)據(jù)
static const String kBaseUrl = 'https://www.wanandroid.com';
完整的json數(shù)據(jù)連接長(zhǎng)這樣-> https://www.wanandroid.com/article/list/0/json 這里只使用一個(gè)獲取文章列表的如下接口,其中 0 是個(gè)可以改變的參數(shù),表示文章的頁(yè)數(shù)。

如圖所示,主要的信息都在datas里面,所以,我們只需要解析datas里面的數(shù)據(jù)即可。
class ArticleApi {
static const String kBaseUrl = 'https://www.wanandroid.com';
final Dio _client = Dio(BaseOptions(
baseUrl: kBaseUrl,
));
Future<List<Article>> loadArticles(int page) async {
String path = '/article/list/$page/json';
var rep = await _client.get(path); //拼接地址
if (rep.statusCode == 200) {
if (rep.data != null) {
var data = rep.data['data']['datas'] as List; //當(dāng)前page獲取所有data里面datas的數(shù)據(jù)
return data.map(Article.formMap).toList();
}
}
return [];
}
}
var data內(nèi)容如下:

Dio
網(wǎng)絡(luò)請(qǐng)求是非常通用的能力,開發(fā)者自己來寫非常復(fù)雜,所以一般使用三方的依賴庫(kù)。對(duì)于 Flutter 網(wǎng)絡(luò)請(qǐng)求來說,最受歡迎的是 dio , 使用前先添加依賴
在 ArticleApi 中持有 Dio 類型的 _client 對(duì)象,構(gòu)造時(shí)可以設(shè)置 baseUrl 。然后提供 loadArticles 方法,用于加載第 page 頁(yè)的數(shù)據(jù),其中的邏輯處理,就是加載網(wǎng)絡(luò)數(shù)據(jù)的核心。
使用起來也很方便,提供 Dio#get 方法就可以異步獲取數(shù)據(jù),得到之后,從結(jié)果中拿到自己想要的數(shù)據(jù),生成 Article 列表即可。
介紹 Dio
Dio 是一個(gè)強(qiáng)大的 Flutter 和 Dart HTTP 客戶端庫(kù),用于處理網(wǎng)絡(luò)請(qǐng)求。它提供了簡(jiǎn)單易用的 API,支持多種功能,如攔截器、請(qǐng)求取消、超時(shí)設(shè)置、文件上傳下載等。以下是 Dio 的一些主要特點(diǎn)和使用方法:
主要特點(diǎn)
- 支持多種請(qǐng)求方法:GET、POST、PUT、DELETE、HEAD、PATCH 等。
- 攔截器:可以在請(qǐng)求發(fā)送前和響應(yīng)接收后進(jìn)行攔截處理。
- 請(qǐng)求取消:可以取消正在進(jìn)行的請(qǐng)求。
- 超時(shí)設(shè)置:可以設(shè)置連接超時(shí)和接收超時(shí)。
- 文件上傳下載:支持文件的上傳和下載。
- 轉(zhuǎn)換器:支持多種數(shù)據(jù)格式的轉(zhuǎn)換,如 JSON、FormData 等。
- Cookie 管理:內(nèi)置 Cookie 管理器。
使用方法
-
添加依賴
在
pubspec.yaml文件中添加 Dio 依賴:dependencies: dio: ^5.0.0 -
導(dǎo)入庫(kù)
在 Dart 文件中導(dǎo)入 Dio 庫(kù):
import 'package:dio/dio.dart'; -
創(chuàng)建 Dio 實(shí)例
創(chuàng)建一個(gè) Dio 實(shí)例,并可以設(shè)置基礎(chǔ) URL 和其他選項(xiàng):
final Dio dio = Dio(BaseOptions( baseUrl: 'https://www.wanandroid.com', connectTimeout: 5000, // 連接超時(shí)時(shí)間 receiveTimeout: 3000, // 接收超時(shí)時(shí)間 )); -
發(fā)送請(qǐng)求
使用 Dio 實(shí)例發(fā)送請(qǐng)求,例如 GET 請(qǐng)求:
try { Response response = await dio.get('/article/list/0/json'); if (response.statusCode == 200) { print(response.data); } } catch (e) { print(e); } -
處理響應(yīng)
處理響應(yīng)數(shù)據(jù),例如解析 JSON 數(shù)據(jù):
try { Response response = await dio.get('/article/list/0/json'); if (response.statusCode == 200) { var data = response.data['data']['datas'] as List; List<Article> articles = data.map(Article.formMap).toList(); print(articles); } } catch (e) { print(e); } -
使用攔截器
添加請(qǐng)求和響應(yīng)攔截器:
dio.interceptors.add(InterceptorsWrapper( onRequest: (options, handler) { print('Sending request: ${options.method} ${options.uri}'); return handler.next(options); }, onResponse: (response, handler) { print('Received response: ${response.statusCode}'); return handler.next(response); }, onError: (DioError e, handler) { print('Request error: ${e.message}'); return handler.next(e); }, ));
ArticleformMap
class Article {
final String title;
final String url;
final String time;
const Article({
required this.title,
required this.url,
required this.time,
});
factory Article.formMap(dynamic map){
return Article(
title: map['title'] ?? '未知',
url: map['link'] ?? '',
time: map['niceDate'] ?? '',
);
}
@override
String toString() {
return 'Article{title: $title, url: $url, time: $time}';
}
}
解釋 factory Article.formMap(dynamic map)
在 Dart 中,factory 構(gòu)造函數(shù)用于創(chuàng)建類的實(shí)例,但它提供了一種靈活性,允許在構(gòu)造函數(shù)中返回一個(gè)已經(jīng)存在的實(shí)例,而不是總是創(chuàng)建一個(gè)新的實(shí)例。這在需要實(shí)現(xiàn)單例模式或從緩存中返回對(duì)象時(shí)非常有用。
具體到 factory Article.formMap(dynamic map),這個(gè)構(gòu)造函數(shù)的作用是從一個(gè) Map 對(duì)象中解析數(shù)據(jù),并創(chuàng)建一個(gè) Article 對(duì)象。以下是詳細(xì)的解釋:
-
工廠構(gòu)造函數(shù):
factory關(guān)鍵字表示這是一個(gè)工廠構(gòu)造函數(shù)。- 工廠構(gòu)造函數(shù)可以返回一個(gè)緩存的實(shí)例,或者根據(jù)條件返回不同的實(shí)例。
-
參數(shù):
dynamic map:表示傳入的數(shù)據(jù)是一個(gè)動(dòng)態(tài)類型的Map對(duì)象,通常是從 JSON 解析得到的數(shù)據(jù)。
-
作用:
- 從傳入的
Map對(duì)象中提取數(shù)據(jù)。 - 使用提取的數(shù)據(jù)創(chuàng)建并返回一個(gè)
Article對(duì)象。
- 從傳入的
示例代碼
假設(shè) Article 類的定義如下:
class Article {
final int id;
final String title;
final String author;
Article({required this.id, required this.title, required this.author});
// 工廠構(gòu)造函數(shù),從 Map 對(duì)象創(chuàng)建 Article 實(shí)例
factory Article.formMap(dynamic map) {
return Article(
id: map['id'] as int,
title: map['title'] as String,
author: map['author'] as String,
);
}
@override
String toString() {
return 'Article{id: $id, title: $title, author: $author}';
}
}
解釋示例代碼
-
類定義:
Article類包含三個(gè)屬性:id、title和author。
-
構(gòu)造函數(shù):
Article({required this.id, required this.title, required this.author}):標(biāo)準(zhǔn)的構(gòu)造函數(shù),用于創(chuàng)建Article對(duì)象。
-
工廠構(gòu)造函數(shù):
factory Article.formMap(dynamic map):工廠構(gòu)造函數(shù),從Map對(duì)象中提取數(shù)據(jù)并創(chuàng)建Article對(duì)象。map['id'] as int:從Map中提取id并轉(zhuǎn)換為int類型。map['title'] as String:從Map中提取title并轉(zhuǎn)換為String類型。map['author'] as String:從Map中提取author并轉(zhuǎn)換為String類型。- 返回一個(gè)新的
Article對(duì)象。
使用示例
假設(shè)從網(wǎng)絡(luò)請(qǐng)求中獲取到的 JSON 數(shù)據(jù)如下:
{
"id": 1,
"title": "Flutter 網(wǎng)絡(luò)請(qǐng)求",
"author": "張三"
}
解析并創(chuàng)建 Article 對(duì)象的代碼如下:
void main() {
Map<String, dynamic> jsonData = {
"id": 1,
"title": "Flutter 網(wǎng)絡(luò)請(qǐng)求",
"author": "張三"
};
Article article = Article.formMap(jsonData);
print(article); // 輸出: Article{id: 1, title: Flutter 網(wǎng)絡(luò)請(qǐng)求, author: 張三}
}
通過這種方式,factory Article.formMap(dynamic map) 提供了一種方便且類型安全的方法,從 JSON 數(shù)據(jù)中創(chuàng)建 Article 對(duì)象。
為什么需要重寫 toString 方法
在 Dart 中,每個(gè)類都繼承自 Object 類,而 Object 類提供了一個(gè)默認(rèn)的 toString 方法。默認(rèn)的 toString 方法返回的是類的名稱和對(duì)象的內(nèi)存地址,例如 Instance of 'Article'。這種默認(rèn)的字符串表示通常不夠詳細(xì),無法提供對(duì)象的具體內(nèi)容。
重寫 toString 方法可以提供一個(gè)更具可讀性的字符串表示,便于調(diào)試和日志記錄。具體來說,重寫 toString 方法有以下幾個(gè)好處:
- 調(diào)試方便:
在調(diào)試過程中,可以直接查看對(duì)象的具體屬性值,而不是只能看到內(nèi)存地址。
便于打印對(duì)象信息,快速定位問題。 - 日志記錄:
在日志中記錄對(duì)象信息時(shí),可以提供更詳細(xì)的上下文。
便于后續(xù)分析和問題排查。 - 代碼可讀性:
提高代碼的可讀性和可維護(hù)性。
其他開發(fā)者可以更容易地理解對(duì)象的內(nèi)容。
AriticleCotent

@override
Widget build(BuildContext context) {
if (_loading) { //如果 _loading 為 true,則執(zhí)行代碼塊中的內(nèi)容。
return Center( //Center 小部件用于將子小部件居中顯示。
child: Wrap(
spacing: 10, //設(shè)置子小部件之間的間距為 10 像素。
direction: Axis.vertical, //設(shè)置排列方向?yàn)榇怪狈较颉? crossAxisAlignment:WrapCrossAlignment.center, //設(shè)置子小部件在交叉軸(水平軸)上的對(duì)齊方式為居中對(duì)齊。
children: const [
CupertinoActivityIndicator(), //顯示一個(gè) iOS 風(fēng)格的加載指示器。
Text(
"數(shù)據(jù)加載中,請(qǐng)稍后...",
style: TextStyle(color: Colors.grey), //顯示一條加載提示文本,文本顏色為灰色。
)
],
),
);
}
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:net_article/api/article_api.dart';
import 'package:net_article/model/article.dart';
import 'package:flutter/cupertino.dart';
import 'package:easy_refresh/easy_refresh.dart';
import 'article_detail_page.dart';
class AriticleCotent extends StatefulWidget {
const AriticleCotent({Key? key}) : super(key: key);
@override
State<AriticleCotent> createState() => _AriticleCotentState();
}
class _AriticleCotentState extends State<AriticleCotent> {
List<Article> _articles = [];
ArticleApi api = ArticleApi();
bool _loading = false;
@override
void initState() {
super.initState();
_loadData();
}
void _loadData() async {
_loading = true;
setState(() {});
_articles = await api.loadArticles(0);
_loading = false;
setState(() {});
}
@override
Widget build(BuildContext context) {
if (_loading) { //如果 _loading 為 true,則執(zhí)行代碼塊中的內(nèi)容。
return Center( //Center 小部件用于將子小部件居中顯示。
child: Wrap(
spacing: 10, //設(shè)置子小部件之間的間距為 10 像素。
direction: Axis.vertical, //設(shè)置排列方向?yàn)榇怪狈较颉? crossAxisAlignment:WrapCrossAlignment.center, //設(shè)置子小部件在交叉軸(水平軸)上的對(duì)齊方式為居中對(duì)齊。
children: const [
CupertinoActivityIndicator(), //顯示一個(gè) iOS 風(fēng)格的加載指示器。
Text(
"數(shù)據(jù)加載中,請(qǐng)稍后...",
style: TextStyle(color: Colors.grey), //顯示一條加載提示文本,文本顏色為灰色。
)
],
),
);
}
return EasyRefresh( //小部件:用于實(shí)現(xiàn)下拉刷新和上拉加載更多的功能。
header: const ClassicHeader( //ClassicHeader 是 EasyRefresh 提供的一個(gè)經(jīng)典風(fēng)格的下拉刷新頭部。
dragText: "下拉加載",
armedText: "釋放刷新",
readyText: "開始加載",
processingText: "正在加載",
processedText: "刷新成功",
),
footer: const ClassicFooter(processingText: "正在加載"),
// dragText: "下拉加載":當(dāng)用戶下拉時(shí)顯示的文本。
// armedText: "釋放刷新":當(dāng)用戶下拉到一定距離時(shí)顯示的文本。
// readyText: "開始加載":當(dāng)用戶釋放手指準(zhǔn)備刷新時(shí)顯示的文本。
// processingText: "正在加載":刷新過程中顯示的文本。
// processedText: "刷新成功":刷新完成后顯示的文本。
// ClassicFooter 是 EasyRefresh 提供的一個(gè)經(jīng)典風(fēng)格的上拉加載更多底部。
// processingText: "正在加載":加載過程中顯示的文本。
onRefresh: _onRefresh,
onLoad: _onLoad, //指定加載更多時(shí)調(diào)用的方法。
child: ListView.builder( //用于創(chuàng)建一個(gè)可滾動(dòng)的列表視圖。
itemExtent: 80,
itemCount: _articles.length,
itemBuilder: _buildItemByIndex,
// itemExtent: 80:設(shè)置每個(gè)列表項(xiàng)的高度為 80 像素。
// itemCount: _articles.length:設(shè)置列表項(xiàng)的數(shù)量為 _articles 列表的長(zhǎng)度。
// itemBuilder: _buildItemByIndex:指定構(gòu)建每個(gè)列表項(xiàng)的方法。
),
);
}
void _onRefresh() async {
_articles = await api.loadArticles(0);
setState(() {});
}
void _onLoad() async {
int nextPage = _articles.length ~/ 20;
List<Article> newArticles = await api.loadArticles(nextPage);
_articles = _articles + newArticles;
setState(() {});
}
Widget _buildItemByIndex(BuildContext context, int index) {
print('點(diǎn)擊了第 $index 個(gè) item}');
return ArticleItem(
article: _articles[index], //index,從0開始自動(dòng)增長(zhǎng)
onTap: _jumpToPage, //把當(dāng)前article傳給_jumpToPage
);
}
void _jumpToPage(Article article) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => ArticleDetailPage(article: article), //跳轉(zhuǎn)到詳情界面
),
);
}
}
class ArticleItem extends StatelessWidget {
final Article article;
final ValueChanged<Article> onTap;
const ArticleItem({Key? key, required this.article, required this.onTap})
: super(key: key);
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => onTap(article),
child: Card(
elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)),
color: Colors.white,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
article.title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style:
TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
),
),
const SizedBox(
width: 10,
),
// Spacer(),
Text(
article.time,
style: TextStyle(fontSize: 12, color: Colors.grey),
),
],
),
const SizedBox(
height: 4,
),
Expanded(
child: Align(
alignment: Alignment.centerLeft,
child: Text(
article.url,
style: TextStyle(fontSize: 12, color: Colors.grey),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
)
],
),
),
),
);
}
}
webview_flutter
代碼解釋
這段代碼定義了一個(gè) Flutter 應(yīng)用中的 ArticleDetailPage 頁(yè)面,用于顯示文章詳情。頁(yè)面中使用了 WebViewWidget 來加載并顯示文章的網(wǎng)頁(yè)內(nèi)容。以下是代碼的具體解釋:
1. 導(dǎo)入包
import 'package:flutter/material.dart';
import '../model/article.dart';
import 'package:webview_flutter/webview_flutter.dart';
flutter/material.dart:導(dǎo)入 Flutter 的 Material Design 組件庫(kù)。../model/article.dart:導(dǎo)入自定義的Article模型類,用于表示文章數(shù)據(jù)。webview_flutter/webview_flutter.dart:導(dǎo)入webview_flutter包,用于在 Flutter 中嵌入 WebView。
2. 定義 ArticleDetailPage 類
class ArticleDetailPage extends StatefulWidget {
final Article article;
const ArticleDetailPage({Key? key, required this.article}) : super(key: key);
@override
State<ArticleDetailPage> createState() => _ArticleDetailPageState();
}
ArticleDetailPage是一個(gè)有狀態(tài)的 Widget (StatefulWidget),它接收一個(gè)Article對(duì)象作為參數(shù),并將其傳遞給其狀態(tài)類_ArticleDetailPageState。
3. 定義 _ArticleDetailPageState 狀態(tài)類
class _ArticleDetailPageState extends State<ArticleDetailPage> {
late WebViewController _controller;
@override
void initState() {
// TODO: implement initState
_controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(const Color(0x00000000))
..loadRequest(Uri.parse(widget.article.url));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.article.title),
),
body: WebViewWidget(controller: _controller),
);
}
}
初始化方法 initState
@override
void initState() {
_controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(const Color(0x00000000))
..loadRequest(Uri.parse(widget.article.url));
}
WebViewController是用于控制 WebView 的控制器。- 使用級(jí)聯(lián)操作符
..連續(xù)調(diào)用多個(gè)方法:setJavaScriptMode(JavaScriptMode.unrestricted):允許 WebView 執(zhí)行 JavaScript。setBackgroundColor(const Color(0x00000000)):設(shè)置 WebView 的背景顏色為透明。loadRequest(Uri.parse(widget.article.url)):加載文章的 URL。
構(gòu)建方法 build
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.article.title),
),
body: WebViewWidget(controller: _controller),
);
}
- 使用
Scaffold作為頁(yè)面的根布局,包含一個(gè)應(yīng)用欄 (AppBar) 和主體 (body)。AppBar顯示文章標(biāo)題。WebViewWidget使用之前初始化的_controller加載并顯示文章的網(wǎng)頁(yè)內(nèi)容。
總結(jié)
這段代碼實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的文章詳情頁(yè)面,通過 WebViewWidget 加載并顯示文章的網(wǎng)頁(yè)內(nèi)容。頁(yè)面的標(biāo)題是文章的標(biāo)題,主體部分是一個(gè) WebView,用于展示文章的網(wǎng)頁(yè)內(nèi)容。
浙公網(wǎng)安備 33010602011771號(hào)