在使用Flutter开发的时候,有时候会存在不少资源图片问题,按照规定,使用的图片资源须要在 pubspec.yaml
文件中配置路径才能够正常使用,若是存在不少50个以上或者更多图片资源,难道须要一个一个配置?显然是不可能的!git
其实,Flutter是支持直接在 pubspec.yaml
中配置图片资源文件夹路径便可,不必每一个图片资源路径都详细配置的,可是无论怎么样,在实际调用的时候,仍是得老老实实写完整的图片路径,显然是很不方便的,那该如何解决?github
不一样的开发有不一样的思路,这里我采起的是使用Dart注解的方式,只须要执行一行注解便可完成资源文件配置。缓存
先看效果: 好比咱们有如下资源文件:
bash
咱们只须要配置一行注释:markdown
@ImagePathSet('assets/', 'ImagePathTest')
void main() => runApp(MyApp());
复制代码
而后运行一行命令:app
flutter packages pub run build_runner build
复制代码
执行完毕便可生成一个 .dart
文件内容以下:ide
class ImagePathTest {
ImagePathTest._();
static const BANNER = 'assets/image/banner.png';
static const PLAY_STOP = 'assets/image/play_stop.png';
static const SAVE_BUTTON = 'assets/image/save_button.png';
static const MINE_HEADER_IMAGE = 'assets/image/test/mine_header_image.png';
static const VERIFY = 'assets/image/verify.png';
static const VERIFY_ERROR = 'assets/image/verify_error.png';
}
复制代码
同时在 pubspec.yaml
文件夹下自动配置好咱们的资源文件:工具
assets:
- assets/image/banner.png
- assets/image/play_stop.png
- assets/image/save_button.png
- assets/image/test/mine_header_image.png
- assets/image/verify.png
- assets/image/verify_error.png
复制代码
使用时便可直接以代码形式直接引用图片资源:post
Image.asset(ImagePathTest.xxx)
复制代码
便可以防止手误出差,又能够提升效率~ 下面重点介绍咱们的开发思路。ui
关于Dart注解使用,能够参考这篇文章,写的比较细:Flutter 注解处理及代码生成,也能够参考官方说明:source_gen
这里简单说明,在 source_gen
官方文档说明里有这么一句话:
source_gen is based on the build package and exposes options for using your Generator in a Builder.
省略部分文档内容 ......
In order to get the Builder used with build_runner it must be configured in a build.yaml file.
翻译成中文即:
source_gen
是基于build
包的,同时提供暴露了一些选项以方便在一个Builder
中使用你本身的生成器Generator
。
...
为了可以使Builder
和build_runner
一块使用,必需要配置一个build.yaml
文件。
所以想要使用Dart注解,咱们须要作这几件事:
source_gen
build_runner
Generator
Build
build.yaml
文件下面一个一个说。
source_gen
这个没什么好说的,只要你是要Dart注解就必须依赖该库:
dependencies:
source_gen: ^0.9.4+5
复制代码
具体版本可到这里查看source_gen
build_runner
同上,直接依赖就是,通常依赖在 dev_dependencies
节点下:
dev_dependencies:
build_runner: ^1.7.1
复制代码
具体版本可到这查看build_runner
该库中内置了编译运行的命令:pub run build_runner <command>
,主要为下面四种编译类型:
build
watch
serve
test
其中在flutter中通常只须要第一种构建方式,同上以上四个命令均可以附加一些命令,例如:--delete-conflicting-outputs
。详细说明可参考这里:build_runner相关说明
Generator
从字面意思理解为 生成器
,官方说明为:
A tool to generate Dart code based on a Dart library source.
一种基于Dart库源代码生成Dart代码的工具。类图以下:
两个类都是抽象类,一般建立一个类继承自 GeneratorForAnnoration
并实现抽象方法,在该抽象方法中完整咱们须要的逻辑功能开发;这里咱们须要搞一个图片资源文件配置功能,该功能具备如下两个要点:
所以咱们先建立一个实例类包含这两个信息:
class ImagePathSet{
/// 资源文件夹路径
final String pathName;
/// 须要生成的资源配置类名
final String newClassName;
const ImagePathSet(this.pathName, this.newClassName);
}
复制代码
最终咱们在使用的第一引用就须要这样引用:
@ImagePathSet('assets/', 'ImagePathTest')
复制代码
此处须要注意的是,这个类的构造方法必须是 const
的。建立好了最终须要使用的注解类以后,咱们建立生成器:
class ImagePathGenerator extends GeneratorForAnnotation<ImagePathSet> {
@override
generateForAnnotatedElement(Element element, ConstantReader annotation, BuildStep buildStep) {
return null;
}
}
复制代码
在 generateForAnnotatedElement()
方法中,咱们便可完成咱们的逻辑部分开发了,这里涉及到三个参数:
element:这个是被注解的类/方法等的详细信息,好比被修饰的部分代码这样:
/// path_test.dart
@ImagePathSet('assets/', 'ImagePathTest')
class PathTest{}
复制代码
则一些相关的element信息以下:
element.name /// PathTest
element.displayName /// PathTest
element.toString() /// class PathTest
element.enclosingElement /// /example/lib/path_test.dart
element.kind /// CLASS
element.metadata /// [@ImagePathSet ImagePathSet(String pathName, String newClassName)]
复制代码
annotation:注解的详细信息
其中最经常使用的两个方法分别是:
read(String field)
peek(String field)
两个都是读取给定的注解参数信息,前者若是没读取到或抛出 FormatException
异常,后者则会返回 null
。
须要注意的是,这两个方法返回的结果是 ConstantReader
类型,若是须要获取到具体注解元素的值,须要调用对应的 xxxValue
方法,xxx
表示具体类型,好比上面的注解,咱们须要获取 pathName
信息,能够写成这样:
String pathName= annotation.peek('pathName').stringValue
复制代码
固然,咱们假如咱们不知道注解参数的类型,能够根据 isXxx
来判断是不是对应的类型,好比:
annotation.peek('pathName').isString ///true
annotation.peek('pathName').isInt ///false
复制代码
buildStep:构建的输入输出信息
本想着修改这个类来修改生成文件名称信息,无奈Dart不支持反射,未找到相关的修改方法,这里最主要的一个信息为:
完整的根据根据生成资源文件的生成器代码以下:
import 'dart:io';
import 'package:analyzer/dart/element/element.dart';
import 'package:image_path_helper/image_path_set.dart';
import 'package:source_gen/source_gen.dart';
import 'package:build/build.dart';
class ImagePathGenerator extends GeneratorForAnnotation<ImagePathSet> {
String _codeContent = '';
String _pubspecContent = '';
@override
generateForAnnotatedElement(Element element, ConstantReader annotation, BuildStep buildStep) {
final explanation = '// **************************************************************************\n'
'// 若是存在新文件须要更新,建议先执行清除命令:\n'
'// flutter packages pub run build_runner clean \n'
'// \n'
'// 而后执行下列命令从新生成相应文件:\n'
'// flutter packages pub run build_runner build --delete-conflicting-outputs \n'
'// **************************************************************************';
var pubspecFile = File('pubspec.yaml');
for (String imageName in pubspecFile.readAsLinesSync()) {
if (imageName.trim() == 'assets:') continue;
if (imageName.trim().toUpperCase().endsWith('.PNG')) continue;
if (imageName.trim().toUpperCase().endsWith('.JPEG')) continue;
if (imageName.trim().toUpperCase().endsWith('.SVG')) continue;
if (imageName.trim().toUpperCase().endsWith('.JPG')) continue;
_pubspecContent = "$_pubspecContent\n$imageName";
}
_pubspecContent = '${_pubspecContent.trim()}\n\n assets:';
/// 图片文件路径
var imagePath = annotation.peek('pathName').stringValue;
if (!imagePath.endsWith('/')) {
imagePath = '$imagePath/';
}
/// 生成新的Dart文件名称
var newClassName = annotation.peek('newClassName').stringValue;
/// 遍历处理图片资源路径
handleFile(imagePath);
/// 添加图片路径到pubspec.yaml文件中
pubspecFile.writeAsString(_pubspecContent);
/// 返回生成的代码文件
return '$explanation\n\n'
'class $newClassName{\n'
' $newClassName._();\n'
' $_codeContent\n'
'}';
}
void handleFile(String path) {
var directory = Directory(path);
if (directory == null) {
throw '$path is not a directory.';
}
for (var file in directory.listSync()) {
var type = file.statSync().type;
if (type == FileSystemEntityType.directory) {
handleFile('${file.path}/');
} else if (type == FileSystemEntityType.file) {
var filePath = file.path;
var keyName = filePath.trim().toUpperCase();
if (!keyName.endsWith('.PNG') &&
!keyName.endsWith('.JPEG') &&
!keyName.endsWith('.SVG') &&
!keyName.endsWith('.JPG')) continue;
var key = keyName
.replaceAll(RegExp(path.toUpperCase()), '')
.replaceAll(RegExp('.PNG'), '')
.replaceAll(RegExp('.JPEG'), '')
.replaceAll(RegExp('.SVG'), '')
.replaceAll(RegExp('.JPG'), '');
_codeContent = '$_codeContent\n\t\t\t\tstatic const $key = \'$filePath\';';
/// 此处用 \t 符号代替空格在读取的时候会报错,不知道什么状况。。。
_pubspecContent = '$_pubspecContent\n - $filePath';
}
}
}
}
复制代码
Build
的做用主要是让生成器执行起来,咱们这里建立的 Build
以下:
Builder imagePathBuilder(BuilderOptions options) =>
LibraryBuilder(ImagePathGenerator());
复制代码
主要引用的包为:
import 'package:build/build.dart';
复制代码
在这里咱们的 build.yaml
文件配置以下:
builders:
image_builder:
import: 'package:image_path_helper/builder.dart'
builder_factories: ['imagePathBuilder']
build_extensions: { '.dart': ['.g.dart'] }
auto_apply: root_package
build_to: source
复制代码
build.yaml
配置的信息,最终都会被 build_config.dart
中的 BuildConfig
类读取到。
关于参数说明,这里推荐官方说明build_config。
一个完整的 build.yaml
结构图以下:
build.yaml
文件最终被一个
BuildConfig
对象所描述,也就是说
build.yaml
文件最终被
BuildConfig
所解析。而
BuildConfig
包含了四个关键的信息:
key | value | default |
---|---|---|
targets |
Map<String, BuildTarget> |
单个的target应该所对应的package名一致 |
builders |
Map<String, BuilderDefinition> |
/ |
post_process_builders |
Map<String, PostProcessBuilderDefinition> |
/ |
global_options |
Map<String, GlobalBuilderOptions> |
/ |
四个关键信息正是对应了 build.yaml
文件中的四个根节点,其中又以 builders
节点最为经常使用。
builders配置的是你的包中的全部 Builder
的配置信息,每一个信息格式是 Map<String, BuilderDefinition>
的,好比咱们存在一个这样的 Builder
:
/// builder.dart
Builder imagePathBuilder(BuilderOptions options) =>
LibraryBuilder(ImagePathGenerator());
复制代码
咱们就能够在 build.yaml
文件中配置成这样:
builders:
image_builder:
import: 'package:image_path_helper/builder.dart'
builder_factories: ['imagePathBuilder']
build_extensions: { '.dart': ['.g.dart'] }
auto_apply: root_package
build_to: source
复制代码
image_builder
对应的就是 Map<String, BuilderDefinition>
中的 String
部分,:
后面的即 BuilderDefinition
信息,对应上面的结构图。下面咱们细说 BuilderDefinition
中的每一个参数的信息:
参数 | 参数类型 | 说明 |
---|---|---|
builder_factories |
List<String> |
必填参数,返回的 Builder 的方法名称的列表,例如上面的 Builder 方法名为 imagePathBuilder ,则写成 ['imagePathBuilder'] |
import |
String |
必填参数,导入Builder 所在的包路径,格式为 package:uri 的字符串 |
build_extensions |
Map<String, List<String>> |
必填参数,从输入扩展名到输出扩展名的映射。举个例子:好比注解使用的位置的文件对应的格式为 .dart ,指定输出的文件格式可由 .dart 转换成 .g.dart 或 .zip 等等其余格式 |
auto_apply |
String |
可选参数,默认值为 none ,对应源码中的 AutoApply 枚举类,有四种可选配置:none :除非手动配置了今生成器,不然请不要应用它dependents :将此Builder应用于包,直接依赖于暴露构建器的包all_packages :将此构建器应用于传递依赖关系图中的全部包root_package :仅将今生成器应用于顶级软件包是否是感受一脸懵逼?不要紧,后面单独解释~~~ |
required_inputs |
List |
可选参数,用于调整构建顺序的,指定一个或一系列文件扩展名,表示在任何可能产生该类型输出的Builder以后运行 |
runs_before |
List<BuilderKey> |
可选参数,用于调整构建循序的,更上面的恰好相反,表示在指定的Builder以前运行 BuilderKey :表示一个 target 的身份标志,主要由对应 Builder 的包名和方法名构成,例如这样 image_path_helper|imagePathBuilder |
applies_builders |
List<BuilderKey> |
可选参数,Builder 键列表,也就是身份标志,跟 builder_factories 参数配置应该是一一对应的 |
is_optional |
bool |
可选参数,默认值 false ,指定是否能够延迟运行 Builder ,一般不须要配置 |
build_to |
String |
可选参数,默认值为 cache ,主要为 BuildTo 枚举类的两个参数:cache :输出将进入隐藏的构建缓存,而且不会发布source :输出进入其主要输入旁边的源树 直白点就是若是你须要编译后生成相应的可在本身编写的源码中看到见的文件,就将这个参数设置成 source ,若是指定的生成器返回的是 null 不须要生成文件,则能够设置为 cache |
defaults |
TargetBuilderConfigDefaults |
可选参数:用户未在其builders 【此处指的是 targets 节点下的builders ,别搞混淆了!】部分中指定相应键时应用的默认值 |
关于 auto_apply
参数的详细说明:
注解package
包含了一些注解功能:
auto_apply
设置成 dependents
时:
注解package
是直接依赖在 sub_package02
上的,那么只能在 sub_package02
上正常使用注解,虽然 Package
包依赖了 sub_package02
,可是依然没法正常使用该注解auto_apply
设置成 all_packages
时:
注解package
是直接依赖在 sub_package02
上的,那么在 sub_package02
和 Package
上都能正常使用注解auto_apply
设置成 root_package
时:
注解package
是直接依赖在 sub_package02
上的,那么只能在 Package
上正常使用注解,虽然是 sub_package02
上作的依赖,可是就是不给用注解package
是直接依赖在 Package
上的时候,无论 auto_apply
设置的是 dependents
、all_packages
或者是 root_package
时,其实都是能正常使用的!至于 build.yaml
其余的三个节点参数,说实话,由于目前用到的很少,只了解一部分,有不少细节还没有理清,只能在这里略过了。
上面的工做完成以后,咱们就须要引用注解了,好比咱们在 main()
方法上引用:
@ImagePathSet('assets/', 'ImagePathTest')
void main() => runApp(MyApp());
复制代码
引用完注解以后,而后咱们在Terminal命令行中执行下面这个命令完成编译:
flutter packages pub run build_runner build
复制代码
编译完成以后会生成对应的文件,好比咱们上面配置的是在 main.dart
文件中的 main
方法上配置的,最终生成的文件为 main.g.dart
,关于文件是如何生成的,你能够参考 run_builder.dart
下的 runBuilder
方法和 expected_outputs.dart
下的 expectedOutputs
方法。
注意:若是须要从新构建建议先进行清除操做:
flutter packages pub run build_runner clean
复制代码
除此以外,建议在构建的时候执行下面的这个命令进行构建:
flutter packages pub run build_runner build --delete-conflicting-outputs
复制代码
至此,一个完整的利用注解一行代码+一行命令完成图片文件配置的功能就作完啦~~~
在Flutter中想要注解,只须要遵循必定的步骤加上本身的逻辑便可轻松完成相关功能开发,主要的流程步骤总结以下:
source_gen
和 build_runner
库Generator
Builder
build.yaml
文件Tips:本文源码位置:image_path_helper