Flutter中的html内容加载

上一篇文章Flutter 中的下拉刷新和上拉加载中,我介绍了如何在Flutter中实现下拉刷新和上拉加载的效果,今天我们继续以上文中的代码为例,来介绍如何加载HTML文档内容。

首先来聊聊如何通过flutter_html这个第三方库来解析html文档内容吧:

这是列表页面的代码,里面包含下拉刷新、上拉加载,以及加载中的动画:

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("我是有底线的"),
      );
    }
  }
}

在上述代码中,点击对应单元格之后响应的代码如下:

//点击对应的条目之后响应
onTap: () {
  //跳转到详情页面,并将aid传递过去
  Navigator.pushNamed(context, "/detailPage",
      arguments: {
        "aid": this._dataSources[index]["aid"]
      });
},

跳入的详情页面的代码如下:

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文本内容,解析的代码如下:

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这一个第三方组件。

详情页面的代码如下:

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中添加

<key>io.flutter.embedded_views_preview</key><true/>

如果不添加,则会报错误:

[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,如下:

minSdkVersion 17

除此之外还会有其他的要求,所以大家一定要在使用之前认真阅读文档!

总结:

本文我们简单讲述了两个第三方框架:flutter_html和flutter_inappbrower。

flutter_html可用于加载轻量级的html文本内容,对于复杂的远程html内容,我们需要使用webview来加载,flutter_inappbrower是Flutter中实现WebView的最好用的第三方组件

以上。

原文发布于微信公众号 - iOS小生活(iOSHappyLife)

原文发表时间:2019-09-07

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券