feat: add State

This commit is contained in:
2025-05-06 16:52:05 +08:00
parent 7c8c0256e8
commit 036017a804
3 changed files with 293 additions and 0 deletions

View File

@@ -444,6 +444,66 @@ val subLayout = Hikageable<LinearLayout.LayoutParams> {
}
```
### 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<Drawable>()
// 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`.

View File

@@ -425,6 +425,66 @@ val subLayout = Hikageable<LinearLayout.LayoutParams> {
}
```
### 状态管理
Hikage 拥有与 Jetpack Compose 类似的状态管理解决方法,它可以轻松地设置布局组件的状态监听。
Hikage 提供了两种状态,`NonNullState``NullableState`,分为持有非空和可空两种状态。
不同于 Jetpack Compose 的重组 (Recompose)Hikage 不会重组,状态通过监听与回调生效。
你可以在如下场景中使用这两种状态。
> 示例如下
```kotlin
val myLayout = Hikageable {
// 声明一个非空可变状态
val mTextState = mutableStateOf("Hello, World!")
// 声明一个可空可变状态
val mDrawState = mutableStateOfNull<Drawable>()
// 你可以将状态委托给一个变量
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 布局装载过程中的事件和监听。

View File

@@ -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<T> : ReadWriteProperty<Any?, T>
/**
* Definition a [Hikage] runtime state interface for non-nullable type.
*/
interface NonNullState<T> : State<T> {
/** 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<T> : State<T?> {
/** 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<T> private constructor() {
/**
* The non-nullable state of [Hikage].
*/
class NonNull<T> internal constructor(private var holder: T) : NonNullState<T> {
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<T> internal constructor(private var holder: T?) : NullableState<T?> {
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 <T> 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 <T> 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 <T, R> R.setState(state: NonNullState<T>, 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 <T, R> R.setState(state: NullableState<T>, crossinline apply: R.(T?) -> Unit) {
state.observe {
this.apply(it)
}
}