|
1 year ago | |
---|---|---|
android | 1 year ago | |
assets | 1 year ago | |
ios | 1 year ago | |
lib | 1 year ago | |
linux | 2 years ago | |
macos | 2 years ago | |
test | 2 years ago | |
web | 2 years ago | |
windows | 2 years ago | |
.gitignore | 2 years ago | |
.metadata | 2 years ago | |
README.md | 1 year ago | |
analysis_options.yaml | 2 years ago | |
pubspec.lock | 1 year ago | |
pubspec.yaml | 1 year ago |
ComPDFKit SDK在Flutter中采用混合兼容方式进行对接。Android和iOS端分别采用PlatformViewLink和UiKitView,并通过MethodChannel和EventChannel进行交互。我们只封装了CPDFReaderView,而其他注释工具和书签列表等功能则使用Flutter开发。 关于MethodChannel、EventChannel使用方法请参考以下文章 Flutter Native交互
graph TD
A[Flutter] --> B(init Configuration)
B --> C[ComPDFKitWidget.dart]
C -->|Platform.isAndroid| D[PlatformViewlink]
C -->|Platform.isIOS| E[UiKitView]
D --> F[Android]
E --> G[iOS]
F --> H[FlutterCPDFReaderView]
G --> H[FlutterCPDFReaderView]
H --> |return| I[PDFReaderView]
I --> |add| ComPDFKitSDK-CPDFReaderView
Flutter与Native端通信使用MethodChannel、EventChannel进行交互,以下将以设置CPDFReaderView翻页模式举例展示交互流程
sequenceDiagram
setIsContinueMode->>MethodChannel: true
MethodChannel->> Native(Android、iOS): invokeMethod(functionName, true)
NOTE right of Native(Android、iOS): CPDFReaderView set isContinueMode = true
Native(Android、iOS)--> MethodChannel: return continue mode
MethodChannel--> setIsContinueMode:get continue mode
请求
方法名 | 通讯方式 | 参数 | 说明 |
---|---|---|---|
event_reader_view_call_back | EventChannel | "readerViewCallBack" | 监听CPDFReaderView滑动位置、点击文档区域、滑动状态监听 |
响应
Key | Value | 类型 | 说明 |
---|---|---|---|
eventType | onTapMainDocArea | String | 点击主文档区域监听 |
eventType | onScrollEnd | String | 滑动结束 |
eventType | onScrolling | String | 滑动中 |
eventType | onMoveToChild | String | 滑动到指定页码 |
eventType | onRecordLastJumpPageNum | String | 记录上次跳转页码 |
eventType | pageIndex | String | 页码,仅onMoveToChild、onRecordLastJumpPageNum |
示例: 1. Flutter中创建EventChannel对象
const _readerViewCallBackEventChannel =
EventChannel('event_reader_view_call_back');
2. 定义一个方法例如setReaderViewCallbackListener
CancelListener setReaderViewCallbackListener() {
var subscription = _readerViewCallBackEventChannel
.receiveBroadcastStream(eventSinkId.readerViewCallBack.index)//任何参数都可
.listen((data) {
Map<dynamic, dynamic> result = data;
String eventType = result[EventParameters.eventType];
switch (eventType) {
case EventParameters.onTapMainDocArea:
...
break;
case EventParameters.onMoveToChild:
int pageIndex = result[EventParameters.pageIndex];
...
break;
case EventParameters.onRecordLastJumpPageNum:
int pageIndex = result[EventParameters.pageIndex];
...
break;
case EventParameters.onScrollEnd:
...
break;
case EventParameters.onScrolling:
...
break;
default:
break;
}
}, cancelOnError: true);
return () {
subscription.cancel();
};
}
3. 在混合集成模式Widget创建完成的回调中调用setReaderViewCallbackListener
dart
..addOnPlatformViewCreatedListener((id) {
setReaderViewCallbackListener();
})
dart
onPlatformViewCreated: (id){
setReaderViewCallbackListener();
}
4. 事件回调 在IReaderViewCallback中通过EventChannel.EventSink.success返回结果
Android
注册EventChannel
val readerViewCallbackEventChannel = EventChannel(messenger, EVENT_CHANNEL_READER_VIEW_CALL_BACK) readerViewCallbackEventChannel.setStreamHandler(object : EventChannel.StreamHandler { override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { readerView.setReaderViewCallbackEventEmitter1(events) } override fun onCancel(arguments: Any?) { } })
在CPDFReaderView对应的回调中返回数据
override fun onTapMainDocArea() { readerViewCallbackEventEmitter?.success(mapOf("eventType" to “onTapMainDocArea”)) } override fun onMoveToChild(pageIndex: Int) { readerViewCallbackEventEmitter?.success(mapOf("eventType" to “onMoveToChild”, "pageIndex" to pageIndex)) } override fun onEndScroll() { readerViewCallbackEventEmitter?.success(mapOf("eventType" to “onScrollEnd”)) } override fun onScrolling() { readerViewCallbackEventEmitter?.success(mapOf("eventType" to “onScrolling”)) } override fun onRecordLastJumpPageNum(pageIndex: Int) { readerViewCallbackEventEmitter?.success(mapOf("eventType" to “onRecordLastJumpPageNum”, "pageIndex" to pageIndex)) }
iOS
- 注册FlutterEventChannel
请求
方法名 | 通讯方式 | 参数 | 说明 |
---|---|---|---|
getScrollDirection | MethodChannel | 无 | 获取当前CPDFReaderView滚动方向 |
响应
参数名称 | 类型 | 说明 |
---|---|---|
vertical | String | 垂直滑动 |
horizontal | String | 水平滑动 |
示例
Flutter获取滚动方向
///这里可以定义一个共用的MethodChannel const _methodChannel = MethodChannel('com.compdfkit.pdf.flutter'); //获取滚动方向 Future<bool> scrollDirectionIsVerticalMode() async { String scrollDirection = await _methodChannel.invokeMethod(Functions.getScrollDirection); return scrollDirection == ScrollDirection.vertical; }
Android 返回当前滚动方向
//定义MethodChannel对象 val methodChannel = MethodChannel(messenger, "com.compdfkit.pdf.flutter") methodChannel.setMethodCallHandler(this) //在onMethodCall方法中返回结果 override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { when (call.method) { “getScrollDirection” -> { result.success(if (cpdfReaderView.isVerticalMode) “vertical” else “horizontal”) } }
iOS 返回当前滚动方向
Objective-C ```objc @implementation AppDelegate
(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary )launchOptions { FlutterViewController controller = (FlutterViewController)self.window.rootViewController; FlutterMethodChannel methodChannel = [FlutterMethodChannel
methodChannelWithName:@"com.compdfkit.pdf.flutter" binaryMessenger:controller.binaryMessenger];
[methodChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
if([@"getScrollDirection" isEqualToString:call.method]) { result(@"horizontal");//此处仅示例作为参考,请从CPDFReaderView中获取滚动方向 }
}];
[GeneratedPluginRegistrant registerWithRegistry:self]; // Override point for customization after application launch. return [super application:application didFinishLaunchingWithOptions:launchOptions]; } @end
* Swift ```swift class ComPDFKitPlugin { init(messenger : FlutterBinaryMessenger){ let channel = FlutterMethodChannel(name: "com.compdfkit.pdf.flutter", binaryMessenger: messenger) channel.setMethodCallHandler{(call : FlutterMethodCall, result : @escaping FlutterResult) in if (call.method == "getScrollDirection"){ result("vertical")//此处仅示例作为参考,请从CPDFReaderView中获取滚动方向 } } } } @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { let controller : FlutterViewController = window?.rootViewController as! FlutterViewController ComPDFKitPlugin(messenger: controller.binaryMessenger)//register FlutterMethodChannel GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } }
请求
方法名 | 通讯方式 | 数据格式 | key | value | 说明 |
---|---|---|---|---|---|
setScrollDirection | MethodChannel | Map | scrollDirection | "vertical","horizontal" | 设置CPDFReaderView滚动方向 |
响应
参数名称 | 类型 | 说明 |
---|---|---|
vertical | String | 垂直滑动 |
horizontal | String | 水平滑动 |
示例
Flutter 设置CPDFReaderView滚动方向
///direction : vertical or horizontal Future<bool> setScrollDirection(String direction) async { String scrollDirection = await _methodChannel.invokeMethod('setScrollDirection', {'scrollDirection': direction}); //原生端返回当前的滚动状态 return scrollDirection == ScrollDirection.vertical; }
Android端
在MethodChannel的onMethodCall方法中获取到Flutter传输的数据
//在onMethodCall方法中返回结果
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
val configuration = call.arguments as? HashMap<String, Any>
when (call.method) {
“setScrollDirection” -> {
(configuration?.get("scrollDirection") as? String)?.let { direction->
pdfReaderView.pdfReaderView.isVerticalMode = direction == "vertical"
result.success(if (pdfReaderView.pdfReaderView.isVerticalMode) "vertical" else "horizontal")
}
else ->{
...
}
}
}
iOS
Objective-C ```objc @implementation AppDelegate
(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary )launchOptions { FlutterViewController controller = (FlutterViewController)self.window.rootViewController; FlutterMethodChannel methodChannel = [FlutterMethodChannel
methodChannelWithName:@"com.compdfkit.pdf.flutter" binaryMessenger:controller.binaryMessenger];
[methodChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
if([@"setScrollDirection" isEqualToString:call.method]) { NSDictionary* arguments = call.arguments; NSString* scrollDirection = arguments[@"scrollDirection"]; //请从CPDFReaderView获取当前滑动方向并返回 result(scrollDirection); }
}]; [GeneratedPluginRegistrant registerWithRegistry:self]; // Override point for customization after application launch. return [super application:application didFinishLaunchingWithOptions:launchOptions]; } @end ```
swift
channel.setMethodCallHandler{(call : FlutterMethodCall, result : @escaping FlutterResult) in
if (call.method == "setScrollDirection"){
var convertData = arguments as! [String: String];
var scrollDirection = convertData["scrollDirection"]
result(scrollDirection)//请从CPDFReaderView获取当前滑动方向并返回
}
...
}
方法名 | 通讯方式 | 参数 | 说明 |
---|---|---|---|
getReaderViewBackgroundColor | MethodChannel | 无 | 背景颜色 |
响应
参数名称 | 类型 | 说明 |
---|---|---|
'#FF0000' | String | String Hex类型颜色值 |
示例:请参考isVerticalMode
请求
方法名 | 通讯方式 | 数据格式 | key | value | 说明 |
---|---|---|---|---|---|
setReaderViewBackgroundColor | MethodChannel | Map | backgroundColor | '#FF0000' | 传入String类型颜色值 |
响应
参数名称 | 类型 | 说明 |
---|---|---|
"#FF0000" | String | 当前设置的背景颜色值 |
示例:请参考isVerticalMode
注释类型 | 说明 |
---|---|
unknown | 未知类型,在设置currentFocusedType使用 |
highlight | 高亮 |
strikeout | 删除线 |
underline | 下划线 |
squiggly | 波浪线 |
ink | 自由绘制 |
shape | 形状注释归类,不直接使用 |
circle | 圆形形状注释 |
square | 矩形形状注释 |
line | 线条形状注释 |
arrow | 箭头形状注释,本质还是line类型注释,增加线条结尾lineType即可 |
freetext | 自由文本 |
signature | 签名注释,本质还是图章注释 |
stamp | 图章注释 |
- 传入对应注释类型从CPDFReaderView获取对应注释的属性值,但请注意以下问题
- 图形注释(矩形、圆形、线条、箭头)都属于shape, 获取图形注释请传入AnnotationType.shape会返回矩形注释属性
- signature、stamp由于是图片,无属性数据
请求
方法名 | 通讯方式 | 数据格式 | key | value | 说明 |
---|---|---|---|---|---|
getAnnotAttribute | MethodChannel | Map | annotType | "highlight","strikeout","underline","squiggly"... | 注释类型 |
响应
注释类型 | Key | value | 类型 | 说明 |
---|---|---|---|---|
highlight | color | "#FF0000" | String | 注释颜色 |
alpha | 0~255 | int | 注释颜色透明度 | |
strokeout | color | "#FF0000" | String | 注释颜色 |
alpha | 0~255 | int | 注释颜色透明度 | |
underline | color | "#FF0000" | String | 注释颜色 |
alpha | 0~255 | int | 注释颜色透明度 | |
squiggly | color | "#FF0000" | String | 注释颜色 |
alpha | 0~255 | int | 注释颜色透明度 | |
shape | borderColor | "#FF0000" | String | square边框颜色 |
borderColorAlpha | 0~255 | int | square边框颜色透明度 | |
borderWidth | 1~10 | int | square边框宽度 | |
fillColor | "#FF0000" | String | square填充颜色 | |
fillColorAlpha | 0~255 | int | square填充颜色透明度 | |
shapeType | square | String | 当前获取的形状类型,只返回square | |
freetext | fontBold | true,false | bool | 字体是否加粗 |
fontItalic | true,false | bool | 文字是否斜体 | |
textColor | "#FF0000" | String | 文字字体颜色 | |
textColorAlpha | 0~255 | int | 文字颜色透明度 | |
fontSize | 1~100 | int | 文字大小 | |
fontType | courier,helvetica,times_roman | String | 字体类型,仅支持三种字体 | |
signature | ||||
stamp |
signature、stamp由于是图片,无属性数据
请求 根据不同的注释,将会通过Map传递给原生端不同的数据
注释类型 | Key | value | 类型 | 说明 |
---|---|---|---|---|
highlight | color | "#FF0000" | String | 注释颜色 |
alpha | 0~255 | int | 注释颜色透明度 | |
strokeout | color | "#FF0000" | String | 注释颜色 |
alpha | 0~255 | int | 注释颜色透明度 | |
underline | color | "#FF0000" | String | 注释颜色 |
alpha | 0~255 | int | 注释颜色透明度 | |
squiggly | color | "#FF0000" | String | 注释颜色 |
alpha | 0~255 | int | 注释颜色透明度 | |
shape | borderColor | "#FF0000" | String | square边框颜色 |
borderColorAlpha | 0~255 | int | square边框颜色透明度 | |
borderWidth | 1~10 | int | square边框宽度 | |
fillColor | "#FF0000" | String | square填充颜色 | |
fillColorAlpha | 0~255 | int | square填充颜色透明度 | |
shapeType | square | String | 当前获取的形状类型,只返回square | |
freetext | fontBold | true,false | bool | 字体是否加粗 |
fontItalic | true,false | bool | 文字是否斜体 | |
textColor | "#FF0000" | String | 文字字体颜色 | |
textColorAlpha | 0~255 | int | 文字颜色透明度 | |
fontSize | 1~100 | int | 文字大小 | |
fontType | courier,helvetica,times_roman | String | 字体类型,仅支持三种字体 | |
signature | imagePath | data/data/... | String | 签名图片本地文件路径 |
stamp | standardStampName | 'notapproved'... | String | SDK内置图章名称 |
textStampContent | 'Hello world' | String | 文本图章内容 | |
textStampDate | '2023/03/28' | String | 文本图章时间 | |
textStampStyleShapeType | 'TEXT_STAMP_NONE','TEXT_STAMP_RECT'... | String | 文本图章样式,一共四种,无、圆角矩形、左箭头,右箭头 | |
textStampStyleColorType | textStampWhite,textStampRed... | String | 文本图章颜色类型,白色,红色,绿色,蓝色 |
响应
请求
方法名 | 通讯方式 | 数据格式 | key | value | 说明 |
---|---|---|---|---|---|
setCurrentFocusedType | MethodChannel | Map | touchMode | "browse","add_annot" | 设置触摸模式,browse:浏览模式, add_annot:添加注释模式 |
Map | focusedType | "highlight","strikeout","underline","squiggly"... | 注释类型 |
请注意,如果需要设置添加形状注释(AnnotationType.shape) 请传入真实的注释类型
- AnnotationType.circle
- AnnotationType.square
- AnnotationType.line
- AnnotationType.arrow
响应 无
在CPDFReaderView视图中选中注释(例如:高亮),会弹出选择菜单,菜单中可以进行注释的属性调整、复制、删除等操作,其中属性调整需要与Flutter进行交互,通知Flutter弹出注释属性调整界面
kotlin
val readerViewContextMenuHelperEventChannel =
EventChannel(messenger, “event_reader_view_context_menu_helper”)
readerViewContextMenuHelperEventChannel.setStreamHandler {
onListen { arguments, events ->
//此处保存events对象
readerView.setContextMenuHelperEventEmitter(events)
}
}
2.在自行实现的CPDFContextMenuShowHelper中例如getMarkupContentView方法,点击attribute按钮需要调整注释属性时,通知Flutter展示属性调整界面
override fun getMarkupContentView(pageView: CPDFPageView, annotImpl: CPDFBaseAnnotImpl<*>, layoutInflater: LayoutInflater): View {
val contentView = layoutInflater.inflate(R.layout.t_markup_annot_menu_layout, null)
val markupAnnotImpl = annotImpl as CPDFMarkupAnnotImpl
val markupAnnotation = markupAnnotImpl.onGetAnnotation()
invokeOnClickListener(contentView, { v: View ->
val id = v.id
if (id == R.id.attr) {
//此处需要将当前注释、CPDFPageView对象保存到全局变量,以便于后续调整注释属性
selectMarkupAnnotImpl = markupAnnotImpl
selectPageView = pageView
//Notify the flutter side to open the annotation attribute modification interface
contextMenuHelperEventEmitter?.success(mapOf(
"annotationType" to markupAnnotation.type.name.lowercase(),
"annotAttrColor" to markupAnnotation.color.toHex(),
"annotAttrAlpha" to markupAnnotation.alpha
))
} else if (id == R.id.copy) {
...
} else if (id == R.id.delete) {
...
}
popupWindow.dismiss()
}, R.id.attr, R.id.copy, R.id.delete)
return contentView
}
方法名 | 注释类型 | key | value | 说明 |
---|---|---|---|---|
getMarkupContentView | highlight... | annotType | highlight,strikeout,underline,squiggly | 注释类型 |
color | "#FF0000" | 注释颜色 | ||
alpha | 0~255 | 注释颜色透明度 | ||
getInkContentView | ink | annotType | ink | 注释类型 |
color | "#FF0000" | 注释颜色 | ||
alpha | 0~255 | 注释颜色透明度 | ||
borderWidth | 注释颜色 | |||
alpha | 1~70 | 画笔宽度 | ||
getShapeContentView | circle... | annotType | circle,square,line,arrow | 注释类型 |
borderColor | "#FF0000" | 边框线条颜色 | ||
borderColorAlpha | 0~255 | 边框线条颜色透明度 | ||
borderWidth | 1~10 | 边框线条宽度 | ||
fillColor | "#FF0000" | 填充颜色 | ||
fillColorAlpha | 0~255 | 填充颜色透明度 | ||
shapeType | circle,square,line,arrow | 选中的形状注释类型,4种 | ||
getFreetextContentView | freetext | annotType | freetext | 注释类型 |
fontBold | true,false | 文字是否加粗 | ||
fontItalic | true,false | 文字是否斜体 | ||
fontType | courier,helvetica,times_roman | 字体类型 | ||
textColor | "#FF0000" | 文字颜色 | ||
textColorAlpha | 0~255 | 文字透明度 | ||
fontSize | 1~100 | 文字大小 |
3.在实现的CPDFContextMenuShowHelper中定义MethodChannel获取Flutter端传入的需要修改的注释属性
modifyAnnotationAttrChannel = MethodChannel(messenger,“method_modify_annotation_attribute”)
modifyAnnotationAttrChannel.setMethodCallHandler { call, result ->
//这里获取flutter传入的颜色、透明度等参数
val configuration = call.arguments as? HashMap<String, Any>
when(call.method){
“modifyAnnotationAttribute” ->{
//颜色、透明度可能为空,注意判断,可能用户只调节一个属性
val annotAttrColor = configuration.getLong("color")
val annotAttrAlpha = configuration.getInt("alpha")
val annotationType = configuration.getString(PluginUtils.KEY_ANNOT_TYPE)
selectMarkupAnnotImpl?.let {
if (it.onGetAnnotation().type.name.equals(annotationType, true)) {
it.onGetAnnotation()?.let {annotation->
if (annotAttrColor != null) {
annotation.color = annotAttrColor.toInt()
}
if (annotAttrAlpha != null) {
annotation.alpha = annotAttrAlpha
}
annotation.updateAp()
selectMarkupAnnotImpl?.onAnnotAttrChange()
selectPageView?.invalidate()
}
}
}
}
...
KEY_DISMISS_MODIFY_ANNOTATION_ATTRIBUTE -> {
selectMarkupAnnotImpl = null
selectPageView = null
}
}
}
注释类型 | Key | value | 类型 | 说明 |
---|---|---|---|---|
highlight | color | "#FF0000" | String | 注释颜色 |
alpha | 0~255 | int | 注释颜色透明度 | |
strokeout | color | "#FF0000" | String | 注释颜色 |
alpha | 0~255 | int | 注释颜色透明度 | |
underline | color | "#FF0000" | String | 注释颜色 |
alpha | 0~255 | int | 注释颜色透明度 | |
squiggly | color | "#FF0000" | String | 注释颜色 |
alpha | 0~255 | int | 注释颜色透明度 | |
shape | borderColor | "#FF0000" | String | square边框颜色 |
borderColorAlpha | 0~255 | int | square边框颜色透明度 | |
borderWidth | 1~10 | int | square边框宽度 | |
fillColor | "#FF0000" | String | square填充颜色 | |
fillColorAlpha | 0~255 | int | square填充颜色透明度 | |
shapeType | square | String | 当前获取的形状类型,只返回square | |
freetext | fontBold | true,false | bool | 字体是否加粗 |
fontItalic | true,false | bool | 文字是否斜体 | |
textColor | "#FF0000" | String | 文字字体颜色 | |
textColorAlpha | 0~255 | int | 文字颜色透明度 | |
fontSize | 1~100 | int | 文字大小 | |
fontType | courier,helvetica,times_roman | String | 字体类型,仅支持三种字体 | |
signature | 无 | |||
stamp | 无 |
4.在用户关闭调节选项弹窗后会发出dismissModifyAnnotationAttr通知,请在此通知中释放之前赋值的全局变量
modifyAnnotationAttrChannel = MethodChannel(messenger,“method_modify_annotation_attribute”)
modifyAnnotationAttrChannel.setMethodCallHandler { call, result ->
//这里获取flutter传入的颜色、透明度等参数
val configuration = call.arguments as? HashMap<String, Any>
when(call.method){
“modifyAnnotationAttribute” ->{
...
}
"dismissModifyAnnotationAttr" -> {
selectMarkupAnnotImpl = null
selectPageView = null
}
}
}
方法名 | 通讯方式 | 说明 |
---|---|---|
dismissModifyAnnotationAttr | MethodChannel | flutter界面关闭属性调整弹窗后发送到原生端 |
在设置完签名注释、图章注释后,当前的currentFocusedType、touchMode会改变,当前焦点会从添加注释状态改为阅读状态,所以需要监听cpdfReaderView.setOnFocusedTypeChangedListener 通知flutter端当前的状态,以便于底部注释工具类选中状态变更
请求
方法名 | 通讯方式 | 参数 | 说明 |
---|---|---|---|
event_reader_view_focused_change_call_back | EventChannel | "readerViewFocusedChange" | 监听CPDFReaderView焦点类型改变 |
原生端响应 原生端请实现cpdfReaderView.setOnFocusedTypeChangedListener方法,并返回当前的注释类型(CPDFAnnotation.Type) iOS端请通过FlutterEventChannel返回当前焦点类型
Android示例 ```kotlin
// 首先定义EventChannel
val readerViewFocusedChangEventChannel = EventChannel(messenger, "event_reader_view_focused_change_call_back")
readerViewFocusedChangEventChannel.setStreamHandler {
onListen { arguments, events ->
readerView.setFocusedChangeEventEmitter(events)
}
}
//CPDFReaderView监听焦点类型变化,通过EventChannel传递给flutter端,直接传入注释类型即可,CPDFAnnotation.Type
pdfReaderView.setOnFocusedTypeChangedListener {
focusedChangeCallbackEventEmitter?.success(it.name)
}
```