扩展原生View
扩展原生View
Kuikly已经封装了常用的View组件,例如Text、Image和List等组件,如果Kuikly提供的组件不满足你的需求或者你想复用native已有的UI组件的话, Kuikly允许你将现有的UI组件通过实现一些接口来暴露给Kuikly侧使用。
下面我们以MyImageView作为例子,来看看将原生的ImageView暴露给Kuikly使用需要做哪些工作
Kuikly侧: 新增ImageView组件,供业务方使用Android侧: 实现IKuiklyRenderViewExport接口,完成android的ImageView暴露给Kuikly侧iOS侧: 实现KuiklyRenderViewExportProtocol协议,完成iOS的UIImageView暴露给Kuikly侧- 鸿蒙侧: 继承
KuiklyRenderBaseView类,按需实现call、createArkUIView和setProp方法
Kuikly侧
Kuikly组件由这四个部分组成:
viewName: 组件对应到原生组件的名字Attr: 组件的属性,用于指定该组件含有哪些属性Event: 用于接收来自原生组件发送的事件test: 组件本身支持的方法,最终实现是在原生侧
我们完成上述的四部分,即可实现一个Kuikly侧的组件。
- 我们首先新建
MyImageView, 并继承DeclarativeBaseView类,继承DeclarativeBaseView的时候,需要指定MyImageView对应的Attr和Event类型
class MyImageView : DeclarativeBaseView<MyImageAttr, MyImageEvent>() {
override fun createAttr(): MyImageAttr {
return MyImageAttr()
}
override fun createEvent(): MyImageEvent {
return MyImageEvent()
}
override fun viewName(): String {
return "HRImageView"
}
fun test() {
performTaskWhenRenderViewDidLoad {
renderView?.callMethod("test", "params")
}
}
}
接着在
viewName()方法中,我们返回了HRImageView,表示该Kuikly的MyImageView对应到原生组件暴露给Kuikly侧的名字是HRImageView最后我们在
createAttr和createEvent中,返回了我们在DeclarativeBaseView泛型中指定的类型对应的实例Attr表示Kuikly侧组件支持的属性集。在这个例子中,我们返回了MyImageAttr来表示支持的属性
class MyImageAttr : Attr() {
/**
* 设置src
* @param src 数据源
* @return this
*/
fun src(src: String): MyImageAttr {
"src" with src
return this
}
}
// MyImageAttr中,我们编写了src方法,表示MyImageView组件支持src属性,用于设置MyImageView的数据源。
// 在src中方法中,我们将传递进来的属性透传给了原生的组件
Event表示Kuikly侧组件支持的事件。在这个例子中,我们返回了MyImageEvent来表示支持的事件
/**
* MyImageEvent支持loadSuccess事件,表示Image的图片加载上屏时
* 会触发loadSuccess中的handler闭包调用
*/
class MyImageEvent : Event() {
/*
* 图片成功加载时回调
* 由原生侧的组件触发
*/
fun loadSuccess(handler: (LoadSuccessParams) -> Unit) {
register(LOAD_SUCCESS) {
handler(LoadSuccessParams.decode(it))
}
}
companion object {
const val LOAD_SUCCESS = "loadSuccess"
}
}
data class LoadSuccessParams(
val src: String
) {
companion object {
fun decode(params: Any?): LoadSuccessParams {
val tempParams = params as? JSONObject ?: JSONObject()
val src = tempParams.optString("src", "")
return LoadSuccessParams(src)
}
}
}
- 实现
test方法
class MyImageView : DeclarativeBaseView<MyImageAttr, MyImageEvent>() {
...
fun test() {
performTaskWhenRenderViewDidLoad {
renderView?.callMethod("test", "params")
}
}
...
}
test为组件自身暴露出去的方法,这种方法真正的实现在原生侧实现的,在这个例子中,如果MyImageView对应到原生的组件为HRImageView,那这个test方法会调用到HRImageView的call("test", "params")方法中
注意
View的方法支持异步回调结果,即调用方法时传入回调函数renderView?.callMethod("test", "params", callback),但不支持同步返回结果。
- 最后我们编写
MyImageView组件声明式的api方法,供业务侧调用
fun ViewContainer<*, *>.MyImage(init: MyImageView.() -> Unit) {
addChild(MyImageView(), init)
}
- 业务侧使用方式
override fun body(): ViewBuilder {
val ctx = this
return {
MyImage {
attr {
size(176f, 132f)
src("https://vfiles.gtimg.cn/wuji_dashboard/xy/starter/844aa82b.png")
}
event {
loadSuccess {
}
}
}
}
}
完成上述步骤后, 业务方使用可以使用这个MyImageView组件了,但是具体的渲染还需要对应的平台实现,下面我们来看android平台和iOS平台如何暴露ImageView给Kuikly侧
android侧
android侧要完成原生ImageView暴露给Kuikly侧,需要完成以下步骤
- 新建
HRImageView并继承原生的ImageView, 然后实现IKuiklyRenderViewExport接口 - 实现
IKuiklyRenderViewExport中的setProp方法 - 实现
IKuiklyRenderViewExport中的call方法 - 注册
HRImageView,完成HRImageView暴露给Kuikly侧
我们完成上述4部分后,即可实现将HRImageView组件暴露给Kuikly侧
- 我们首先新建HRImageView,并继承原生的
ImageView,然后实现IKuiklyRenderViewExport接口
open class HRImageView(context: Context) : ImageView(context), IKuiklyRenderViewExport {
override fun setProp(propKey: String, propValue: Any): Boolean {
return super.setProp(propKey, propValue)
}
override fun call(method: String, params: String?, callback: KuiklyRenderCallback?): Any? {
return super.call(method, params, callback)
}
}
HRImageView中实现了setProp和call方法
setProp方法:Kuikly侧组件支持的属性和事件调用都会走到这个方法。例如上述的属性src和事件loadSuccesscall方法:Kuikly侧组件支持的方法调用都会走到这个方法。例如上述的方法test
实现src属性和loadSuccess事件
前面讲到Kuikly的ImageView组件,它支持src属性和loadSuccess事件,在运行的时候会调用到我们新建的HRImageView的setProp方法,我们来看下如何实现
实现src属性
open class HRImageView(context: Context) : ImageView(context), IKuiklyRenderViewExport {
private var src = ""
override fun setProp(propKey: String, propValue: Any): Boolean {
return when (propKey) {
"src" -> {
setSrc(propValue as String)
true
}
else -> super.setProp(propKey, propValue)
}
}
private fun setSrc(url: String) {
if (src == url) {
return
}
src = url
setImageDrawable(null)
// 加载并设置图片
val creator = Picasso.get().load(src)
val target = object : Target {
override fun onBitmapLoaded(bitmap: Bitmap, from: Picasso.LoadedFrom) {
setImageDrawable(BitmapDrawable(bitmap))
}
override fun onBitmapFailed(e: Exception?, errorDrawable: Drawable?) {
setImageDrawable(null)
}
override fun onPrepareLoad(placeHolderDrawable: Drawable?) {
}
}
creator.into(target)
}
}
当Kuikly侧的MyImageView运行到设置src属性时,对应到端的组件,会走到HRImageView的setProp方法,propKey为属性的名字,即src,而propValue为 属性对应的值。HRImageView识别到propKey为src时,使用Kuikly侧组件传递过来的src来加载Drawable,然后设置给HRImageView。
实现loadSuccess事件
open class HRImageView(context: Context) : ImageView(context), IKuiklyRenderViewExport {
private var loadSuccessCallback: KuiklyRenderCallback? = null
override fun setProp(propKey: String, propValue: Any): Boolean {
return when (propKey) {
...
"loadSuccess" -> {
loadSuccessCallback = propValue as KuiklyRenderCallback
true
}
else -> super.setProp(propKey, propValue)
}
}
private fun setSrc(url: String) {
if (src == url) {
return
}
src = url
setImageDrawable(null)
// 加载并设置图片
val creator = Picasso.get().load(src)
val target = object : Target {
override fun onBitmapLoaded(bitmap: Bitmap, from: Picasso.LoadedFrom) {
setImageDrawable(BitmapDrawable(bitmap))
}
override fun onBitmapFailed(e: Exception?, errorDrawable: Drawable?) {
setImageDrawable(null)
}
override fun onPrepareLoad(placeHolderDrawable: Drawable?) {
}
}
creator.into(target)
}
override fun setImageDrawable(drawable: Drawable?) {
super.setImageDrawable(drawable)
loadSuccessCallback?.invoke(mapOf(
PROP_SRC to src
))
}
}
loadSuccess表示,原生的ImageView的图片加载成功后,回调该事件给Kuikly侧的组件,即MyImageView。 所以我们在HRImageView中定义了private var loadSuccessCallback: KuiklyRenderCallback? = null变量,并在setProp方法中赋值
override fun setProp(propKey: String, propValue: Any): Boolean {
return when (propKey) {
...
"loadSuccess" -> {
loadSuccessCallback = propValue as KuiklyRenderCallback
true
}
else -> super.setProp(propKey, propValue)
}
}
最后重写HRImageView的setImageDrawable,在此方法中触发loadSuccessCallback回调
实现call方法
在Kuikly侧的ImageView含有一个test方法,该方法实现为:
fun test() {
performTaskWhenRenderViewDidLoad {
renderView?.callMethod("test", "params")
}
}
我们看到renderView?.callMethod方法传递了test方法名字和一个params字符串,这个调用会对应到HRImageView的call方法, 即
open class HRImageView(context: Context) : ImageView(context), IKuiklyRenderViewExport {
override fun call(method: String, params: String?, callback: KuiklyRenderCallback?): Any? {
return when(method) {
"test" -> callTestMethod(params)
else -> super.call(method, params, callback)
}
}
private fun callTestMethod(params: String?) {
Log.d("HRImageView", "callTestMethod: $params")
}
}
在上述的代码中,我们重写HRImageView的侧call方法,识别到方法名字为"test"时, 调用callTestMethod方法,以此来响应Kuikly侧的ImageView.test方法的调用
注册HRImageView到Kuikly中
原生侧完成HRImageView的编写后,还需要注册暴露给Kuikly侧,指定这个UI组件对应Kuikly侧组件的名字。我们在实现了KuiklyRenderViewDelegatorDelegate接口的类中重写registerExternalRenderView方法, 然后调用renderViewExport完成HRImageView的注册暴露
override fun registerExternalRenderView(kuiklyRenderExport: IKuiklyRenderExport) {
super.registerExternalRenderView(kuiklyRenderExport)
with(kuiklyRenderExport) {
renderViewExport("HRImageView", { context ->
HRImageView(context)
})
}
}
注意
"HRImageView"为Kuikly中ImageView.getName返回的字符串
iOS侧
iOS侧要完成原生UIImageView暴露给Kuikly侧,需要完成以下步骤
- 新建
HRImageView并继承原生的UIImageView, 然后实现KuiklyRenderViewExportProtocol协议 - 实现
KuiklyRenderViewExportProtocol中的hrv_setPropWithKey方法 - 实现
KuiklyRenderViewExportProtocol中的hrv_callWithMethod方法
我们完成上述3部分后,即可实现将HRImageView组件暴露给Kuikly侧
我们首先新建HRImageView并继承原生的UIImageView, 然后实现KuiklyRenderViewExportProtocol协议
注意
类名必须与Kuikly侧的ImageView.viewName()返回的值一样
#import <UIKIt/UIKit.h>
#import "KuiklyRenderViewExportProtocol.h"
NS_ASSUME_NONNULL_BEGIN
/*
* @brief 暴露给Kotlin侧调用的Image组件
*/
@interface HRImageView : UIImageView<KuiklyRenderViewExportProtocol>
@end
NS_ASSUME_NONNULL_END
实现hrv_setPropWithKey方法
接下来,我们实现hrv_setPropWithKey,并且调用KUIKLY_SET_CSS_COMMON_PROP宏。
#import "HRImageView.h"
#import "KRComponentDefine.h"
/*
* @brief 暴露给Kotlin侧调用的Image组件
*/
@interface HRImageView()
@end
@implementation HRImageView
@synthesize hr_rootView;
#pragma mark - KuiklyRenderViewExportProtocol
...
- (void)hrv_setPropWithKey:(NSString *)propKey propValue:(id)propValue {
KUIKLY_SET_CSS_COMMON_PROP;
}
...
@end
实现src属性
当Kuikly侧的ImageView调用到ImageAttr的src方法时,会调用到iOS侧的HRImageView的hrv_setPropWithKey方法。 然后KUIKLY_SET_CSS_COMMON_PROP会使用运行时,调用HRImageView中匹配setCss_propKey的方法。以src属性作为例子,会调用 到HRImageView的setCss_src方法。因此,我们在HRImageView中新增setCss_src方法,以响应Kuikly侧的调用
#import "HRImageView.h"
#import "HRComponentDefine.h"
/*
* @brief 暴露给Kotlin侧调用的Image组件
*/
@interface HRImageView()
@end
@implementation HRImageView
@synthesize hr_rootView;
#pragma mark - KuiklyRenderViewExportProtocol
...
- (void)hrv_setPropWithKey:(NSString *)propKey propValue:(id)propValue {
KUIKLY_SET_CSS_COMMON_PROP;
}
- (void)setCss_src:(NSString *)css_src {
1. css_src为kuikly传递过来的参数
2. 使用css_src去下载图片得到一个UIImage
3. 最后将UIImage设置给UIImageVIew
}
...
@end
实现loadSuccess事件
loadSuccess回调的实现步骤与src相似。
#import "HRImageView.h"
#import "HRComponentDefine.h"
/*
* @brief 暴露给Kotlin侧调用的Image组件
*/
@interface HRImageView()
/** 图片加载成功回调事件 */
@property (nonatomic, strong, nullable) KuiklyRenderCallback css_loadSuccess;
@end
@implementation HRImageView
@synthesize hr_rootView;
#pragma mark - KuiklyRenderViewExportProtocol
...
- (void)hrv_setPropWithKey:(NSString *)propKey propValue:(id)propValue {
KUIKLY_SET_CSS_COMMON_PROP;
}
- (void)setCss_loadSuccess:(KuiklyRenderCallback)css_loadSuccess {
if (_css_loadSuccess != css_loadSuccess) {
_css_loadSuccess = css_loadSuccess;
if (css_loadSuccess && self.image) {
[self p_fireLoadSuccessEvent];
}
}
}
- (void)setImage:(UIImage *)image {
if (image && self.image != image) {
[self p_fireLoadSuccessEvent];
}
[super setImage:image];
}
-(void)p_fireLoadSuccessEvent {
if (_css_loadSuccess) {
_css_loadSuccess( @{@"src" : self.css_src ?: @"" } );
}
}
...
@end
实现hrv_callWithMethod方法
在实现完属性和回调后,我们来看看如何实现Kuikly侧的test方法调用。
#import "HRImageView.h"
#import "HRComponentDefine.h"
/*
* @brief 暴露给Kotlin侧调用的Image组件
*/
@interface HRImageView()
@end
@implementation HRImageView
@synthesize hr_rootView;
#pragma mark - KuiklyRenderViewExportProtocol
...
- (NSString * _Nullable)hrv_callWithMethod:(NSString *)method params:(NSString *)params callback:(KuiklyRenderCallback)callback {
KUIKLY_CALL_CSS_METHOD;
return nil;
}
...
@end
当Kuikly侧的ImageView调用test方法时,会调用到iOS侧的HRImageView的hrv_callWithMethod方法。 然后KUIKLY_CALL_CSS_METHOD会使用运行时,调用HRImageView中匹配css_method的方法。以test方法作为例子,会调用 到HRImageView的css_test方法。因此,我们在HRImageView中新增css_test方法,以响应Kuikly侧的调用
#import "HRImageView.h"
#import "HRComponentDefine.h"
/*
* @brief 暴露给Kotlin侧调用的Image组件
*/
@interface HRImageView()
@end
@implementation HRImageView
@synthesize hr_rootView;
#pragma mark - KuiklyRenderViewExportProtocol
...
- (NSString * _Nullable)hrv_callWithMethod:(NSString *)method params:(NSString *)params callback:(KuiklyRenderCallback)callback {
KUIKLY_CALL_CSS_METHOD;
return nil;
}
- (void)css_test {
// 实现test方法
}
...
@end
完成以上步骤后,即可在iOS侧扩展Kuikly组件
提示
iOS侧的kuikly组件是通过运行时暴露给Kuikly侧,因此无需手动注册
鸿蒙侧
鸿蒙侧要完成原生Image暴露给Kuikly侧,需要完成以下步骤
- 新建
HRImageView并继承KuiklyRenderBaseView类 - 重写
KuiklyRenderBaseView中的setProp方法 - 实现
KuiklyRenderBaseView中的call方法 - 实现
KuiklyRenderBaseView中的createArkUIView方法 - 注册
HRImageView,将其暴露给Kuikly侧
我们完成上述5部分后,即可实现将HRImageView组件暴露给Kuikly侧
我们首先新建HRImageView,继承KuiklyRenderBaseView类
@Observed
export class HRImageView extends KuiklyRenderBaseView {
static readonly VIEW_NAME = 'HRImageView';
...
}
重写setProp方法
前面讲到Kuikly的MyImageView组件,它支持src属性和loadSuccess事件,在运行的时候会调用到我们新建的HRImageView的setProp方法,我们来看下如何实现
实现src属性
@Observed
export class HRImageView extends KuiklyRenderBaseView {
static readonly VIEW_NAME = 'HRImageView';
cssSrc: string | null = null;
image: PixelMap | undefined = undefined;
setProp(propKey: string, propValue: KRAny | KuiklyRenderCallback): boolean {
switch (propKey) {
case 'src':
this.setSrc(propValue as string);
return true;
default:
return super.setProp(propKey, propValue);
}
}
setSrc(url: string): void {
1. 将this.cssSrc设为kuikly传递过来的参数
2. 使用this.cssSrc去下载图片得到一个PixelMap
3. 最后将PixelMap设置给this.image
}
}
当Kuikly侧的MyImageView运行到设置src属性时,对应到端的组件,会走到HRImageView的setProp方法,propKey为属性的名字,即src,而propValue为属性对应的值。HRImageView识别到propKey为src时,使用kuikly侧组件传递过来的src来加载PixelMap。
实现loadSuccess事件
@Observed
export class HRImageView extends KuiklyRenderBaseView {
static readonly VIEW_NAME = 'HRImageView';
src: string | null = null;
image: PixelMap | undefined = undefined;
loadSuccessCallback: KuiklyRenderCallback | null = null;
setProp(propKey: string, propValue: KRAny | KuiklyRenderCallback): boolean {
switch (propKey) {
case 'src':
this.setSrc(propValue as string);
return true;
case 'loadSuccess':
this.setLoadSuccessCallback(propValue as KuiklyRenderCallback);
return true;
default:
return super.setProp(propKey, propValue);
}
}
setSrc(url: string): void {
1. 将this.src设为kuikly传递过来的参数
2. 使用this.src去下载图片得到一个PixelMap
3. 最后将PixelMap设置给this.image
}
setLoadSuccessCallback(callback: KuiklyRenderCallback): void {
if (this.loadSuccessCallback != callback) {
this.loadSuccessCallback = callback;
if (this.loadSuccessCallback != null && this.image) {
this.fileLoadSuccess();
}
}
}
setImage(image: PixelMap): void {
if (image && this.image != image) {
this.image = image;
this.fileLoadSuccess();
}
}
fileLoadSuccess(): void {
if (this.loadSuccessCallback) {
this.loadSuccessCallback({
"src": this.src
});
}
}
}
loadSuccess表示,原生的PixelMap图片加载成功后,回调该事件给Kuikly侧的组件,即MyImageView。所以我们在HRImageView中定义了loadSuccessCallback: KuiklyRenderCallback | null = null;变量,并在setProp方法中赋值,并在PixelMap成功加载时调用loadSuccessCallback
实现call方法
在Kuikly侧的ImageView含有一个test方法,该方法实现为:
fun test() {
performTaskWhenRenderViewDidLoad {
renderView?.callMethod("test", "params")
}
}
我们看到renderView?.callMethod方法传递了test方法名字和一个params字符串,这个调用会对应到HRImageView的call方法, 即
@Observed
export class HRImageView extends KuiklyRenderBaseView {
...
call(method: string, params: KTAny, callback: KuiklyRenderCallback | null): void {
switch (method) {
case 'test':
this.callTestMethod(params as string);
return;
}
}
callTestMethod(params: string) {
console.log(`HRImageView callTestMethod: ${params}`);
}
...
}
在上述的代码中,我们实现KuiklyRenderBaseView的call方法,识别到方法名字为"test"时, 调用callTestMethod方法,以此来响应Kuikly侧的ImageView.test方法的调用。
实现createArkUIView方法
createArkUIView方法用于创建组件实际的ArkUI视图,返回ComponentContent实例。
@Observed
export class HRImageView extends KuiklyRenderBaseView {
...
createArkUIView(): ComponentContent<KuiklyRenderBaseView> {
const uiContext = this.getUIContext() as UIContext
return new ComponentContent<KuiklyRenderBaseView>(uiContext, wrapBuilder<[KuiklyRenderBaseView]>(createMyImageView), this)
}
...
}
@Builder
function createMyImageView(view: KuiklyRenderBaseView) {
// 构造你的UI,如Column
Column(){
Image((view as HRImageView).image)
.width('100%')
.height('100%')
}.width('100%').height('100%')
}
注册HRImageView到Kuikly中
原生侧完成HRImageView的编写后,还需要注册暴露给Kuikly侧,指定这个UI组件对应Kuikly侧组件的名字。我们在实现了IKuiklyViewDelegate接口的类中重写getCustomRenderViewCreatorRegisterMap方法:
export class KuiklyViewDelegate extends IKuiklyViewDelegate {
...
getCustomRenderViewCreatorRegisterMap(): Map<string, KRRenderViewExportCreator> {
const map: Map<string, KRRenderViewExportCreator> = new Map();
map.set(HRImageView.VIEW_NAME, () => new HRImageView());
return map;
}
}
注意
HRImageView.VIEW_NAME需要与Kuikly侧的组件名相同,为"HRImageView"
H5侧
H5侧要完成原生image标签暴露给Kuikly侧,需要完成以下步骤
- 新建HRImageView实现IKuiklyRenderViewExport接口,并且创建具体img标签作为实际操作的dom节点
- 实现IKuiklyRenderViewExport中的setProp方法
- 实现IKuiklyRenderViewExport中的call方法
- 注册HRImageView,完成HRImageView暴露给Kuikly侧
我们完成上述4部分后,即可实现将HRImageView组件暴露给Kuikly侧
class HRImageView: IKuiklyRenderViewExport {
private val img = document.createElement("img")
override val ele: HTMLImageElement
get() = img.unsafeCast<HTMLImageElement>()
override fun setProp(propKey: String, propValue: Any): Boolean {
return super.setProp(propKey, propValue)
}
override fun call(method: String, params: String?, callback: KuiklyRenderCallback?): Any? {
return super.call(method, params, callback)
}
}
HRImageView中实现了setProp和call方法
setProp方法:Kuikly侧组件支持的属性和事件调用都会走到这个方法。例如上述的属性src和事件loadSuccesscall方法:Kuikly侧组件支持的方法调用都会走到这个方法。例如上述的方法test- 这里的
ele是最终会参与dom布局的抽象属性,这里我们返回实际创建的img标签,后续我们会操作这个ele
实现src属性和loadSuccess事件
前面讲到Kuikly的ImageView组件,它支持src属性和loadSuccess事件,在运行的时候会调用到我们新建的HRImageView的setProp方法,我们来看下如何实现
实现src属性
class HRImageView: IKuiklyRenderViewExport {
private val img = document.createElement("img")
override val ele: HTMLImageElement
get() = img.unsafeCast<HTMLImageElement>()
override fun setProp(propKey: String, propValue: Any): Boolean {
return when (propKey) {
"src" -> {
ele.src = propValue as String
true
}
else -> super.setProp(propKey, propValue)
}
}
}
实现loadSuccess事件
class HRImageView: IKuiklyRenderViewExport {
private val img = document.createElement("img")
private var loadSuccessCallback: KuiklyRenderCallback? = null
private var hasAddLoadListener = false
override val ele: HTMLImageElement
get() = img.unsafeCast<HTMLImageElement>()
override fun setProp(propKey: String, propValue: Any): Boolean {
return when (propKey) {
"src" -> {
ele.src = propValue as String
true
}
"loadSuccess" -> {
loadSuccessCallback = propValue as KuiklyRenderCallback
if (!hasAddLoadListener) {
hasAddLoadListener = true
ele.addEventListener("load", {
// When loading succeeds, callback the actual image source content
loadSuccessCallback?.invoke(
mapOf(
"src" to imageElement.src
)
)
})
}
true
}
else -> super.setProp(propKey, propValue)
}
}
}
实现call方法
在Kuikly侧的ImageView含有一个test方法,该方法实现为:
fun test() {
performTaskWhenRenderViewDidLoad {
renderView?.callMethod("test", "params")
}
}
我们看到renderView?.callMethod方法传递了test方法名字和一个params字符串,这个调用会对应到HRImageView的call方法, 即
class HRImageView: IKuiklyRenderViewExport {
private val img = document.createElement("img")
override val ele: HTMLImageElement
get() = img.unsafeCast<HTMLImageElement>()
override fun call(method: String, params: String?, callback: KuiklyRenderCallback?): Any? {
return when(method) {
"test" -> callTestMethod(params)
else -> super.call(method, params, callback)
}
}
private fun callTestMethod(params: String?) {
Log.d("HRImageView", "callTestMethod: $params")
}
}
在上述的代码中,我们重写HRImageView的侧call方法,识别到方法名字为"test"时, 调用callTestMethod方法,以此来响应Kuikly侧的ImageView.test方法的调用
注册HRImageView到Kuikly中
原生侧完成HRImageView的编写后,还需要注册暴露给Kuikly侧,指定这个UI组件对应Kuikly侧组件的名字。我们在实现了KuiklyRenderViewDelegatorDelegate接口的类中重写registerExternalRenderView方法, 然后调用renderViewExport完成HRImageView的注册暴露
拓展更多H5组件
要拓展更多的H5组件,只需要把document.createElement("img")里的img换成你想要操作的h5标签,然后操作这个标签做你想要的行为即可
微信小程序侧
微信小程序侧要完成小程序的image标签暴露给Kuikly侧,需要完成以下步骤
- 新建HRImageView实现IKuiklyRenderViewExport接口,并且创建MiniImageElement作为实际操作的dom节点
- 实现IKuiklyRenderViewExport中的setProp方法
- 实现IKuiklyRenderViewExport中的call方法
- 注册HRImageView,完成HRImageView暴露给Kuikly侧
我们完成上述4部分后,即可实现将HRImageView组件暴露给Kuikly侧
class HRImageView: IKuiklyRenderViewExport {
private val img = MiniImageElement()
override val ele: HTMLImageElement
get() = img.unsafeCast<HTMLImageElement>()
override fun setProp(propKey: String, propValue: Any): Boolean {
return super.setProp(propKey, propValue)
}
override fun call(method: String, params: String?, callback: KuiklyRenderCallback?): Any? {
return super.call(method, params, callback)
}
}
HRImageView中实现了setProp和call方法
setProp方法:Kuikly侧组件支持的属性和事件调用都会走到这个方法。例如上述的属性src和事件loadSuccesscall方法:Kuikly侧组件支持的方法调用都会走到这个方法。例如上述的方法test- 这里的
ele是最终会参与dom布局的抽象属性,这里我们返回实际创建的img标签,后续我们会操作这个ele
实现src属性和loadSuccess事件
前面讲到Kuikly的ImageView组件,它支持src属性和loadSuccess事件,在运行的时候会调用到我们新建的HRImageView的setProp方法,我们来看下如何实现
实现src属性
class HRImageView: IKuiklyRenderViewExport {
private val img = MiniImageElement()
override val ele: HTMLImageElement
get() = img.unsafeCast<HTMLImageElement>()
override fun setProp(propKey: String, propValue: Any): Boolean {
return when (propKey) {
"src" -> {
ele.src = propValue as String
true
}
else -> super.setProp(propKey, propValue)
}
}
}
实现loadSuccess事件
class HRImageView: IKuiklyRenderViewExport {
private val img = MiniImageElement()
private var loadSuccessCallback: KuiklyRenderCallback? = null
private var hasAddLoadListener = false
override val ele: HTMLImageElement
get() = img.unsafeCast<HTMLImageElement>()
override fun setProp(propKey: String, propValue: Any): Boolean {
return when (propKey) {
"src" -> {
ele.src = propValue as String
true
}
"loadSuccess" -> {
loadSuccessCallback = propValue as KuiklyRenderCallback
if (!hasAddLoadListener) {
hasAddLoadListener = true
ele.addEventListener("load", {
// When loading succeeds, callback the actual image source content
loadSuccessCallback?.invoke(
mapOf(
"src" to imageElement.src
)
)
})
}
true
}
else -> super.setProp(propKey, propValue)
}
}
}
实现call方法
在Kuikly侧的ImageView含有一个test方法,该方法实现为:
fun test() {
performTaskWhenRenderViewDidLoad {
renderView?.callMethod("test", "params")
}
}
我们看到renderView?.callMethod方法传递了test方法名字和一个params字符串,这个调用会对应到HRImageView的call方法, 即
class HRImageView: IKuiklyRenderViewExport {
private val img = MiniImageElement()
override val ele: HTMLImageElement
get() = img.unsafeCast<HTMLImageElement>()
override fun call(method: String, params: String?, callback: KuiklyRenderCallback?): Any? {
return when(method) {
"test" -> callTestMethod(params)
else -> super.call(method, params, callback)
}
}
private fun callTestMethod(params: String?) {
Log.d("HRImageView", "callTestMethod: $params")
}
}
在上述的代码中,我们重写HRImageView的侧call方法,识别到方法名字为"test"时, 调用callTestMethod方法,以此来响应Kuikly侧的ImageView.test方法的调用
注册HRImageView到Kuikly中
原生侧完成HRImageView的编写后,还需要注册暴露给Kuikly侧,指定这个UI组件对应Kuikly侧组件的名字。我们在实现了KuiklyRenderViewDelegatorDelegate接口的类中重写registerExternalRenderView方法, 然后调用renderViewExport完成HRImageView的注册暴露
拓展更多小程序组件
上面的例子里,我们使用的MiniImageElement是内置的Element实现,目前kuikly并没有实现全部的微信小程序基础组件,当你需要的微信小程序组件不是内置组件的时候,需要参照下面的方式进行拓展,这里以小程序的web-view组件为例子
1. 在Kuikly创建KRWebView
internal class KRWebView: DeclarativeBaseView<WebViewAttr, WebViewEvent>() {
override fun createEvent(): WebViewEvent {
return WebViewEvent()
}
override fun createAttr(): WebViewAttr {
return WebViewAttr().apply {
overflow(true)
}
}
override fun viewName(): String {
return "KRWebView"
}
}
internal class WebViewAttr : ComposeAttr() {
fun src(src: String): WebViewAttr {
"src" with src
return this
}
}
internal class WebViewEvent : ComposeEvent() {
}
internal fun ViewContainer<*, *>.WebView(init: KRWebView.() -> Unit) {
addChild(KRWebView(), init)
}
2. 在微信小程序工程创建KRWebView
这里和上面创建HRImageView的流程很像,但是多了个MiniWebViewElement,这个不是内置的类,我们需要自己创建
class KRWebView : IKuiklyRenderViewExport {
private val webElement = MiniWebViewElement()
override val ele: Element
get() = webElement.unsafeCast<Element>()
override fun setProp(propKey: String, propValue: Any): Boolean {
return when (propKey) {
SRC -> {
webElement.src = propValue.unsafeCast<String>()
true
}
KRCssConst.FRAME -> {
if (!MiniGlobal.globalThis.hasWebViewShowDialog.unsafeCast<Boolean>()) {
MiniGlobal.globalThis.hasWebViewShowDialog = true
NativeApi.plat.showToast(
json(
"title" to "Mini program web-view will automatically " +
"fill the full screen, cannot set width, height and position",
"duration" to 3000,
"icon" to "none"
)
)
}
Log.warn(
"Mini program web-view will automatically fill the full screen, cannot set width, height and position"
)
true
}
else -> super.setProp(propKey, propValue)
}
}
companion object {
const val SRC = "src"
const val VIEW_NAME = "KRWebView"
}
}
3. 实现MiniWebViewElement
实现MiniWebViewElement, 继承MiniElement, 然后补充NODE_NAME和componentsAlias
- NODE_NAME是小程序实际标签名
- componentsAlias是后续要添加到小程序渲染模版里的相关参数Map
- 补充我们的自定义逻辑,这里我添加了src的操作
class MiniWebViewElement(
nodeName: String = NODE_NAME,
nodeType: Int = MiniElementUtil.ELEMENT_NODE
) : MiniElement(nodeName, nodeType) {
var src: String = ""
set(value) {
setAttribute("src", value)
}
companion object {
const val NODE_NAME = "web-view"
val componentsAlias = js("{_num: '74', src: 'p0'}")
}
}
4. 注册KRWebView到Kuikly中
这里比起上面注册内置的MiniImageElement,我们多了Transform.addComponentsAlias函数的调用,这里需要添加新组件的模版信息
class KuiklyWebRenderViewDelegator : KuiklyRenderViewDelegatorDelegate {
override fun registerExternalRenderView(kuiklyRenderExport: IKuiklyRenderExport) {
super.registerExternalRenderView(kuiklyRenderExport)
// 补充KRWebView的模版信息
Transform.addComponentsAlias(
MiniWebViewElement.NODE_NAME,
MiniWebViewElement.componentsAlias
)
// 注册KRWebView
kuiklyRenderExport.renderViewExport(KRWebView.VIEW_NAME, {
KRWebView()
})
}
}
5. 补充小程序web-view的模版信息
在小程序壳工程的base.wxml里, 添加下面的代码
<template name="tmpl_0_74">
<web-view src="{{i.p0}}" bindmessage="eh" bindload="eh" binderror="eh" bindtap="eh" id="{{i.uid||i.sid}}" >
</web-view>
</template>
这里简单解释下,目前kuikly的小程序依赖循环基础模板来完成整个UI的渲染,默认只内置了Kuikly运行需要的微信小程序组件模板,如果新增新的微信小程序组件,需要补充模板定义
- _num配置的值74, 会对应到模板的tmpl_0_74
- src配置的p0, 对应到模板的
ps: 因为web-view组件不支持有子组件,所以只有一个模板,类似view这种支持嵌套的组件,可以参考base.wxml里view组件模板的写法