参考链接 Kuikly - 组件
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" ) internal class MyCustomPage : BasePager () { companion object { private const val TAG = "MyCustomPageTag" } var currentName by observable("" ) override fun build () : ViewBuilder { val ctx = this return { attr { backgroundColor(Color.WHITE) } 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 { } } internal class MyCustomPage : BasePager () { val viewModel: MyCustomPageModel by lazy { getViewModel(MyCustomPageModel::class ) } } 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() } View { attr { height(56f ) } } List { attr { flex(1f ) } } }
绝对布局 与 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 { ctx.listViewRef = it } attr { absolutePositionAllZero() } vfor({ ctx.dataList }) { itemData -> PersonInfoCustomizeCell { attr { item = itemData } } } } 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 ) } }
subView 回调 1 2 3 4 5 6 7 8 var pager = getPager()if (pager is SomePager){ 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 var clickTs = clickTsBefore?.toLongOrNull() ?: 0L val tsStr = clickTs.toString()
延时处理
获取时间戳 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" ) ?: "" var from = fromOne.ifEmpty { fromTwo }
通知 1 2 3 4 5 6 7 8 9 10 11 12 notifyModule.addNotify("xx" ) { it?.getString("xx" )?.let { returnName -> } } 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 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 { 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) } 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 " ) }