参考链接
Kuikly - 组件
Pager
Pager为Kuikly页面的入口类,类似iOS UI中的VC, Pager也有类似VC的生命周期:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
// 已创建 override fun created () { super .created() } // 页面在屏幕上可见 override fun pageDidAppear () { super .pageDidAppear() } // 页面在屏幕上消失 override fun pageDidDisappear () { super .pageDidDisappear() } // 页面即将消失 override fun pageWillDestroy () { super .pageWillDestroy() }
一个常见的Page
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
// 设置路由跳转名 @Page("my_ctstom_name" ) // 定义一个自己的类, 继承于 BasePager internal class MyCustomPage : BasePager () { // 伴生对象, Kotlin 中,类没有静态成员的概念,但通过 companion object,你可以模拟静态成员的行为 companion object { // 定义一个静态常量 TAG private const val TAG = "MyCustomPageTag" } // 定义 currentName 是个可观察的属性, 设置给 UI 组件后, 值变化会直接更新 UI 组件的内容, 类似 OC 的 RAC var currentName by observable("" ) // 构造 self.view override fun build () : ViewBuilder { // 使用 this 会导致循环引用, 所以一般在这里定义一个 ctx val ctx = this return { // 布局属性 attr { backgroundColor(Color.WHITE) } // 子 View View { } } } override fun createPlugins () : Map<String, () -> PluginModule> { val plugins = HashMap<String, () -> PluginModule>().apply { putAll(super .createPlugins()) // 基类的 hashMapOf( MediaPlugin.PLUGIN_NAME to { MediaPlugin() } // 再增加一个 ).let { putAll(it) } } return plugins + BizPluginBuilderList.getSomePlugin() // 这里也能加 } }
VM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
internal class MyCustomPageModel : BasePageStateViewModel <MyCustomPage >() { // 监听数组 var languageList: ObservableList<LanguageInfo> by observableList() // 监听变量 var displayName by observable("" ) // 懒加载 val settingInfo by lazy { SettingItemInfo("" ) } // 函数定义 fun updateJumpParams (param : Boolean = false ) : Boolean { // ... } } // 在 Page 中 internal class MyCustomPage : BasePager () { val viewModel: MyCustomPageModel by lazy { getViewModel(MyCustomPageModel::class ) } } // 在 ViewModelFactory.kt 增加一行 fun createViewModel (modelClass: KClass <out BaseViewModel <out BasePager >>) : BaseViewModel<out BasePager>? { return when (modelClass) { // ... MyCustomPageModel::class -> MyCustomPageModel() // ... } }
数据刷新
by observable
变量被设置为 by observable
后, UI 绑定这个变量会直接变化
1 2 3 4 5 6 7
var isOpen by observable(false )Switch { attr { isOn(ctx.isOpen) } }
vbind
组件使用vbind包裹,当vbind内属性发生改变时,整个组件会重新绘制
1 2 3 4 5 6 7 8 9 10 11
vbind({ ctx.displaySomething }) { if ( xx ) { View { } } else if ( xx ) { View { } } }
vfor
对于数量可变的列表数据,使用vfor包裹来实现cell,列表变化时会重新绘制
1 2 3 4 5 6 7
vfor({ ctx.dataList }) { itemData -> PersonInfoCustomizeCell { attr { item = itemData } } }
vif
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
vif({ xxx }) { View { } } velseif({ }) { View { } } velse { View { } }
ComposeView
1 2 3 4 5
class CustomView : ComposeView < >() {} internal fun ViewContainer<*, *> .Custom (init : CustomView .() -> Unit ) { addChild(CustomView(), init ) }
外部设置值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
class CustomView : ComposeView <CustomViewAttr >() { override fun body () : ViewBuilder { val ctx = this val bottomArrowX = ctx.attr.bottomArrowX // ... } override fun createAttr () : CustomViewAttr { return CustomViewAttr() } } internal class CustomViewAttr : ComposeAttr () { var bottomArrowX = 0f // ... } // 其他类调用 class OtherClassView : ComposeView () { override fun body () : ViewBuilder { val ctx = this View { Custom { attr { bottomArrowX = showX } } } } }
事件处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
class CustomView : ComposeView <CustomViewEvent >() { override fun body () : ViewBuilder { val ctx = this View { event { click { KLog.i(TAG, "onSendClick" ) ctx.event.onSendClick?.invoke(ctx.attr.customViewParam) } } } } } class CustomViewEvent : ComposeEvent () { var onSendClick: ((CustomViewParam) -> Unit )? = null } // 其他类调用 class OtherClassView : ComposeView () { override fun body () : ViewBuilder { val ctx = this View { Custom { event { onSendClick = { customViewParam -> // 事件处理 } } } } } }
布局
布局属性
Flex 布局
flexDirection 主轴
flexDirectionColumn(默认): 主轴方向为竖直方向,子孩子从上往下布局
flexDirectionRow: 主轴方向为水平方向,子孩子从左往右布局
flexDirectionColumnReverse: 主轴方向为竖直方向,子孩子从下往上布局
flexDirectionRowReverse: 主轴方向为水平方向,子孩子从右往左布局
justifyContent 主轴分布模式
justifyContentFlexStart(默认): 主轴开始的位置进行对齐
justifyContentFlexEnd: 主轴结束的位置进行对齐
justifyContentCenter: 主轴的中间位置进行对齐
justifyContentSpaceBetween: 主轴两端对齐,子孩子之间的间隔都相等
justifyContentSpaceAround: 每个项目两侧的间隔相等。所以,子孩子之间的间隔比项目与边框的间隔大一倍
justifyContentSpaceEvenly: Flex Item之间的间距相等,包括与边缘位置的距离
alignItems 交叉轴
alignItemsFlexStart: 交叉轴的起点对齐
alignItemsFlexEnd: 交叉轴的终点位置对齐
alignItemsCenter: 交叉轴的中点位置对齐
alignItemsStretch(默认): 如果 Flex 容器的孩子没有指定大小(高度或者宽度,取决于交叉轴是水平还是竖直)的话,将占满 Flex 容器
当 flexDirection 为 flexDirectionRow 时,交叉轴的方向为竖直方向,此时 alignItems 各个属性的效果为
当 flexDirection 为 flexDirectionColumn 时,交叉轴的方向为水平方向,此时 alignItems 的各个属性效果为:
Flex Item 布局属性
上面讲述的 Flex Container 属性,是针对 Flex Container 下的所有孩子生效。Flex Item 也可自己设置布局属性,覆盖 Flex Container 的属性。
alignSelf
alignSelf属性是控制Flex Item自身在 Flex 容器的交叉轴上的对齐方式,会覆盖 Flex 容器指定的 alignItems 属性,可选值为:
alignSelfFlexStart: Flex Item 自身在 Flex 容器的交叉轴的起点对齐
alignSelfCenter: Flex Item 自身在 Flex 容器的交叉轴中点对齐
alignSelfFlexEnd: Flex Item 自身在 Flex 容器的交叉轴终点对齐
alignItemsStretch: Flex Item 自身在交叉轴方向上铺满Flex 容器
alignSelf 与 alignItems 差不多,具体的对齐方向与 flexDirection 的值有关
当 flexDirection 的值为 flexDirectionColumn 时,Flex Item 设置 alignSelf 的效果如下:
flex
Flex Item 在主轴上,占据 Flex 容器的剩余可用空间的比例。 可用空间是指 Flex 容器除去已经被占用的空间,剩下的空间大小, 比如: 顶部是一个 titlebar,剩下的空间分配给列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
View { attr { size(screenWidth, screenHeight) flexDirectionColumn() // 主轴方向为竖直方向,孩子从上往下排列 // alignItems默认值为 alignItemsStretch, 因此在交叉轴,即水平方向上 // 其孩子的宽度与父亲一样大 } View { // title bar attr { height(56f ) // 宽度为父亲的宽度, 因为父亲的alignItems 默认为 alignItemsStretch, // 在交叉轴上的大小会占满父亲 } } List { attr { flex(1f ) // 表示占满父容器主轴上的可用空间,即占满父亲可用的高度(screenHeight - 56f) } } }
绝对布局
与 superView 一样大
1 2 3 4 5
View { attr { absolutePositionAllZero() } }
保持间距
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
View { attr { size(screenWidth, screenHeight) } Image { attr { positionAbsolute() // 表示宽高与父容器的宽高一样大 top(0f ) bottom(0f ) right(0f ) left(0f ) } } }
常用UI组件
设置背景色/圆角
1 2 3 4
attr { backgroundColor(ctx.buildThemedColor("skin_floor_color" )) borderRadius(10f ) }
文本
1 2 3 4 5 6 7 8 9 10
Text { attr { fontSize(36f .pxw) fontWeight500() marginTop(40f .pxw) marginBottom(10f .pxw) text(itemData.title) color(ctx.buildThemedColor("skin_text_main_color" )) } }
加载图片
1 2 3 4 5 6 7
Image { attr { src("https://musicx.y.qq.com/kuikly/assets/ic_svip_quality_guide_check.png" ) size(DESIGN_DESC_ICON_SIZE.pxh(), DESIGN_DESC_ICON_SIZE.pxh()) marginRight(DESIGN_DESC_ICON_MARGIN_RIGHT.pxh()) } }
列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
List { attr { positionAbsolute() } vfor({ ctx.dataList }) { itemData -> View { attr { Size(pagerWidth, 40F ) flexDirectionRow() } Image { attr { size(30F , 30F ) src(itemData.avatarUrl) } } Text { attr { fontSize(36f .pxw) fontWeight500() marginTop(40f .pxw) marginBottom(10f .pxw) text(itemData.title) color(ctx.buildThemedColor("skin_text_main_color" )) } } } } }
列表 cell 高度设置(在 cell 设置)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
List { ref { // it 是一个隐式名称(implicit name)的参数,用于表示单个参数的 lambda 表达式中的参数。当你在 lambda 表达式中只有一个参数时,可以使用 it 来代替显式声明参数名称 ctx.listViewRef = it } attr { absolutePositionAllZero() } vfor({ ctx.dataList }) { itemData -> PersonInfoCustomizeCell { attr { item = itemData } } } } // cell 单独设置 override fun body () : ViewBuilder { val ctx = this return { attr { height(120F ) } View { attr { backgroundColor(Color.WHITE) borderRadius(10F ) positionAbsolute() top(0F ) left(20F ) right(20F ) bottom(10F ) flexDirectionRow() } } } }
列表调用方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
import com.tencent.kuikly.core.views.ListViewinternal class xxxPager : BasePager () { var listViewRef : ViewRef<ListView<*, *>>? = null override fun build () : ViewBuilder { val ctx = this return { List { ref { ctx.listViewRef = it } attr { absolutePositionAllZero() } } } } override fun viewDidLayout () { super .viewDidLayout() this .listViewRef?.view?.setContentInset(top = 500F ) this .listViewRef?.view?.setContentOffset(0F , -500F ) // 设置 inset 后 自动产生了一个 offsetY 偏移 } }
subView 回调
1 2 3 4 5 6 7 8
// subView 先获取到 pager var pager = getPager()// 判定 pager 类型, if (pager is SomePager){ // 调用被标记为 observable 的对象, 达到调用的目的 pager.isShowAutoTranslateWarningDialog = true }
弹窗
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
// 是否需要展示自动翻译弹窗 var isShowAutoTranslateWarningDialog by observable(false )return { View { // 自动翻译弹窗 ctx.buildAutoTanslateWarningDialog().invoke(this ); } } private fun buildAutoTanslateWarningDialog () : ViewBuilder { val ctx = this return { SendMsgWarningDialog { attr { showDialog = ctx.isShowAutoTranslateWarningDialog title = StringConst.AUTOTRANSLATE_DIALOG_TITLE content = StringConst.AUTOTRANSLATE_DIALOG_CONTENT } event { onButtonSureClick = { ctx.isShowAutoTranslateWarningDialog = false KLog.i(TAG, "[clickedTranslateBtnForAlert] click sure " ) } onButtonCancelClick = { // 取消 ctx.isShowAutoTranslateWarningDialog = false KLog.i(TAG, "[clickedTranslateBtnForAlert] click cancel " ) } } } } } // 最后 在需要的时候 this .isShowAutoTranslateWarningDialog = true
逻辑
日志
1 2 3 4 5
KLog.i( TAG, "[onMessageBodyLongPress] msgFrame=$msgFrame , msgBodyFrame=$msgBodyFrame , clickX=$clickX , clickY=$clickY , msgBodyLeft=$msgBodyLeft , messageModel=$messageModel " ) KLog.i(TAG, "created artist_enc_uin: fromOne ${fromOne} fromTwo ${fromTwo} " )
缓存
1
bubblePlugin?.read(key = KEY_HAVE_DISPLAYED_GUIDE_VIEW + userId + myOwnEncryptUin)
判空(null 执行 toInt() 会崩溃)
1
!clickCountBefore.isNullOrEmpty()
类型转换
1 2 3 4
// toLong() 在遇到 "abc" 这样的字符串 会崩溃, 要用 toLongOrNull, toInt() 也有类似问题 var clickTs = clickTsBefore?.toLongOrNull() ?: 0L val tsStr = clickTs.toString()
延时处理
1 2 3
setTimeout(500 ) { // xxx }
获取时间戳
1
var ts = DateTime.currentTimestamp(); // 毫秒
枚举
1 2 3 4 5 6
internal enum class MediaType { PHOTO, CAMERA, VIDEO, VOICE }
字符串兜底值
1 2 3 4
var fromOne = pagerData.getParamString("artist_enc_uin" ) ?: "" var fromTwo = pagerData.getExtrasParamString("artist_enc_uin" ) ?: "" // 兼容web侧跳转 var from = fromOne.ifEmpty { fromTwo }
通知
1 2 3 4 5 6 7 8 9 10 11 12
// 监听 notifyModule.addNotify("xx" ) { // let 在非空的情况下对一个对象执行一个代码块(block), 作用是对非空对象进行操作,避免了空值检查的需要 it?.getString("xx" )?.let { returnName -> // xx } } // 发通知 notifyModule.postNotify("xx" , JSONObject().apply { put("xx" , "value" ) })
页面跳转
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
// 跳转封装 fun openPage (page: String , jsonObject: JSONObject ? = null , mode: Int = TABBAR_DISPLAY_HIDE_ALL, statusLight : Boolean = true , extras: JSONObject ? = null ) { Bridge.getPlugin<UIPlugin>(UIPlugin.PLUGIN_NAME)?.apply { openKuikly(KuiklyRouterInfo().apply { router = KuiklyRouterInfo.ROUTER_OPEN_QMPAGE pageName = page params = jsonObject // 状态栏模式 container = Container().apply { tabbarMode = mode this .statusLight = statusLight } pExtras = extras }) } } // 调用 openPage("my_ctstom_name" , JSONObject().apply { put("uin" , uin) put("XX" , xx) })
调用 Native 的 scheme
1 2 3 4 5 6 7 8
// 使用 plugin 记得在 pager 的 createPlugins 加一下, uiPlugin?.openNative( module = "ui" , method = "xxxMethod" , params = JSONObject().apply { put("color" , 1 ) } )
ViewBuilder
直接返回一个方法体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
override fun build () : ViewBuilder { val ctx = this return { // 复用分割线, invoke 是 kotlin 的语法 ctx.cutOffLineViews().invoke(this ) } } // 定义一个可复用的标签 private fun cutOffLineViews () : ViewBuilder { return { View { attr { backgroundColor(Color(0xffEBEBEB )) height(1f ) margin(40f ) } } } }
数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// 数组 listOf("*.jar" , "*.aar" ) // 可变数组 val topImages = mutableListOf<DressDialogTopImage>()topImages.add(topImage) // 遍历 val array = intArrayOf(1 , 2 , 3 , 4 , 5 )for (element in array) { println(element) } // 使用 for 循环和索引 val array = intArrayOf(1 , 2 , 3 , 4 , 5 )for (index in array.indices) { println("Index: $index , Value: ${array[index]} " ) }
哈希表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// 哈希表 mapOf("h" to h, "m" to m, "s" to s) // 可变哈希表 val data = mutableMapOf()data ["" ] = "" // 遍历 val map = mapOf("a" to 1 , "b" to 2 , "c" to 3 )map.forEach { (key, value) -> println("Key: $key , Value: $value " ) } // 遍历键/值 val map = mapOf("a" to 1 , "b" to 2 , "c" to 3 )for (key in map.keys) { println("Key: $key " ) }