上一篇文章Flutter 中的下拉刷新和上拉加载中,我介绍了如何在Flutter中实现下拉刷新和上拉加载的效果,今天我们继续以上文中的代码为例,来介绍如何加载HTML文档内容。
首先来聊聊如何通过flutter_html这个第三方库来解析html文档内容吧:
这是列表页面的代码,里面包含下拉刷新、上拉加载,以及加载中的动画:
代码语言:javascript复制import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
class RefreshPage extends StatefulWidget {
RefreshPage({Key key}) : super(key: key);
_RefreshPageState createState() => _RefreshPageState();
}
class _RefreshPageState extends State<RefreshPage> {
List _dataSources = List();
ScrollController _scrollController = ScrollController();
int _page = 1; //请求第几页数据,用于分页请求数据
bool _haveMore = true; //是否还有更多的数据可以请求
//网络请求数据
_requestData() async {
String urlStr =
"http://www.phonegap100.com/appapi.php?a=getPortalList&catid=20&page=$_page";
var response = await Dio().get(urlStr);
if (response.statusCode == 200) {
setState(() {
List resultList = jsonDecode(response.data)["result"];
// print(resultList);
if (this._page == 1) {
//第一次加载或者下拉加载
this._dataSources = resultList;
} else {
//上拉刷新(将新加载的数据拼接到原来的数据数组中)
this._dataSources.addAll(resultList);
}
this._page ; //每请求成功一次,page都要加1
/**
* 这里根据当前返回的数组长度是否小于pagesize来判断接下来是否还有更多数据
* 这里的pagesize是20
*/
if (resultList.length < 20) {
this._haveMore = false;
}
});
// print(this._dataSources);
} else {
print("Request failed with status: ${response.statusCode}.");
}
}
//下拉刷新
/**
* 注意,这里只是给大家演示一下下拉刷新组件,所以下拉刷新的逻辑写的比较简单
* 如果真的在项目中使用的话,大家还是思考全面,不要简单拷贝如下代码!
*/
Future<void> _refreshData() async {
await Future.delayed(Duration(seconds: 2), () {
print("请求数据完成");
this._page = 1;
_requestData();
});
}
@override
void initState() {
super.initState();
//页面一加载就执行网络请求
this._requestData();
//监听滚动条的滚动事件
_scrollController.addListener(() {
// print(_scrollController.position.pixels); //滚动的距离
// print(_scrollController.position.maxScrollExtent); //最大滚动范围
//当滚动到最底部的时候,加载新的数据
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
//当还有更多数据的时候才会进行加载新数据
if (this._haveMore) {
this._requestData();
}
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("refreshDemo")),
body: _dataSources.length == 0
? _loadMoreWidget()
: RefreshIndicator(
child: ListView.builder(
controller: _scrollController,
itemCount: this._dataSources.length,
itemBuilder: (context, index) {
/**
* 当当前index等于数据源数据的长度减1的时候,
* 说明当前的ListTile是最后一个ListTile,
* 此时需要上拉加载新的数据,因此要在最底部显示一个加载中的圈圈
*/
if (index == this._dataSources.length - 1) {
return Column(
children: <Widget>[
ListTile(
title: Text(this._dataSources[index]["title"],
maxLines: 1),
onTap: () {
//这里响应用户点击选择事件
Navigator.pushNamed(context, "/detailPage",
arguments: {
"aid": this._dataSources[index]["aid"]
});
},
),
Divider(),
_loadMoreWidget()
],
);
} else {
return Column(
children: <Widget>[
ListTile(
title: Text(this._dataSources[index]["title"],
maxLines: 1),
//点击对应的条目之后响应
onTap: () {
//跳转到详情页面,并将aid传递过去
Navigator.pushNamed(context, "/detailPage",
arguments: {
"aid": this._dataSources[index]["aid"]
});
},
),
Divider()
],
);
}
},
),
onRefresh: _refreshData,
),
);
}
//加载中的圈圈
Widget _loadMoreWidget() {
if (this._haveMore) {
//还有更多数据可以加载
return Center(
child: Padding(
padding: EdgeInsets.all(10),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text("加载中......"),
CircularProgressIndicator(
strokeWidth: 1,
)
],
),
),
);
} else {
//当没有更多数据可以加载的时候,
return Center(
child: Text("我是有底线的"),
);
}
}
}
在上述代码中,点击对应单元格之后响应的代码如下:
代码语言:javascript复制//点击对应的条目之后响应
onTap: () {
//跳转到详情页面,并将aid传递过去
Navigator.pushNamed(context, "/detailPage",
arguments: {
"aid": this._dataSources[index]["aid"]
});
},
跳入的详情页面的代码如下:
代码语言:javascript复制mport 'dart:convert';
import 'package:flutter_html/flutter_html.dart';
import 'package:html/dom.dart' as dom;
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
class DetailPage extends StatefulWidget {
DetailPage({Key key, this.arguments}) : super(key: key);
//传递的参数
final Map arguments;
_DetailPageState createState() => _DetailPageState(arguments);
}
class _DetailPageState extends State<DetailPage> {
//传递的参数
Map arguments;
//记录网络请求回来的数据
Map contentMap;
_DetailPageState(this.arguments);
_requestData() async {
String url =
"http://www.phonegap100.com/appapi.php?a=getPortalArticle&aid=${this.arguments["aid"]}";
var response = await Dio().get(url);
if (response.statusCode == 200) {
print(response.data);
setState(() {
this.contentMap = jsonDecode(response.data)["result"][0];
});
} else {
print("Request failed with status: ${response.statusCode}.");
}
}
@override
void initState() {
super.initState();
//页面加载时即请求数据
_requestData();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
this.contentMap == null ? "新闻详情" : this.contentMap["title"])),
body: ListView(
children: <Widget>[
Html(
//通过data参数来配置html文档
data: '''
${this.contentMap == null ? "888" : this.contentMap["content"]}
''',
//网页内容的内边距
padding: EdgeInsets.all(8.0),
//网址链接的展示样式
linkStyle: const TextStyle(
color: Colors.redAccent,
decorationColor: Colors.redAccent,
decoration: TextDecoration.underline,
),
//点击链接地址的时候响应
onLinkTap: (url) {
print("Opening $url...");
},
//点击图片的时候响应
onImageTap: (src) {
print(src);
},
//Must have useRichText set to false for this to work
customRender: (node, children) {
if (node is dom.Element) {
switch (node.localName) {
case "custom_tag":
return Column(children: children);
}
}
return null;
},
customTextAlign: (dom.Node node) {
if (node is dom.Element) {
switch (node.localName) {
case "p":
return TextAlign.justify;
}
}
return null;
},
customTextStyle: (dom.Node node, TextStyle baseStyle) {
if (node is dom.Element) {
switch (node.localName) {
case "p":
return baseStyle.merge(TextStyle(height: 2, fontSize: 20));
}
}
return baseStyle;
},
)
],
),
);
}
}
在详情页面,我们首先通过列表页面传递过来的参数来网络请求页面详情数据,然后就能够得到网络返回的html文本,之后我们通过flutter_html这个第三方来解析html文本内容,解析的代码如下:
代码语言:javascript复制Html(
//通过data参数来配置html文档
data: '''
${this.contentMap == null ? "888" : this.contentMap["content"]}
''',
//网页内容的内边距
padding: EdgeInsets.all(8.0),
//网址链接的展示样式
linkStyle: const TextStyle(
color: Colors.redAccent,
decorationColor: Colors.redAccent,
decoration: TextDecoration.underline,
),
//点击链接地址的时候响应
onLinkTap: (url) {
print("Opening $url...");
},
//点击图片的时候响应
onImageTap: (src) {
print(src);
},
//Must have useRichText set to false for this to work
customRender: (node, children) {
if (node is dom.Element) {
switch (node.localName) {
case "custom_tag":
return Column(children: children);
}
}
return null;
},
customTextAlign: (dom.Node node) {
if (node is dom.Element) {
switch (node.localName) {
case "p":
return TextAlign.justify;
}
}
return null;
},
customTextStyle: (dom.Node node, TextStyle baseStyle) {
if (node is dom.Element) {
switch (node.localName) {
case "p":
return baseStyle.merge(TextStyle(height: 2, fontSize: 20));
}
}
return baseStyle;
},
)
其实所谓的解析,无非就是通过Html组件来展示html文本的内容。
flutter_html这个第三方库适合解析轻量的、不是特别复杂的html文本内容,它仅能够解析常用的那些html标签,所以对于复杂的html内容,我们通常不使用flutter_html,而是使用webView。
flutter_inappbrower
前面我们使用flutter_html加载html内容的步骤如下:
- 首先通过网络请求获取到对应的html内容文本
- 通过Html这个第三方库中的组件来展示html内容文本。
接下来我们介绍一下如何通过WebView来加载html。通过WebView加载html内容,实际上就是应用内的浏览器展示网页内容。在Flutter中,实现WebView加载html内容的第三方组件有很多,这里我们给推荐flutter_inappbrower这一个第三方组件。
详情页面的代码如下:
代码语言:javascript复制import 'package:flutter/material.dart';
import 'package:flutter_inappbrowser/flutter_inappbrowser.dart';
class DetailPage extends StatefulWidget {
DetailPage({Key key, this.arguments}) : super(key: key);
//传递的参数
final Map arguments;
_DetailPageState createState() => _DetailPageState(arguments);
}
class _DetailPageState extends State<DetailPage> {
//传递的参数
Map arguments;
//记录网络请求回来的数据
Map contentMap;
//记录网页加载的进度(0-100)
int progress = 0;
_DetailPageState(this.arguments);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("新闻详情")),
body: Column(
children: <Widget>[
//在网页加载完成之前展示“加载中”的样式。
this.progress < 100 ? _loadingWidget() : Text(""),
Expanded(
child: InAppWebView(
//网页的url
initialUrl:
"http://www.phonegap100.com/newscontent.php?aid=${this.arguments["aid"]}",
//监听网页加载的进度
onProgressChanged:
(InAppWebViewController controller, int progress) {
print(progress);
setState(() {
this.progress = progress;
});
},
),
)
],
),
);
}
//加载中的圈圈
Widget _loadingWidget() {
return Center(
child: Padding(
padding: EdgeInsets.all(10),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text("加载中......"),
CircularProgressIndicator(
strokeWidth: 1,
)
],
),
),
);
}
}
效果如下:
需要注意的是:
1,
要在你的 info.plist中添加
代码语言:javascript复制<key>io.flutter.embedded_views_preview</key><true/>
如果不添加,则会报错误:
代码语言:javascript复制[VERBOSE-2:platform_view_layer.cc(22)] Trying to embed a platform view but the PrerollContext does not support embedding
[VERBOSE-2:platform_view_layer.cc(31)] Trying to embed a platform view but the PaintContext does not support embedding
2,使用之前要认真阅读文档,按照文档对项目进行对应的配置,比如文档中要求最低安卓SDK版本是17,那么我们就需要搜索minSdkVersion,然后将minSdkVersion的值改为17,如下:
代码语言:javascript复制minSdkVersion 17
除此之外还会有其他的要求,所以大家一定要在使用之前认真阅读文档!
总结:
本文我们简单讲述了两个第三方框架:flutter_html和flutter_inappbrower。
flutter_html可用于加载轻量级的html文本内容,对于复杂的远程html内容,我们需要使用webview来加载,flutter_inappbrower是Flutter中实现WebView的最好用的第三方组件。
以上。