diff --git a/docs-source/src/en/library/hikage-core.md b/docs-source/src/en/library/hikage-core.md index f7c8735..d53b9ef 100644 --- a/docs-source/src/en/library/hikage-core.md +++ b/docs-source/src/en/library/hikage-core.md @@ -444,6 +444,66 @@ val subLayout = Hikageable { } ``` +### State Management + +Hikage has a similar state management workaround to Jetpack Compose, which makes it easy to set up state listening for layout components. + +Hikage provides two states, `NonNullState` and `NullableState`, which are divided into two states: holding non-null and nullable. + +Unlike the recompose of Jetpack Compose, Hikage will not be recomposed, and the states takes effect through listening and callbacks. + +You can use both states in the following scenarios. + +> The following example + +```kotlin +val myLayout = Hikageable { + // Declare a non-null variable state. + val mTextState = mutableStateOf("Hello, World!") + // Declare a nullable and variable state. + val mDrawState = mutableStateOfNull() + // You can delegate the state to a variable. + var mText by mTextState + var mDraw by mDrawState + LinearLayout( + lparams = LayoutParams(matchParent = true), + init = { + orientation = LinearLayout.VERTICAL + } + ) { + TextView { + textSize = 16f + gravity = Gravity.CENTER + // Set (binding) state to text. + setState(mTextState) { + text = it + } + } + ImageView { + // Set (binding) state to Drawable. + setState(mDrawState) { + setImageDrawable(it) + } + } + Button { + text = "Click Me!" + setOnClickListener { + // Modify the value of non-null state. + mText = "Hello, Hikage!" + // Modify the value of the nullable state. + mDraw = drawableResource(R.drawable.ic_my_drawable) + } + } + } +} +``` + +In the example above, we declare a non-null state `mTextState` with `"Hello, World!"` with `mutableStateOf` +Then continue to declare a nullable state `mDrawState` with `null` using `mutableStateOfNull`. + +When clicking the button, we modify the value of `mTextState` to `"Hello, Hikage!"` and the value of `mDrawState` is the property resource `R.drawable.ic_my_drawable`. +At this time, the text and images of `TextView` and `ImageView` will be automatically updated. + ### Custom Layout Factory Hikage supports custom layout factories and is compatible with `LayoutInflater.Factory2`. diff --git a/docs-source/src/zh-cn/library/hikage-core.md b/docs-source/src/zh-cn/library/hikage-core.md index 4a4269b..254772f 100644 --- a/docs-source/src/zh-cn/library/hikage-core.md +++ b/docs-source/src/zh-cn/library/hikage-core.md @@ -425,6 +425,66 @@ val subLayout = Hikageable { } ``` +### 状态管理 + +Hikage 拥有与 Jetpack Compose 类似的状态管理解决方法,它可以轻松地设置布局组件的状态监听。 + +Hikage 提供了两种状态,`NonNullState` 和 `NullableState`,分为持有非空和可空两种状态。 + +不同于 Jetpack Compose 的重组 (Recompose),Hikage 不会重组,状态通过监听与回调生效。 + +你可以在如下场景中使用这两种状态。 + +> 示例如下 + +```kotlin +val myLayout = Hikageable { + // 声明一个非空可变状态 + val mTextState = mutableStateOf("Hello, World!") + // 声明一个可空可变状态 + val mDrawState = mutableStateOfNull() + // 你可以将状态委托给一个变量 + var mText by mTextState + var mDraw by mDrawState + LinearLayout( + lparams = LayoutParams(matchParent = true), + init = { + orientation = LinearLayout.VERTICAL + } + ) { + TextView { + textSize = 16f + gravity = Gravity.CENTER + // 设置 (绑定) 状态到文本 + setState(mTextState) { + text = it + } + } + ImageView { + // 设置 (绑定) 状态到 Drawable + setState(mDrawState) { + setImageDrawable(it) + } + } + Button { + text = "Click Me!" + setOnClickListener { + // 修改非空状态的值 + mText = "Hello, Hikage!" + // 修改可空状态的值 + mDraw = drawableResource(R.drawable.ic_my_drawable) + } + } + } +} +``` + +在上面的示例中,我们使用 `mutableStateOf` 声明了一个非空状态 `mTextState`,它的初始值为 `"Hello, World!"`, +然后继续使用 `mutableStateOfNull` 声明了一个可空状态 `mDrawState`,它的初始值为 `null`。 + +在点击按钮时,我们修改 `mTextState` 的值为 `"Hello, Hikage!"`,`mDrawState` 的值为属性资源 `R.drawable.ic_my_drawable`, +这时 `TextView` 和 `ImageView` 的文本和图片将会自动更新。 + ### 自定义布局装载器 Hikage 支持自定义布局装载器并同时兼容 `LayoutInflater.Factory2`,你可以通过以下方式自定义在 Hikage 布局装载过程中的事件和监听。 diff --git a/hikage-core/src/main/java/com/highcapable/hikage/core/runtime/State.kt b/hikage-core/src/main/java/com/highcapable/hikage/core/runtime/State.kt new file mode 100644 index 0000000..f3d5e9d --- /dev/null +++ b/hikage-core/src/main/java/com/highcapable/hikage/core/runtime/State.kt @@ -0,0 +1,173 @@ +/* + * Hikage - An Android responsive UI building tool. + * Copyright (C) 2019 HighCapable + * https://github.com/BetterAndroid/Hikage + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/5/2. + */ +@file:Suppress("unused") +@file:JvmName("StateUtils") + +package com.highcapable.hikage.core.runtime + +import com.highcapable.hikage.core.Hikage +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty + +/** + * Definition a [Hikage] runtime state interface. + */ +interface State : ReadWriteProperty + +/** + * Definition a [Hikage] runtime state interface for non-nullable type. + */ +interface NonNullState : State { + + /** The current value of the state. */ + var value: T + + /** + * Observe the state changes. + * @param observer the observer to be notified when the state changes. + */ + fun observe(observer: (T) -> Unit) +} + +/** + * Definition a [Hikage] runtime state interface for nullable type. + */ +interface NullableState : State { + + /** The current value of the state. */ + var value: T? + + /** + * Observe the state changes. + * @param observer the observer to be notified when the state changes. + */ + fun observe(observer: (T?) -> Unit) +} + +/** + * Implementing the [State] interface mutable state of [Hikage]. + */ +class MutableState private constructor() { + + /** + * The non-nullable state of [Hikage]. + */ + class NonNull internal constructor(private var holder: T) : NonNullState { + + private val observers = mutableSetOf<(T) -> Unit>() + + override var value get() = holder + set(value) { + if (holder == value) return + holder = value + observers.forEach { it(value) } + } + + override fun getValue(thisRef: Any?, property: KProperty<*>) = value + + override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { + this.value = value + } + + override fun observe(observer: (T) -> Unit) { + observers += observer + observer(value) + } + } + + /** + * The nullable state of [Hikage]. + */ + class Nullable internal constructor(private var holder: T?) : NullableState { + + private val observers = mutableSetOf<(T?) -> Unit>() + + override var value get() = holder + set(value) { + if (holder == value) return + holder = value + observers.forEach { it(value) } + } + + override fun getValue(thisRef: Any?, property: KProperty<*>) = value + + override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) { + this.value = value + } + + override fun observe(observer: (T?) -> Unit) { + observers += observer + observer(value) + } + } +} + +/** + * Create a mutable state of [Hikage] with the specified value. + * @param value the initial value of the state. + * @return [MutableState.NonNull] + */ +fun mutableStateOf(value: T) = MutableState.NonNull(value) + +/** + * Create a mutable state of [Hikage] with the specified value. + * @param value the initial value of the state. + * @return [MutableState.Nullable] + */ +fun mutableStateOfNull(value: T? = null) = MutableState.Nullable(value) + +/** + * Set the [Hikage] state value. + * + * Usage: + * + * ```kotlin + * val textState = mutableStateOf("Hello World!") + * var text by textState + * TextView { + * setState(textState) { + * text = it + * } + * } + * // Modify the state. + * text = "Hello Hikage!" + * ``` + * @param state the state to be set. + * @param apply the apply body. + */ +inline fun R.setState(state: NonNullState, crossinline apply: R.(T) -> Unit) { + state.observe { + this.apply(it) + } +} + +/** + * Set the [Hikage] state value. + * @see setState + * @param state the state to be set. + * @param apply the apply body. + */ +inline fun R.setState(state: NullableState, crossinline apply: R.(T?) -> Unit) { + state.observe { + this.apply(it) + } +} \ No newline at end of file