hikage-core

Maven CentralMaven metadata URLAndroid Min SDK

这是 Hikage 的核心依赖,你需要引入此模块才能使用 Hikage 的基本功能。

配置依赖

你可以使用如下方式将此模块添加到你的项目中。

SweetDependency (推荐)

在你的项目 SweetDependency 配置文件中添加依赖。

libraries:
  com.highcapable.hikage:
    hikage-core:
      version: +

在你的项目 build.gradle.kts 中配置依赖。

implementation(com.highcapable.hikage.hikage.core)

传统方式

在你的项目 build.gradle.kts 中配置依赖。

implementation("com.highcapable.hikage:hikage-core:<version>")

请将 <version> 修改为此文档顶部显示的版本。

功能介绍

你可以 点击这里在新窗口中打开 查看 KDoc。

基本用法

使用下方的代码创建你的第一个 Hikage 布局。

首先,使用 Hikageable 创建一个 Hikage.Delegate 对象。

示例如下

val myLayout = Hikageable {
    LinearLayout {
        TextView {
            text = "Hello, World!"
        }
    }
}

然后,将其设置到你想要显示的父布局或根布局上。

示例如下

// 假设这就是你的 Activity
val activity: Activity
// 实例化 Hikage 对象
val hikage = myLayout.create(activity)
// 得到根布局
val root = hikage.root
// 设置为 Activity 的内容视图
activity.setContentView(root)

这样我们就完成了一个简单的布局创建与设置。

布局约定

Hikage 的布局基本元素基于 Android 原生的 View 组件,所有的布局元素都可以直接使用 Android 原生的 View 组件进行创建。

所有布局的创建过程都会被限定在指定的作用域 Hikage.Performer 中,它被称为布局的 “演奏者”,即饰演布局的角色对象,这个对象可以通过以下几种方式创建并维护。

Hikageable

正如 基本用法 所示,Hikageable 可以直接创建一个 Hikage.DelegateHikage 对象,在 DSL 中,你可以得到 Hikage.Performer 对象对布局内容进行创建。

第一种方案,在任意地方创建。

示例如下

// myLayout 是 Hikage.Delegate 对象
val myLayout = Hikageable {
    // ...
}
// 假设这就是你的 Context
val context: Context
// 在需要 Context 的地方实例化 Hikage 对象
val hikage = myLayout.create(context)

第二种方案,在存在 Context 的地方直接创建。

示例如下

// 假设这就是你的 Context
val context: Context
// 创建布局,myLayout 是 Hikage 对象
val myLayout = Hikageable(context) {
    // ...
}

HikageBuilder

除了上述的方式以外,你还可以维护一个 HikageBuilder 对象来预创建布局。

首先,我们需要创建一个 HikageBuilder 对象并定义为单例。

示例如下

object MyLayout : HikageBuilder {

    override fun build() = Hikageable {
        // ...
    }
}

然后,在需要的地方使用它,可以有如下两种方案。

第一种方案,直接使用 build 创建 Hikage.Delegate 对象。

示例如下

// myLayout 是 Hikage.Delegate 对象
val myLayout = MyLayout.build()
// 假设这就是你的 Context
val context: Context
// 在需要 Context 的地方实例化 Hikage 对象
val hikage = myLayout.create(context)

第二种方案,使用 Context.lazyHikage 创建 Hikage 委托对象。

例如,我们可以在 Activity 中像 ViewBinding 一样使用它。

示例如下

class MyActivity : AppCompatActivity() {

    private val myLayout by lazyHikage(MyLayout)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 得到根布局
        val root = myLayout.root
        // 设置为 Activity 的内容视图
        setContentView(root)
    }
}

基本布局组件

Hikage 采用与 Jetpack Compose 一致的函数式创建组件方案,它的布局使用两种基础组件完成,ViewViewGroup 函数, 它们分别对应于 Android 原生基于 ViewViewGroup 的组件。

View

View 函数的基础参数为以下三个,使用泛型定义创建的 View 对象类型。

如果不声明泛型类型,默认使用 android.view.View 作为创建的对象类型。

参数名称描述
lparams布局参数,即 ViewGroup.LayoutParams,使用 LayoutParams 进行创建
id用于查找已创建对象的 ID,使用字符串定义
initView 的初始化方法体,作为最后一位 DSL 参数传入

示例如下

View<TextView>(
    lparams = LayoutParams(),
    id = "my_text_view"
) {
    text = "Hello, World!"
    textSize = 16f
    gravity = Gravity.CENTER
}

ViewGroup

ViewGroup 函数的基础参数为四个,比较于 View 函数多了一个 performer 参数。

它必须声明一个泛型类型,因为 ViewGroup 是抽象类,需要一个具体的实现类。

ViewGroup 额外提供一个基于 ViewGroup.LayoutParams 的泛型参数,用于为子布局提供布局参数,不声明时默认使用 ViewGroup.LayoutParams

参数名称描述
lparams布局参数,即 ViewGroup.LayoutParams,使用 LayoutParams 进行创建
id用于查找已创建对象的 ID,使用字符串定义
initViewGroup 的初始化方法体,作为 DSL 参数传入
performerHikage.Performer 对象,作为最后一位 DSL 参数传入

performer 参数的作用是向下传递新的 Hikage.Performer 对象,作为子布局的创建者。

示例如下

ViewGroup<LinearLayout, LinearLayout.LayoutParams>(
    lparams = LayoutParams(),
    id = "my_linear_layout",
    // 初始化方法体将在这里使用 `init` 体现
    init = {
        orientation = LinearLayout.VERTICAL
        gravity = Gravity.CENTER
    }
) {
    // 可在这里继续创建子布局
    View()
}

LayoutParams

Hikage 中的布局均可使用 LayoutParams 函数设置布局参数,你可以使用以下参数创建它。

参数名称描述
width手动指定布局宽度
height手动指定布局高度
matchParent是否使用 MATCH_PARENT 作为布局宽度和高度
wrapContent是否使用 WRAP_CONTENT 作为布局宽度和高度
widthMatchParent仅设置宽度为 MATCH_PARENT
heightMatchParent仅设置高度为 MATCH_PARENT
body布局参数的初始化方法体,作为最后一位 DSL 参数传入

在你不设置 LayoutParams 对象或不指定 widthheight 时,Hikage 会自动使用 WRAP_CONTENT 作为布局参数。

body 方法体的类型来源于上层 ViewGroup 提供的第二位泛型参数。

示例如下

View(
    // 假设上层提供的布局参数类型为 LinearLayout.LayoutParams
    lparams = LayoutParams(width = 100.dp) {
        topMargin = 20.dp
    }
)

如果你只需要一个横向填充的布局,可以直接使用 widthMatchParent = true

示例如下

View(
    lparams = LayoutParams(widthMatchParent = true)
)

Layout

Hikage 支持引用第三方布局,你可以传入 XML 布局资源 ID、其它 Hikage 对象以及 View 对象,甚至是 ViewBinding

示例如下

ViewGroup<...> {
    // 引用 XML 布局资源 ID
    Layout(R.layout.my_layout)
    // 引用 ViewBinding
    Layout<MyLayoutBinding>()
    // 引用另一个 Hikage 或 Hikage.Delegate 对象
    Layout(myLayout)
}

定位布局组件

Hikage 支持使用 id 定位组件,在上面的示例中,我们使用了 id 参数设置了组件的 ID。

在设置 ID 后,你可以使用 Hikage.get 方法获取它们。

示例如下

val myLayout = Hikageable {
    View<TextView>(id = "my_text_view") {
        text = "Hello, World!"
    }
}
// 假设这就是你的 Context
val context: Context
// 在需要 Context 的地方实例化 Hikage 对象
val hikage = myLayout.create(context)
// 获取指定的组件,返回 View 类型
val textView = hikage["my_text_view"]
// 获取指定的组件并声明组件类型
val textView = hikage.get<TextView>("my_text_view")
// 如果不确定 ID 是否存在,可以使用 `getOrNull` 方法
val textView = hikage.getOrNull<TextView>("my_text_view")

自定义布局组件

Hikage 为 Android 基础的布局组件提供了组件类名对应的函数,你可以直接使用这些函数创建组件,而无需再使用泛型声明它们,如果你需要 Jetpack 或者 Material 提供的组件, 可以引入 hikage-widget-androidxhikage-widget-material 模块。

示例如下

LinearLayout(
    lparams = LayoutParams(),
    id = "my_linear_layout",
    init = {
        orientation = LinearLayout.VERTICAL
        gravity = Gravity.CENTER
    }
) {
    TextView(
        lparams = LayoutParams(),
        id = "my_text_view"
    ) {
        text = "Hello, World!"
        textSize = 16f
        gravity = Gravity.CENTER
    }
}

初始化后的 ViewViewGroup 对象会返回其自身对象类型的实例,你可以在接下来的布局中使用它们。

示例如下

val textView = TextView {
    text = "Hello, World!"
    textSize = 16f
    gravity = Gravity.CENTER
}
Button {
    text = "Click Me!"
    setOnClickListener {
        // 直接使用 textView 对象
        textView.text = "Clicked!"
    }
}

如果提供的组件不满足你的需求,你可以手动创建自己的组件。

示例如下

// 假设你已经定义好了你的自定义组件
class MyCustomView(context: Context, attrs: AttributeSet? = null) : View(context, attrs) {
    // ...
}

// 下面,创建组件对应的函数
// 自定义组件必须声明此注解
// 声明组件的注解具有传染性,在每个用于构建布局的作用域中,都需要存在此注解
@Hikageable
// 函数的命名可以随意,但是建议使用大驼峰命名
// 函数的签名部分需要固定声明为 `inline fun <reified LP : ViewGroup.LayoutParams> Hikage.Performer<LP>`
inline fun <reified LP : ViewGroup.LayoutParams> Hikage.Performer<LP>.MyCustomView(
    lparams: Hikage.LayoutParams? = null,
    id: String? = null,
    init: HikageView<MyCustomView> = {},
    // 如果此组件是容器,可以声明一个 `performer` 参数
    // performer: HikagePerformer<LP> = {}
) = View<MyCustomView>(lparams, id, init)

每次都手动实现这样复杂的函数看起来会很繁琐,如果你希望能够自动生成组件函数,可以引入并参考 hikage-compiler 模块。

组合与拆分布局

在搭建 UI 时,我们通常会将可复用的布局作为组件来使用,如果你不想每一个部分都使用原生的自定义 View 将其分别定制,你可以直接将布局逻辑部分进行拆分。

Hikage 支持将布局拆分为多个部分进行组合,你可以在任何地方使用 Hikageable 函数创建一个新的 Hikage.Delegate 对象。

示例如下

// 假设这是你的主布局
val mainLayout = Hikageable {
    LinearLayout(
        lparams = LayoutParams(matchParent = true),
        init = {
            orientation = LinearLayout.VERTICAL
        }
    ) {
        TextView {
            text = "Hello, World!"
        }
        // 组合子布局
        Layout(subLayout)
    }
}
// 假设这是你的布局子模块
// 由于上层布局使用了 LinearLayout,所以你可以为子布局声明 LinearLayout.LayoutParams
val subLayout = Hikageable<LinearLayout.LayoutParams> {
    TextView(
        lparams = LayoutParams {
            topMargin = 16.dp
        }
    ) {
        text = "Hello, Sub World!"
    }
}

自定义布局装载器

Hikage 支持自定义布局装载器并同时兼容 LayoutInflater.Factory2,你可以通过以下方式自定义在 Hikage 布局装载过程中的事件和监听。

示例如下

val factory = HikageFactory { parent, base, context, params ->
    // 你可以在这里自定义布局装载器的行为
    // 例如,使用你自己的方式创建一个新的 View 对象
    // `parent` 为当前组件要添加到的 ViewGroup 对象,如果没有则为 `null`
    // `base` 为上一个 HikageFactory 创建的 View 对象,如果没有则为 `null`
    // `params` 对象中包含了组件 ID、AttributeSet 以及 View 的 Class 对象
    val view = MyLayoutFactory.createView(context, params)
    // 你还可以在这里对创建的 View 对象进行初始化和设置
    view.setBackgroundColor(Color.RED)
    // 返回创建的 View 对象
    // 返回 `null` 将会使用默认的组件装载方式
    view
}

你还可以直接传入 LayoutInflater 对象以自动装载并使用其中的 LayoutInflater.Factory2

示例如下

// 假设这就是你的 LayoutInflater 对象
val layoutInflater: LayoutInflater
// 通过 LayoutInflater 创建 HikageFactory 对象
val factory = HikageFactory(layoutInflater)

然后使用以下方式将其设置到你需要装载的 Hikage 布局上。

示例如下

// 假设这就是你的 Context
val context: Context
// 创建 Hikage 对象
val hikage = Hikageable(
    context = context,
    factory = {
        // 添加自定义的 HikageFactory 对象
        add(factory)
        // 直接添加
        add { parent, base, context, params ->
            // ...
            null
        }
        // 连续添加多个
        addAll(factories)
    }
) {
    LinearLayout {
        TextView {
            text = "Hello, World!"
        }
    }
}

小提示

Hikage 在默认装载时将会根据传入 Context 对象的 LayoutInflater.Factory2 对布局进行装载,如果你正在使用 AppCompatActivity, 布局中的组件将会自动被替换为对应的 Compat 组件或 Material 组件,与 XML 布局的特性保持一致。

如果你不需要默认生效此特性,可以使用以下方式全局关闭。

示例如下

Hikage.isAutoProcessWithFactory2 = false

预览布局

Hikage 支持在 Android Studio 中预览布局,借助于 Android Studio 自带的自定义 View 预览插件,你可以使用以下方式预览布局。

你只需要定义一个预览布局的自定义 View 并继承于 HikagePreview

示例如下

class MyLayoutPreview(context: Context, attrs: AttributeSet?) : HikagePreview(context, attrs) {

    override fun build() = Hikageable {
        LinearLayout {
            TextView {
                text = "Hello, World!"
            }
        }
    }
}

然后在你当前的窗口右侧应该会出现预览窗格,打开后点击 “Build & Refresh”,等待编译完成后将会自动显示预览。

小提示

HikagePreview 实现了 HikageBuilder 接口,你可以在 build 方法中返回任意的 Hikage 布局以进行预览。

特别注意

HikagePreview 仅支持在 Android Studio 中预览布局,请勿在运行时使用它或将其添加到任何 XML 布局中。