前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Flutter中的html内容加载

Flutter中的html内容加载

作者头像
拉维
发布2019-09-10 16:03:13
16.5K1
发布2019-09-10 16:03:13
举报
文章被收录于专栏:iOS小生活iOS小生活

上一篇文章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的最好用的第三方组件

以上。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-09-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 iOS小生活 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档