网页数据访问
接口准备json数据
static const String kBaseUrl = 'https://www.wanandroid.com';
完整的json数据连接长这样-> https://www.wanandroid.com/article/list/0/json 这里只使用一个获取文章列表的如下接口,其中 0 是个可以改变的参数,表示文章的页数。

如图所示,主要的信息都在datas里面,所以,我们只需要解析datas里面的数据即可。
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; //当前page获取所有data里面datas的数据
return data.map(Article.formMap).toList();
}
}
return [];
}
}
var data内容如下:

Dio
网络请求是非常通用的能力,开发者自己来写非常复杂,所以一般使用三方的依赖库。对于 Flutter 网络请求来说,最受欢迎的是 dio , 使用前先添加依赖
在 ArticleApi 中持有 Dio 类型的 _client 对象,构造时可以设置 baseUrl 。然后提供 loadArticles 方法,用于加载第 page 页的数据,其中的逻辑处理,就是加载网络数据的核心。
使用起来也很方便,提供 Dio#get 方法就可以异步获取数据,得到之后,从结果中拿到自己想要的数据,生成 Article 列表即可。
介绍 Dio
Dio 是一个强大的 Flutter 和 Dart HTTP 客户端库,用于处理网络请求。它提供了简单易用的 API,支持多种功能,如拦截器、请求取消、超时设置、文件上传下载等。以下是 Dio 的一些主要特点和使用方法:
主要特点
- 支持多种请求方法:GET、POST、PUT、DELETE、HEAD、PATCH 等。
- 拦截器:可以在请求发送前和响应接收后进行拦截处理。
- 请求取消:可以取消正在进行的请求。
- 超时设置:可以设置连接超时和接收超时。
- 文件上传下载:支持文件的上传和下载。
- 转换器:支持多种数据格式的转换,如 JSON、FormData 等。
- Cookie 管理:内置 Cookie 管理器。
使用方法
-
添加依赖
在
pubspec.yaml文件中添加 Dio 依赖:dependencies: dio: ^5.0.0 -
导入库
在 Dart 文件中导入 Dio 库:
import 'package:dio/dio.dart'; -
创建 Dio 实例
创建一个 Dio 实例,并可以设置基础 URL 和其他选项:
final Dio dio = Dio(BaseOptions( baseUrl: 'https://www.wanandroid.com', connectTimeout: 5000, // 连接超时时间 receiveTimeout: 3000, // 接收超时时间 )); -
发送请求
使用 Dio 实例发送请求,例如 GET 请求:
try { Response response = await dio.get('/article/list/0/json'); if (response.statusCode == 200) { print(response.data); } } catch (e) { print(e); } -
处理响应
处理响应数据,例如解析 JSON 数据:
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); } -
使用拦截器
添加请求和响应拦截器:
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 构造函数用于创建类的实例,但它提供了一种灵活性,允许在构造函数中返回一个已经存在的实例,而不是总是创建一个新的实例。这在需要实现单例模式或从缓存中返回对象时非常有用。
具体到 factory Article.formMap(dynamic map),这个构造函数的作用是从一个 Map 对象中解析数据,并创建一个 Article 对象。以下是详细的解释:
-
工厂构造函数:
factory关键字表示这是一个工厂构造函数。- 工厂构造函数可以返回一个缓存的实例,或者根据条件返回不同的实例。
-
参数:
dynamic map:表示传入的数据是一个动态类型的Map对象,通常是从 JSON 解析得到的数据。
-
作用:
- 从传入的
Map对象中提取数据。 - 使用提取的数据创建并返回一个
Article对象。
- 从传入的
示例代码
假设 Article 类的定义如下:
class Article {
final int id;
final String title;
final String author;
Article({required this.id, required this.title, required this.author});
// 工厂构造函数,从 Map 对象创建 Article 实例
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类包含三个属性:id、title和author。
-
构造函数:
Article({required this.id, required this.title, required this.author}):标准的构造函数,用于创建Article对象。
-
工厂构造函数:
factory Article.formMap(dynamic map):工厂构造函数,从Map对象中提取数据并创建Article对象。map['id'] as int:从Map中提取id并转换为int类型。map['title'] as String:从Map中提取title并转换为String类型。map['author'] as String:从Map中提取author并转换为String类型。- 返回一个新的
Article对象。
使用示例
假设从网络请求中获取到的 JSON 数据如下:
{
"id": 1,
"title": "Flutter 网络请求",
"author": "张三"
}
解析并创建 Article 对象的代码如下:
void main() {
Map<String, dynamic> jsonData = {
"id": 1,
"title": "Flutter 网络请求",
"author": "张三"
};
Article article = Article.formMap(jsonData);
print(article); // 输出: Article{id: 1, title: Flutter 网络请求, author: 张三}
}
通过这种方式,factory Article.formMap(dynamic map) 提供了一种方便且类型安全的方法,从 JSON 数据中创建 Article 对象。
为什么需要重写 toString 方法
在 Dart 中,每个类都继承自 Object 类,而 Object 类提供了一个默认的 toString 方法。默认的 toString 方法返回的是类的名称和对象的内存地址,例如 Instance of 'Article'。这种默认的字符串表示通常不够详细,无法提供对象的具体内容。
重写 toString 方法可以提供一个更具可读性的字符串表示,便于调试和日志记录。具体来说,重写 toString 方法有以下几个好处:
- 调试方便:
在调试过程中,可以直接查看对象的具体属性值,而不是只能看到内存地址。
便于打印对象信息,快速定位问题。 - 日志记录:
在日志中记录对象信息时,可以提供更详细的上下文。
便于后续分析和问题排查。 - 代码可读性:
提高代码的可读性和可维护性。
其他开发者可以更容易地理解对象的内容。
AriticleCotent

@override
Widget build(BuildContext context) {
if (_loading) { //如果 _loading 为 true,则执行代码块中的内容。
return Center( //Center 小部件用于将子小部件居中显示。
child: Wrap(
spacing: 10, //设置子小部件之间的间距为 10 像素。
direction: Axis.vertical, //设置排列方向为垂直方向。
crossAxisAlignment:WrapCrossAlignment.center, //设置子小部件在交叉轴(水平轴)上的对齐方式为居中对齐。
children: const [
CupertinoActivityIndicator(), //显示一个 iOS 风格的加载指示器。
Text(
"数据加载中,请稍后...",
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,则执行代码块中的内容。
return Center( //Center 小部件用于将子小部件居中显示。
child: Wrap(
spacing: 10, //设置子小部件之间的间距为 10 像素。
direction: Axis.vertical, //设置排列方向为垂直方向。
crossAxisAlignment:WrapCrossAlignment.center, //设置子小部件在交叉轴(水平轴)上的对齐方式为居中对齐。
children: const [
CupertinoActivityIndicator(), //显示一个 iOS 风格的加载指示器。
Text(
"数据加载中,请稍后...",
style: TextStyle(color: Colors.grey), //显示一条加载提示文本,文本颜色为灰色。
)
],
),
);
}
return EasyRefresh( //小部件:用于实现下拉刷新和上拉加载更多的功能。
header: const ClassicHeader( //ClassicHeader 是 EasyRefresh 提供的一个经典风格的下拉刷新头部。
dragText: "下拉加载",
armedText: "释放刷新",
readyText: "开始加载",
processingText: "正在加载",
processedText: "刷新成功",
),
footer: const ClassicFooter(processingText: "正在加载"),
// dragText: "下拉加载":当用户下拉时显示的文本。
// armedText: "释放刷新":当用户下拉到一定距离时显示的文本。
// readyText: "开始加载":当用户释放手指准备刷新时显示的文本。
// processingText: "正在加载":刷新过程中显示的文本。
// processedText: "刷新成功":刷新完成后显示的文本。
// ClassicFooter 是 EasyRefresh 提供的一个经典风格的上拉加载更多底部。
// processingText: "正在加载":加载过程中显示的文本。
onRefresh: _onRefresh,
onLoad: _onLoad, //指定加载更多时调用的方法。
child: ListView.builder( //用于创建一个可滚动的列表视图。
itemExtent: 80,
itemCount: _articles.length,
itemBuilder: _buildItemByIndex,
// itemExtent: 80:设置每个列表项的高度为 80 像素。
// itemCount: _articles.length:设置列表项的数量为 _articles 列表的长度。
// itemBuilder: _buildItemByIndex:指定构建每个列表项的方法。
),
);
}
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('点击了第 $index 个 item}');
return ArticleItem(
article: _articles[index], //index,从0开始自动增长
onTap: _jumpToPage, //把当前article传给_jumpToPage
);
}
void _jumpToPage(Article article) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => ArticleDetailPage(article: article), //跳转到详情界面
),
);
}
}
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
代码解释
这段代码定义了一个 Flutter 应用中的 ArticleDetailPage 页面,用于显示文章详情。页面中使用了 WebViewWidget 来加载并显示文章的网页内容。以下是代码的具体解释:
1. 导入包
import 'package:flutter/material.dart';
import '../model/article.dart';
import 'package:webview_flutter/webview_flutter.dart';
flutter/material.dart:导入 Flutter 的 Material Design 组件库。../model/article.dart:导入自定义的Article模型类,用于表示文章数据。webview_flutter/webview_flutter.dart:导入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是一个有状态的 Widget (StatefulWidget),它接收一个Article对象作为参数,并将其传递给其状态类_ArticleDetailPageState。
3. 定义 _ArticleDetailPageState 状态类
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 的控制器。- 使用级联操作符
..连续调用多个方法:setJavaScriptMode(JavaScriptMode.unrestricted):允许 WebView 执行 JavaScript。setBackgroundColor(const Color(0x00000000)):设置 WebView 的背景颜色为透明。loadRequest(Uri.parse(widget.article.url)):加载文章的 URL。
构建方法 build
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.article.title),
),
body: WebViewWidget(controller: _controller),
);
}
- 使用
Scaffold作为页面的根布局,包含一个应用栏 (AppBar) 和主体 (body)。AppBar显示文章标题。WebViewWidget使用之前初始化的_controller加载并显示文章的网页内容。
总结
这段代码实现了一个简单的文章详情页面,通过 WebViewWidget 加载并显示文章的网页内容。页面的标题是文章的标题,主体部分是一个 WebView,用于展示文章的网页内容。
浙公网安备 33010602011771号