Flutter项目实战

flutter 数据请求(dio)

在pubspec.yaml,引入dio请求库

dio: ^4.0.6

初始化Dio

创建一个新的文件:dio_client.dart包含DioClient

class DioClient {
  // TODO: Set up and define the methods for network operations
}

您可以使用以下方法初始化 Dio:

import 'package:dio/dio.dart';

class DioClient {
  final Dio _dio = Dio();
}

定义 API 服务器的基本 URL:

import 'package:dio/dio.dart';

class DioClient {
  final Dio _dio = Dio();

  final _baseUrl = 'http://192.168.1.128';

  // TODO: Add methods
}

[!note|labelVisibility:hidden|iconVisibility:hidden] 定义 GET 请求

Future<Page?> getBanner() async {
  Page? res;
  try {
    Response response = await _dio.get(
      "/content/page?siteId=1&channelIds=1752&page=1&size=5&typeIds=175",
    );
    LogUtil.v("结果是:${response.data}");
    if (response.data['code'] == 200) {
      res = Page.fromJson(response.data['data']);
    }
  } catch (e) {
    LogUtil.e("Error: $e");
  }
  return res;
}

[!note|labelVisibility:hidden|iconVisibility:hidden] 定义 POST 请求

Future<Menu?> getMenu() async {
  Menu? res;
  try {
    Response response = await _dio.post(
        "/api/pldk-servicehall/homeHall/queryListColumn",
        data: {'laborunCode': 1410, 'platform': 2});
    LogUtil.v("结果是:${response.data}");
    res = Menu.fromJson(response.data);
  } catch (e) {
    LogUtil.e("Error: $e");
  }
  return res;
}

[!note|style:flat|labelVisibility:hidden|iconVisibility:hidden] 选择和定义您的请求头

baseUrl您可以在内部定义它BaseOptions并在实例化时传递一次,而不是每次都传递端点Dio。

为此,您需要进行Dio如下初始化:

final Dio _dio = Dio(
  BaseOptions(
    baseUrl: 'http://192.168.1.128',
    connectTimeout: 5000,
    receiveTimeout: 3000,
  ),
);

[!note|style:flat|labelVisibility:hidden|iconVisibility:hidden] 上传文件

Dio 使上传文件到服务器的过程变得更加简单。它可以同时处理多个文件上传,并有一个简单的回调来跟踪它们的进度,这使得它比http包更容易使用。

您可以使用FormDataDio轻松地将文件上传到服务器。以下是向 API 发送图像文件的示例:

String imagePath;

FormData formData = FormData.fromMap({
  "image": await MultipartFile.fromFile(
    imagePath,
    filename: "upload.jpeg",
  ),
});

Response response = await _dio.post(
  '/search',
  data: formData,
  onSendProgress: (int sent, int total) {
    print('$sent $total');
  },
);

[!note|style:flat|labelVisibility:hidden|iconVisibility:hidden] 拦截器

您可以在使用then处理 Dio 请求、响应错误之前拦截它们catchError。在实际场景中,拦截器可用于使用JSON Web Tokens (JWT)进行授权、解析 JSON、处理错误以及轻松调试 Dio 网络请求。

您可以通过重写回调运行拦截:onRequestonResponse,和onError

对于我们的示例,我们将定义一个简单的拦截器来记录不同类型的请求。创建一个名为LoggingInterceptor以下扩展的新类:

import 'package:dio/dio.dart';

import 'log_util.dart';

/// @author 田琪
/// class Logging
/// component : ${options.path}/Logging { ... }
/// comment : dio日志拦截系统
class Logging extends Interceptor {
  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    LogUtil.v(
      'REQUEST[${options.method}] => PATH: ${options.path} => HEADER: ${options.headers}',
    );
    super.onRequest(options, handler);
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    LogUtil.d(
      'RESPONSE[${response.statusCode}] => PATH: ${response.requestOptions.path}',
    );
    super.onResponse(response, handler);
  }

  @override
  void onError(DioError err, ErrorInterceptorHandler handler) {
    LogUtil.e(
      'ERROR[${err.response?.statusCode}] => PATH: ${err.requestOptions.path}',
    );
    super.onError(err, handler);
  }
}

在这里,我们覆盖了由 Dio 请求触发的各种回调,并为每个回调添加了一个打印语句,用于在控制台中记录请求。

Dio在初始化期间添加拦截器:

final Dio _dio = Dio(
  BaseOptions(
    baseUrl: 'http://192.168.1.128',
    connectTimeout: 5000,
  ),
)..interceptors.add(Logging());

dart实例类快速生成

[!Tip|style:flat] 根据 JSON 快速生成 Dart 的 bean 类的网址JSON to Dart

打开网址,把json文件复制进入输入框,然后输入类名,点击Generate Dart快速生成实体类

项目实战案例

构建我们自己的日志系统

import 'dart:developer';

/// Log Util.
class LogUtil {
  static const String _defTag = 'common_utils';
  static bool _debugMode = false; //是否是debug模式,true: log v 不输出.
  static int _maxLen = 128;
  static String _tagValue = _defTag;

  static void init({
    String tag = _defTag,
    bool isDebug = false,
    int maxLen = 128,
  }) {
    _tagValue = tag;
    _debugMode = isDebug;
    _maxLen = maxLen;
  }

  static void d(Object? object, {String? tag}) {
    if (_debugMode) {
      log('$tag d | ${object?.toString()}');
    }
  }

  static void e(Object? object, {String? tag}) {
    _printLog(tag, ' e ', object);
  }

  static void v(Object? object, {String? tag}) {
    if (_debugMode) {
      _printLog(tag, ' v ', object);
    }
  }

  static void _printLog(String? tag, String stag, Object? object) {
    String da = object?.toString() ?? 'null';
    tag = tag ?? _tagValue;
    if (da.length <= _maxLen) {
      // ignore: avoid_print
      print('$tag$stag $da');
      return;
    }
    // ignore: avoid_print
    print(
        '$tag$stag — — — — — — — — — — — — — — — — st — — — — — — — — — — — — — — — —');
    while (da.isNotEmpty) {
      if (da.length > _maxLen) {
        // ignore: avoid_print
        print('$tag$stag| ${da.substring(0, _maxLen)}');
        da = da.substring(_maxLen, da.length);
      } else {
        // ignore: avoid_print
        print('$tag$stag| $da');
        da = '';
      }
    }
    // ignore: avoid_print
    print(
        '$tag$stag — — — — — — — — — — — — — — — — ed — — — — — — — — — — — — — — — —');
  }
}

[!note|style:flat|labelVisibility:hidden|iconVisibility:hidden] 在 main.dart 的 main 方法中初始化日志

void main() {
  // 初始化日志系统
  LogUtil.init(tag: "package:yuncheng-utils", isDebug: true);

  runApp(const Myapp());
}

调用jecms的接口开始渲染轮播图

上文中的GET方法返回了cms请求的banner的列表

  • page.dart
import 'banner.dart';

class Page {
  List<Banner>? _content;
  bool? _first;
  bool? _last;
  int? _size;
  int? _totalElements;
  int? _totalPages;

  Page(
      {List<Banner>? content,
      bool? first,
      bool? last,
      int? size,
      int? totalElements,
      int? totalPages}) {
    if (content != null) {
      this._content = content;
    }
    if (first != null) {
      this._first = first;
    }
    if (last != null) {
      this._last = last;
    }
    if (size != null) {
      this._size = size;
    }
    if (totalElements != null) {
      this._totalElements = totalElements;
    }
    if (totalPages != null) {
      this._totalPages = totalPages;
    }
  }

  List<Banner>? get content => _content;
  set content(List<Banner>? content) => _content = content;
  bool? get first => _first;
  set first(bool? first) => _first = first;
  bool? get last => _last;
  set last(bool? last) => _last = last;
  int? get size => _size;
  set size(int? size) => _size = size;
  int? get totalElements => _totalElements;
  set totalElements(int? totalElements) => _totalElements = totalElements;
  int? get totalPages => _totalPages;
  set totalPages(int? totalPages) => _totalPages = totalPages;

  Page.fromJson(Map<String, dynamic> json) {
    if (json['content'] != null) {
      _content = <Banner>[];
      json['content'].forEach((v) {
        _content!.add(new Banner.fromJson(v));
      });
    }
    _first = json['first'];
    _last = json['last'];
    _size = json['size'];
    _totalElements = json['totalElements'];
    _totalPages = json['totalPages'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    if (this._content != null) {
      data['content'] = this._content!.map((v) => v.toJson()).toList();
    }
    data['first'] = this._first;
    data['last'] = this._last;
    data['size'] = this._size;
    data['totalElements'] = this._totalElements;
    data['totalPages'] = this._totalPages;
    return data;
  }
}
  • lib\bean\banner.dart
class Banner {
  int? _id;
  String? _title;
  String? _txt;
  String? _releaseTime;
  String? _iconUrl;

  Banner(
      {int? id,
      String? title,
      String? txt,
      String? releaseTime,
      String? iconUrl}) {
    if (id != null) {
      this._id = id;
    }
    if (title != null) {
      this._title = title;
    }
    if (txt != null) {
      this._txt = txt;
    }
    if (releaseTime != null) {
      this._releaseTime = releaseTime;
    }
    if (iconUrl != null) {
      this._iconUrl = iconUrl;
    }
  }

  int? get id => _id;
  set id(int? id) => _id = id;
  String? get title => _title;
  set title(String? title) => _title = title;
  String? get txt => _txt;
  set txt(String? txt) => _txt = txt;
  String? get releaseTime => _releaseTime;
  set releaseTime(String? releaseTime) => _releaseTime = releaseTime;
  String? get iconUrl => _iconUrl;
  set iconUrl(String? iconUrl) => _iconUrl = iconUrl;

  Banner.fromJson(Map<String, dynamic> json) {
    _id = json['id'];
    _title = json['title'];
    _txt = json['txt'];
    _releaseTime = json['releaseTime'];
    _iconUrl = json['iconUrl'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['id'] = this._id;
    data['title'] = this._title;
    data['txt'] = this._txt;
    data['releaseTime'] = this._releaseTime;
    data['iconUrl'] = this._iconUrl;
    return data;
  }
}

准备工作已完成,开始页面的编写

  • lib\tabbar\home_screen.dart
import 'package:cached_network_image/cached_network_image.dart';
import 'package:card_swiper/card_swiper.dart';
import 'package:flutter/material.dart';

import '../utils/dio_client.dart';
import '../bean/page.dart' as bp;
import '../bean/banner.dart' as bank;

class HomeScreen extends StatefulWidget {
  const HomeScreen({Key? key}) : super(key: key);

  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  final DioClient _client = DioClient();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("临汾市总工会"),
      ),
      drawer: Drawer(
        child: Column(children: [
          DrawerHeader(
            decoration: const BoxDecoration(
              color: Colors.white,
            ),
            padding: const EdgeInsets.all(0),
            margin: const EdgeInsets.all(0),
            child: UserAccountsDrawerHeader(
              margin: const EdgeInsets.all(0),
              currentAccountPicture: ClipOval(
                child: Image.network(
                  "https://pic.tianqinote.com/classroom_1.png",
                  fit: BoxFit.fill,
                ),
              ),
              currentAccountPictureSize: const Size.square(72.0),
              accountName: const Text(
                "田琪",
                style: TextStyle(color: Colors.black),
              ),
              accountEmail: const Text(
                "[email protected]",
                style: TextStyle(color: Colors.black),
              ),
              decoration: const BoxDecoration(
                image: DecorationImage(
                  image: NetworkImage("https://pic.tianqinote.com/loading.png"),
                  fit: BoxFit.cover,
                ),
              ),
            ),
          ),
          const ListTile(
            leading: CircleAvatar(
              child: Icon(Icons.settings),
            ),
            title: Text("设置"),
          ),
          const ListTile(
            leading: CircleAvatar(
              child: Icon(Icons.person),
            ),
            title: Text("个人"),
          ),
          const ListTile(
            leading: CircleAvatar(
              child: Icon(Icons.edit),
            ),
            title: Text("修改密码"),
          )
        ]),
      ),
      body: Padding(
        padding: const EdgeInsets.all(0.0),
        child: FutureBuilder(
          future: _client.getBanner(),
          builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
            if (snapshot.hasData) {
              bp.Page? res = snapshot.data;
              if (res != null) {
                return _buildSwiper(res.content);
              }
            }
            return const CircularProgressIndicator();
          },
        ),
      ),
    );
  }

  Widget _buildSwiper(List<bank.Banner>? list) {
    return SizedBox(
      width: 360.0,
      height: 200.0,
      child: Swiper(
        itemBuilder: (context, index) {
          return CachedNetworkImage(
            imageUrl: DioClient.myBaseUrl + list![index].iconUrl.toString(),
            placeholder: (context, url) => Image.network(
              "https://pic.tianqinote.com/loading.png",
              fit: BoxFit.cover,
            ),
            errorWidget: (context, url, error) =>
                const Icon(Icons.image_not_supported),
            fit: BoxFit.cover,
          );
        },
        itemCount: list!.length,
        pagination: const SwiperPagination(),
        autoplay: true,
        onTap: (index) {
          Navigator.pushNamed(context, "/web",
              arguments: {'txt': list[index].txt, 'title': list[index].title});
        },
      ),
    );
  }
}

这里边我们使用了Swiper组件,我们单独介绍一下card_swiper组件

card_swiper组件

  • 首先在pubspec.yaml文件中引入card_swiper
card_swiper: ^2.0.4

基础

参数 默认值 说明
scrollDirection Axis.horizontal 如果是 Axis.horizontal, 滚动视图的子视图水平排列在一行中,而不是垂直排列在一列中。
axisDirection AxisDirection.left 如果是 AxisDirection.right, 滚动视图的子视图按行排列在右侧,而不是左侧。注意:当前仅支持水平堆栈布局
loop true 设置为 false 可禁用连续循环模式。
index 0 初始幻灯片的索引号。
autoplay false 设置为 true 启用自动播放模式。
onIndexChanged void onIndexChanged(int index) 用户刷卡或自动播放时使用新索引调用
onTap void onTap(int index) 当用户点击ui时调用。
duration 300.0 每笔交易动画成本的milliscends
pagination null 设置 SwiperPagination() 以显示默认分页
control null 设置 SwiperControl() 以显示默认控件按钮

分页码

参数 默认值 说明
alignment Alignment.bottomCenter 如果要将分页置于其他位置,请更改此值
margin const EdgeInsets.all(10.0) 父容器内侧之间的距离。
builder SwiperPagination.dots 有三种默认的样式SwiperPagination.dots, SwiperPagination.fraction and SwiperPagination.rect,这些可以定制。

如果您想自定义自己的分页,可以这样做:

Swiper(
    ...,
    pagination: SwiperCustomPagination(
        builder:(BuildContext context, SwiperPluginConfig config){
            return YourOwnPagination();
        }
    )
);

实现分页

DotSwiperPaginationBuilder

参数 默认值 说明 是否必传
activeColor Theme.of(context).primaryColor Active bullet color false
color Theme.of(context).scaffoldBackgroundColor Bullet color false
activeSize 10.0 Active bullet size false
size 10.0 Bullet size false
space 3.0 Distance between bullets false
key - key false

FractionPaginationBuilder

参数 默认值 说明 是否必传
activeColor Theme.of(context).primaryColor Active font color false
color Theme.of(context).scaffoldBackgroundColor font color false
activeFontSize 35.0 Active font size false
fontSize 20.0 Font size false
key - key false

RectSwiperPaginationBuilder

参数 默认值 说明 是否必传
activeColor Theme.of(context).primaryColor Active bullet color false
color Theme.of(context).scaffoldBackgroundColor Bullet color false
activeSize 10.0 Active bullet size false
size 10.0 Bullet size false
space 3.0 Distance between bullets false
key - key false

Control buttons

控件也从SwiperPlugin扩展,设置SwiperControl()以显示默认控件按钮。

参数 默认值 说明
iconPrevious Icons.arrow_back_ios 显示上一个控制按钮的图标数据
iconNext Icons.arrow_forward_ios 接下来要显示的图标数据。
color Theme.of(context).primaryColor 控制按钮颜色
disableColor Theme.of(context).disabledColor 禁用控制按钮颜色
size 30.0 控制按钮大小
padding const EdgeInsets.all(5.0) 控制按钮填充

Controller

控制器用于控制刷卡器的索引,启动或停止自动播放。您可以通过SwipController()创建控制器,并通过进一步使用保存实例。

方法 说明
void move(int index, {bool animation: true}) 移动到指定索引,是否带有动画
void next({bool animation: true}) 下一个
void previous({bool animation: true}) 上一个
void startAutoplay() 开始自动播放
void stopAutoplay() 停止自动播放

Autoplay

参数 默认值 说明
autoplayDelay 3000 自动播放延迟毫秒。
autoplayDisableOnInteraction true 如果设置为true,则在使用滑动时禁用自动播放。

内置布局

Swiper(
  itemBuilder: (BuildContext context, int index) {
    return Image.network(
      "https://via.placeholder.com/288x188",
      fit: BoxFit.fill,
    );
  },
  itemCount: 10,
  viewportFraction: 0.8,
  scale: 0.9,
)

Swiper(
  itemBuilder: (BuildContext context, int index) {
    return Image.network(
      "https://via.placeholder.com/288x188",
      fit: BoxFit.fill,
    );
  },
  itemCount: 10,
  itemWidth: 300.0,
  layout: SwiperLayout.STACK,
)

Swiper(
    itemBuilder: (BuildContext context, int index) {
      return Image.network(
        "https://via.placeholder.com/288x188",
        fit: BoxFit.fill,
      );
    },
    itemCount: 10,
    itemWidth: 300.0,
    itemHeight: 400.0,
    layout: SwiperLayout.TINDER,
 )

非常容易创建您自己的自定义动画:

Swiper(
  layout: SwiperLayout.CUSTOM,
  customLayoutOption: CustomLayoutOption(
    startIndex: -1,
    stateCount: 3
  )..addRotate([
    -45.0/180,
    0.0,
    45.0/180
  ])..addTranslate([
    Offset(-370.0, -40.0),
    Offset(0.0, 0.0),
    Offset(370.0, -40.0)
  ]),
  itemWidth: 300.0,
  itemHeight: 200.0,
  itemBuilder: (context, index) {
    return Container(
      color: Colors.grey,
      child: Center(
        child: Text("$index"),
      ),
    );
  },
  itemCount: 10,
)

CustomLayoutOption 用于描述动画,很容易在Swiper中指定项目的每个状态。

CustomLayoutOption(
  // Which index is the first item of array below
  startIndex: -1,
  // array length
  stateCount: 3
)..addRotate([
  // rotation of every item
  -45.0/180,
  0.0,
  45.0/180
])..addTranslate([
  // offset of every item
  Offset(-370.0, -40.0),
  Offset(0.0, 0.0),
  Offset(370.0, -40.0)
]);

MD5 字符串加密

  • 首先在pubspec.yaml文件中引入crypto
crypto: ^3.0.2

字符串md5加密,转出之后需要使用 .toString() 转为String类型

import 'dart:convert';

var cont = const Utf8Encoder().convert(pwd);
var md5pwd = md5.convert(cont);

SM4 国密加密 dart版本

  • 首先在pubspec.yaml文件中引入sm_crypto
sm_crypto: ^1.0.3

SM4.encrypt() 必传两个参数data(json字符串),key(后台加密数据)

Future<Person?> login(tel, pwd) async {
  Person? res;
  try {
    var cont = const Utf8Encoder().convert(pwd);
    var md5pwd = md5.convert(cont);
    Map<String, String> datas = {
      "name": tel, //账号
      "pwd": md5pwd.toString(), //密码
    };
    String key = "980U9OCE9D565E7284052240B72EFC2E";
    var encryptData = SM4.encrypt(
      data: jsonEncode(datas),
      key: key,
    );
    LogUtil.v("encryptData: $encryptData");

    Response response = await _dio.post(
        "/api/blade-auth/oauth/token?pldk_encrypt=$encryptData",
        options: Options(headers: {
          "Content-Type": "application/x-www-form-urlencoded",
        }));
    res = Person.fromJson(response.data);
  } catch (e) {
    LogUtil.e("Error: $e");
  }
  return res;
}

接下来我们可以新建登录页面了

  • login.dart
import 'dart:ui';

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:yuncheng/bean/person.dart';
import 'package:yuncheng/utils/log_util.dart';

import '../../utils/dio_client.dart';

class Login extends StatefulWidget {
  const Login({Key? key}) : super(key: key);

  @override
  _LoginState createState() => _LoginState();
}

class _LoginState extends State<Login> {
  final DioClient _client = DioClient();

  ///用户名使用
  late final TextEditingController _nameController = TextEditingController();

  ///密码输入框使用
  late final TextEditingController _passwordController =
      TextEditingController();
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        width: double.infinity,
        height: double.infinity,
        child: Stack(
          children: [
            //第一层 背景图片
            buildFunction1(),
            //第二层 高斯模糊
            buildFunction2(),
            //第三层 登录输入层
            buildFunction3(),
          ],
        ),
      ),
    );
  }

  Widget buildFunction1() {
    return Positioned.fill(
      child: Image.asset(
        "assets/images/bg.jpeg",
        fit: BoxFit.cover,
      ),
    );
  }

  Widget buildFunction2() {
    return Positioned.fill(
      child: BackdropFilter(
        filter: ImageFilter.blur(sigmaX: 3, sigmaY: 3),
        child: Container(
          color: Colors.white.withOpacity(0.4),
        ),
      ),
    );
  }

  void _login(String name, String password) async {
    Person? user = await _client.login(name, password);
    LogUtil.v("user是 ${user?.toJson()}");
    SharedPreferences sp = await SharedPreferences.getInstance();
    if (user!.t != null) {
      sp.setString("token", user.t!);
    }
    // ignore: use_build_context_synchronously
    Navigator.of(context).pop();
    // ignore: use_build_context_synchronously
    ScaffoldMessenger.of(context)
        .showSnackBar(const SnackBar(content: Text("登录成功")));
  }

  Widget buildFunction3() {
    return Positioned.fill(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          SizedBox(
            width: 300,
            child: TextField(
              controller: _nameController,
              decoration: const InputDecoration(
                hintText: "请输入用户名",
                border: OutlineInputBorder(
                  borderRadius: BorderRadius.all(Radius.circular(33)),
                  borderSide: BorderSide(
                    color: Colors.grey,
                  ),
                ),
                contentPadding: EdgeInsets.all(10.0),
              ),
            ),
          ),
          const SizedBox(height: 20),
          SizedBox(
            width: 300,
            child: TextField(
              controller: _passwordController,
              textAlignVertical: TextAlignVertical.center,
              decoration: const InputDecoration(
                hintText: "请输入密码",
                border: OutlineInputBorder(
                  borderRadius: BorderRadius.all(Radius.circular(33)),
                  borderSide: BorderSide(
                    color: Colors.grey,
                  ),
                ),
                contentPadding: EdgeInsets.all(10.0),
                filled: true,
              ),
              obscureText: true,
            ),
          ),
          const SizedBox(height: 40),
          SizedBox(
            width: 300,
            height: 48,
            child: ClipRRect(
              borderRadius: const BorderRadius.all(Radius.circular(33)),
              child: ElevatedButton(
                onPressed: () {
                  String name = _nameController.text;
                  String password = _passwordController.text;
                  LogUtil.v("获取到的内容是 $name  $password");
                  _login(name, password);
                },
                child: const Text(
                  "登录",
                  style: TextStyle(
                    fontSize: 17,
                  ),
                ),
              ),
            ),
          )
        ],
      ),
    );
  }
}

flutter 富文本展示

  • 首先在pubspec.yaml文件中引入webview_flutter
webview_flutter: ^3.0.4

新建展示webview的页面,简单展示我们获取自cms的内容

import 'dart:async';
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:yuncheng/utils/log_util.dart';

class CommonWebview extends StatefulWidget {
  final arguments;
  const CommonWebview({Key? key, this.arguments}) : super(key: key);

  @override
  _CommonWebviewState createState() =>
      _CommonWebviewState(arguments: arguments);
}

class _CommonWebviewState extends State<CommonWebview> {
  final arguments;

  _CommonWebviewState({this.arguments});

  Future<void> _onNavigationDelegateExample(
      WebViewController controller, BuildContext context) async {
    final String contentBase64 =
        base64Encode(const Utf8Encoder().convert("""<!DOCTYPE html>
    <html>
      <head><meta name="viewport" content="width=device-width, initial-scale=1.0"></head>
      <body style='"margin: 0; padding: 0;'>
        <div>
          ${arguments["txt"]}
        </div>
      </body>
    </html>"""));
    await controller.loadUrl('data:text/html;base64,$contentBase64');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("${arguments["title"]}")),
      body: WebView(
        javascriptMode: JavascriptMode.unrestricted,
        onWebViewCreated: (controller) {
          _onNavigationDelegateExample(controller, context);
        },
      ),
    );
  }
}

项目地址

results matching ""

    No results matching ""