在使用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