网页数据访问

接口准备json数据

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

img

如图所示,主要的信息都在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内容如下:

img

Dio

网络请求是非常通用的能力,开发者自己来写非常复杂,所以一般使用三方的依赖库。对于 Flutter 网络请求来说,最受欢迎的是 dio , 使用前先添加依赖

在 ArticleApi 中持有 Dio 类型的 _client 对象,构造时可以设置 baseUrl 。然后提供 loadArticles 方法,用于加载第 page 页的数据,其中的逻辑处理,就是加载网络数据的核心。

使用起来也很方便,提供 Dio#get 方法就可以异步获取数据,得到之后,从结果中拿到自己想要的数据,生成 Article 列表即可。

介绍 Dio

Dio 是一个强大的 Flutter 和 Dart HTTP 客户端库,用于处理网络请求。它提供了简单易用的 API,支持多种功能,如拦截器、请求取消、超时设置、文件上传下载等。以下是 Dio 的一些主要特点和使用方法:

主要特点

  1. 支持多种请求方法:GET、POST、PUT、DELETE、HEAD、PATCH 等。
  2. 拦截器:可以在请求发送前和响应接收后进行拦截处理。
  3. 请求取消:可以取消正在进行的请求。
  4. 超时设置:可以设置连接超时和接收超时。
  5. 文件上传下载:支持文件的上传和下载。
  6. 转换器:支持多种数据格式的转换,如 JSON、FormData 等。
  7. Cookie 管理:内置 Cookie 管理器。

使用方法

  1. 添加依赖

    pubspec.yaml 文件中添加 Dio 依赖:

    dependencies:
      dio: ^5.0.0
    
  2. 导入库

    在 Dart 文件中导入 Dio 库:

    import 'package:dio/dio.dart';
    
  3. 创建 Dio 实例

    创建一个 Dio 实例,并可以设置基础 URL 和其他选项:

    final Dio dio = Dio(BaseOptions(
      baseUrl: 'https://www.wanandroid.com',
      connectTimeout: 5000, // 连接超时时间
      receiveTimeout: 3000, // 接收超时时间
    ));
    
  4. 发送请求

    使用 Dio 实例发送请求,例如 GET 请求:

    try {
      Response response = await dio.get('/article/list/0/json');
      if (response.statusCode == 200) {
        print(response.data);
      }
    } catch (e) {
      print(e);
    }
    
  5. 处理响应

    处理响应数据,例如解析 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);
    }
    
  6. 使用拦截器

    添加请求和响应拦截器:

    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 对象。以下是详细的解释:

  1. 工厂构造函数

    • factory 关键字表示这是一个工厂构造函数。
    • 工厂构造函数可以返回一个缓存的实例,或者根据条件返回不同的实例。
  2. 参数

    • dynamic map:表示传入的数据是一个动态类型的 Map 对象,通常是从 JSON 解析得到的数据。
  3. 作用

    • 从传入的 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}';
  }
}

解释示例代码

  1. 类定义

    • Article 类包含三个属性:idtitleauthor
  2. 构造函数

    • Article({required this.id, required this.title, required this.author}):标准的构造函数,用于创建 Article 对象。
  3. 工厂构造函数

    • 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

img

 @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,用于展示文章的网页内容。

posted on 2025-02-14 15:26  爱吐水的小火龙  阅读(68)  评论(0)    收藏  举报