Zhengcaiyun Flutter dinámico iconfont práctica exploración

Equipo técnico de Zhengcaiyun.png

Norte 1.png

prefacio

¿Modificar dinámicamente iconfont? ¿Es posible?

En la actualidad, cada vez más equipos están comenzando a usar Flutter para desarrollar aplicaciones. En Flutter, podemos usar fácilmente iconfont en lugar de imágenes para mostrar íconos como la interfaz: coloque el archivo de fuente iconfont en el directorio del proyecto, en el código de Flutter. el icono en el archivo. Sin embargo, este método es difícil de satisfacer las necesidades de modificar dinámicamente el ícono.Si el gerente de producto de repente quiere reemplazar el ícono en el proyecto (como reemplazar el ícono durante los festivales), generalmente solo podemos resolverlo emitiendo la versión, pero los pasos para emitir la versión son engorrosos y no son adecuados para la versión anterior.

A continuación, exploremos un conjunto de soluciones de carga dinámica iconfont basadas en Flutter.

principio iconfont

iconfont es "icono de fuente", que consiste en convertir el icono en un archivo de fuente y luego mostrar diferentes iconos especificando diferentes caracteres. En el archivo de fuentes, cada carácter corresponde a un código Unicode, y cada código Unicode corresponde a un glifo de visualización. Diferentes fuentes se refieren a diferentes glifos, es decir, los glifos correspondientes a los caracteres son diferentes. En iconfont, solo el glifo correspondiente al código Unicode se convierte en un icono, por lo que los diferentes caracteres eventualmente se representarán en diferentes iconos.

En el desarrollo de Flutter, iconfont tiene las siguientes ventajas sobre las imágenes:

  • Tamaño pequeño: el tamaño del paquete de instalación se puede reducir.
  • Vector: iconfont son todos iconos vectoriales, la ampliación no afectará su claridad
  • Se pueden aplicar estilos de texto: el color del icono de fuente, la alineación del tamaño, etc. se pueden cambiar al igual que el texto.

Es con las ventajas anteriores que daremos prioridad al uso de iconfonts en lugar de imágenes en el proyecto Flutter.

Cómo se usaba iconfont antes de la dinamización

在我们现有的 Flutter 项目中,关于 iconfont 的使用,都是通过 [icontfont 官网](https://www.iconfont.cn)下载 ttf 字体文件至项目中的 assets 文件夹下,然后在 pubsepc.yaml 文件中配置来实现 ttf 字体文件的静态加载。
fonts:
    - family: IconFont
      fonts:
        - asset: assets/fonts/iconfont.ttf
复制代码

Luego defina una clase ( ZcyIcons ) para administrar todos los IconData en el archivo iconfont:

El código de esta clase se puede generar automáticamente escribiendo un script, de modo que cada vez que se actualiza el archivo iconfont, el último código se puede generar solo ejecutando el script.

class _MyIcon {
  static const font_name = 'iconfont';
  static const package_name = 'flutter_common';
  const _MyIcon(int codePoint) : super(codePoint, fontFamily: font_name, fontPackage: package_name,);
}

class ZcyIcons {
  static const IconData tongzhi = const _MyIcon(0xe784);
  
  static Map<String, IconData> _map = Map();
  
  ZcyIcons._();

  static IconData from(String name) {
    if(_map.isEmpty) {
      initialization();
    }
    return _map[name];
  }

  static void initialization() {
    _map["tongzhi"] = tongzhi;
  }
}

复制代码

Al usar, hay dos formas de llamar

  /// 方法1:直接加载
  Icon(ZcyIcons.arrow)
  /// 方法2:通过name的值去取map中对应的IconData
  Icon(ZcyIcons.from(name))
复制代码

虽然第二种方法能通过改变 key 的值来动态的从 map 中加载对应的 IconData ,但是仅局限于所有的 IconData 都已经在 map 中配置好且不再更改。

既然 iconfont 是字体文件,那么如果系统能动态加载字体文件,那么一定也能用同样的方式去动态加载 iconfont。

iconfont 动态化方案

步骤1: 加载远程下发的 ttf 文件

Flutter SDK 提供了 FontLoader 类来实现字体的动态加载。而我们解决这个问题的核心就是这个 FontLoader 类。

它有一个 addFont 方法,支持将 ByteData 格式数据转化为字体包并加载到应用字体资源库:

class FontLoader{
  ...
  
  void addFont(Future<ByteData> bytes) {
    if (_loaded)
      throw StateError('FontLoader is already loaded');
    _fontFutures.add(bytes.then(
        (ByteData data) => Uint8List.view(data.buffer, data.offsetInBytes, data.lengthInBytes)
    ));
  }
  
  Future<void> load() async {
    if (_loaded)
      throw StateError('FontLoader is already loaded');
    _loaded = true;
    final Iterable<Future<void>> loadFutures = _fontFutures.map(
        (Future<Uint8List> f) => f.then<void>(
            (Uint8List list) => loadFont(list, family)
        )
    );
    return Future.wait(loadFutures.toList());
  }
}
复制代码

我们可以创建一个接口来下发 iconfont 字体文件远端地址及该文件的 hash 值,每次启动 APP 将本地字体文件的 hash 值与接口中的值对比,当存在差异时将远端的字体文件下载到本地并以 ByteData 的数据格式供 FontLoader 加载即可。附上部分关键代码:

/// 下载远端的字体文件
static Future<ByteData> httpFetchFontAndSaveToDevice(Uri fontUri) {
  return () async {
    http.Response response;
    try {
      response = await _httpClient.get(uri);
    } catch (e) {
      throw Exception('Failed to get font with url: ${fontUrl.path}');
    }
    if (response.statusCode == 200) {
      return ByteData.view(response.bodyBytes.buffer);
    } else {
      /// 如果执行失败, 抛出异常.
      throw Exception('Failed to download font with url: ${fontUrl.path}');
    }
  };
}

/// 加载字体,先从本地文件加载,如果不存在,则使用[loader]加载
static Future<void> loadFontIfNecessary(ByteData loader, String fontFamilyToLoad) async {
  assert(fontFamilyToLoad != null && loader != null);
  
  if (_loadedFonts.contains(fontFamilyToLoad)) {
    return;
  } else {
    _loadedFonts.add(fontFamilyToLoad);
  }
  
  try {
    Future<ByteData> byteData;
    byteData = file_io.loadFontFromDeviceFileSystem(fontFamilyToLoad);
    if (await byteData != null) {
      return _loadFontByteData(fontFamilyToLoad, byteData);
    }
    
    byteData = loader();
    if (await byteData != null) {
      /// 通过 FontLoader 加载下载好的字体文件
      final fontLoader = FontLoader(familyWithVariantString);
      fontLoader.addFont(byteData);
      await fontLoader.load();
      successLoadedFonts.add(familyWithVariantString);
    }
  } catch (e) {
    _loadedFonts.remove(fontFamilyToLoad);
    print('Error: unable to load font $fontFamilyToLoad because the following exception occured:\n$e');
  }
}
复制代码

步骤2: 通过 icon 的名称获取需要加载的 unicode 值

在实际使用时我们发现需要指定 icon 对应字体文件的 codePoint ,也就是 unicode 值:

代码中通过iconfont 的 unicde 值获取 icon 的用法如下:

/// StringToInt 方法是定义的将 "&#xe67b;" 从 String 类型的16进制值转为 int 类型方法
MyIcons.from(StringToInt('&#xe67b;'));
复制代码

这样的用法对于我们开发来说不是很友好,每次都需要去查找这个 unicde 值对应的是哪个图标,因此我们可以在之前下载 ttf 文件的接口创建一个映射关系表,然后在 iconfont 初始化的时候通过代码将动态下发的 icon 名称和 Unicode 进行关联。

接口返回数据格式:

更改接口格式后代码中 icon 的用法:

/// _aliasMap 是将接口下发的nameList保存起来的 Map
MyIcons.from(StringToInt(_aliasMap['tongzhi']);
复制代码

假设我们有这么一个场景:APP进入首页,下载最新的 iconfont.ttf 文件并加载,但是Icon已经加载完成,此时怎么做到动态刷新当前Icon里面的内容呢?

步骤3:动态加载异步优化

之前的步骤已经可以完成 APP 启动后本地字体文件的更新,但是无法解决 icon 已经加载完成后的数据更新,因此我们的动态化方案需要依赖于 FutureBuilder。

FutureBuilder 会依赖一个 Future,它会根据所依赖的 Future 的状态来动态构建自身。

我们可以扩展一个 Icon 的 dynamic 方法去返回一个依赖于 FutureBuilder 的 Icon,当我们的 iconfont 字体文件更新成功后让 FutureBuilder 强制去刷新这个 Icon。

主要代码如下:

/// Icon的扩展方法,主要实现Icon组件的动态刷新
/// [dynamic] 方法主要通过[FutureBuilder]实现动态加载的核心原理
extension DynamicIconExtension on Icon {
  /// 用来监听新icon字体加载成功后的回调及时刷新icon,
  Widget get dynamic {
    /// 没有使用动态iconfont的情况下直接返回
    if (this.icon is! DynamicIconDataMixin) return this;
    final mix = this.icon as DynamicIconDataMixin;
    final loadedKey = UniqueKey();
    return FutureBuilder(
      future: mix.dynamicIconFont.loadedAsync,
      builder: (context, snapshot) {
        /// 由于icon的配置未发生变化但实际上其使用的字体已经发生了变化,所以这里通过使用不同的key让其强制刷新
        return KeyedSubtree(
          key: snapshot.hasData ? loadedKey : null,
          child: this,
        );
      },
    );
  }
} 

/// 调用代码如下:
Icon(MyIcons.from('&#xe67b;')).dynamic
复制代码

至此,我们的动态化方案支持的能力如下:

  • 可动态修改项目中已有的 icon
  • 通过 name/code 的形式动态设置 icon
  • 可在项目中使用新增的 icon

整个方案的流程图如下:

总结

总体来说,整个方案的核心原理就是通过 FontLoader 来实现字体文件的动态加载。但是其中涉及到一些动态化的处理和 iconfont 的原理探究,涉及到多点多面的知识,需要融会贯通并组合在一起使用。

参考资料

Flutter中文网

推荐阅读

政采云Flutter低成本屏幕适配方案探索

Redis系列之Bitmaps

MySQL 之 InnoDB 锁系统源码分析

招贤纳士

El equipo técnico de Zhengcaiyun (Zero), un equipo lleno de pasión, creatividad y ejecución, la Base se encuentra en la pintoresca Hangzhou. El equipo cuenta actualmente con más de 300 socios de I+D, incluidos soldados "veteranos" de Ali, Huawei y NetEase, así como recién llegados de la Universidad de Zhejiang, la Universidad de Ciencia y Tecnología de China, la Universidad de Hangdian y otras escuelas. Además del desarrollo comercial diario, el equipo también lleva a cabo exploración técnica y práctica en los campos de la nube nativa, blockchain, inteligencia artificial, plataforma de código bajo, middleware, big data, sistema de materiales, plataforma de ingeniería, experiencia de rendimiento, visualización, etc. Y aterrizó una serie de productos de tecnología interna y continuó explorando nuevos límites de tecnología. Además, el equipo también se ha dedicado a la creación de comunidades. Actualmente, son colaboradores de muchas comunidades excelentes de código abierto, como google flutter, scikit-learn, Apache Dubbo, Apache Rocketmq, Apache Pulsar, CNCF Dapr, Apache DolphinScheduler, alibaba Seata , etc. Si quieres cambiar, te han tirado con cosas, y quieres empezar a tirar cosas; si quieres cambiar, te han dicho que necesitas más ideas, pero no puedes romper el juego; si quieres cambiar, tienes la capacidad de hacer eso, pero no te necesitas, si quieres cambiar lo que quieres hacer, necesitas un equipo que lo apoye, pero no hay lugar para que lideres a la gente; si quieres cambiar, tienes un buen entendimiento, pero siempre existe esa capa de papel borroso... Si crees en el poder de la creencia, creo que la gente común puede lograr cosas extraordinarias, y creo que pueden cumplir un yo mejor Si desea participar en el proceso de despegue con el negocio y promover personalmente el crecimiento de un equipo técnico con un conocimiento profundo del negocio, un sistema técnico sólido, tecnología que crea valor e influencia indirecta, creo que deberíamos hablar. En cualquier momento, esperando que escribas algo, envíalo a [email protected]

Cuenta pública de WeChat

El artículo se publica simultáneamente, la cuenta pública del equipo técnico de Zhengcaiyun, bienvenido a prestar atención.

政采云技术团队.png

Supongo que te gusta

Origin juejin.im/post/7084024140046270478
Recomendado
Clasificación