Bump hikage-core, hikage-extension, hikage-extension-betterandroid, hikage-extension-compose, hikage-compiler, hikage-widget-androidx, hikage-widget-material version to 1.0.0
6
.idea/AndroidProjectSystem.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AndroidProjectSystem">
|
||||
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
|
||||
</component>
|
||||
</project>
|
BIN
.idea/icon.png
generated
Before Width: | Height: | Size: 4.4 KiB |
11
.idea/icon.svg
generated
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="512" height="512" rx="128" fill="#4EA16B"/>
|
||||
<rect x="100" y="220" width="79" height="79" rx="24" fill="white" fill-opacity="0.9"/>
|
||||
<rect x="100" y="315" width="79" height="79" rx="39.5" fill="white" fill-opacity="0.9"/>
|
||||
<rect x="96" y="118" width="320" height="86" rx="30" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M250.829 235.577C249.606 235.736 248.427 236.135 247.359 236.752C246.281 237.36 245.334 238.175 244.574 239.152C243.813 240.129 243.254 241.247 242.929 242.442C242.604 243.637 242.518 244.884 242.679 246.112C242.839 247.341 243.241 248.524 243.862 249.596L260.34 278.47C232.519 295.058 214 324.376 214 357.705C214 359.809 214.074 361.925 214.22 363.997C214.798 372.166 221.607 378.5 229.813 378.5H396.187C404.497 378.5 411.294 372.019 411.789 363.849C411.929 361.822 412 359.756 412 357.705C412 324.376 393.48 295.058 365.66 278.47L382.138 249.596C382.759 248.524 383.161 247.341 383.321 246.112C383.482 244.884 383.396 243.637 383.071 242.442C382.746 241.247 382.187 240.129 381.426 239.152C380.666 238.175 379.719 237.36 378.641 236.752C377.573 236.135 376.394 235.736 375.171 235.577C373.948 235.419 372.706 235.504 371.517 235.829C370.327 236.154 369.213 236.711 368.24 237.468C367.266 238.226 366.452 239.169 365.845 240.244L348.649 270.375C337.583 266.323 325.561 264.1 313.001 264.1C300.44 264.1 288.417 266.323 277.351 270.376L260.155 240.244C259.547 239.169 258.734 238.226 257.76 237.468C256.787 236.711 255.673 236.154 254.483 235.829C253.294 235.504 252.052 235.419 250.829 235.577ZM286.6 328.45C286.6 334.221 281.921 338.9 276.15 338.9C270.379 338.9 265.7 334.221 265.7 328.45C265.7 322.679 270.379 318 276.15 318C281.921 318 286.6 322.679 286.6 328.45ZM349.85 338.9C355.621 338.9 360.3 334.221 360.3 328.45C360.3 322.679 355.621 318 349.85 318C344.079 318 339.4 322.679 339.4 328.45C339.4 334.221 344.079 338.9 349.85 338.9Z" fill="white"/>
|
||||
<path d="M149.849 146.129C148.678 144.957 146.778 144.957 145.607 146.129L132.879 158.857C132.289 159.446 131.996 160.22 132 160.993C131.996 161.765 132.289 162.539 132.879 163.129L145.607 175.857C146.778 177.028 148.678 177.028 149.849 175.857C151.021 174.685 151.021 172.786 149.849 171.614L139.228 160.993L149.849 150.371C151.021 149.2 151.021 147.3 149.849 146.129Z" fill="#4EA16B"/>
|
||||
<path d="M138.757 160.995C138.757 159.338 140.101 157.995 141.757 157.995H183.757C185.414 157.995 186.757 159.338 186.757 160.995C186.757 162.652 185.414 163.995 183.757 163.995H141.757C140.101 163.995 138.757 162.652 138.757 160.995Z" fill="#4EA16B"/>
|
||||
<path d="M362.151 146.129C363.322 144.957 365.222 144.957 366.393 146.129L379.121 158.857C379.711 159.446 380.004 160.22 380 160.993C380.004 161.765 379.711 162.539 379.121 163.129L366.393 175.857C365.222 177.028 363.322 177.028 362.151 175.857C360.979 174.685 360.979 172.786 362.151 171.614L372.772 160.993L362.151 150.371C360.979 149.2 360.979 147.3 362.151 146.129Z" fill="#4EA16B"/>
|
||||
<path d="M373.243 160.995C373.243 159.338 371.9 157.995 370.243 157.995H328.243C326.586 157.995 325.243 159.338 325.243 160.995C325.243 162.652 326.586 163.995 328.243 163.995H370.243C371.9 163.995 373.243 162.652 373.243 160.995Z" fill="#4EA16B"/>
|
||||
</svg>
|
After Width: | Height: | Size: 3.2 KiB |
2
.idea/kotlinc.xml
generated
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="version" value="2.0.0" />
|
||||
<option name="version" value="2.0.10" />
|
||||
</component>
|
||||
</project>
|
17
.idea/runConfigurations.xml
generated
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RunConfigurationProducerService">
|
||||
<option name="ignoredProducers">
|
||||
<set>
|
||||
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
|
||||
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
|
||||
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
193
README-zh-CN.md
@@ -5,7 +5,7 @@
|
||||
[](https://t.me/HighCapable_Dev)
|
||||
[](https://qm.qq.com/cgi-bin/qm/qr?k=Pnsc5RY6N2mBKFjOLPiYldbAbprAU3V7&jump_from=webapi&authKey=X5EsOVzLXt1dRunge8ryTxDRrh9/IiW1Pua75eDLh9RE3KXE+bwXIYF5cWri/9lf)
|
||||
|
||||
<img src="img-src/icon.png" width = "100" height = "100" alt="LOGO"/>
|
||||
<img src="img-src/icon.svg" width = "100" height = "100" alt="LOGO"/>
|
||||
|
||||
一个 Android 响应式 UI 构建工具。
|
||||
|
||||
@@ -18,199 +18,30 @@
|
||||
|
||||
## 这是什么
|
||||
|
||||
这是一个 Android 响应式 UI 构建工具,它的设计聚焦于 **实时代码构建 UI**。
|
||||
`Hikage` (发音 /ˈhɪkɑːɡeɪ/),这是一个 Android 响应式 UI 构建工具,它的设计聚焦于 **实时代码构建 UI**。
|
||||
|
||||
名称取自 「BanG Dream It's MyGO!!!!!」 中的原创歌曲《春日影》(Haru**hikage**)。
|
||||
项目图标由 [MaiTungTM](https://github.com/Lagrio) 设计,名称取自 「BanG Dream It's MyGO!!!!!」 中的原创歌曲《春日影》(Haru**hikage**)。
|
||||
|
||||
<details><summary>为什么要...</summary>
|
||||
<div align="center">
|
||||
<img src="https://i0.hdslb.com/bfs/garb/item/fa1ffd8af57626ca4f6bd562bac097239d36838b.png" width = "100" height = "100" alt="LOGO"/>
|
||||
<img src="img-src/nagasaki_soyo.png" width = "100" height = "100" alt="LOGO"/>
|
||||
|
||||
**なんで春日影レイアウト使いの?**
|
||||
**なんで春日影レイアウト使いの?**
|
||||
</div>
|
||||
</details>
|
||||
|
||||
不同于 Jetpack Compose 的声明式 UI,Hikage 专注于 Android 原生平台,它的设计目标是为了让开发者能够快速构建 UI 并可直接支持 Android 原生组件。
|
||||
|
||||
Hikage 只是一个 UI 构建工具,自身并不提供任何 UI 组件。
|
||||
**<u>Hikage 只是一个 UI 构建工具,自身并不提供任何 UI 组件</u>**。
|
||||
|
||||
拒绝重复造轮子,我们的方案始终是兼容与高效,现在你可以抛弃 ViewBinding 和 XML 甚至是 `findViewById`,直接来尝试使用代码布局吧。
|
||||
|
||||
Android View 中的属性将配合 Gradle 插件实现自动生成,你可以像在 XML 一样去使用它,
|
||||
而不需要考虑在代码中如何完成复杂的属性设置,特别是一些第三方库并未对它们的自定义 View 提供代码中的属性动态修改。
|
||||
`Hikage` 配合我们的另一个项目 [BetterAndroid](https://github.com/BetterAndroid/BetterAndroid) 使用效果更佳,同时 `Hikage` 自身将自动引用
|
||||
`BetterAndroid` 相关依赖作为核心内容。
|
||||
|
||||
## 效果展示
|
||||
## 开始使用
|
||||
|
||||
> 原始布局
|
||||
|
||||
```xml
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Hello, World!"
|
||||
android:textSize="16sp"
|
||||
android:textColor="#000000"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:gravity="center" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Click Me!"
|
||||
android:textSize="16sp"
|
||||
android:textColor="#FFFFFF"
|
||||
android:backgroundTint="#FF0000"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:gravity="center" />
|
||||
</LinearLayout>
|
||||
```
|
||||
|
||||
> 使用 Hikage
|
||||
|
||||
```kotlin
|
||||
// 使用 Hikage 构建布局,需要有一个 UI Context
|
||||
val context: Context
|
||||
// 确保 Context 为 UI Context
|
||||
if (!context.isUiContext) return
|
||||
// 开始构建布局,请注意确保 context 参数已初始化
|
||||
// 根据 Android 原生组件特性,布局构建后属性 (`attrs`) 将固定,无法动态修改
|
||||
val hikage = Hikageable(
|
||||
context = context,
|
||||
// 你还可以自定义每个 View 被创建后的操作
|
||||
onViewCreated = { name, view ->
|
||||
// ...
|
||||
}
|
||||
) {
|
||||
LinearLayout(
|
||||
attrs = {
|
||||
android.layout_width = MATCH_PARENT
|
||||
android.layout_height = MATCH_PARENT
|
||||
android.orientation = VERTICAL
|
||||
android.padding = 16.dp
|
||||
},
|
||||
// 你可以手动指定布局参数
|
||||
lpparams = {
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
) {
|
||||
TextView(
|
||||
// 使用字符串形式设置 ID,可以使用大驼峰、小驼峰或下划线形式,在生成时将被转换为小驼峰形式
|
||||
id = "text_view",
|
||||
// 你可以直接使用 attrs 来设置属性,无需考虑它们属于谁
|
||||
attrs = {
|
||||
android.layout_width = WRAP_CONTENT
|
||||
android.layout_height = WRAP_CONTENT
|
||||
android.text = "Hello, World!"
|
||||
android.textSize = 16.sp
|
||||
android.textColor = Color.BLACK
|
||||
android.layout_marginTop = 16.dp
|
||||
android.layout_marginStart = 16.dp
|
||||
android.layout_marginEnd = 16.dp
|
||||
android.layout_marginBottom = 16.dp
|
||||
android.gravity = Gravity.CENTER
|
||||
// 或者使用字符串形式设置属性 (注意没有拼写检查)
|
||||
namespace("android") {
|
||||
set("id", R.id.text_view)
|
||||
set("layout_margin", 16.dp)
|
||||
set("layout_gravity", Gravity.CENTER)
|
||||
// ...
|
||||
}
|
||||
},
|
||||
// 你也可以手动指定布局参数
|
||||
lpparams = {
|
||||
gravity = Gravity.CENTER
|
||||
},
|
||||
// 执行初始化后的操作
|
||||
// 你也可以手动设置属性
|
||||
initialize = {
|
||||
text = "Hello, World!"
|
||||
textSize = 16f
|
||||
setTextColor(Color.BLACK)
|
||||
// 或者更多操作
|
||||
doOnLayout {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
)
|
||||
// 使用第三方 View
|
||||
View<MaterialButton>(
|
||||
id = "button",
|
||||
attrs = {
|
||||
android.layout_width = WRAP_CONTENT
|
||||
android.layout_height = WRAP_CONTENT
|
||||
android.text = "Click Me!"
|
||||
android.textSize = 16.sp
|
||||
android.textColor = Color.WHITE
|
||||
android.backgroundTint = Color.RED
|
||||
android.layout_marginTop = 16.dp
|
||||
android.layout_marginStart = 16.dp
|
||||
android.layout_marginEnd = 16.dp
|
||||
android.layout_marginBottom = 16.dp
|
||||
android.gravity = Gravity.CENTER
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
// 获取根布局
|
||||
val root = hikage.root
|
||||
// 你还可以将其转换为第一个布局的类型,等价于 hikage.root as LinearLayout
|
||||
// 得益于 Kotlin 的特性,直接使用 Hikageable(...) { ... }.rootAsType() 可以不需要填写泛型
|
||||
val root = hikage.rootAsType<LinearLayout>()
|
||||
// 设置到 Activity 上
|
||||
setContentView(root)
|
||||
// 获取构建的布局内部组件 (第一种方案)
|
||||
val textView = hikage.textView
|
||||
val button = hikage.button
|
||||
// 获取构建的布局内部组件 (第二种方案)
|
||||
val textView = hikage.get<TextView>("text_view")
|
||||
val button = hikage.get<MaterialButton>("button")
|
||||
```
|
||||
|
||||
## 使用 Android Studio 预览
|
||||
|
||||
不同于 XML,Hikage 不支持实时预览,但你可以继承于 Hikage 提供的 `HikagePreview` 在其中传入你的布局,然后在 Android Studio 右侧窗格中查看预览。
|
||||
|
||||
你还可以在代码中使用 `isInEditMode` 来避免在预览模式中展示无法显示的实际逻辑代码。
|
||||
|
||||
```kotlin
|
||||
class MyPreview(context: Context, attrs: AttributeSet?) : HikagePreview(context, attrs) {
|
||||
|
||||
override fun onPreview(): Hikage {
|
||||
// 返回你的布局
|
||||
return Hikageable {
|
||||
Button(
|
||||
attrs = {
|
||||
android.layout_width = WRAP_CONTENT
|
||||
android.layout_height = WRAP_CONTENT
|
||||
android.text = "Click Me!"
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
注意 `HikagePreview` 仅用于预览,不应该在实际代码中使用,否则会抛出异常。
|
||||
|
||||
Hikage 可能会有计划支持 Java,但依然推荐使用 Kotlin。
|
||||
|
||||
## WIP
|
||||
|
||||
该项目仍在开发中,如果您有任何建议或反馈,请随时开启 `issue` 或 PR。
|
||||
[点击这里](https://betterandroid.github.io/Hikage/zh-cn) 前往文档页面查看更多详细教程和内容。
|
||||
|
||||
## 项目推广
|
||||
|
||||
@@ -227,6 +58,10 @@ Hikage 可能会有计划支持 Java,但依然推荐使用 Kotlin。
|
||||
|
||||

|
||||
|
||||
## 第三方开源使用声明
|
||||
|
||||
- [AndroidHiddenApiBypass](https://github.com/LSPosed/AndroidHiddenApiBypass)
|
||||
|
||||
## 许可证
|
||||
|
||||
- [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
199
README.md
@@ -5,7 +5,7 @@
|
||||
[](https://t.me/HighCapable_Dev)
|
||||
[](https://qm.qq.com/cgi-bin/qm/qr?k=Pnsc5RY6N2mBKFjOLPiYldbAbprAU3V7&jump_from=webapi&authKey=X5EsOVzLXt1dRunge8ryTxDRrh9/IiW1Pua75eDLh9RE3KXE+bwXIYF5cWri/9lf)
|
||||
|
||||
<img src="img-src/icon.png" width = "100" height = "100" alt="LOGO"/>
|
||||
<img src="img-src/icon.svg" width = "100" height = "100" alt="LOGO"/>
|
||||
|
||||
An Android responsive UI building tool.
|
||||
|
||||
@@ -18,206 +18,33 @@ This project belongs to the above-mentioned organization, **click the link above
|
||||
|
||||
## What's this
|
||||
|
||||
This is an Android responsive UI build tool designed to focus on **Real-time code building UI**.
|
||||
`Hikage` (Pronunciation /ˈhɪkɑːɡeɪ/), this is an Android responsive UI build tool designed to focus on **Real-time code building UI**.
|
||||
|
||||
The name is taken from the original song "Haru**hikage**" in "BanG Dream It's MyGO!!!!!".
|
||||
The project icon was designed by [MaiTungTM](https://github.com/Lagrio),
|
||||
the name is taken from the original song "Haru**hikage**" in "BanG Dream It's MyGO!!!!!".
|
||||
|
||||
<details><summary>Why...</summary>
|
||||
<div align="center">
|
||||
<img src="https://i0.hdslb.com/bfs/garb/item/fa1ffd8af57626ca4f6bd562bac097239d36838b.png" width = "100" height = "100" alt="LOGO"/>
|
||||
<img src="img-src/nagasaki_soyo.png" width = "100" height = "100" alt="LOGO"/>
|
||||
|
||||
**なんで春日影レイアウト使いの?**
|
||||
**なんで春日影レイアウト使いの?**
|
||||
</div>
|
||||
</details>
|
||||
|
||||
Unlike Jetpack Compose's declarative UI, Hikage focuses on Android native platforms,
|
||||
and its design goal is to enable developers to quickly build UIs and directly support Android native components.
|
||||
|
||||
Hikage is just a UI build tool and does not provide any UI components themselves.
|
||||
**<u>Hikage is just a UI build tool and does not provide any UI components themselves</u>**.
|
||||
|
||||
Rejecting duplicate wheels, our solution is always compatible and efficient. Now you can abandon ViewBinding and XML and even `findViewById` and try
|
||||
to use the code layout directly.
|
||||
|
||||
The properties in Android view will be automatically generated with the Gradle plugin, and you can use it like in XML.
|
||||
`Hikage` works better with another project [BetterAndroid](https://github.com/BetterAndroid/BetterAndroid) and
|
||||
`Hikage` itself will automatically reference the `BetterAndroid` related dependencies as the core content.
|
||||
|
||||
It does not need to consider how to complete complex attribute settings in the code, especially some third-party libraries do not provide dynamic
|
||||
modifications to their custom views.
|
||||
## Get Started
|
||||
|
||||
## Effects
|
||||
|
||||
> Original layout
|
||||
|
||||
```xml
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Hello, World!"
|
||||
android:textSize="16sp"
|
||||
android:textColor="#000000"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:gravity="center" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Click Me!"
|
||||
android:textSize="16sp"
|
||||
android:textColor="#FFFFFF"
|
||||
android:backgroundTint="#FF0000"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:gravity="center" />
|
||||
</LinearLayout>
|
||||
```
|
||||
|
||||
> Using Hikage
|
||||
|
||||
```kotlin
|
||||
// Using Hikage to build a layout requires a UI Context.
|
||||
val context: Context
|
||||
// Make sure the Context is UI Context.
|
||||
if (!context.isUiContext) return
|
||||
// Start building the layout, be careful to make sure the context parameter is initialized.
|
||||
// According to the Android native component features,
|
||||
// the attributes (`attrs`) after layout construction will be fixed and cannot be modified dynamically.
|
||||
val hikage = Hikageable(
|
||||
context = context,
|
||||
// You can also customize the actions after each view is created.
|
||||
onViewCreated = { name, view ->
|
||||
// ...
|
||||
}
|
||||
) {
|
||||
LinearLayout(
|
||||
attrs = {
|
||||
android.layout_width = MATCH_PARENT
|
||||
android.layout_height = MATCH_PARENT
|
||||
android.orientation = VERTICAL
|
||||
android.padding = 16.dp
|
||||
},
|
||||
// You can manually specify layout parameters.
|
||||
lpparams = {
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
) {
|
||||
TextView(
|
||||
// Set the ID using string form, you can use large camel, small camel,
|
||||
// or underscore form, which will be converted to small camel form when generated.
|
||||
id = "text_view",
|
||||
// You can set properties directly using attrs without considering who they belong to.
|
||||
attrs = {
|
||||
android.layout_width = WRAP_CONTENT
|
||||
android.layout_height = WRAP_CONTENT
|
||||
android.text = "Hello, World!"
|
||||
android.textSize = 16.sp
|
||||
android.textColor = Color.BLACK
|
||||
android.layout_marginTop = 16.dp
|
||||
android.layout_marginStart = 16.dp
|
||||
android.layout_marginEnd = 16.dp
|
||||
android.layout_marginBottom = 16.dp
|
||||
android.gravity = Gravity.CENTER
|
||||
// Or use string form to set properties (note that there is no spelling check).
|
||||
namespace("android") {
|
||||
set("id", R.id.text_view)
|
||||
set("layout_margin", 16.dp)
|
||||
set("layout_gravity", Gravity.CENTER)
|
||||
// ...
|
||||
}
|
||||
},
|
||||
// You can also manually specify layout parameters.
|
||||
lpparams = {
|
||||
gravity = Gravity.CENTER
|
||||
},
|
||||
// Perform initialization operations.
|
||||
// You can also manually set properties.
|
||||
initialize = {
|
||||
text = "Hello, World!"
|
||||
textSize = 16f
|
||||
setTextColor(Color.BLACK)
|
||||
// Or more operations.
|
||||
doOnLayout {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
)
|
||||
// Use third-party views.
|
||||
View<MaterialButton>(
|
||||
id = "button",
|
||||
attrs = {
|
||||
android.layout_width = WRAP_CONTENT
|
||||
android.layout_height = WRAP_CONTENT
|
||||
android.text = "Click Me!"
|
||||
android.textSize = 16.sp
|
||||
android.textColor = Color.WHITE
|
||||
android.backgroundTint = Color.RED
|
||||
android.layout_marginTop = 16.dp
|
||||
android.layout_marginStart = 16.dp
|
||||
android.layout_marginEnd = 16.dp
|
||||
android.layout_marginBottom = 16.dp
|
||||
android.gravity = Gravity.CENTER
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
// Get the root layout.
|
||||
val root = hikage.root
|
||||
// You can also convert it to the type of the first layout, equivalent to hikage.root as LinearLayout.
|
||||
// Thanks to Kotlin's features, using Hikageable(...) { ... }.rootAsType() directly does not require filling in generics.
|
||||
val root = hikage.rootAsType<LinearLayout>()
|
||||
// Set to Activity.
|
||||
setContentView(root)
|
||||
// Get the built layout internal components (first solution).
|
||||
val textView = hikage.textView
|
||||
val button = hikage.button
|
||||
// Get the built layout internal components (second solution).
|
||||
val textView = hikage.get<TextView>("text_view")
|
||||
val button = hikage.get<MaterialButton>("button")
|
||||
```
|
||||
|
||||
## Preview with Android Studio
|
||||
|
||||
Unlike XML, Hikage does not support live previews, but you can inherit the `HikagePreview` provided by Hikage,
|
||||
and pass in your layout, and then view the preview in the pane on the right of Android Studio.
|
||||
|
||||
You can also use `isInEditMode` in your code to avoid displaying actual logical code that cannot be displayed in preview mode.
|
||||
|
||||
```kotlin
|
||||
class MyPreview(context: Context, attrs: AttributeSet?) : HikagePreview(context, attrs) {
|
||||
|
||||
override fun onPreview(): Hikage {
|
||||
// Return to your layout.
|
||||
return Hikageable {
|
||||
Button(
|
||||
attrs = {
|
||||
android.layout_width = WRAP_CONTENT
|
||||
android.layout_height = WRAP_CONTENT
|
||||
android.text = "Click Me!"
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note `HikagePreview` is for preview only and should not be used in actual code, otherwise an exception will be thrown.
|
||||
|
||||
Hikage may have plans to support Java, but Kotlin is still recommended.
|
||||
|
||||
## WIP
|
||||
|
||||
This project is still a work in progress. If you have any suggestions or feedback, feel free to open an issue or a pull request.
|
||||
[Click here](https://betterandroid.github.io/Hikage/en) go to the documentation page for more detailed tutorials and content.
|
||||
|
||||
## Promotion
|
||||
|
||||
@@ -234,6 +61,10 @@ This project is still a work in progress. If you have any suggestions or feedbac
|
||||
|
||||

|
||||
|
||||
## Third-Party Open Source Usage Statement
|
||||
|
||||
- [AndroidHiddenApiBypass](https://github.com/LSPosed/AndroidHiddenApiBypass)
|
||||
|
||||
## License
|
||||
|
||||
- [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
@@ -1,31 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<group
|
||||
android:scaleX="0.4686"
|
||||
android:scaleY="0.4686"
|
||||
android:translateX="12.7536"
|
||||
android:translateY="12.4412">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M43.901,36H4.099C5.102,25.893 13.629,18 24,18C34.371,18 42.898,25.893 43.901,36Z"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="@android:color/white"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:pathData="M14,20L10,13"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="@android:color/white"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:pathData="M33,20L37,13"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="@android:color/white"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
</group>
|
||||
</vector>
|
@@ -1,30 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="20dp"
|
||||
android:text="@string/app_name"
|
||||
android:textSize="20sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="20dp"
|
||||
android:paddingRight="20dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Hello World!"
|
||||
android:textSize="15sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
Before Width: | Height: | Size: 838 B |
Before Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 642 B |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 7.7 KiB |
@@ -1,8 +0,0 @@
|
||||
<resources>
|
||||
<!-- Base application theme. -->
|
||||
<style name="Base.Theme.DefaultAppTheme" parent="Theme.Material3.DayNight.NoActionBar">
|
||||
<!-- Customize your dark theme here. -->
|
||||
<!-- <item name="colorPrimary">@color/my_dark_primary</item> -->
|
||||
<item name="android:background">@color/background_night</item>
|
||||
</style>
|
||||
</resources>
|
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#FF639F70</color>
|
||||
</resources>
|
@@ -1,3 +0,0 @@
|
||||
<resources>
|
||||
<string name="app_name">Hikage Demo</string>
|
||||
</resources>
|
@@ -1,12 +0,0 @@
|
||||
<resources>
|
||||
<!-- Base application theme. -->
|
||||
<style name="Base.Theme.DefaultAppTheme" parent="Theme.Material3.DayNight.NoActionBar">
|
||||
<!-- Customize your light theme here. -->
|
||||
<!-- <item name="colorPrimary">@color/my_light_primary</item> -->
|
||||
<item name="colorPrimary">@color/theme</item>
|
||||
<item name="colorAccent">@color/theme</item>
|
||||
<item name="android:background">@color/background_day</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.DefaultAppTheme" parent="Base.Theme.DefaultAppTheme" />
|
||||
</resources>
|
@@ -1,5 +1,78 @@
|
||||
import com.vanniktech.maven.publish.AndroidSingleVariantLibrary
|
||||
import com.vanniktech.maven.publish.MavenPublishBaseExtension
|
||||
import org.jetbrains.dokka.gradle.DokkaTask
|
||||
|
||||
plugins {
|
||||
autowire(libs.plugins.android.application) apply false
|
||||
autowire(libs.plugins.android.library) apply false
|
||||
autowire(libs.plugins.kotlin.android) apply false
|
||||
autowire(libs.plugins.kotlin.jvm) apply false
|
||||
autowire(libs.plugins.kotlin.ksp) apply false
|
||||
autowire(libs.plugins.compose.compiler) apply false
|
||||
autowire(libs.plugins.kotlin.dokka) apply false
|
||||
autowire(libs.plugins.maven.publish) apply false
|
||||
}
|
||||
|
||||
libraryProjects {
|
||||
afterEvaluate {
|
||||
configure<PublishingExtension> {
|
||||
repositories {
|
||||
val repositoryDir = gradle.gradleUserHomeDir
|
||||
.resolve("highcapable-maven-repository")
|
||||
.resolve("repository")
|
||||
maven {
|
||||
name = "HighCapableMavenReleases"
|
||||
url = repositoryDir.resolve("releases").toURI()
|
||||
}
|
||||
maven {
|
||||
name = "HighCapableMavenSnapShots"
|
||||
url = repositoryDir.resolve("snapshots").toURI()
|
||||
}
|
||||
}
|
||||
}
|
||||
configure<MavenPublishBaseExtension> {
|
||||
if (name != Libraries.HIKAGE_COMPILER)
|
||||
configure(AndroidSingleVariantLibrary(publishJavadocJar = false))
|
||||
}
|
||||
}
|
||||
tasks.withType<DokkaTask>().configureEach {
|
||||
val configuration = """{ "footerMessage": "Hikage | Apache-2.0 License | Copyright (C) 2019 HighCapable" }"""
|
||||
pluginsMapConfiguration.set(mapOf("org.jetbrains.dokka.base.DokkaBase" to configuration))
|
||||
}
|
||||
tasks.register("publishKDoc") {
|
||||
group = "documentation"
|
||||
dependsOn("dokkaHtml")
|
||||
doLast {
|
||||
val docsDir = rootProject.projectDir
|
||||
.resolve("docs-source")
|
||||
.resolve("dist")
|
||||
.resolve("KDoc")
|
||||
.resolve(project.name)
|
||||
if (docsDir.exists()) docsDir.deleteRecursively() else docsDir.mkdirs()
|
||||
layout.buildDirectory.dir("dokka/html").get().asFile.copyRecursively(docsDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun libraryProjects(action: Action<in Project>) {
|
||||
val libraries = listOf(
|
||||
Libraries.HIKAGE_CORE,
|
||||
Libraries.HIKAGE_EXTENSION,
|
||||
Libraries.HIKAGE_EXTENSION_COMPOSE,
|
||||
Libraries.HIKAGE_EXTENSION_BETTERANDROID,
|
||||
Libraries.HIKAGE_COMPILER,
|
||||
Libraries.HIKAGE_WIDGET_ANDROIDX,
|
||||
Libraries.HIKAGE_WIDGET_MATERIAL
|
||||
)
|
||||
allprojects { if (libraries.contains(name)) action.execute(this) }
|
||||
}
|
||||
|
||||
object Libraries {
|
||||
const val HIKAGE_CORE = "hikage-core"
|
||||
const val HIKAGE_EXTENSION = "hikage-extension"
|
||||
const val HIKAGE_EXTENSION_COMPOSE = "hikage-extension-compose"
|
||||
const val HIKAGE_EXTENSION_BETTERANDROID = "hikage-extension-betterandroid"
|
||||
const val HIKAGE_COMPILER = "hikage-compiler"
|
||||
const val HIKAGE_WIDGET_ANDROIDX = "hikage-widget-androidx"
|
||||
const val HIKAGE_WIDGET_MATERIAL = "hikage-widget-material"
|
||||
}
|
4
docs-source/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/node_modules
|
||||
/src/.vuepress/.cache
|
||||
/src/.vuepress/.temp
|
||||
/dist
|
3
docs-source/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"git.ignoreLimitWarning": true
|
||||
}
|
2
docs-source/build-dokka.sh
Normal file
@@ -0,0 +1,2 @@
|
||||
cd ..
|
||||
./gradlew :hikage-core:publishKDoc :hikage-extension:publishKDoc :hikage-extension-betterandroid:publishKDoc :hikage-extension-compose:publishKDoc
|
17
docs-source/package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "hikage_docs",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@mr-hope/vuepress-plugin-copy-code": "^1.30.0",
|
||||
"@vuepress/plugin-prismjs": "2.0.0-rc.0",
|
||||
"@vuepress/plugin-search": "2.0.0-rc.0",
|
||||
"@vuepress/plugin-shiki": "2.0.0-rc.0",
|
||||
"vuepress": "2.0.0-rc.0"
|
||||
},
|
||||
"scripts": {
|
||||
"docs:dev": "vuepress dev src",
|
||||
"docs:build": "vuepress build src",
|
||||
"docs:build-gh-pages": "vuepress build src && touch dist/.nojekyll && sh build-dokka.sh"
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
64
docs-source/src/.vuepress/config.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { defaultTheme } from 'vuepress';
|
||||
import { shikiPlugin } from '@vuepress/plugin-shiki';
|
||||
import { searchPlugin } from '@vuepress/plugin-search';
|
||||
import { navBarItems, sideBarItems, configs, pageLinkRefs } from './configs/template';
|
||||
import { env, markdown } from './configs/utils';
|
||||
|
||||
export default {
|
||||
dest: configs.dev.dest,
|
||||
port: configs.dev.port,
|
||||
base: configs.website.base,
|
||||
head: [['link', { rel: 'icon', href: configs.website.icon }]],
|
||||
title: configs.website.title,
|
||||
description: configs.website.locales['/en/'].description,
|
||||
locales: configs.website.locales,
|
||||
theme: defaultTheme({
|
||||
logo: configs.website.logo,
|
||||
repo: configs.github.repo,
|
||||
docsRepo: configs.github.repo,
|
||||
docsBranch: configs.github.branch,
|
||||
docsDir: configs.github.dir,
|
||||
editLinkPattern: ':repo/edit/:branch/:path',
|
||||
sidebar: sideBarItems,
|
||||
sidebarDepth: 2,
|
||||
locales: {
|
||||
'/en/': {
|
||||
navbar: navBarItems['/en/'],
|
||||
selectLanguageText: 'English (US)',
|
||||
selectLanguageName: 'English',
|
||||
editLinkText: 'Edit this page on GitHub',
|
||||
tip: 'Tips',
|
||||
warning: 'Notice',
|
||||
danger: 'Pay Attention',
|
||||
},
|
||||
'/zh-cn/': {
|
||||
navbar: navBarItems['/zh-cn/'],
|
||||
selectLanguageText: '简体中文 (CN)',
|
||||
selectLanguageName: '简体中文',
|
||||
editLinkText: '在 GitHub 上编辑此页',
|
||||
notFound: ['这里什么都没有', '我们怎么到这来了?', '这是一个 404 页面', '看起来我们进入了错误的链接'],
|
||||
backToHome: '回到首页',
|
||||
contributorsText: '贡献者',
|
||||
lastUpdatedText: '上次更新',
|
||||
tip: '小提示',
|
||||
warning: '注意',
|
||||
danger: '特别注意',
|
||||
openInNewWindow: '在新窗口中打开',
|
||||
toggleColorMode: '切换颜色模式'
|
||||
}
|
||||
},
|
||||
}),
|
||||
extendsMarkdown: (md: markdownit) => {
|
||||
markdown.injectLinks(md, env.dev ? pageLinkRefs.dev : pageLinkRefs.prod);
|
||||
},
|
||||
plugins: [
|
||||
shikiPlugin({ theme: 'github-dark-dimmed' }),
|
||||
searchPlugin({
|
||||
isSearchable: (page) => page.path !== '/',
|
||||
locales: {
|
||||
'/en/': { placeholder: 'Search' },
|
||||
'/zh-cn/': { placeholder: '搜索' }
|
||||
}
|
||||
})
|
||||
]
|
||||
};
|
151
docs-source/src/.vuepress/configs/template.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import { i18n } from './utils';
|
||||
|
||||
interface PageLinkRefs {
|
||||
dev: Record<string, string>[];
|
||||
prod: Record<string, string>[];
|
||||
}
|
||||
|
||||
const navigationLinks = {
|
||||
start: [
|
||||
'/guide/home',
|
||||
'/guide/quick-start'
|
||||
],
|
||||
library: [
|
||||
'/library/hikage-core',
|
||||
'/library/hikage-compiler',
|
||||
'/library/hikage-extension',
|
||||
'/library/hikage-extension-betterandroid',
|
||||
'/library/hikage-extension-compose',
|
||||
'/library/hikage-widget-androidx',
|
||||
'/library/hikage-widget-material'
|
||||
],
|
||||
config: [
|
||||
'/config/r8-proguard'
|
||||
],
|
||||
about: [
|
||||
'/about/changelog',
|
||||
'/about/future',
|
||||
'/about/contacts',
|
||||
'/about/about'
|
||||
]
|
||||
};
|
||||
|
||||
export const configs = {
|
||||
dev: {
|
||||
dest: 'dist',
|
||||
port: 9000
|
||||
},
|
||||
website: {
|
||||
base: '/Hikage/',
|
||||
icon: '/Hikage/images/logo.svg',
|
||||
logo: '/images/logo.svg',
|
||||
title: 'Hikage',
|
||||
locales: {
|
||||
'/en/': {
|
||||
lang: 'en-US',
|
||||
description: 'An Android responsive UI building tool'
|
||||
},
|
||||
'/zh-cn/': {
|
||||
lang: 'zh-CN',
|
||||
description: '一个 Android 响应式 UI 构建工具'
|
||||
}
|
||||
}
|
||||
},
|
||||
github: {
|
||||
repo: 'https://github.com/BetterAndroid/Hikage',
|
||||
page: 'https://betterandroid.github.io/Hikage',
|
||||
branch: 'main',
|
||||
dir: 'docs-source/src'
|
||||
}
|
||||
};
|
||||
|
||||
export const pageLinkRefs: PageLinkRefs = {
|
||||
dev: [
|
||||
{ 'repo://': `${configs.github.repo}/` },
|
||||
// KDoc URL for local debugging, non-fixed value, adjust according to your own needs.
|
||||
// You can run ./build-dokka.sh and start the local server in dist/KDoc.
|
||||
{ 'kdoc://': 'http://localhost:9001/' }
|
||||
],
|
||||
prod: [
|
||||
{ 'repo://': `${configs.github.repo}/` },
|
||||
{ 'kdoc://': `${configs.github.page}/KDoc/` }
|
||||
]
|
||||
};
|
||||
|
||||
export const navBarItems = {
|
||||
'/en/': [{
|
||||
text: 'Navigation',
|
||||
children: [{
|
||||
text: 'Get Started',
|
||||
children: i18n.array(navigationLinks.start, 'en')
|
||||
}, {
|
||||
text: 'Libraries',
|
||||
children: i18n.array(navigationLinks.library, 'en')
|
||||
}, {
|
||||
text: 'Configs',
|
||||
children: i18n.array(navigationLinks.config, 'en')
|
||||
}, {
|
||||
text: 'About',
|
||||
children: i18n.array(navigationLinks.about, 'en')
|
||||
}]
|
||||
}, {
|
||||
text: 'Contact Us',
|
||||
link: i18n.string(navigationLinks.about[2], 'en')
|
||||
}],
|
||||
'/zh-cn/': [{
|
||||
text: '导航',
|
||||
children: [{
|
||||
text: '入门',
|
||||
children: i18n.array(navigationLinks.start, 'zh-cn')
|
||||
}, {
|
||||
text: '依赖',
|
||||
children: i18n.array(navigationLinks.library, 'zh-cn')
|
||||
}, {
|
||||
text: '配置',
|
||||
children: i18n.array(navigationLinks.config, 'zh-cn')
|
||||
}, {
|
||||
text: '关于',
|
||||
children: i18n.array(navigationLinks.about, 'zh-cn')
|
||||
}]
|
||||
}, {
|
||||
text: '联系我们',
|
||||
link: i18n.string(navigationLinks.about[2], 'zh-cn')
|
||||
}]
|
||||
};
|
||||
|
||||
export const sideBarItems = {
|
||||
'/en/': [{
|
||||
text: 'Get Started',
|
||||
collapsible: true,
|
||||
children: i18n.array(navigationLinks.start, 'en')
|
||||
}, {
|
||||
text: 'Libraries',
|
||||
collapsible: true,
|
||||
children: i18n.array(navigationLinks.library, 'en')
|
||||
}, {
|
||||
text: 'Configs',
|
||||
collapsible: true,
|
||||
children: i18n.array(navigationLinks.config, 'en')
|
||||
}, {
|
||||
text: 'About',
|
||||
collapsible: true,
|
||||
children: i18n.array(navigationLinks.about, 'en')
|
||||
}],
|
||||
'/zh-cn/': [{
|
||||
text: '入门',
|
||||
collapsible: true,
|
||||
children: i18n.array(navigationLinks.start, 'zh-cn')
|
||||
}, {
|
||||
text: '依赖',
|
||||
collapsible: true,
|
||||
children: i18n.array(navigationLinks.library, 'zh-cn')
|
||||
}, {
|
||||
text: '配置',
|
||||
collapsible: true,
|
||||
children: i18n.array(navigationLinks.config, 'zh-cn')
|
||||
}, {
|
||||
text: '关于',
|
||||
collapsible: true,
|
||||
children: i18n.array(navigationLinks.about, 'zh-cn')
|
||||
}]
|
||||
};
|
39
docs-source/src/.vuepress/configs/utils.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
export const env = {
|
||||
dev: process.env.NODE_ENV === 'development'
|
||||
};
|
||||
|
||||
export const i18n = {
|
||||
space: ' ',
|
||||
string: (content: string, locale: string) => {
|
||||
return '/' + locale + content;
|
||||
},
|
||||
array: (contents: string[], locale: string) => {
|
||||
const newContents: string[] = [];
|
||||
contents.forEach((content) => {
|
||||
newContents.push(i18n.string(content, locale));
|
||||
});
|
||||
return newContents;
|
||||
}
|
||||
};
|
||||
|
||||
export const markdown = {
|
||||
injectLinks: (md: markdownit, maps: Record<string, string>[]) => {
|
||||
const defaultRender = md.renderer.rules.link_open || function (tokens, idx, options, _env, self) {
|
||||
return self.renderToken(tokens, idx, options);
|
||||
};
|
||||
md.renderer.rules.link_open = function (tokens, idx, options, env, self) {
|
||||
const hrefIndex = tokens[idx].attrIndex('href');
|
||||
let current = tokens[idx].attrs!![hrefIndex][1];
|
||||
for (const map of maps) {
|
||||
for (const [search, replace] of Object.entries(map)) {
|
||||
if (current.startsWith(search)) {
|
||||
current = current.replace(search, replace);
|
||||
tokens[idx].attrs!![hrefIndex][1] = current;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaultRender(tokens, idx, options, env, self);
|
||||
};
|
||||
}
|
||||
};
|
11
docs-source/src/.vuepress/public/images/logo.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="512" height="512" rx="128" fill="#4EA16B"/>
|
||||
<rect x="100" y="220" width="79" height="79" rx="24" fill="white" fill-opacity="0.9"/>
|
||||
<rect x="100" y="315" width="79" height="79" rx="39.5" fill="white" fill-opacity="0.9"/>
|
||||
<rect x="96" y="118" width="320" height="86" rx="30" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M250.829 235.577C249.606 235.736 248.427 236.135 247.359 236.752C246.281 237.36 245.334 238.175 244.574 239.152C243.813 240.129 243.254 241.247 242.929 242.442C242.604 243.637 242.518 244.884 242.679 246.112C242.839 247.341 243.241 248.524 243.862 249.596L260.34 278.47C232.519 295.058 214 324.376 214 357.705C214 359.809 214.074 361.925 214.22 363.997C214.798 372.166 221.607 378.5 229.813 378.5H396.187C404.497 378.5 411.294 372.019 411.789 363.849C411.929 361.822 412 359.756 412 357.705C412 324.376 393.48 295.058 365.66 278.47L382.138 249.596C382.759 248.524 383.161 247.341 383.321 246.112C383.482 244.884 383.396 243.637 383.071 242.442C382.746 241.247 382.187 240.129 381.426 239.152C380.666 238.175 379.719 237.36 378.641 236.752C377.573 236.135 376.394 235.736 375.171 235.577C373.948 235.419 372.706 235.504 371.517 235.829C370.327 236.154 369.213 236.711 368.24 237.468C367.266 238.226 366.452 239.169 365.845 240.244L348.649 270.375C337.583 266.323 325.561 264.1 313.001 264.1C300.44 264.1 288.417 266.323 277.351 270.376L260.155 240.244C259.547 239.169 258.734 238.226 257.76 237.468C256.787 236.711 255.673 236.154 254.483 235.829C253.294 235.504 252.052 235.419 250.829 235.577ZM286.6 328.45C286.6 334.221 281.921 338.9 276.15 338.9C270.379 338.9 265.7 334.221 265.7 328.45C265.7 322.679 270.379 318 276.15 318C281.921 318 286.6 322.679 286.6 328.45ZM349.85 338.9C355.621 338.9 360.3 334.221 360.3 328.45C360.3 322.679 355.621 318 349.85 318C344.079 318 339.4 322.679 339.4 328.45C339.4 334.221 344.079 338.9 349.85 338.9Z" fill="white"/>
|
||||
<path d="M149.849 146.129C148.678 144.957 146.778 144.957 145.607 146.129L132.879 158.857C132.289 159.446 131.996 160.22 132 160.993C131.996 161.765 132.289 162.539 132.879 163.129L145.607 175.857C146.778 177.028 148.678 177.028 149.849 175.857C151.021 174.685 151.021 172.786 149.849 171.614L139.228 160.993L149.849 150.371C151.021 149.2 151.021 147.3 149.849 146.129Z" fill="#4EA16B"/>
|
||||
<path d="M138.757 160.995C138.757 159.338 140.101 157.995 141.757 157.995H183.757C185.414 157.995 186.757 159.338 186.757 160.995C186.757 162.652 185.414 163.995 183.757 163.995H141.757C140.101 163.995 138.757 162.652 138.757 160.995Z" fill="#4EA16B"/>
|
||||
<path d="M362.151 146.129C363.322 144.957 365.222 144.957 366.393 146.129L379.121 158.857C379.711 159.446 380.004 160.22 380 160.993C380.004 161.765 379.711 162.539 379.121 163.129L366.393 175.857C365.222 177.028 363.322 177.028 362.151 175.857C360.979 174.685 360.979 172.786 362.151 171.614L372.772 160.993L362.151 150.371C360.979 149.2 360.979 147.3 362.151 146.129Z" fill="#4EA16B"/>
|
||||
<path d="M373.243 160.995C373.243 159.338 371.9 157.995 370.243 157.995H328.243C326.586 157.995 325.243 159.338 325.243 160.995C325.243 162.652 326.586 163.995 328.243 163.995H370.243C371.9 163.995 373.243 162.652 373.243 160.995Z" fill="#4EA16B"/>
|
||||
</svg>
|
After Width: | Height: | Size: 3.2 KiB |
BIN
docs-source/src/.vuepress/public/images/nagasaki_soyo.png
Normal file
After Width: | Height: | Size: 44 KiB |
179
docs-source/src/.vuepress/styles/index.scss
Normal file
@@ -0,0 +1,179 @@
|
||||
$primary-color: rgb(99, 159, 112);
|
||||
$accent-color: rgb(130, 180, 140);
|
||||
$content-width: 965px;
|
||||
$scroll-bar-width: 8px;
|
||||
$scroll-bar-height: 6.5px;
|
||||
$scroll-bar-border-radius: 50px;
|
||||
$scroll-bar-track-color-code: rgb(86, 96, 110);
|
||||
$scroll-bar-thumb-hover-color-code: rgb(121, 135, 155);
|
||||
|
||||
:root {
|
||||
--c-brand: #{$primary-color};
|
||||
--c-brand-light: #{$accent-color};
|
||||
--content-width: #{$content-width};
|
||||
}
|
||||
|
||||
code {
|
||||
padding: 3px 5px 3px 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.custom-container {
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.sidebar-item {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.language-text {
|
||||
::-webkit-scrollbar-track {
|
||||
background: #{$scroll-bar-track-color-code};
|
||||
border-radius: #{$scroll-bar-border-radius};
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #{$scroll-bar-thumb-hover-color-code};
|
||||
}
|
||||
}
|
||||
|
||||
.language-kotlin {
|
||||
::-webkit-scrollbar-track {
|
||||
background: #{$scroll-bar-track-color-code};
|
||||
border-radius: #{$scroll-bar-border-radius};
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #{$scroll-bar-thumb-hover-color-code};
|
||||
}
|
||||
}
|
||||
|
||||
.language-java {
|
||||
::-webkit-scrollbar-track {
|
||||
background: #{$scroll-bar-track-color-code};
|
||||
border-radius: #{$scroll-bar-border-radius};
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #{$scroll-bar-thumb-hover-color-code};
|
||||
}
|
||||
}
|
||||
|
||||
.language-groovy {
|
||||
::-webkit-scrollbar-track {
|
||||
background: #{$scroll-bar-track-color-code};
|
||||
border-radius: #{$scroll-bar-border-radius};
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #{$scroll-bar-thumb-hover-color-code};
|
||||
}
|
||||
}
|
||||
|
||||
.language-xml {
|
||||
::-webkit-scrollbar-track {
|
||||
background: #{$scroll-bar-track-color-code};
|
||||
border-radius: #{$scroll-bar-border-radius};
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #{$scroll-bar-thumb-hover-color-code};
|
||||
}
|
||||
}
|
||||
|
||||
.hidden-anchor-page {
|
||||
h6 {
|
||||
color: transparent;
|
||||
margin-bottom: -35px;
|
||||
padding-top: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
.code-page {
|
||||
h1 {
|
||||
font-size: 24pt;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 18pt;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 15pt;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 12pt;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 9.6pt;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 8.4pt;
|
||||
}
|
||||
|
||||
.symbol {
|
||||
color: rgb(142, 155, 168);
|
||||
}
|
||||
|
||||
.deprecated {
|
||||
color: rgb(142, 155, 168);
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: #{$scroll-bar-width};
|
||||
height: #{$scroll-bar-height};
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgb(234, 236, 239);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgb(189, 189, 189);
|
||||
border-radius: #{$scroll-bar-border-radius};
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgb(133, 133, 133);
|
||||
border-radius: #{$scroll-bar-border-radius};
|
||||
}
|
||||
}
|
||||
|
||||
html.dark {
|
||||
--c-brand: #{$primary-color};
|
||||
--c-brand-light: #{$accent-color};
|
||||
--content-width: #{$content-width};
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: #{$scroll-bar-width};
|
||||
height: #{$scroll-bar-height};
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgb(41, 46, 53);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgb(65, 72, 83);
|
||||
border-radius: #{$scroll-bar-border-radius};
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgb(56, 62, 72);
|
||||
border-radius: #{$scroll-bar-border-radius};
|
||||
}
|
||||
}
|
27
docs-source/src/en/about/about.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# About This Document
|
||||
|
||||
> This document is powered by [VuePress](https://v2.vuepress.vuejs.org/en).
|
||||
|
||||
## License
|
||||
|
||||
[Apache-2.0](https://github.com/BetterAndroid/Hikage/blob/main/LICENSE)
|
||||
|
||||
```:no-line-numbers
|
||||
Apache License Version 2.0
|
||||
|
||||
Copyright (C) 2019 HighCapable
|
||||
|
||||
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.
|
||||
```
|
||||
|
||||
Copyright © 2019 HighCapable
|
59
docs-source/src/en/about/changelog.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Changelog
|
||||
|
||||
> The version update history of `Hikage` is recorded here.
|
||||
|
||||
::: danger
|
||||
|
||||
We will only maintain the latest API version, if you are using an outdate API version, you voluntarily renounce any possibility of maintenance.
|
||||
|
||||
:::
|
||||
|
||||
::: warning
|
||||
|
||||
To avoid translation time consumption, Changelog will use **Google Translation** from **Chinese** to **English**, please refer to the original text for actual reference.
|
||||
|
||||
Time zone of version release date: **UTC+8**
|
||||
|
||||
:::
|
||||
|
||||
## hikage-core
|
||||
|
||||
### 1.0.0 | 2025.04.20  <Badge type="tip" text="latest" vertical="middle" />
|
||||
|
||||
- The first version is submitted to Maven
|
||||
|
||||
## hikage-compiler
|
||||
|
||||
### 1.0.0 | 2025.04.20  <Badge type="tip" text="latest" vertical="middle" />
|
||||
|
||||
- The first version is submitted to Maven
|
||||
|
||||
## hikage-extension
|
||||
|
||||
### 1.0.0 | 2025.04.20  <Badge type="tip" text="latest" vertical="middle" />
|
||||
|
||||
- The first version is submitted to Maven
|
||||
|
||||
## hikage-extension-betterandroid
|
||||
|
||||
### 1.0.0 | 2025.04.20  <Badge type="tip" text="latest" vertical="middle" />
|
||||
|
||||
- The first version is submitted to Maven
|
||||
|
||||
## hikage-extension-compose
|
||||
|
||||
### 1.0.0 | 2025.04.20  <Badge type="tip" text="latest" vertical="middle" />
|
||||
|
||||
- The first version is submitted to Maven
|
||||
|
||||
## hikage-widget-androidx
|
||||
|
||||
### 1.0.0 | 2025.04.20  <Badge type="tip" text="latest" vertical="middle" />
|
||||
|
||||
- The first version is submitted to Maven
|
||||
|
||||
## hikage-widget-material
|
||||
|
||||
### 1.0.0 | 2025.04.20  <Badge type="tip" text="latest" vertical="middle" />
|
||||
|
||||
- The first version is submitted to Maven
|
16
docs-source/src/en/about/contacts.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Contact Us
|
||||
|
||||
> If you have any questions in use, or have any constructive suggestions, you can contact us.
|
||||
|
||||
Join our developers group.
|
||||
|
||||
- [Click to join Telegram group](https://t.me/BetterAndroid)
|
||||
- [Click to join Telegram group (Developer)](https://t.me/HighCapable_Dev)
|
||||
|
||||
Find me on **Twitter** [@fankesyooni](https://twitter.com/fankesyooni).
|
||||
|
||||
## Help with Maintenance
|
||||
|
||||
Thank you for choosing and using `Hikage`.
|
||||
|
||||
If you have code-related suggestions and requests, you can submit a Pull Request on GitHub.
|
75
docs-source/src/en/about/future.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# Looking for Future
|
||||
|
||||
> The future is bright and uncertain, let us look forward to the future development space of `Hikage`.
|
||||
|
||||
## Future Plans
|
||||
|
||||
> Features that `Hikage` may add later are included here.
|
||||
|
||||
### Process AttrtibuteSet
|
||||
|
||||
`Hikage` will support processing `AttributeSet` in the future to dock with the original XML properties to implement the takeover
|
||||
of some third-party components that are not open to customization of layout properties in the code.
|
||||
|
||||
`Hikage` currently supports automated creation of `XmlBlock`, but does not support the direct processing
|
||||
of customized `AttributeSet`. Because of its historical problems and high processing difficulty, it may compromise whether to continue to improve this function in the later stage.
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
TextView(
|
||||
id = "text_view",
|
||||
// Attributes passed through AttributeSet.
|
||||
attrs = {
|
||||
namespace("android") {
|
||||
set("text", "Hello, World!")
|
||||
set("textSize", "16sp")
|
||||
set("gravity", "center")
|
||||
}
|
||||
}
|
||||
) {
|
||||
// Attributes passed through code.
|
||||
text = "Hello, World!"
|
||||
textSize = 16f
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
```
|
||||
|
||||
### Generate Components ID
|
||||
|
||||
`Hikage` may support the direct call function to generate component IDs customized with strings as required in the future.
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
object MyLayout : HikageBuilder {
|
||||
|
||||
override fun build() = Hikageable(context) {
|
||||
LinearLayout(
|
||||
id = "lin_layout",
|
||||
lparams = LayoutParams(matchParent = true),
|
||||
init = {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
) {
|
||||
TextView(id = "text_view") {
|
||||
text = "Hello, World!"
|
||||
textSize = 16f
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val context: Context
|
||||
// Create TypedHikage.
|
||||
val myLayout = MyLayout.asTyped().build().create(context)
|
||||
// Or, use lazy init.
|
||||
val myLayout by context.lazyTypedHikage(MyLayout)
|
||||
// Directly call the ID generated from the string.
|
||||
val linLayout = myLayout.linLayout
|
||||
val textView = myLayout.textView
|
||||
// Get the root layout, i.e. LinearLayout.
|
||||
val root = myLayout.root
|
||||
```
|
18
docs-source/src/en/config/r8-proguard.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# R8 & Proguard Obfuscate
|
||||
|
||||
> In most scenarios, the app packages can be compressed through obfuscation,
|
||||
> here is an introduction to how to configure obfuscation rules.
|
||||
|
||||
`Hikage` does not require additional configuration of obfuscation rules, since `View` loaded by Hikage does not need to be defined in XML, they can be equally obfuscated.
|
||||
|
||||
You can force them to be confused with your custom `View`, such as `com.yourpackage.YourView`, using the following rules.
|
||||
|
||||
```
|
||||
-allowobfuscation class com.yourpackage.YourView
|
||||
```
|
||||
|
||||
If you must prevent `Hikage` from being confused or something that occurs after being confused, you can use the following rules to prevent `Hikage` from being confused.
|
||||
|
||||
```
|
||||
-keep class com.highcapable.hikage**
|
||||
```
|
120
docs-source/src/en/guide/home.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# Introduce
|
||||
|
||||
> `Hikage` (Pronunciation /ˈhɪkɑːɡeɪ/) is an Android responsive UI building tool.
|
||||
|
||||
## Background
|
||||
|
||||
This is an Android responsive UI build tool designed to focus on **Real-time code building UI**.
|
||||
|
||||
The project icon was designed by [MaiTungTM](https://github.com/Lagrio),
|
||||
the name is taken from the original song "Haru**hikage**" in "BanG Dream It's MyGO!!!!!".
|
||||
|
||||
<details><summary>Why...</summary>
|
||||
<div align="center">
|
||||
<img src="/images/nagasaki_soyo.png" width = "100" height = "100" alt="LOGO"/>
|
||||
|
||||
**なんで春日影レイアウト使いの?**
|
||||
</div>
|
||||
</details>
|
||||
|
||||
Unlike Jetpack Compose's declarative UI, Hikage focuses on Android native platforms,
|
||||
and its design goal is to enable developers to quickly build UIs and directly support Android native components.
|
||||
|
||||
**<u>Hikage is just a UI build tool and does not provide any UI components themselves</u>**.
|
||||
|
||||
Rejecting duplicate wheels, our solution is always compatible and efficient. Now you can abandon ViewBinding and XML and even `findViewById` and try
|
||||
to use the code layout directly.
|
||||
|
||||
`Hikage` works better with another project [BetterAndroid](https://github.com/BetterAndroid/BetterAndroid) and
|
||||
`Hikage` itself will automatically reference the `BetterAndroid` related dependencies as the core content.
|
||||
|
||||
## Usage
|
||||
|
||||
Hikage is mainly suitable for developers focusing on native Android platform development.
|
||||
Since Kotlin became the primary development language, there hasn't been a perfect tool to implement dynamic code layouts using DSL.
|
||||
Therefore, projects that do not use Jetpack Compose still need to use the original XML. Although ViewBinding provides support, it is still not very user-friendly.
|
||||
|
||||
Hikage inherits the design schemes of [Anko](https://github.com/Kotlin/anko) and [Splitties](https://github.com/LouisCAD/Splitties),
|
||||
and draws on the DSL function naming scheme of Jetpack Compose. On this basis, it has made many improvements,
|
||||
making it closer to native in terms of usage cost and closer to Jetpack Compose in terms of writing style.
|
||||
|
||||
> Comparison of various DSL layout schemes
|
||||
|
||||
:::: code-group
|
||||
::: code-group-item Hikage
|
||||
|
||||
```kotlin
|
||||
LinearLayout(
|
||||
lparams = LayoutParams(matchParent = true) {
|
||||
topMargin = 16.dp
|
||||
},
|
||||
init = {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
) {
|
||||
TextView {
|
||||
text = "Hello, World!"
|
||||
textSize = 16f
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
::: code-group-item Anko、Splitties
|
||||
|
||||
```kotlin
|
||||
verticalLayout {
|
||||
gravity = Gravity.CENTER
|
||||
textView("Hello, World!") {
|
||||
textSize = 16f
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
}.lparams(
|
||||
width = matchParent,
|
||||
height = matchParent
|
||||
) {
|
||||
topMargin = dip(16)
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
::: code-group-item Jetpack Compose
|
||||
|
||||
```kotlin
|
||||
Column(
|
||||
modifier = Modifier.padding(top = 16.dp),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = "Hello, World!",
|
||||
fontSize = 16.sp,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
::::
|
||||
|
||||
The basic part of Hikage **does not require any external or additional compilation plugins**.
|
||||
It can be **plug-and-play** and **create a View object anywhere** that can be set to the parent layout and `Window`.
|
||||
|
||||
Hikage **fully supports** hybrid layouts. You can embed XML (using the `R.layout` scheme to load layouts), ViewBinding, and even Jetpack Compose within Hikage.
|
||||
|
||||
## Language Requirement
|
||||
|
||||
It is recommended to use Kotlin as the preferred development language. This project is entirely written in Kotlin, and there are no plans to support Java compatibility.
|
||||
|
||||
All demo examples in the documentation will be described using Kotlin. If you are not familiar with Kotlin, you may encounter difficulties in using this project effectively.
|
||||
|
||||
## Contribution
|
||||
|
||||
The maintenance of this project is inseparable from the support and contributions of all developers.
|
||||
|
||||
This project is currently in its early stages, and there may still be some problems or lack of functions you need.
|
||||
|
||||
If possible, feel free to submit a PR to contribute features you think are needed to this project or goto [GitHub Issues](repo://issues)
|
||||
to make suggestions to us.
|
89
docs-source/src/en/guide/quick-start.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# Quick Start
|
||||
|
||||
> Integrate `Hikage` into your project.
|
||||
|
||||
## Project Requirements
|
||||
|
||||
The project needs to be created using `Android Studio` or `IntelliJ IDEA` and be of type Android or Kotlin Multiplatform
|
||||
project and have integrated Kotlin environment dependencies.
|
||||
|
||||
- Android Studio (It is recommended to get the latest version [from here](https://developer.android.com/studio))
|
||||
|
||||
- IntelliJ IDEA (It is recommended to get the latest version [from here](https://www.jetbrains.com/idea))
|
||||
|
||||
- Kotlin 1.9.0+, Gradle 8+, Java 17+, Android Gradle Plugin 8+
|
||||
|
||||
### Configure Repositories
|
||||
|
||||
The dependencies of `Hikage` are published in **Maven Central** and our public repository,
|
||||
you can use the following method to configure repositories.
|
||||
|
||||
We recommend using Kotlin DSL as the Gradle build script language and [SweetDependency](https://github.com/HighCapable/SweetDependency)
|
||||
to manage dependencies.
|
||||
|
||||
#### SweetDependency (Recommended)
|
||||
|
||||
Configure repositories in your project's `SweetDependency` configuration file.
|
||||
|
||||
```yaml
|
||||
repositories:
|
||||
google:
|
||||
maven-central:
|
||||
# (Optional) You can add this URL to use our public repository
|
||||
# When Sonatype-OSS fails and cannot publish dependencies, this repository is added as a backup
|
||||
# For details, please visit: https://github.com/HighCapable/maven-repository
|
||||
highcapable-maven-releases:
|
||||
url: https://raw.githubusercontent.com/HighCapable/maven-repository/main/repository/releases
|
||||
```
|
||||
|
||||
#### Traditional Method
|
||||
|
||||
Configure repositories in your project `build.gradle.kts`.
|
||||
|
||||
```kotlin
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
// (Optional) You can add this URL to use our public repository
|
||||
// When Sonatype-OSS fails and cannot publish dependencies, this repository is added as a backup
|
||||
// For details, please visit: https://github.com/HighCapable/maven-repository
|
||||
maven("https://raw.githubusercontent.com/HighCapable/maven-repository/main/repository/releases")
|
||||
}
|
||||
```
|
||||
|
||||
### Configure Java Version
|
||||
|
||||
Modify the Java version of Kotlin in your project `build.gradle.kts` to 17 or above.
|
||||
|
||||
> Kotlin DSL
|
||||
|
||||
```kt
|
||||
android {
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Functional Overview
|
||||
|
||||
The project is divided into multiple modules. You can choose the module you wish to include as a dependency in your project, but be sure to include the **hikage-core** module.
|
||||
|
||||
Click the corresponding module below to view detailed feature descriptions.
|
||||
|
||||
- [hikage-core](../library/hikage-core.md)
|
||||
- [hikage-compiler](../library/hikage-compiler.md)
|
||||
- [hikage-extension](../library/hikage-extension.md)
|
||||
- [hikage-extension-betterandroid](../library/hikage-extension-betterandroid.md)
|
||||
- [hikage-extension-compose](../library/hikage-extension-compose.md)
|
||||
- [hikage-widget-androidx](../library/hikage-widget-androidx.md)
|
||||
- [hikage-widget-material](../library/hikage-widget-material.md)
|
||||
|
||||
## Demo
|
||||
|
||||
You can find some samples [here](repo://tree/main/samples) view the corresponding demo project to better understand how these functions work and quickly
|
||||
select the functions you need.
|
65
docs-source/src/en/index.md
Normal file
@@ -0,0 +1,65 @@
|
||||
---
|
||||
home: true
|
||||
title: Home
|
||||
heroImage: /images/logo.svg
|
||||
actions:
|
||||
- text: Get Started
|
||||
link: /en/guide/home
|
||||
type: primary
|
||||
- text: Changelog
|
||||
link: /en/about/changelog
|
||||
type: secondary
|
||||
features:
|
||||
- title: Native Control
|
||||
details: Using View as the foundation and Kotlin as the development language, 100% dynamic code layout, no additional configuration required, supports custom Views.
|
||||
- title: Fully Compatible
|
||||
details: Supports embedding and mixing XML, ViewBinding, and Jetpack Compose, and provides support for Material components and Jetpack.
|
||||
- title: Quickly Started
|
||||
details: Simple and easy to use it now! Do not need complex configuration and full development experience, Integrate dependencies and enjoy yourself.
|
||||
footer: Apache-2.0 License | Copyright (C) 2019 HighCapable
|
||||
---
|
||||
|
||||
### Layout, it's that flexible.
|
||||
|
||||
:::: code-group
|
||||
::: code-group-item Hikage (Kotlin DSL)
|
||||
|
||||
```kotlin
|
||||
LinearLayout(
|
||||
lparams = LayoutParams(matchParent = true),
|
||||
init = {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
) {
|
||||
TextView(id = "text_view") {
|
||||
text = "Hello, World!"
|
||||
textSize = 16f
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
::: code-group-item XML
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Hello, World!"
|
||||
android:textSize="16sp"
|
||||
android:gravity="center" />
|
||||
</LinearLayout>
|
||||
```
|
||||
|
||||
:::
|
||||
::::
|
171
docs-source/src/en/library/hikage-compiler.md
Normal file
@@ -0,0 +1,171 @@
|
||||
# hikage-compiler
|
||||
|
||||

|
||||
<span style="margin-left: 5px"/>
|
||||

|
||||
<span style="margin-left: 5px"/>
|
||||

|
||||
|
||||
This is a Hikage automatic compilation module.
|
||||
|
||||
## Configure Dependency
|
||||
|
||||
You can add this module to your project using the following method.
|
||||
|
||||
::: warning
|
||||
|
||||
You need to integrate the [Google KSP](https://github.com/google/ksp/releases) plugin in your project that is suitable for the current Kotlin version of your project.
|
||||
|
||||
:::
|
||||
|
||||
### SweetDependency (Recommended)
|
||||
|
||||
Add dependency in your project's `SweetDependency` configuration file.
|
||||
|
||||
```yaml
|
||||
plugins:
|
||||
com.google.devtools.ksp:
|
||||
version: +
|
||||
|
||||
libraries:
|
||||
com.highcapable.hikage:
|
||||
hikage-compiler:
|
||||
version: +
|
||||
```
|
||||
|
||||
Configure dependency in your root project `build.gradle.kts`.
|
||||
|
||||
```kotlin
|
||||
plugins {
|
||||
// ...
|
||||
autowire(libs.plugins.com.google.devtools.ksp) apply false
|
||||
}
|
||||
```
|
||||
|
||||
Configure dependency in your project `build.gradle.kts`.
|
||||
|
||||
```kotlin
|
||||
plugins {
|
||||
// ...
|
||||
autowire(libs.plugins.com.google.devtools.ksp)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// ...
|
||||
ksp(com.highcapable.hikage.hikage.compiler)
|
||||
}
|
||||
```
|
||||
|
||||
### Traditional Method
|
||||
|
||||
Configure dependency in your root project `build.gradle.kts`.
|
||||
|
||||
```kotlin
|
||||
plugins {
|
||||
// ...
|
||||
id("com.google.devtools.ksp") version "<ksp-version>" apply false
|
||||
}
|
||||
```
|
||||
|
||||
Configure dependency in your project `build.gradle.kts`.
|
||||
|
||||
```kotlin
|
||||
plugins {
|
||||
// ...
|
||||
id("com.google.devtools.ksp")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// ...
|
||||
ksp("com.highcapable.hikage:hikage-compiler:<version>")
|
||||
}
|
||||
```
|
||||
|
||||
Please change `<version>` to the version displayed at the top of this document,
|
||||
and change `<ksp-version>` to the KSP version corresponding to the Kotlin version currently used by your project.
|
||||
|
||||
## Function Introduction
|
||||
|
||||
Hikage's compilation module will automatically generate code at runtime.
|
||||
After update, please re-run the `assembleDebug` or `assembleRelease` task to generate the latest code.
|
||||
|
||||
### Generate Layout Components
|
||||
|
||||
Hikage can automatically generate the `Hikageable` function corresponding to the layout component for the specified layout component at compile time.
|
||||
|
||||
#### Custom View
|
||||
|
||||
You can add the `HikageView` annotation on your custom `View` to mark it as a Hikage layout component.
|
||||
|
||||
| Parameter Name | Description |
|
||||
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `lparams` | LayoutParams `Class` object, if your custom `View` is a subclass of `ViewGroup`, you can declare or leave it blank to use the default value |
|
||||
| `alias` | The alias of the layout component, that is, the function name to be generated, gets the name of the current Class by default |
|
||||
| `requireInit` | Whether to fill in the initialization method block of the layout, the default is the omitted parameters |
|
||||
| `requirePerformer` | Whether to fill in the `performer` method block of the layout, the default is an omitted parameter, which only takes effect when your custom `View` is a subclass of `ViewGroup` |
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
@HikageView(lparams = LinearLayout.LayoutParams::class)
|
||||
class MyLayout(context: Context, attrs: AttributeSet? = null) : LinearLayout(context, attrs) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Once compiled, you can use `MyLayout` as the layout component in the Hikage layout.
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
Hikageable {
|
||||
MyLayout {
|
||||
TextView(
|
||||
lparams = LayoutParams {
|
||||
topMargin = 16.dp
|
||||
}
|
||||
) {
|
||||
text = "Hello, World!"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Third-party Components
|
||||
|
||||
Hikage can also automatically generate layout component functions for the `View` component provided by third parties, and you can use the `HikageViewDeclaration` annotation to complete it.
|
||||
|
||||
| Parameter Name | Description |
|
||||
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `view` | Class object of layout component that needs to be declared |
|
||||
| `lparams` | LayoutParams `Class` object, if your custom `View` is a subclass of `ViewGroup`, you can declare or leave it blank to use the default value |
|
||||
| `alias` | The alias of the layout component, that is, the name of the function to be generated, obtains the name of the `view` Class by default |
|
||||
| `requireInit` | Whether to fill in the initialization method block of the layout, the default is the omitted parameters |
|
||||
| `requirePerformer` | Whether to fill in the `performer` method block of the layout, the default is an omitted parameter, which only takes effect when your custom `View` is a subclass of `ViewGroup` |
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
@HikageViewDeclaration(ThirdPartyView::class)
|
||||
object ThirdPartyViewDeclaration
|
||||
```
|
||||
|
||||
This annotation can be declared on any `object` class and is only used as a class that needs to be automatically included by the annotation scanner. You can set visibility to `private`, but make sure that the annotated class must be modified with `object`.
|
||||
|
||||
Similarly, after compilation, you can use `ThirdPartyView` as the layout component in the Hikage layout.
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
Hikageable {
|
||||
ThirdPartyView {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
::: tip
|
||||
|
||||
Hikage The function package name path for generating layout components is `com.highcapable.hikage.widget` + the full package name of your `View` or third-party `View` component.
|
||||
|
||||
:::
|
524
docs-source/src/en/library/hikage-core.md
Normal file
@@ -0,0 +1,524 @@
|
||||
# hikage-core
|
||||
|
||||

|
||||
<span style="margin-left: 5px"/>
|
||||

|
||||
<span style="margin-left: 5px"/>
|
||||

|
||||
|
||||
This is the core dependency of Hikage, and you need to introduce this module to use the basic features of Hikage.
|
||||
|
||||
## Configure Dependency
|
||||
|
||||
You can add this module to your project using the following method.
|
||||
|
||||
### SweetDependency (Recommended)
|
||||
|
||||
Add dependency in your project's `SweetDependency` configuration file.
|
||||
|
||||
```yaml
|
||||
libraries:
|
||||
com.highcapable.hikage:
|
||||
hikage-core:
|
||||
version: +
|
||||
```
|
||||
|
||||
Configure dependency in your project `build.gradle.kts`.
|
||||
|
||||
```kotlin
|
||||
implementation(com.highcapable.hikage.hikage.core)
|
||||
```
|
||||
|
||||
### Traditional Method
|
||||
|
||||
Configure dependency in your project `build.gradle.kts`.
|
||||
|
||||
```kotlin
|
||||
implementation("com.highcapable.hikage:hikage-core:<version>")
|
||||
```
|
||||
|
||||
Please change `<version>` to the version displayed at the top of this document.
|
||||
|
||||
## Function Introduction
|
||||
|
||||
You can view the KDoc [click here](kdoc://hikage-core).
|
||||
|
||||
### Basic Usage
|
||||
|
||||
Use the code below to create your first Hikage layout.
|
||||
|
||||
First, use `Hikageable` to create a `Hikage.Delegate` object.
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
val myLayout = Hikageable {
|
||||
LinearLayout {
|
||||
TextView {
|
||||
text = "Hello, World!"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then, set it to the parent or root layout you want to display.
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
// Assume that's your Activity.
|
||||
val activity: Activity
|
||||
// Instantiate the Hikage object.
|
||||
val hikage = myLayout.create(activity)
|
||||
// Get the root layout.
|
||||
val root = hikage.root
|
||||
// Content view set to Activity.
|
||||
activity.setContentView(root)
|
||||
```
|
||||
|
||||
In this way, we complete a simple layout creation and setting.
|
||||
|
||||
### Layout Agreement
|
||||
|
||||
The basic layout elements of Hikage are based on the Android native `View` component.
|
||||
|
||||
All layout elements can be created directly using the Android native `View` component.
|
||||
|
||||
The creation process of all layouts will be limited to the specified scope `Hikage.Performer`,
|
||||
which is called the "player" of the layout, that is, the role object that plays the layout.
|
||||
|
||||
This object can be created and maintained in the following ways.
|
||||
|
||||
#### Hikageable
|
||||
|
||||
As shown in [Basic Usage](#basic-usage), `Hikageable` can directly create a `Hikage.Delegate` or `Hikage` object.
|
||||
|
||||
In DSL, you can get the `Hikage.Performer` object to create the layout content.
|
||||
|
||||
The first solution is created anywhere.
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
// myLayout is a Hikage.Delegate object.
|
||||
val myLayout = Hikageable {
|
||||
// ...
|
||||
}
|
||||
// Assume that's your Context.
|
||||
val context: Context
|
||||
// Instantiate the Hikage object where the Context is needed.
|
||||
val hikage = myLayout.create(context)
|
||||
```
|
||||
|
||||
The second solution is created directly where `Context` exists.
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
// Assume that's your Context.
|
||||
val context: Context
|
||||
// Create a layout, myLayout is a Hikage object.
|
||||
val myLayout = Hikageable(context) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### HikageBuilder
|
||||
|
||||
In addition to the above methods, you can also maintain a `HikageBuilder` object to pre-create the layout.
|
||||
|
||||
First, we need to create a `HikageBuilder` object and define it as a singleton.
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
object MyLayout : HikageBuilder {
|
||||
|
||||
override fun build() = Hikageable {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then, use it where needed, there are two options as follows.
|
||||
|
||||
The first solution is to create a `Hikage.Delegate` object directly using `build`.
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
// myLayout is a Hikage.Delegate object.
|
||||
val myLayout = MyLayout.build()
|
||||
// Assume that's your Context.
|
||||
val context: Context
|
||||
// Instantiate the Hikage object where the Context is needed.
|
||||
val hikage = myLayout.create(context)
|
||||
```
|
||||
|
||||
The second solution is to create the `Hikage` delegate object using `Context.lazyHikage`.
|
||||
|
||||
For example, we can use it like `ViewBinding` in `Activity`.
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
class MyActivity: AppCompatActivity() {
|
||||
|
||||
private val myLayout by lazyHikage(MyLayout)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
// Get the root layout.
|
||||
val root = myLayout.root
|
||||
// Content view set to Activity.
|
||||
setContentView(root)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Basic Layout Components
|
||||
|
||||
Hikage uses a functional component creation scheme consistent with Jetpack Compose.
|
||||
|
||||
Its layout is done using two basic components, the `View` and `ViewGroup` functions.
|
||||
They correspond to Android native components based on `View` and `ViewGroup`, respectively.
|
||||
|
||||
#### View
|
||||
|
||||
The basic parameters of the `View` function are the following three, and the `View` object type created using generic definitions.
|
||||
|
||||
If the generic type is not declared, the default is to use `android.view.View` as the object type created.
|
||||
|
||||
| Parameter Name | Description |
|
||||
| -------------- | ----------------------------------------------------------------------------- |
|
||||
| `lparams` | Layout parameter, i.e. `ViewGroup.LayoutParams`, created using `LayoutParams` |
|
||||
| `id` | Used to find the ID of the created object, defined using a string |
|
||||
| `init` | The initialization method body of `View`, passed as the last DSL parameter |
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
View<TextView>(
|
||||
lparams = LayoutParams(),
|
||||
id = "my_text_view"
|
||||
) {
|
||||
text = "Hello, World!"
|
||||
textSize = 16f
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
```
|
||||
|
||||
#### ViewGroup
|
||||
|
||||
The basic parameters of the `ViewGroup` function are four, and compared with the `View` function, there is one more `performer` parameter.
|
||||
|
||||
It must declare a generic type because `ViewGroup` is an abstract class and requires a concrete implementation class.
|
||||
|
||||
`ViewGroup` provides an additional generic parameter based on `ViewGroup.LayoutParams` to provide layout parameters for sub-layouts.
|
||||
|
||||
`ViewGroup.LayoutParams` is used by default when not declared.
|
||||
|
||||
| Parameter Name | Description |
|
||||
| -------------- | ----------------------------------------------------------------------------- |
|
||||
| `lparams` | Layout parameter, i.e. `ViewGroup.LayoutParams`, created using `LayoutParams` |
|
||||
| `id` | Used to find the ID of the created object, defined using a string |
|
||||
| `init` | The initialization method body of `ViewGroup`, passed in as DSL parameter |
|
||||
| `performer` | `Hikage.Performer` object, passed as the last DSL parameter |
|
||||
|
||||
The function of the `performer` parameter is to pass a new `Hikage.Performer` object downward as the creator of the sub-layout.
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
ViewGroup<LinearLayout, LinearLayout.LayoutParams>(
|
||||
lparams = LayoutParams(),
|
||||
id = "my_linear_layout",
|
||||
// Initialization method body will be reflected here using `init`.
|
||||
init = {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
) {
|
||||
// You can continue to create sub-layouts here.
|
||||
View()
|
||||
}
|
||||
```
|
||||
|
||||
#### LayoutParams
|
||||
|
||||
Layouts in Hikage can be set using the `LayoutParams` function, you can create it using the following parameters.
|
||||
|
||||
| Parameter Name | Description |
|
||||
| ------------------- | --------------------------------------------------------------------------------------------- |
|
||||
| `width` | Manually specify layout width |
|
||||
| `height` | Manually specify layout height |
|
||||
| `matchParent` | Whether to use `MATCH_PARENT` as layout width and height |
|
||||
| `wrapContent` | Whether to use `WRAP_CONTENT` as layout width and height |
|
||||
| `widthMatchParent` | Set width to `MATCH_PARENT` only |
|
||||
| `heightMatchParent` | Set the height to `MATCH_PARENT` only |
|
||||
| `body` | The initialization method body of the layout parameter, passed into as the last DSL parameter |
|
||||
|
||||
When you do not set the `LayoutParams` object or specify `width` and `height`, Hikage will automatically use `WRAP_CONTENT` as layout parameters.
|
||||
|
||||
The type of the `body` method body comes from the second generic parameter provided by the upper layer [ViewGroup](#viewgroup).
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
View(
|
||||
// Assume that the layout parameter type provided by the upper layer is LinearLayout.LayoutParams.
|
||||
lparams = LayoutParams(width = 100.dp) {
|
||||
topMargin = 20.dp
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
If you only need a horizontally filled layout, you can use `widthMatchParent = true` directly.
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
View(
|
||||
lparams = LayoutParams(widthMatchParent = true)
|
||||
)
|
||||
```
|
||||
|
||||
#### Layout
|
||||
|
||||
Hikage supports references to third-party layouts, you can pass in XML layout resource IDs, other Hikage objects, and `View` objects, and even `ViewBinding`.
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
ViewGroup<...> {
|
||||
// Quote XML layout resource ID.
|
||||
Layout(R.layout.my_layout)
|
||||
// Quote ViewBinding.
|
||||
Layout<MyLayoutBinding>()
|
||||
// Reference another Hikage or Hikage.Delegate object.
|
||||
Layout(myLayout)
|
||||
}
|
||||
```
|
||||
|
||||
### Positioning Layout Components
|
||||
|
||||
Hikage supports locating components using `id`. In the example above, we used the `id` parameter to set the component's ID.
|
||||
|
||||
After setting the ID, you can use the `Hikage.get` method to get them.
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
val myLayout = Hikageable {
|
||||
View<TextView>(id = "my_text_view") {
|
||||
text = "Hello, World!"
|
||||
}
|
||||
}
|
||||
// Assume that's your Context.
|
||||
val context: Context
|
||||
// Instantiate the Hikage object where the Context is needed.
|
||||
val hikage = myLayout.create(context)
|
||||
// Get the specified component and return the View type.
|
||||
val textView = hikage["my_text_view"]
|
||||
// Get the specified component and declare the component type.
|
||||
val textView = hikage.get<TextView>("my_text_view")
|
||||
// If you are not sure whether the ID exists, you can use the `getOrNull` method.
|
||||
val textView = hikage.getOrNull<TextView>("my_text_view")
|
||||
```
|
||||
|
||||
### Custom Layout Components
|
||||
|
||||
Hikage provides functions corresponding to component class names for Android basic layout components.
|
||||
|
||||
You can directly use these functions to create components without using generics to declare them. If you need components provided by Jetpack or Material,
|
||||
the [hikage-widget-androidx](../library/hikage-widget-androidx.md) or [hikage-widget-material](../library/hikage-widget-material.md) modules can be introduced.
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
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
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The initialized `View` or `ViewGroup` objects return instances of their own object type, which you can use in the following layout.
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
val textView = TextView {
|
||||
text = "Hello, World!"
|
||||
textSize = 16f
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
Button {
|
||||
text = "Click Me!"
|
||||
setOnClickListener {
|
||||
// Use the textView object directly.
|
||||
textView.text = "Clicked!"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If the provided components do not meet your needs, you can create your own components manually.
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
// Suppose you have defined your custom components.
|
||||
class MyCustomView(context: Context, attrs: AttributeSet? = null) : View(context, attrs) {
|
||||
// ...
|
||||
}
|
||||
|
||||
// Below, create the function corresponding to the component.
|
||||
// Custom components must declare this annotation.
|
||||
// Declaring the annotation of the component is contagious,
|
||||
// and this annotation is required in every scope used to build the layout.
|
||||
@Hikageable
|
||||
// The naming of functions can be done at will, but it is recommended to use a big camel name.
|
||||
// The signature part of the function needs to be fixedly
|
||||
// declared as `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> = {},
|
||||
// If this component is a container, you can declare a `performer` parameter.
|
||||
// performer: HikagePerformer<LP> = {}
|
||||
) = View<MyCustomView>(lparams, id, init)
|
||||
```
|
||||
|
||||
It would seem tedious to implement such complex functions manually every time.
|
||||
If you want to be able to automatically generate component functions, you can introduce and refer to the [hikage-compiler](../library/hikage-compiler.md) module.
|
||||
|
||||
### Custom Layout Factory
|
||||
|
||||
Hikage supports custom layout factories and is compatible with `LayoutInflater.Factory2`.
|
||||
You can customize events and listening during the Hikage layout inflating process in the following ways.
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
val factory = HikageFactory { parent, base, context, params ->
|
||||
// You can customize the behavior of the layout factory here.
|
||||
// For example, create a new View object in your own way.
|
||||
// `parent` is the ViewGroup object to which the current component is to be added,
|
||||
// and if not, it is `null`.
|
||||
// `base` is the View object created for the previous HikageFactory, if not, it is `null`.
|
||||
// `params` object contains the component ID, AttributeSet and Class objects of View.
|
||||
val view = MyLayoutFactory.createView(context, params)
|
||||
// You can also initialize and set the created View object here.
|
||||
view.setBackgroundColor(Color.RED)
|
||||
// Return the created View object.
|
||||
// Return `null` will use the default component inflating method.
|
||||
view
|
||||
}
|
||||
```
|
||||
|
||||
You can also pass in the `LayoutInflater` object directly to automatically inflate and use the `LayoutInflater.Factory2` in it.
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
// Assume that this is your LayoutInflater object.
|
||||
val layoutInflater: LayoutInflater
|
||||
// Create HikageFactory object through LayoutInflater.
|
||||
val factory = HikageFactory(layoutInflater)
|
||||
```
|
||||
|
||||
Then set it to the Hikage layout you need to inflate.
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
// Assume that's your Context.
|
||||
val context: Context
|
||||
// Create Hikage object.
|
||||
val hikage = Hikageable(
|
||||
context = context,
|
||||
factory = {
|
||||
// Add a custom HikageFactory object.
|
||||
add(factory)
|
||||
// Add directly.
|
||||
add { parent, base, context, params ->
|
||||
// ...
|
||||
null
|
||||
}
|
||||
// Add multiple consecutively.
|
||||
addAll(factories)
|
||||
}
|
||||
) {
|
||||
LinearLayout {
|
||||
TextView {
|
||||
text = "Hello, World!"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
::: tip
|
||||
|
||||
Hikage will inflate the layout according to the `LayoutInflater.Factory2` of the `Context` object, if you are using `AppCompatActivity`,
|
||||
Components in the layout will be automatically replaced with the corresponding Compat component or Material component,
|
||||
which is consistent with the characteristics of the XML layout.
|
||||
|
||||
If you do not need this feature to be effective by default, you can turn it off globally using the following method.
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
Hikage.isAutoProcessWithFactory2 = false
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Preview Layout
|
||||
|
||||
Hikage supports previewing layouts in Android Studio.
|
||||
|
||||
With the help of the custom `View` preview plugin that comes with Android Studio, you can preview the layout using the following methods.
|
||||
|
||||
You just need to define a custom `View` for the preview layout and inherit from `HikagePreview`.
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
class MyLayoutPreview(context: Context, attrs: AttributeSet?) : HikagePreview(context, attrs) {
|
||||
|
||||
override fun build() = Hikageable {
|
||||
LinearLayout {
|
||||
TextView {
|
||||
text = "Hello, World!"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then the preview pane should appear on the right side of your current window.
|
||||
After opening, click "Build & Refresh". The preview will be automatically displayed after the compilation is completed.
|
||||
|
||||
:::tip
|
||||
|
||||
`HikagePreview` implements the `HikageBuilder` interface, you can return any Hikage layout in the `build` method for preview.
|
||||
|
||||
:::
|
||||
|
||||
::: danger
|
||||
|
||||
`HikagePreview` supports previewing layouts in Android Studio only, do not use it at runtime or add it to any XML layout.
|
||||
|
||||
:::
|
74
docs-source/src/en/library/hikage-extension-betterandroid.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# hikage-extension-betterandroid
|
||||
|
||||

|
||||
<span style="margin-left: 5px"/>
|
||||

|
||||
<span style="margin-left: 5px"/>
|
||||

|
||||
|
||||
This is a Hikage extension dependency for [BetterAndroid](https://github.com/BetterAndroid/BetterAndroid) UI component-related features.
|
||||
|
||||
## Configure Dependency
|
||||
|
||||
You can add this module to your project using the following method.
|
||||
|
||||
### SweetDependency (Recommended)
|
||||
|
||||
Add dependency in your project's `SweetDependency` configuration file.
|
||||
|
||||
```yaml
|
||||
libraries:
|
||||
com.highcapable.hikage:
|
||||
hikage-extension-betterandroid:
|
||||
version: +
|
||||
```
|
||||
|
||||
Configure dependency in your project `build.gradle.kts`.
|
||||
|
||||
```kotlin
|
||||
implementation(com.highcapable.hikage.hikage.extension.betterandroid)
|
||||
```
|
||||
|
||||
### Traditional Method
|
||||
|
||||
Configure dependency in your project `build.gradle.kts`.
|
||||
|
||||
```kotlin
|
||||
implementation("com.highcapable.hikage:hikage-extension-betterandroid:<version>")
|
||||
```
|
||||
|
||||
Please change `<version>` to the version displayed at the top of this document.
|
||||
|
||||
## Function Introduction
|
||||
|
||||
You can view the KDoc [click here](kdoc://hikage-extension-betterandroid).
|
||||
|
||||
### Adapter Extension
|
||||
|
||||
Hikage provides layout extension function for BetterAndroid's [Adapter](https://betterandroid.github.io/BetterAndroid/en/library/ui-component#adapter),
|
||||
you can use the Hikage layout directly on the original extension method of the adapter.
|
||||
|
||||
It uses the `ViewHolderDelegate` provided by BetterAndroid to create extension methods.
|
||||
|
||||
Here is a simple example based on `RecyclerView`.
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
// Assume this is the dataset you need to bind to.
|
||||
val listData = ArrayList<CustomBean>()
|
||||
// Create and bind to a custom RecyclerView.Adapter.
|
||||
val adapter = recyclerView.bindAdapter<CustomBean> {
|
||||
onBindData { listData }
|
||||
onBindItemView(
|
||||
Hikageable = {
|
||||
TextView(id = "text_view") {
|
||||
text = "Hello, World!"
|
||||
textSize = 16f
|
||||
}
|
||||
}
|
||||
) { hikage, bean, position ->
|
||||
hikage.get<TextView>("text_view").text = bean.name
|
||||
}
|
||||
}
|
||||
```
|
89
docs-source/src/en/library/hikage-extension-compose.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# hikage-extension-compose
|
||||
|
||||

|
||||
<span style="margin-left: 5px"/>
|
||||

|
||||
<span style="margin-left: 5px"/>
|
||||

|
||||
|
||||
This is a Hikage extension dependency for Jetpack Compose component-related features.
|
||||
|
||||
## Configure Dependency
|
||||
|
||||
You can add this module to your project using the following method.
|
||||
|
||||
::: warning
|
||||
|
||||
This module relies on the Jetpack Compose compiler plugin.
|
||||
Please make sure that your project has integrated Jetpack Compose-related dependencies.
|
||||
Please refer to [here](https://developer.android.com/develop/ui/compose/compiler) for details.
|
||||
|
||||
:::
|
||||
|
||||
### SweetDependency (Recommended)
|
||||
|
||||
Add dependency in your project's `SweetDependency` configuration file.
|
||||
|
||||
```yaml
|
||||
libraries:
|
||||
com.highcapable.hikage:
|
||||
hikage-extension-compose:
|
||||
version: +
|
||||
```
|
||||
|
||||
Configure dependency in your project `build.gradle.kts`.
|
||||
|
||||
```kotlin
|
||||
implementation(com.highcapable.hikage.hikage.extension.compose)
|
||||
```
|
||||
|
||||
### Traditional Method
|
||||
|
||||
Configure dependency in your project `build.gradle.kts`.
|
||||
|
||||
```kotlin
|
||||
implementation("com.highcapable.hikage:hikage-extension-compose:<version>")
|
||||
```
|
||||
|
||||
Please change `<version>` to the version displayed at the top of this document.
|
||||
|
||||
## Function Introduction
|
||||
|
||||
You can view the KDoc [click here](kdoc://hikage-extension-compose).
|
||||
|
||||
### Use Jetpack Compose in Hikage
|
||||
|
||||
You can use the following methods to embed Jetpack Compose components in a Hikage layout.
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
Hikageable {
|
||||
ComposeView(
|
||||
lparams = LayoutParams(matchParent = true)
|
||||
) {
|
||||
Text("Hello, World!")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Use Hikage in Jetpack Compose
|
||||
|
||||
You can use the following methods to embed Hikage components in a Jetpack Compose layout.
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
HikageView {
|
||||
TextView(
|
||||
lparams = LayoutParams(matchParent = true)
|
||||
) {
|
||||
text = "Hello, World!"
|
||||
textSize = 20f
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
210
docs-source/src/en/library/hikage-extension.md
Normal file
@@ -0,0 +1,210 @@
|
||||
# hikage-extension
|
||||
|
||||

|
||||
<span style="margin-left: 5px"/>
|
||||

|
||||
<span style="margin-left: 5px"/>
|
||||

|
||||
|
||||
This is a Hikage extension dependency for UI component-related features.
|
||||
|
||||
## Configure Dependency
|
||||
|
||||
You can add this module to your project using the following method.
|
||||
|
||||
### SweetDependency (Recommended)
|
||||
|
||||
Add dependency in your project's `SweetDependency` configuration file.
|
||||
|
||||
```yaml
|
||||
libraries:
|
||||
com.highcapable.hikage:
|
||||
hikage-extension:
|
||||
version: +
|
||||
```
|
||||
|
||||
Configure dependency in your project `build.gradle.kts`.
|
||||
|
||||
```kotlin
|
||||
implementation(com.highcapable.hikage.hikage.extension)
|
||||
```
|
||||
|
||||
### Traditional Method
|
||||
|
||||
Configure dependency in your project `build.gradle.kts`.
|
||||
|
||||
```kotlin
|
||||
implementation("com.highcapable.hikage:hikage-extension:<version>")
|
||||
```
|
||||
|
||||
Please change `<version>` to the version displayed at the top of this document.
|
||||
|
||||
## Function Introduction
|
||||
|
||||
You can view the KDoc [click here](kdoc://hikage-extension).
|
||||
|
||||
### Activity
|
||||
|
||||
Hikage provides better extensions for `Activity`, and creating a Hikage in `Activity` will be easier.
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView {
|
||||
LinearLayout(
|
||||
lparams = LayoutParams(matchParent = true) {
|
||||
topMargin = 16.dp
|
||||
},
|
||||
init = {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
) {
|
||||
TextView {
|
||||
text = "Hello, World!"
|
||||
textSize = 16f
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
With the `setContentView` extension method of `Hikage`, you can set the layout using the `setContent` method like Jetpack Compose.
|
||||
|
||||
### Window
|
||||
|
||||
Using Hikage to create a layout in Window is consistent with [Activity](#activity), you just need to use the `setContentView` method to pass in a `Hikage` layout.
|
||||
|
||||
### Dialog
|
||||
|
||||
If you want to create a layout using Hikage directly in `AlertDialog`, you can now do it more simply using the following scheme.
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
// Assume this is your Context.
|
||||
val context: Context
|
||||
// Create a dialog box and display it.
|
||||
AlertDialog.Builder(context)
|
||||
.setTitle("Hello, World!")
|
||||
.setView {
|
||||
TextView {
|
||||
text = "Hello, World!"
|
||||
textSize = 16f
|
||||
}
|
||||
}
|
||||
.show()
|
||||
```
|
||||
|
||||
To create a layout using Hikage in `AlertDialog`, you just need to use the `setView` method to pass in a `Hikage` layout.
|
||||
|
||||
If you inherited from `Dialog` for customization, you can use the `setContentView` method as in [Activity](#activity).
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
class CustomDialog(context: Context) : Dialog(context) {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView {
|
||||
LinearLayout(
|
||||
lparams = LayoutParams(matchParent = true) {
|
||||
topMargin = 16.dp
|
||||
},
|
||||
init = {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
) {
|
||||
TextView {
|
||||
text = "Hello, World!"
|
||||
textSize = 16f
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### PopupWindow
|
||||
|
||||
You can inherit from `PopupWindow` for customization and then use Hikage to create the layout,
|
||||
and you can use the `setContentView` method like in [Activity](#activity).
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
class CustomPopupWindow(context: Context) : PopupWindow(context) {
|
||||
|
||||
init {
|
||||
setContentView(context) {
|
||||
LinearLayout(
|
||||
lparams = LayoutParams(matchParent = true) {
|
||||
topMargin = 16.dp
|
||||
},
|
||||
init = {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
) {
|
||||
TextView {
|
||||
text = "Hello, World!"
|
||||
textSize = 16f
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
::: danger
|
||||
|
||||
To create a `PopupWindow` for Hikage layout, you need to use the `Context` constructor method to initialize it.
|
||||
If the `Context` cannot be obtained immediately, please pass the `Context` instance to the `setContentView` method.
|
||||
|
||||
:::
|
||||
|
||||
### ViewGroup
|
||||
|
||||
Hikage extends the `addView` method of `ViewGroup`, and you can use the Hikage layout directly to quickly add new layouts to the current `ViewGroup`.
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
// Assume this is your ViewGroup.
|
||||
val root: FrameLayout
|
||||
// Add Hikage layout.
|
||||
root.addView {
|
||||
TextView {
|
||||
text = "Hello, World!"
|
||||
textSize = 16f
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Or, use in a custom `View`.
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
class CustomView(context: Context, attrs: AttributeSet? = null) : FrameLayout(context, attrs) {
|
||||
|
||||
init {
|
||||
addView {
|
||||
TextView {
|
||||
text = "Hello, World!"
|
||||
textSize = 16f
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
64
docs-source/src/en/library/hikage-widget-androidx.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# hikage-widget-androidx
|
||||
|
||||

|
||||
<span style="margin-left: 5px"/>
|
||||

|
||||
<span style="margin-left: 5px"/>
|
||||

|
||||
|
||||
This is a Hikage extension dependency for Jetpack Compact component-related features.
|
||||
|
||||
## Configure Dependency
|
||||
|
||||
You can add this module to your project using the following method.
|
||||
|
||||
### SweetDependency (Recommended)
|
||||
|
||||
Add dependency in your project's `SweetDependency` configuration file.
|
||||
|
||||
```yaml
|
||||
libraries:
|
||||
com.highcapable.hikage:
|
||||
hikage-widget-androidx:
|
||||
version: +
|
||||
```
|
||||
|
||||
Configure dependency in your project `build.gradle.kts`.
|
||||
|
||||
```kotlin
|
||||
implementation(com.highcapable.hikage.hikage.widget.androidx)
|
||||
```
|
||||
|
||||
### Traditional Method
|
||||
|
||||
Configure dependency in your project `build.gradle.kts`.
|
||||
|
||||
```kotlin
|
||||
implementation("com.highcapable.hikage:hikage-widget-androidx:<version>")
|
||||
```
|
||||
|
||||
Please change `<version>` to the version displayed at the top of this document.
|
||||
|
||||
## Function Introduction
|
||||
|
||||
This dependency inherits the available components from Jetpack Compact, which you can directly reference to use in Hikage.
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
LinearLayoutCompact(
|
||||
lparams = LayoutParams(matchParent = true) {
|
||||
topMargin = 16.dp
|
||||
},
|
||||
init = {
|
||||
orientation = LinearLayoutCompat.VERTICAL
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
) {
|
||||
AppCompatTextView {
|
||||
text = "Hello, World!"
|
||||
textSize = 16f
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
}
|
||||
```
|
80
docs-source/src/en/library/hikage-widget-material.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# hikage-widget-material
|
||||
|
||||

|
||||
<span style="margin-left: 5px"/>
|
||||

|
||||
<span style="margin-left: 5px"/>
|
||||

|
||||
|
||||
This is a Hikage extension dependency for Google Material (MDC) component-related features.
|
||||
|
||||
## Configure Dependency
|
||||
|
||||
You can add this module to your project using the following method.
|
||||
|
||||
### SweetDependency (Recommended)
|
||||
|
||||
Add dependency in your project's `SweetDependency` configuration file.
|
||||
|
||||
```yaml
|
||||
libraries:
|
||||
com.highcapable.hikage:
|
||||
hikage-widget-material:
|
||||
version: +
|
||||
```
|
||||
|
||||
Configure dependency in your project `build.gradle.kts`.
|
||||
|
||||
```kotlin
|
||||
implementation(com.highcapable.hikage.hikage.widget.material)
|
||||
```
|
||||
|
||||
### Traditional Method
|
||||
|
||||
Configure dependency in your project `build.gradle.kts`.
|
||||
|
||||
```kotlin
|
||||
implementation("com.highcapable.hikage:hikage-widget-material:<version>")
|
||||
```
|
||||
|
||||
Please change `<version>` to the version displayed at the top of this document.
|
||||
|
||||
## Function Introduction
|
||||
|
||||
This dependency inherits the available components from Google Material (MDC), which you can directly reference to use in Hikage.
|
||||
|
||||
> The following example
|
||||
|
||||
```kotlin
|
||||
LinearLayout(
|
||||
lparams = LayoutParams(matchParent = true) {
|
||||
topMargin = 16.dp
|
||||
},
|
||||
init = {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
) {
|
||||
MaterialTextView {
|
||||
text = "Hello, World!"
|
||||
textSize = 16f
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
MaterialButton {
|
||||
text = "Hello, World!"
|
||||
textSize = 16f
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
TextInputLayout(
|
||||
lparams = LayoutParams {
|
||||
topMargin = 16.dp
|
||||
},
|
||||
init = {
|
||||
minWidth = 200.dp
|
||||
hint = "Enter your text"
|
||||
}
|
||||
) {
|
||||
TextInputEditText()
|
||||
}
|
||||
}
|
||||
```
|
17
docs-source/src/index.md
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
home: true
|
||||
navbar: false
|
||||
sidebar: false
|
||||
title: null
|
||||
heroAlt: null
|
||||
heroText: null
|
||||
tagline: Select a language
|
||||
actions:
|
||||
- text: English
|
||||
link: /en/
|
||||
type: secondary
|
||||
- text: 简体中文
|
||||
link: /zh-cn/
|
||||
type: secondary
|
||||
footer: Apache-2.0 License | Copyright (C) 2019 HighCapable
|
||||
---
|
27
docs-source/src/zh-cn/about/about.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# 关于此文档
|
||||
|
||||
> 此文档由 [VuePress](https://v2.vuepress.vuejs.org/zh) 强力驱动。
|
||||
|
||||
## 许可证
|
||||
|
||||
[Apache-2.0](https://github.com/BetterAndroid/Hikage/blob/main/LICENSE)
|
||||
|
||||
```:no-line-numbers
|
||||
Apache License Version 2.0
|
||||
|
||||
Copyright (C) 2019 HighCapable
|
||||
|
||||
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.
|
||||
```
|
||||
|
||||
版权所有 © 2019 HighCapable
|
51
docs-source/src/zh-cn/about/changelog.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# 更新日志
|
||||
|
||||
> 这里记录了 `Hikage` 的版本更新历史。
|
||||
|
||||
::: danger
|
||||
|
||||
我们只会对最新的 API 版本进行维护,若你正在使用过时的 API 版本则代表你自愿放弃一切维护的可能性。
|
||||
|
||||
:::
|
||||
|
||||
## hikage-core
|
||||
|
||||
### 1.0.0 | 2025.04.20  <Badge type="tip" text="最新" vertical="middle" />
|
||||
|
||||
- 首个版本提交至 Maven
|
||||
|
||||
## hikage-compiler
|
||||
|
||||
### 1.0.0 | 2025.04.20  <Badge type="tip" text="最新" vertical="middle" />
|
||||
|
||||
- 首个版本提交至 Maven
|
||||
|
||||
## hikage-extension
|
||||
|
||||
### 1.0.0 | 2025.04.20  <Badge type="tip" text="最新" vertical="middle" />
|
||||
|
||||
- 首个版本提交至 Maven
|
||||
|
||||
## hikage-extension-betterandroid
|
||||
|
||||
### 1.0.0 | 2025.04.20  <Badge type="tip" text="最新" vertical="middle" />
|
||||
|
||||
- 首个版本提交至 Maven
|
||||
|
||||
## hikage-extension-compose
|
||||
|
||||
### 1.0.0 | 2025.04.20  <Badge type="tip" text="最新" vertical="middle" />
|
||||
|
||||
- 首个版本提交至 Maven
|
||||
|
||||
## hikage-widget-androidx
|
||||
|
||||
### 1.0.0 | 2025.04.20  <Badge type="tip" text="最新" vertical="middle" />
|
||||
|
||||
- 首个版本提交至 Maven
|
||||
|
||||
## hikage-widget-material
|
||||
|
||||
### 1.0.0 | 2025.04.20  <Badge type="tip" text="最新" vertical="middle" />
|
||||
|
||||
- 首个版本提交至 Maven
|
15
docs-source/src/zh-cn/about/contacts.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# 联系我们
|
||||
|
||||
> 如在使用中有任何问题,或有任何建设性的建议,都可以联系我们。
|
||||
|
||||
加入我们的开发者群组。
|
||||
|
||||
- [点击加入 Telegram 群组](https://t.me/BetterAndroid)
|
||||
- [点击加入 Telegram 群组 (开发者)](https://t.me/HighCapable_Dev)
|
||||
- [点击加入 QQ 群 (开发者)](https://qm.qq.com/cgi-bin/qm/qr?k=Pnsc5RY6N2mBKFjOLPiYldbAbprAU3V7&jump_from=webapi&authKey=X5EsOVzLXt1dRunge8ryTxDRrh9/IiW1Pua75eDLh9RE3KXE+bwXIYF5cWri/9lf)
|
||||
|
||||
在 **酷安** 找到我 [@星夜不荟](http://www.coolapk.com/u/876977)。
|
||||
|
||||
## 助力维护
|
||||
|
||||
感谢您选择并使用 `Hikage`,如有代码相关的建议和请求,可在 GitHub 提交 Pull Request。
|
73
docs-source/src/zh-cn/about/future.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# 展望未来
|
||||
|
||||
> 未来是美好的,也是不确定的,让我们共同期待 `Hikage` 在未来的发展空间。
|
||||
|
||||
## 未来的计划
|
||||
|
||||
> 这里收录了 `Hikage` 可能会在后期添加的功能。
|
||||
|
||||
### 处理 AttrtibuteSet
|
||||
|
||||
`Hikage` 未来将会支持处理 `AttributeSet` 来对接 XML 原始的属性以实现接管一些并未在代码中对布局属性开放自定义的第三方组件。
|
||||
|
||||
`Hikage` 目前已经支持自动化创建 `XmlBlock`,但尚未支持直接处理自定义的 `AttributeSet`,因为其历史遗留问题和处理难度较高,可能会折中考虑后期是否要继续完善此功能。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
TextView(
|
||||
id = "text_view",
|
||||
// 通过 AttributeSet 传入的属性
|
||||
attrs = {
|
||||
namespace("android") {
|
||||
set("text", "Hello, World!")
|
||||
set("textSize", "16sp")
|
||||
set("gravity", "center")
|
||||
}
|
||||
}
|
||||
) {
|
||||
// 通过代码传入的属性
|
||||
text = "Hello, World!"
|
||||
textSize = 16f
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
```
|
||||
|
||||
### 生成组件 ID
|
||||
|
||||
`Hikage` 未来可能会根据需求支持生成使用字符串自定义的组件 ID 的直接调用功能。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
object MyLayout : HikageBuilder {
|
||||
|
||||
override fun build() = Hikageable(context) {
|
||||
LinearLayout(
|
||||
id = "lin_layout",
|
||||
lparams = LayoutParams(matchParent = true),
|
||||
init = {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
) {
|
||||
TextView(id = "text_view") {
|
||||
text = "Hello, World!"
|
||||
textSize = 16f
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val context: Context
|
||||
// 创建 TypedHikage
|
||||
val myLayout = MyLayout.asTyped().build().create(context)
|
||||
// 或者,使用懒加载
|
||||
val myLayout by context.lazyTypedHikage(MyLayout)
|
||||
// 直接调用根据字符串生成的 ID
|
||||
val linLayout = myLayout.linLayout
|
||||
val textView = myLayout.textView
|
||||
// 获取根布局,即 LinearLayout
|
||||
val root = myLayout.root
|
||||
```
|
17
docs-source/src/zh-cn/config/r8-proguard.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# R8 与 Proguard 混淆
|
||||
|
||||
> 大部分场景下应用程序安装包可通过混淆压缩体积,这里介绍了混淆规则的配置方法。
|
||||
|
||||
`Hikage` 不需要额外配置混淆规则,由于 Hikage 装载的 `View` 不需要在 XML 中被定义,它们也可以同样被混淆。
|
||||
|
||||
你可以将你的自定义 `View`,例如 `com.yourpackage.YourView` 使用以下规则强制让它们被混淆。
|
||||
|
||||
```
|
||||
-allowobfuscation class com.yourpackage.YourView
|
||||
```
|
||||
|
||||
如果你一定要防止 `Hikage` 被混淆或者混淆后发生了问题,那么你可以使用以下规则来防止 `Hikage` 被混淆。
|
||||
|
||||
```
|
||||
-keep class com.highcapable.hikage**
|
||||
```
|
109
docs-source/src/zh-cn/guide/home.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# 介绍
|
||||
|
||||
> `Hikage` (发音 /ˈhɪkɑːɡeɪ/) 是一个 Android 响应式 UI 构建工具。
|
||||
|
||||
## 背景
|
||||
|
||||
这是一个 Android 响应式 UI 构建工具,它的设计聚焦于 **实时代码构建 UI**。
|
||||
|
||||
项目图标由 [MaiTungTM](https://github.com/Lagrio) 设计,名称取自 「BanG Dream It's MyGO!!!!!」 中的原创歌曲《春日影》(Haru**hikage**)。
|
||||
|
||||
<details><summary>为什么要...</summary>
|
||||
<div align="center">
|
||||
<img src="/images/nagasaki_soyo.png" width = "100" height = "100" alt="LOGO"/>
|
||||
|
||||
**なんで春日影レイアウト使いの?**
|
||||
</div>
|
||||
</details>
|
||||
|
||||
不同于 Jetpack Compose 的声明式 UI,Hikage 专注于 Android 原生平台,它的设计目标是为了让开发者能够快速构建 UI 并可直接支持 Android 原生组件。
|
||||
|
||||
**<u>Hikage 只是一个 UI 构建工具,自身并不提供任何 UI 组件</u>**。
|
||||
|
||||
拒绝重复造轮子,我们的方案始终是兼容与高效,现在你可以抛弃 ViewBinding 和 XML 甚至是 `findViewById`,直接来尝试使用代码布局吧。
|
||||
|
||||
`Hikage` 配合我们的另一个项目 [BetterAndroid](https://github.com/BetterAndroid/BetterAndroid) 使用效果更佳,同时 `Hikage` 自身将自动引用 `BetterAndroid` 相关依赖作为核心内容。
|
||||
|
||||
## 用途
|
||||
|
||||
Hikage 主要适用于专注原生 Android 平台开发的开发者,自从 Kotlin 作为主要开发语言后,依然没有一套比较完美的工具能够使用 DSL 实现动态代码布局,
|
||||
所以没有使用 Jetpack Compose 的项目依然需要使用原始的 XML,虽然有着 ViewBinding 的支持,但是依然不是很友好。
|
||||
|
||||
Hikage 继承了 [Anko](https://github.com/Kotlin/anko)、[Splitties](https://github.com/LouisCAD/Splitties) 的设计方案以及借鉴了 Jetpack Compose 的 DSL 函数命名方案,
|
||||
并且在此基础上进行了大量改进,使得它在使用成本上更贴近原生,写法上更贴近 Jetpack Compose。
|
||||
|
||||
> 各种 DSL 布局方案对比
|
||||
|
||||
:::: code-group
|
||||
::: code-group-item Hikage
|
||||
|
||||
```kotlin
|
||||
LinearLayout(
|
||||
lparams = LayoutParams(matchParent = true) {
|
||||
topMargin = 16.dp
|
||||
},
|
||||
init = {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
) {
|
||||
TextView {
|
||||
text = "Hello, World!"
|
||||
textSize = 16f
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
::: code-group-item Anko、Splitties
|
||||
|
||||
```kotlin
|
||||
verticalLayout {
|
||||
gravity = Gravity.CENTER
|
||||
textView("Hello, World!") {
|
||||
textSize = 16f
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
}.lparams(
|
||||
width = matchParent,
|
||||
height = matchParent
|
||||
) {
|
||||
topMargin = dip(16)
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
::: code-group-item Jetpack Compose
|
||||
|
||||
```kotlin
|
||||
Column(
|
||||
modifier = Modifier.padding(top = 16.dp),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = "Hello, World!",
|
||||
fontSize = 16.sp,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
::::
|
||||
|
||||
Hikage 的基础部分**完全不需要借助外部及额外的编译插件**,它能**即插即用**并在**任何地方创建**一个可被设置到父布局以及 `Window` 上的 `View` 对象。
|
||||
|
||||
Hikage **全面兼容**混合式布局,你可以在 Hikage 中嵌入 XML (使用 `R.layout` 方案装载布局)、ViewBinding 甚至是 Jetpack Compose。
|
||||
|
||||
## 语言要求
|
||||
|
||||
推荐使用 Kotlin 作为首选开发语言,本项目完全使用 Kotlin 编写,且不再有计划兼容 Java。
|
||||
|
||||
文档全部的 Demo 示例代码都将使用 Kotlin 进行描述,如果你完全不会使用 Kotlin,那么你将有可能无法正常使用本项目。
|
||||
|
||||
## 功能贡献
|
||||
|
||||
本项目的维护离不开各位开发者的支持和贡献,目前这个项目处于初期阶段,可能依然存在一些问题或者缺少你需要的功能,
|
||||
如果可能,欢迎提交 PR 为此项目贡献你认为需要的功能或前往 [GitHub Issues](repo://issues) 向我们提出建议。
|
85
docs-source/src/zh-cn/guide/quick-start.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# 快速开始
|
||||
|
||||
> 集成 `Hikage` 到你的项目中。
|
||||
|
||||
## 项目要求
|
||||
|
||||
项目需要使用 `Android Studio` 或 `IntelliJ IDEA` 创建且类型为 Android 或 Kotlin Multiplatform 项目并已集成 Kotlin 环境依赖。
|
||||
|
||||
- Android Studio (建议 [从这里](https://developer.android.com/studio) 获取最新版本)
|
||||
|
||||
- IntelliJ IDEA (建议 [从这里](https://www.jetbrains.com/idea) 获取最新版本)
|
||||
|
||||
- Kotlin 1.9.0+、Gradle 8+、Java 17+、Android Gradle Plugin 8+
|
||||
|
||||
### 配置存储库
|
||||
|
||||
`Hikage` 的依赖发布在 **Maven Central** 和我们的公共存储库中,你可以使用如下方式配置存储库。
|
||||
|
||||
我们推荐使用 Kotlin DSL 作为 Gradle 构建脚本语言并推荐使用 [SweetDependency](https://github.com/HighCapable/SweetDependency) 来管理依赖。
|
||||
|
||||
#### SweetDependency (推荐)
|
||||
|
||||
在你的项目 `SweetDependency` 配置文件中配置存储库。
|
||||
|
||||
```yaml
|
||||
repositories:
|
||||
google:
|
||||
maven-central:
|
||||
# (可选) 你可以添加此 URL 以使用我们的公共存储库
|
||||
# 当 Sonatype-OSS 发生故障无法发布依赖时,此存储库作为备选进行添加
|
||||
# 详情请前往:https://github.com/HighCapable/maven-repository
|
||||
highcapable-maven-releases:
|
||||
# 中国大陆用户请将下方的 "raw.githubusercontent.com" 修改为 "raw.gitmirror.com"
|
||||
url: https://raw.githubusercontent.com/HighCapable/maven-repository/main/repository/releases
|
||||
```
|
||||
|
||||
#### 传统方式
|
||||
|
||||
在你的项目 `build.gradle.kts` 中配置存储库。
|
||||
|
||||
```kotlin
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
// (可选) 你可以添加此 URL 以使用我们的公共存储库
|
||||
// 当 Sonatype-OSS 发生故障无法发布依赖时,此存储库作为备选进行添加
|
||||
// 详情请前往:https://github.com/HighCapable/maven-repository
|
||||
// 中国大陆用户请将下方的 "raw.githubusercontent.com" 修改为 "raw.gitmirror.com"
|
||||
maven("https://raw.githubusercontent.com/HighCapable/maven-repository/main/repository/releases")
|
||||
}
|
||||
```
|
||||
|
||||
### 配置 Java 版本
|
||||
|
||||
在你的项目 `build.gradle.kts` 中修改 Kotlin 的 Java 版本为 17 及以上。
|
||||
|
||||
```kt
|
||||
android {
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 功能一览
|
||||
|
||||
整个项目分为多个模块,你可以选择你希望引入的模块作为依赖应用到你的项目中,但一定要包含 **hikage-core** 模块。
|
||||
|
||||
你可以点击下方对应的模块前往查看详细的功能介绍。
|
||||
|
||||
- [hikage-core](../library/hikage-core.md)
|
||||
- [hikage-compiler](../library/hikage-compiler.md)
|
||||
- [hikage-extension](../library/hikage-extension.md)
|
||||
- [hikage-extension-betterandroid](../library/hikage-extension-betterandroid.md)
|
||||
- [hikage-extension-compose](../library/hikage-extension-compose.md)
|
||||
- [hikage-widget-androidx](../library/hikage-widget-androidx.md)
|
||||
- [hikage-widget-material](../library/hikage-widget-material.md)
|
||||
|
||||
## Demo
|
||||
|
||||
你可以在 [这里](repo://tree/main/samples) 找到一些示例,查看对应的演示项目来更好地了解这些功能的运作方式,快速地挑选出你需要的功能。
|
65
docs-source/src/zh-cn/index.md
Normal file
@@ -0,0 +1,65 @@
|
||||
---
|
||||
home: true
|
||||
title: 首页
|
||||
heroImage: /images/logo.svg
|
||||
actions:
|
||||
- text: 快速上手
|
||||
link: /zh-cn/guide/home
|
||||
type: primary
|
||||
- text: 更新日志
|
||||
link: /zh-cn/about/changelog
|
||||
type: secondary
|
||||
features:
|
||||
- title: 原生可控
|
||||
details: 使用 View 作为基础,Kotlin 作为开发语言,100% 动态代码布局,无需任何额外配置,支持自定义 View。
|
||||
- title: 全面兼容
|
||||
details: 支持 XML、ViewBinding 以及 Jetpack Compose 嵌入混合使用,并对 Material 组件及 Jetpack 提供支持。
|
||||
- title: 快速上手
|
||||
details: 简单易用,不需要繁琐的配置,不需要十足的开发经验,搭建环境集成依赖即可立即开始使用。
|
||||
footer: Apache-2.0 License | Copyright (C) 2019 HighCapable
|
||||
---
|
||||
|
||||
### 布局,就是这么灵活。
|
||||
|
||||
:::: code-group
|
||||
::: code-group-item Hikage (Kotlin DSL)
|
||||
|
||||
```kotlin
|
||||
LinearLayout(
|
||||
lparams = LayoutParams(matchParent = true),
|
||||
init = {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
) {
|
||||
TextView(id = "text_view") {
|
||||
text = "Hello, World!"
|
||||
textSize = 16f
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
::: code-group-item XML
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Hello, World!"
|
||||
android:textSize="16sp"
|
||||
android:gravity="center" />
|
||||
</LinearLayout>
|
||||
```
|
||||
|
||||
:::
|
||||
::::
|
169
docs-source/src/zh-cn/library/hikage-compiler.md
Normal file
@@ -0,0 +1,169 @@
|
||||
# hikage-compiler
|
||||
|
||||

|
||||
<span style="margin-left: 5px"/>
|
||||

|
||||
<span style="margin-left: 5px"/>
|
||||

|
||||
|
||||
这是 Hikage 的自动化编译模块。
|
||||
|
||||
## 配置依赖
|
||||
|
||||
你可以使用如下方式将此模块添加到你的项目中。
|
||||
|
||||
::: warning
|
||||
|
||||
你需要在你的项目中集成适合于你项目当前 Kotlin 版本的 [Google KSP](https://github.com/google/ksp/releases) 插件。
|
||||
|
||||
:::
|
||||
|
||||
### SweetDependency (推荐)
|
||||
|
||||
在你的项目 `SweetDependency` 配置文件中添加依赖。
|
||||
|
||||
```yaml
|
||||
plugins:
|
||||
com.google.devtools.ksp:
|
||||
version: +
|
||||
|
||||
libraries:
|
||||
com.highcapable.hikage:
|
||||
hikage-compiler:
|
||||
version: +
|
||||
```
|
||||
|
||||
在你的根项目 `build.gradle.kts` 中配置依赖。
|
||||
|
||||
```kotlin
|
||||
plugins {
|
||||
// ...
|
||||
autowire(libs.plugins.com.google.devtools.ksp) apply false
|
||||
}
|
||||
```
|
||||
|
||||
在你的项目 `build.gradle.kts` 中配置依赖。
|
||||
|
||||
```kotlin
|
||||
plugins {
|
||||
// ...
|
||||
autowire(libs.plugins.com.google.devtools.ksp)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// ...
|
||||
ksp(com.highcapable.hikage.hikage.compiler)
|
||||
}
|
||||
```
|
||||
|
||||
### 传统方式
|
||||
|
||||
在你的根项目 `build.gradle.kts` 中配置依赖。
|
||||
|
||||
```kotlin
|
||||
plugins {
|
||||
// ...
|
||||
id("com.google.devtools.ksp") version "<ksp-version>" apply false
|
||||
}
|
||||
```
|
||||
|
||||
在你的项目 `build.gradle.kts` 中配置依赖。
|
||||
|
||||
```kotlin
|
||||
plugins {
|
||||
// ...
|
||||
id("com.google.devtools.ksp")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// ...
|
||||
ksp("com.highcapable.hikage:hikage-compiler:<version>")
|
||||
}
|
||||
```
|
||||
|
||||
请将 `<version>` 修改为此文档顶部显示的版本,并将 `<ksp-version>` 修改为你项目当前使用的 Kotlin 版本对应的 KSP 版本。
|
||||
|
||||
## 功能介绍
|
||||
|
||||
Hikage 的编译模块将在运行时自动生成代码,在更新后,请重新运行 `assembleDebug` 或 `assembleRelease` Task 以生成最新的代码。
|
||||
|
||||
### 生成布局组件
|
||||
|
||||
Hikage 可以在编译时为指定的布局组件自动生成布局组件对应的 `Hikageable` 函数。
|
||||
|
||||
#### 自定义 View
|
||||
|
||||
你可以在你的自定义 `View` 上加入 `HikageView` 注解,以标记它生成为 Hikage 布局组件。
|
||||
|
||||
| 参数名称 | 描述 |
|
||||
| ------------------ | --------------------------------------------------------------------------------------------------------------------- |
|
||||
| `lparams` | 布局参数 `ViewGroup.LayoutParams` Class 对象,如果你的自定义 `View` 是 `ViewGroup` 的子类,则可以声明或留空使用默认值 |
|
||||
| `alias` | 布局组件的别名,即要生成的函数名称,默认获取当前 Class 的名称 |
|
||||
| `requireInit` | 是否要求填写布局的初始化方法块,默认为可省略的参数 |
|
||||
| `requirePerformer` | 是否要求填写布局的 `performer` 方法块,默认为可省略的参数,仅在你的自定义 `View` 是 `ViewGroup` 的子类时生效 |
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
@HikageView(lparams = LinearLayout.LayoutParams::class)
|
||||
class MyLayout(context: Context, attrs: AttributeSet? = null) : LinearLayout(context, attrs) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
编译后,你就可以在 Hikage 布局中使用 `MyLayout` 作为布局组件了。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
Hikageable {
|
||||
MyLayout {
|
||||
TextView(
|
||||
lparams = LayoutParams {
|
||||
topMargin = 16.dp
|
||||
}
|
||||
) {
|
||||
text = "Hello, World!"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 第三方组件
|
||||
|
||||
Hikage 同样可以为第三方提供的 `View` 组件自动生成布局组件函数,你可以使用 `HikageViewDeclaration` 注解来完成。
|
||||
|
||||
| 参数名称 | 描述 |
|
||||
| ------------------ | --------------------------------------------------------------------------------------------------------------------- |
|
||||
| `view` | 需要声明的布局组件的 Class 对象 |
|
||||
| `lparams` | 布局参数 `ViewGroup.LayoutParams` Class 对象,如果你的自定义 `View` 是 `ViewGroup` 的子类,则可以声明或留空使用默认值 |
|
||||
| `alias` | 布局组件的别名,即要生成的函数名称,默认获取 `view` Class 的名称 |
|
||||
| `requireInit` | 是否要求填写布局的初始化方法块,默认为可省略的参数 |
|
||||
| `requirePerformer` | 是否要求填写布局的 `performer` 方法块,默认为可省略的参数,仅在你的自定义 `View` 是 `ViewGroup` 的子类时生效 |
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
@HikageViewDeclaration(ThirdPartyView::class)
|
||||
object ThirdPartyViewDeclaration
|
||||
```
|
||||
|
||||
这个注解可以声明到任意一个 `object` 类上,仅作为注解扫描器需要自动纳入的类来使用,你可以将可见性设为 `private`,但要确保被注解的类一定是使用 `object` 修饰的。
|
||||
|
||||
同样地,编译后,你就可以在 Hikage 布局中使用 `ThirdPartyView` 作为布局组件了。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
Hikageable {
|
||||
ThirdPartyView {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
::: tip
|
||||
|
||||
Hikage 生成布局组件的函数包名路径为 `com.highcapable.hikage.widget` + 你的 `View` 或第三方 `View` 组件的完整包名。
|
||||
|
||||
:::
|
502
docs-source/src/zh-cn/library/hikage-core.md
Normal file
@@ -0,0 +1,502 @@
|
||||
# hikage-core
|
||||
|
||||

|
||||
<span style="margin-left: 5px"/>
|
||||

|
||||
<span style="margin-left: 5px"/>
|
||||

|
||||
|
||||
这是 Hikage 的核心依赖,你需要引入此模块才能使用 Hikage 的基本功能。
|
||||
|
||||
## 配置依赖
|
||||
|
||||
你可以使用如下方式将此模块添加到你的项目中。
|
||||
|
||||
### SweetDependency (推荐)
|
||||
|
||||
在你的项目 `SweetDependency` 配置文件中添加依赖。
|
||||
|
||||
```yaml
|
||||
libraries:
|
||||
com.highcapable.hikage:
|
||||
hikage-core:
|
||||
version: +
|
||||
```
|
||||
|
||||
在你的项目 `build.gradle.kts` 中配置依赖。
|
||||
|
||||
```kotlin
|
||||
implementation(com.highcapable.hikage.hikage.core)
|
||||
```
|
||||
|
||||
### 传统方式
|
||||
|
||||
在你的项目 `build.gradle.kts` 中配置依赖。
|
||||
|
||||
```kotlin
|
||||
implementation("com.highcapable.hikage:hikage-core:<version>")
|
||||
```
|
||||
|
||||
请将 `<version>` 修改为此文档顶部显示的版本。
|
||||
|
||||
## 功能介绍
|
||||
|
||||
你可以 [点击这里](kdoc://hikage-core) 查看 KDoc。
|
||||
|
||||
### 基本用法
|
||||
|
||||
使用下方的代码创建你的第一个 Hikage 布局。
|
||||
|
||||
首先,使用 `Hikageable` 创建一个 `Hikage.Delegate` 对象。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
val myLayout = Hikageable {
|
||||
LinearLayout {
|
||||
TextView {
|
||||
text = "Hello, World!"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
然后,将其设置到你想要显示的父布局或根布局上。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
// 假设这就是你的 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.Delegate` 或 `Hikage` 对象,在 DSL 中,你可以得到 `Hikage.Performer` 对象对布局内容进行创建。
|
||||
|
||||
第一种方案,在任意地方创建。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
// myLayout 是 Hikage.Delegate 对象
|
||||
val myLayout = Hikageable {
|
||||
// ...
|
||||
}
|
||||
// 假设这就是你的 Context
|
||||
val context: Context
|
||||
// 在需要 Context 的地方实例化 Hikage 对象
|
||||
val hikage = myLayout.create(context)
|
||||
```
|
||||
|
||||
第二种方案,在存在 `Context` 的地方直接创建。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
// 假设这就是你的 Context
|
||||
val context: Context
|
||||
// 创建布局,myLayout 是 Hikage 对象
|
||||
val myLayout = Hikageable(context) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### HikageBuilder
|
||||
|
||||
除了上述的方式以外,你还可以维护一个 `HikageBuilder` 对象来预创建布局。
|
||||
|
||||
首先,我们需要创建一个 `HikageBuilder` 对象并定义为单例。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
object MyLayout : HikageBuilder {
|
||||
|
||||
override fun build() = Hikageable {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
然后,在需要的地方使用它,可以有如下两种方案。
|
||||
|
||||
第一种方案,直接使用 `build` 创建 `Hikage.Delegate` 对象。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
// myLayout 是 Hikage.Delegate 对象
|
||||
val myLayout = MyLayout.build()
|
||||
// 假设这就是你的 Context
|
||||
val context: Context
|
||||
// 在需要 Context 的地方实例化 Hikage 对象
|
||||
val hikage = myLayout.create(context)
|
||||
```
|
||||
|
||||
第二种方案,使用 `Context.lazyHikage` 创建 `Hikage` 委托对象。
|
||||
|
||||
例如,我们可以在 `Activity` 中像 `ViewBinding` 一样使用它。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
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 一致的函数式创建组件方案,它的布局使用两种基础组件完成,`View` 和 `ViewGroup` 函数,
|
||||
它们分别对应于 Android 原生基于 `View` 和 `ViewGroup` 的组件。
|
||||
|
||||
#### View
|
||||
|
||||
`View` 函数的基础参数为以下三个,使用泛型定义创建的 `View` 对象类型。
|
||||
|
||||
如果不声明泛型类型,默认使用 `android.view.View` 作为创建的对象类型。
|
||||
|
||||
| 参数名称 | 描述 |
|
||||
| --------- | ------------------------------------------------------------------- |
|
||||
| `lparams` | 布局参数,即 `ViewGroup.LayoutParams`,使用 `LayoutParams` 进行创建 |
|
||||
| `id` | 用于查找已创建对象的 ID,使用字符串定义 |
|
||||
| `init` | `View` 的初始化方法体,作为最后一位 DSL 参数传入 |
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
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,使用字符串定义 |
|
||||
| `init` | `ViewGroup` 的初始化方法体,作为 DSL 参数传入 |
|
||||
| `performer` | `Hikage.Performer` 对象,作为最后一位 DSL 参数传入 |
|
||||
|
||||
`performer` 参数的作用是向下传递新的 `Hikage.Performer` 对象,作为子布局的创建者。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
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` 对象或不指定 `width` 和 `height` 时,Hikage 会自动使用 `WRAP_CONTENT` 作为布局参数。
|
||||
|
||||
`body` 方法体的类型来源于上层 [ViewGroup](#viewgroup) 提供的第二位泛型参数。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
View(
|
||||
// 假设上层提供的布局参数类型为 LinearLayout.LayoutParams
|
||||
lparams = LayoutParams(width = 100.dp) {
|
||||
topMargin = 20.dp
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
如果你只需要一个横向填充的布局,可以直接使用 `widthMatchParent = true`。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
View(
|
||||
lparams = LayoutParams(widthMatchParent = true)
|
||||
)
|
||||
```
|
||||
|
||||
#### Layout
|
||||
|
||||
Hikage 支持引用第三方布局,你可以传入 XML 布局资源 ID、其它 Hikage 对象以及 `View` 对象,甚至是 `ViewBinding`。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
ViewGroup<...> {
|
||||
// 引用 XML 布局资源 ID
|
||||
Layout(R.layout.my_layout)
|
||||
// 引用 ViewBinding
|
||||
Layout<MyLayoutBinding>()
|
||||
// 引用另一个 Hikage 或 Hikage.Delegate 对象
|
||||
Layout(myLayout)
|
||||
}
|
||||
```
|
||||
|
||||
### 定位布局组件
|
||||
|
||||
Hikage 支持使用 `id` 定位组件,在上面的示例中,我们使用了 `id` 参数设置了组件的 ID。
|
||||
|
||||
在设置 ID 后,你可以使用 `Hikage.get` 方法获取它们。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
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-androidx](../library/hikage-widget-androidx.md) 或 [hikage-widget-material](../library/hikage-widget-material.md) 模块。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
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
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
初始化后的 `View` 或 `ViewGroup` 对象会返回其自身对象类型的实例,你可以在接下来的布局中使用它们。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
val textView = TextView {
|
||||
text = "Hello, World!"
|
||||
textSize = 16f
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
Button {
|
||||
text = "Click Me!"
|
||||
setOnClickListener {
|
||||
// 直接使用 textView 对象
|
||||
textView.text = "Clicked!"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
如果提供的组件不满足你的需求,你可以手动创建自己的组件。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
// 假设你已经定义好了你的自定义组件
|
||||
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](../library/hikage-compiler.md) 模块。
|
||||
|
||||
### 自定义布局装载器
|
||||
|
||||
Hikage 支持自定义布局装载器并同时兼容 `LayoutInflater.Factory2`,你可以通过以下方式自定义在 Hikage 布局装载过程中的事件和监听。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
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`。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
// 假设这就是你的 LayoutInflater 对象
|
||||
val layoutInflater: LayoutInflater
|
||||
// 通过 LayoutInflater 创建 HikageFactory 对象
|
||||
val factory = HikageFactory(layoutInflater)
|
||||
```
|
||||
|
||||
然后使用以下方式将其设置到你需要装载的 Hikage 布局上。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
// 假设这就是你的 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!"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
::: tip
|
||||
|
||||
Hikage 在默认装载时将会根据传入 `Context` 对象的 `LayoutInflater.Factory2` 对布局进行装载,如果你正在使用 `AppCompatActivity`,
|
||||
布局中的组件将会自动被替换为对应的 Compat 组件或 Material 组件,与 XML 布局的特性保持一致。
|
||||
|
||||
如果你不需要默认生效此特性,可以使用以下方式全局关闭。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
Hikage.isAutoProcessWithFactory2 = false
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### 预览布局
|
||||
|
||||
Hikage 支持在 Android Studio 中预览布局,借助于 Android Studio 自带的自定义 `View` 预览插件,你可以使用以下方式预览布局。
|
||||
|
||||
你只需要定义一个预览布局的自定义 `View` 并继承于 `HikagePreview`。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
class MyLayoutPreview(context: Context, attrs: AttributeSet?) : HikagePreview(context, attrs) {
|
||||
|
||||
override fun build() = Hikageable {
|
||||
LinearLayout {
|
||||
TextView {
|
||||
text = "Hello, World!"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
然后在你当前的窗口右侧应该会出现预览窗格,打开后点击 “Build & Refresh”,等待编译完成后将会自动显示预览。
|
||||
|
||||
::: tip
|
||||
|
||||
`HikagePreview` 实现了 `HikageBuilder` 接口,你可以在 `build` 方法中返回任意的 Hikage 布局以进行预览。
|
||||
|
||||
:::
|
||||
|
||||
::: danger
|
||||
|
||||
`HikagePreview` 仅支持在 Android Studio 中预览布局,请勿在运行时使用它或将其添加到任何 XML 布局中。
|
||||
|
||||
:::
|
@@ -0,0 +1,74 @@
|
||||
# hikage-extension-betterandroid
|
||||
|
||||

|
||||
<span style="margin-left: 5px"/>
|
||||

|
||||
<span style="margin-left: 5px"/>
|
||||

|
||||
|
||||
这是 Hikage 针对 [BetterAndroid](https://github.com/BetterAndroid/BetterAndroid) UI 组件相关功能的扩展依赖。
|
||||
|
||||
## 配置依赖
|
||||
|
||||
你可以使用如下方式将此模块添加到你的项目中。
|
||||
|
||||
### SweetDependency (推荐)
|
||||
|
||||
在你的项目 `SweetDependency` 配置文件中添加依赖。
|
||||
|
||||
```yaml
|
||||
libraries:
|
||||
com.highcapable.hikage:
|
||||
hikage-extension-betterandroid:
|
||||
version: +
|
||||
```
|
||||
|
||||
在你的项目 `build.gradle.kts` 中配置依赖。
|
||||
|
||||
```kotlin
|
||||
implementation(com.highcapable.hikage.hikage.extension.betterandroid)
|
||||
```
|
||||
|
||||
### 传统方式
|
||||
|
||||
在你的项目 `build.gradle.kts` 中配置依赖。
|
||||
|
||||
```kotlin
|
||||
implementation("com.highcapable.hikage:hikage-extension-betterandroid:<version>")
|
||||
```
|
||||
|
||||
请将 `<version>` 修改为此文档顶部显示的版本。
|
||||
|
||||
## 功能介绍
|
||||
|
||||
你可以 [点击这里](kdoc://hikage-extension-betterandroid) 查看 KDoc。
|
||||
|
||||
### 适配器 (Adapter) 扩展
|
||||
|
||||
Hikage 为 BetterAndroid 提供的 [适配器](https://betterandroid.github.io/BetterAndroid/zh-cn/library/ui-component#%E9%80%82%E9%85%8D%E5%99%A8-adapter)
|
||||
提供了布局扩展功能,你可以直接在适配器的原始扩展方法上使用 Hikage 布局。
|
||||
|
||||
它使用了 BetterAndroid 提供的 `ViewHolderDelegate` 来创建扩展方法。
|
||||
|
||||
下面提供了一个基于 `RecyclerView` 的简单示例。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
// 假设这就是你需要绑定的数据集
|
||||
val listData = ArrayList<CustomBean>()
|
||||
// 创建并绑定到自定义的 RecyclerView.Adapter
|
||||
val adapter = recyclerView.bindAdapter<CustomBean> {
|
||||
onBindData { listData }
|
||||
onBindItemView(
|
||||
Hikageable = {
|
||||
TextView(id = "text_view") {
|
||||
text = "Hello, World!"
|
||||
textSize = 16f
|
||||
}
|
||||
}
|
||||
) { hikage, bean, position ->
|
||||
hikage.get<TextView>("text_view").text = bean.name
|
||||
}
|
||||
}
|
||||
```
|
87
docs-source/src/zh-cn/library/hikage-extension-compose.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# hikage-extension-compose
|
||||
|
||||

|
||||
<span style="margin-left: 5px"/>
|
||||

|
||||
<span style="margin-left: 5px"/>
|
||||

|
||||
|
||||
这是 Hikage 针对 Jetpack Compose 组件相关功能的扩展依赖。
|
||||
|
||||
## 配置依赖
|
||||
|
||||
你可以使用如下方式将此模块添加到你的项目中。
|
||||
|
||||
::: warning
|
||||
|
||||
此模块依赖于 Jetpack Compose 编译插件,请确保你的项目中已经集成了 Jetpack Compose 相关依赖,详情请参考 [这里](https://developer.android.com/develop/ui/compose/compiler)。
|
||||
|
||||
:::
|
||||
|
||||
### SweetDependency (推荐)
|
||||
|
||||
在你的项目 `SweetDependency` 配置文件中添加依赖。
|
||||
|
||||
```yaml
|
||||
libraries:
|
||||
com.highcapable.hikage:
|
||||
hikage-extension-compose:
|
||||
version: +
|
||||
```
|
||||
|
||||
在你的项目 `build.gradle.kts` 中配置依赖。
|
||||
|
||||
```kotlin
|
||||
implementation(com.highcapable.hikage.hikage.extension.compose)
|
||||
```
|
||||
|
||||
### 传统方式
|
||||
|
||||
在你的项目 `build.gradle.kts` 中配置依赖。
|
||||
|
||||
```kotlin
|
||||
implementation("com.highcapable.hikage:hikage-extension-compose:<version>")
|
||||
```
|
||||
|
||||
请将 `<version>` 修改为此文档顶部显示的版本。
|
||||
|
||||
## 功能介绍
|
||||
|
||||
你可以 [点击这里](kdoc://hikage-extension-compose) 查看 KDoc。
|
||||
|
||||
### 在 Hikage 中使用 Jetpack Compose
|
||||
|
||||
你可以使用以下方式在一个 Hikage 布局中嵌入 Jetpack Compose 组件。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
Hikageable {
|
||||
ComposeView(
|
||||
lparams = LayoutParams(matchParent = true)
|
||||
) {
|
||||
Text("Hello, World!")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 在 Jetpack Compose 中使用 Hikage
|
||||
|
||||
你可以使用以下方式在一个 Jetpack Compose 布局中嵌入 Hikage 组件。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
HikageView {
|
||||
TextView(
|
||||
lparams = LayoutParams(matchParent = true)
|
||||
) {
|
||||
text = "Hello, World!"
|
||||
textSize = 20f
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
208
docs-source/src/zh-cn/library/hikage-extension.md
Normal file
@@ -0,0 +1,208 @@
|
||||
# hikage-extension
|
||||
|
||||

|
||||
<span style="margin-left: 5px"/>
|
||||

|
||||
<span style="margin-left: 5px"/>
|
||||

|
||||
|
||||
这是 Hikage 针对 UI 组件相关功能的扩展依赖。
|
||||
|
||||
## 配置依赖
|
||||
|
||||
你可以使用如下方式将此模块添加到你的项目中。
|
||||
|
||||
### SweetDependency (推荐)
|
||||
|
||||
在你的项目 `SweetDependency` 配置文件中添加依赖。
|
||||
|
||||
```yaml
|
||||
libraries:
|
||||
com.highcapable.hikage:
|
||||
hikage-extension:
|
||||
version: +
|
||||
```
|
||||
|
||||
在你的项目 `build.gradle.kts` 中配置依赖。
|
||||
|
||||
```kotlin
|
||||
implementation(com.highcapable.hikage.hikage.extension)
|
||||
```
|
||||
|
||||
### 传统方式
|
||||
|
||||
在你的项目 `build.gradle.kts` 中配置依赖。
|
||||
|
||||
```kotlin
|
||||
implementation("com.highcapable.hikage:hikage-extension:<version>")
|
||||
```
|
||||
|
||||
请将 `<version>` 修改为此文档顶部显示的版本。
|
||||
|
||||
## 功能介绍
|
||||
|
||||
你可以 [点击这里](kdoc://hikage-extension) 查看 KDoc。
|
||||
|
||||
### Activity
|
||||
|
||||
Hikage 为 `Activity` 提供了更好用的扩展,在 `Activity` 中创建 Hikage 将会变得更加简单。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView {
|
||||
LinearLayout(
|
||||
lparams = LayoutParams(matchParent = true) {
|
||||
topMargin = 16.dp
|
||||
},
|
||||
init = {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
) {
|
||||
TextView {
|
||||
text = "Hello, World!"
|
||||
textSize = 16f
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
借助 `Hikage` 的 `setContentView` 扩展方法,你可以像 Jetpack Compose 一样使用 `setContent` 方法来设置布局。
|
||||
|
||||
### Window
|
||||
|
||||
在 `Window` 中使用 Hikage 创建布局与 [Activity](#activity) 保持一致,你只需要使用 `setContentView` 方法传入一个 `Hikage` 布局即可。
|
||||
|
||||
### Dialog
|
||||
|
||||
如果你想直接在 `AlertDialog` 中使用 Hikage 创建布局,现在你可以使用以下方案更加简单地进行。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
// 假设这就是你的 Context
|
||||
val context: Context
|
||||
// 创建对话框并显示
|
||||
AlertDialog.Builder(context)
|
||||
.setTitle("Hello, World!")
|
||||
.setView {
|
||||
TextView {
|
||||
text = "Hello, World!"
|
||||
textSize = 16f
|
||||
}
|
||||
}
|
||||
.show()
|
||||
```
|
||||
|
||||
在 `AlertDialog` 中使用 Hikage 创建布局,你只需要使用 `setView` 方法传入一个 `Hikage` 布局即可。
|
||||
|
||||
如果你是继承于 `Dialog` 进行自定义,那么你可以和像在 [Activity](#activity) 一样使用 `setContentView` 方法。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
class CustomDialog(context: Context) : Dialog(context) {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView {
|
||||
LinearLayout(
|
||||
lparams = LayoutParams(matchParent = true) {
|
||||
topMargin = 16.dp
|
||||
},
|
||||
init = {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
) {
|
||||
TextView {
|
||||
text = "Hello, World!"
|
||||
textSize = 16f
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### PopupWindow
|
||||
|
||||
你可以继承于 `PopupWindow` 进行自定义,然后使用 Hikage 创建布局,你可以和像在 [Activity](#activity) 一样使用 `setContentView` 方法。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
class CustomPopupWindow(context: Context) : PopupWindow(context) {
|
||||
|
||||
init {
|
||||
setContentView(context) {
|
||||
LinearLayout(
|
||||
lparams = LayoutParams(matchParent = true) {
|
||||
topMargin = 16.dp
|
||||
},
|
||||
init = {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
) {
|
||||
TextView {
|
||||
text = "Hello, World!"
|
||||
textSize = 16f
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
::: danger
|
||||
|
||||
创建 Hikage 布局的 `PopupWindow` 需要使用 `Context` 构造方法进行初始化,如果当前无法立即获取到 `Context`,请对 `setContentView` 方法传入 `Context` 实例。
|
||||
|
||||
:::
|
||||
|
||||
### ViewGroup
|
||||
|
||||
Hikage 对 `ViewGroup` 的 `addView` 方法进行了扩展,你可以直接使用 Hikage 布局来为当前 `ViewGroup` 快速添加新的布局。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
// 假设这就是你的 ViewGroup
|
||||
val root: FrameLayout
|
||||
// 添加 Hikage 布局
|
||||
root.addView {
|
||||
TextView {
|
||||
text = "Hello, World!"
|
||||
textSize = 16f
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
或者,在自定义 `View` 中使用。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
class CustomView(context: Context, attrs: AttributeSet? = null) : FrameLayout(context, attrs) {
|
||||
|
||||
init {
|
||||
addView {
|
||||
TextView {
|
||||
text = "Hello, World!"
|
||||
textSize = 16f
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
64
docs-source/src/zh-cn/library/hikage-widget-androidx.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# hikage-widget-androidx
|
||||
|
||||

|
||||
<span style="margin-left: 5px"/>
|
||||

|
||||
<span style="margin-left: 5px"/>
|
||||

|
||||
|
||||
这是 Hikage 针对 Jetpack Compact 组件相关功能的扩展依赖。
|
||||
|
||||
## 配置依赖
|
||||
|
||||
你可以使用如下方式将此模块添加到你的项目中。
|
||||
|
||||
### SweetDependency (推荐)
|
||||
|
||||
在你的项目 `SweetDependency` 配置文件中添加依赖。
|
||||
|
||||
```yaml
|
||||
libraries:
|
||||
com.highcapable.hikage:
|
||||
hikage-widget-androidx:
|
||||
version: +
|
||||
```
|
||||
|
||||
在你的项目 `build.gradle.kts` 中配置依赖。
|
||||
|
||||
```kotlin
|
||||
implementation(com.highcapable.hikage.hikage.widget.androidx)
|
||||
```
|
||||
|
||||
### 传统方式
|
||||
|
||||
在你的项目 `build.gradle.kts` 中配置依赖。
|
||||
|
||||
```kotlin
|
||||
implementation("com.highcapable.hikage:hikage-widget-androidx:<version>")
|
||||
```
|
||||
|
||||
请将 `<version>` 修改为此文档顶部显示的版本。
|
||||
|
||||
## 功能介绍
|
||||
|
||||
这个依赖中继承了来自 Jetpack Compact 中的可用组件,你可以直接引用它们到 Hikage 中使用。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
LinearLayoutCompact(
|
||||
lparams = LayoutParams(matchParent = true) {
|
||||
topMargin = 16.dp
|
||||
},
|
||||
init = {
|
||||
orientation = LinearLayoutCompat.VERTICAL
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
) {
|
||||
AppCompatTextView {
|
||||
text = "Hello, World!"
|
||||
textSize = 16f
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
}
|
||||
```
|
80
docs-source/src/zh-cn/library/hikage-widget-material.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# hikage-widget-material
|
||||
|
||||

|
||||
<span style="margin-left: 5px"/>
|
||||

|
||||
<span style="margin-left: 5px"/>
|
||||

|
||||
|
||||
这是 Hikage 针对 Google Material (MDC) 组件相关功能的扩展依赖。
|
||||
|
||||
## 配置依赖
|
||||
|
||||
你可以使用如下方式将此模块添加到你的项目中。
|
||||
|
||||
### SweetDependency (推荐)
|
||||
|
||||
在你的项目 `SweetDependency` 配置文件中添加依赖。
|
||||
|
||||
```yaml
|
||||
libraries:
|
||||
com.highcapable.hikage:
|
||||
hikage-widget-material:
|
||||
version: +
|
||||
```
|
||||
|
||||
在你的项目 `build.gradle.kts` 中配置依赖。
|
||||
|
||||
```kotlin
|
||||
implementation(com.highcapable.hikage.hikage.widget.material)
|
||||
```
|
||||
|
||||
### 传统方式
|
||||
|
||||
在你的项目 `build.gradle.kts` 中配置依赖。
|
||||
|
||||
```kotlin
|
||||
implementation("com.highcapable.hikage:hikage-widget-material:<version>")
|
||||
```
|
||||
|
||||
请将 `<version>` 修改为此文档顶部显示的版本。
|
||||
|
||||
## 功能介绍
|
||||
|
||||
这个依赖中继承了来自 Google Material (MDC) 中的可用组件,你可以直接引用它们到 Hikage 中使用。
|
||||
|
||||
> 示例如下
|
||||
|
||||
```kotlin
|
||||
LinearLayout(
|
||||
lparams = LayoutParams(matchParent = true) {
|
||||
topMargin = 16.dp
|
||||
},
|
||||
init = {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
) {
|
||||
MaterialTextView {
|
||||
text = "Hello, World!"
|
||||
textSize = 16f
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
MaterialButton {
|
||||
text = "Hello, World!"
|
||||
textSize = 16f
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
TextInputLayout(
|
||||
lparams = LayoutParams {
|
||||
topMargin = 16.dp
|
||||
},
|
||||
init = {
|
||||
minWidth = 200.dp
|
||||
hint = "Enter your text"
|
||||
}
|
||||
) {
|
||||
TextInputEditText()
|
||||
}
|
||||
}
|
||||
```
|
2004
docs-source/yarn.lock
Normal file
@@ -6,9 +6,46 @@ kotlin.code.style=official
|
||||
kotlin.incremental.useClasspathSnapshot=true
|
||||
# Project Configuration
|
||||
project.name=Hikage
|
||||
project.url=https://github.com/BetterAndroid/Hikage
|
||||
project.groupName=com.highcapable.hikage
|
||||
project.android.compileSdk=35
|
||||
project.android.minSdk=21
|
||||
project.android.targetSdk=35
|
||||
project.app.packageName=com.highcapable.hikage.demo
|
||||
project.app.versionName=1.0.0
|
||||
project.app.versionCode=1
|
||||
project.samples-app.packageName=com.highcapable.hikage.demo
|
||||
project.samples-app.versionName=universal
|
||||
project.samples-app.versionCode=1
|
||||
project.hikage-core.namespace=${project.groupName}.core
|
||||
project.hikage-core.version="1.0.0"
|
||||
project.hikage-core-lint.namespace=${project.groupName}.core.lint
|
||||
project.hikage-core-lint.identifier=${project.groupName}:hikage-core:${project.hikage-core.version}
|
||||
project.hikage-core-lint.min-api=9
|
||||
project.hikage-core-lint.registry-v2-class="${project.hikage-core-lint.namespace}.HikageIssueRegistry"
|
||||
project.hikage-extension.namespace=${project.groupName}.extension
|
||||
project.hikage-extension.version="1.0.0"
|
||||
project.hikage-extension-betterandroid.namespace=${project.groupName}.extension.betterandroid
|
||||
project.hikage-extension-betterandroid.version="1.0.0"
|
||||
project.hikage-extension-compose.namespace=${project.groupName}.extension.androidx.compose
|
||||
project.hikage-extension-compose.version="1.0.0"
|
||||
project.hikage-compiler.namespace="${project.groupName}.compiler"
|
||||
project.hikage-compiler.version="1.0.0"
|
||||
project.hikage-widget-androidx.namespace=${project.groupName}.widget.androidx
|
||||
project.hikage-widget-androidx.version="1.0.0"
|
||||
project.hikage-widget-material.namespace=${project.groupName}.widget.google.material
|
||||
project.hikage-widget-material.version="1.0.0"
|
||||
# Maven Publish Configuration
|
||||
SONATYPE_HOST=CENTRAL_PORTAL
|
||||
RELEASE_SIGNING_ENABLED=true
|
||||
# Maven POM Configuration
|
||||
POM_NAME=Hikage
|
||||
POM_DESCRIPTION=An Android responsive UI building tool.
|
||||
POM_URL=https://github.com/BetterAndroid/Hikage
|
||||
POM_LICENSE_NAME=Apache License 2.0
|
||||
POM_LICENSE_URL=https://github.com/BetterAndroid/Hikage/blob/main/LICENSE
|
||||
POM_LICENSE_DIST=repo
|
||||
POM_SCM_URL=https://github.com/BetterAndroid/Hikage
|
||||
POM_SCM_CONNECTION=scm:git:git://github.com/BetterAndroid/Hikage.git
|
||||
POM_SCM_DEV_CONNECTION=scm:git:ssh://github.com/BetterAndroid/Hikage.git
|
||||
POM_DEVELOPER_ID=0
|
||||
POM_DEVELOPER_NAME=fankes
|
||||
POM_DEVELOPER_EMAIL=qzmmcn@163.com
|
||||
POM_DEVELOPER_URL=https://github.com/fankes
|
@@ -7,26 +7,72 @@ repositories:
|
||||
scope: PLUGINS
|
||||
google:
|
||||
maven-central:
|
||||
highcapable-maven-releases:
|
||||
url: https://raw.githubusercontent.com/HighCapable/maven-repository/main/repository/releases
|
||||
|
||||
plugins:
|
||||
com.android.application:
|
||||
alias: android-application
|
||||
version: 8.5.2
|
||||
com.android.library:
|
||||
alias: android-library
|
||||
version-ref: com.android.application
|
||||
org.jetbrains.kotlin.jvm:
|
||||
alias: kotlin-jvm
|
||||
version: 2.0.10
|
||||
auto-update: false
|
||||
org.jetbrains.kotlin.android:
|
||||
alias: kotlin-android
|
||||
version: 2.0.0
|
||||
version-ref: kotlin-jvm
|
||||
com.google.devtools.ksp:
|
||||
alias: kotlin-ksp
|
||||
version: 2.0.10-1.0.24
|
||||
auto-update: false
|
||||
com.android.application:
|
||||
alias: android-application
|
||||
version: 8.9.0
|
||||
com.android.library:
|
||||
alias: android-library
|
||||
version-ref: android-application
|
||||
org.jetbrains.kotlin.plugin.compose:
|
||||
alias: compose-compiler
|
||||
version-ref: kotlin-jvm
|
||||
org.jetbrains.dokka:
|
||||
alias: kotlin-dokka
|
||||
version: 1.9.20
|
||||
auto-update: false
|
||||
com.vanniktech.maven.publish:
|
||||
alias: maven-publish
|
||||
version: 0.31.0
|
||||
|
||||
libraries:
|
||||
org.jetbrains.kotlin:
|
||||
kotlin-stdlib:
|
||||
version-ref: <plugins>::kotlin-jvm
|
||||
com.google.devtools.ksp:
|
||||
symbol-processing-api:
|
||||
version-ref: <plugins>::kotlin-ksp
|
||||
com.google.auto.service:
|
||||
auto-service-annotations:
|
||||
version: 1.1.1
|
||||
dev.zacsweers.autoservice:
|
||||
auto-service-ksp:
|
||||
version: 1.2.0
|
||||
com.squareup:
|
||||
kotlinpoet:
|
||||
version: 2.1.0
|
||||
kotlinpoet-ksp:
|
||||
version-ref: <this>::kotlinpoet
|
||||
com.highcapable.betterandroid:
|
||||
ui-component:
|
||||
version: 1.0.6
|
||||
version: 1.0.7
|
||||
ui-extension:
|
||||
version: 1.0.5
|
||||
version: 1.0.6
|
||||
system-extension:
|
||||
version: 1.0.2
|
||||
org.lsposed.hiddenapibypass:
|
||||
hiddenapibypass:
|
||||
version: 6.1
|
||||
com.highcapable.yukireflection:
|
||||
api:
|
||||
version: 1.0.3
|
||||
com.highcapable.pangutext:
|
||||
pangutext-android:
|
||||
version: 1.0.2
|
||||
androidx.core:
|
||||
core:
|
||||
version: 1.15.0
|
||||
@@ -37,10 +83,39 @@ libraries:
|
||||
version: 1.7.0
|
||||
com.google.android.material:
|
||||
material:
|
||||
version: 1.12.0
|
||||
auto-update: false
|
||||
# Workaround for a bug in version 1.12.0
|
||||
version: 1.11.0
|
||||
androidx.constraintlayout:
|
||||
constraintlayout:
|
||||
version: 2.2.0
|
||||
version: 2.2.1
|
||||
androidx.coordinatorlayout:
|
||||
coordinatorlayout:
|
||||
version: 1.3.0
|
||||
androidx.swiperefreshlayout:
|
||||
swiperefreshlayout:
|
||||
version: 1.1.0
|
||||
androidx.slidingpanelayout:
|
||||
slidingpanelayout:
|
||||
version: 1.2.0
|
||||
androidx.drawerlayout:
|
||||
drawerlayout:
|
||||
version: 1.2.0
|
||||
androidx.cardview:
|
||||
cardview:
|
||||
version: 1.0.0
|
||||
androidx.viewpager:
|
||||
viewpager:
|
||||
version: 1.1.0
|
||||
androidx.viewpager2:
|
||||
viewpager2:
|
||||
version: 1.1.0
|
||||
androidx.recyclerview:
|
||||
recyclerview:
|
||||
version: 1.4.0
|
||||
androidx.compose.ui:
|
||||
ui:
|
||||
version: 1.7.8
|
||||
junit:
|
||||
junit:
|
||||
version: 4.13.2
|
||||
@@ -49,4 +124,13 @@ libraries:
|
||||
version: 1.2.1
|
||||
androidx.test.espresso:
|
||||
espresso-core:
|
||||
version: 3.6.1
|
||||
version: 3.6.1
|
||||
com.android.tools.lint:
|
||||
lint:
|
||||
version: 31.9.0
|
||||
lint-api:
|
||||
version-ref: <this>::lint
|
||||
lint-checks:
|
||||
version-ref: <this>::lint
|
||||
lint-tests:
|
||||
version-ref: <this>::lint
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
6
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
296
gradlew
vendored
Normal file → Executable file
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env sh
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -15,69 +15,103 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
@@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
@@ -98,88 +132,120 @@ Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
37
gradlew.bat
vendored
@@ -13,8 +13,10 @@
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@@ -25,7 +27,8 @@
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@@ -40,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
@@ -56,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
@@ -75,13 +78,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
32
hikage-compiler/build.gradle.kts
Normal file
@@ -0,0 +1,32 @@
|
||||
plugins {
|
||||
autowire(libs.plugins.kotlin.jvm)
|
||||
autowire(libs.plugins.kotlin.ksp)
|
||||
autowire(libs.plugins.maven.publish)
|
||||
}
|
||||
|
||||
group = property.project.groupName
|
||||
version = property.project.hikage.compiler.version
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(17)
|
||||
compilerOptions {
|
||||
freeCompilerArgs = listOf(
|
||||
"-Xno-param-assertions",
|
||||
"-Xno-call-assertions",
|
||||
"-Xno-receiver-assertions"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(com.google.devtools.ksp.symbol.processing.api)
|
||||
ksp(dev.zacsweers.autoservice.auto.service.ksp)
|
||||
implementation(com.google.auto.service.auto.service.annotations)
|
||||
implementation(com.squareup.kotlinpoet)
|
||||
implementation(com.squareup.kotlinpoet.ksp)
|
||||
}
|
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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/3/23.
|
||||
*/
|
||||
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||
|
||||
package com.highcapable.hikage.compiler
|
||||
|
||||
object DeclaredSymbol {
|
||||
|
||||
const val ANNOTATION_PACKAGE_NAME = "com.highcapable.hikage.annotation"
|
||||
|
||||
const val HIKAGEABLE_ANNOTATION_CLASS_NAME = "Hikageable"
|
||||
const val HIKAGEABLE_ANNOTATION_CLASS = "$ANNOTATION_PACKAGE_NAME.$HIKAGEABLE_ANNOTATION_CLASS_NAME"
|
||||
const val HIKAGE_VIEW_ANNOTATION_CLASS_NAME = "HikageView"
|
||||
const val HIKAGE_VIEW_ANNOTATION_CLASS = "$ANNOTATION_PACKAGE_NAME.$HIKAGE_VIEW_ANNOTATION_CLASS_NAME"
|
||||
const val HIKAGE_HIKAGE_VIEW_DECLARATION_ANNOTATION_CLASS_NAME = "HikageViewDeclaration"
|
||||
const val HIKAGE_HIKAGE_VIEW_DECLARATION_ANNOTATION_CLASS = "$ANNOTATION_PACKAGE_NAME.$HIKAGE_HIKAGE_VIEW_DECLARATION_ANNOTATION_CLASS_NAME"
|
||||
|
||||
const val ANDROID_VIEW_PACKAGE_NAME = "android.view"
|
||||
|
||||
const val ANDROID_VIEW_CLASS = "$ANDROID_VIEW_PACKAGE_NAME.View"
|
||||
const val ANDROID_VIEW_GROUP_CLASS_NAME = "ViewGroup"
|
||||
const val ANDROID_VIEW_GROUP_CLASS = "$ANDROID_VIEW_PACKAGE_NAME.$ANDROID_VIEW_GROUP_CLASS_NAME"
|
||||
|
||||
const val ANDROID_LAYOUT_PARAMS_CLASS_NAME = "ViewGroup.LayoutParams"
|
||||
const val ANDROID_LAYOUT_PARAMS_CLASS = "$ANDROID_VIEW_PACKAGE_NAME.$ANDROID_LAYOUT_PARAMS_CLASS_NAME"
|
||||
|
||||
const val HIKAGE_CORE_PACKAGE_NAME = "com.highcapable.hikage.core"
|
||||
|
||||
const val HIKAGE_CLASS_NAME = "Hikage"
|
||||
const val HIKAGE_CLASS = "$HIKAGE_CORE_PACKAGE_NAME.$HIKAGE_CLASS_NAME"
|
||||
|
||||
const val HIKAGE_PERFORMER_CLASS_NAME = "Hikage.Performer"
|
||||
const val HIKAGE_PERFORMER_CLASS = "$HIKAGE_CORE_PACKAGE_NAME.$HIKAGE_PERFORMER_CLASS_NAME"
|
||||
|
||||
const val HIKAGE_LAYOUT_PARAMS_CLASS_NAME = "Hikage.LayoutParams"
|
||||
const val HIKAGE_LAYOUT_PARAMS_CLASS = "$HIKAGE_CORE_PACKAGE_NAME.$HIKAGE_LAYOUT_PARAMS_CLASS_NAME"
|
||||
|
||||
const val HIKAGE_BASE_PACKAGE_NAME = "com.highcapable.hikage.core.base"
|
||||
|
||||
const val HIKAGE_VIEW_LAMBDA_CLASS_NAME = "HikageView"
|
||||
const val HIKAGE_VIEW_LAMBDA_CLASS = "$HIKAGE_BASE_PACKAGE_NAME.$HIKAGE_VIEW_LAMBDA_CLASS_NAME"
|
||||
|
||||
const val HIKAGE_PERFORMER_LAMBDA_CLASS_NAME = "HikagePerformer"
|
||||
const val HIKAGE_PERFORMER_LAMBDA_CLASS = "$HIKAGE_BASE_PACKAGE_NAME.$HIKAGE_PERFORMER_LAMBDA_CLASS_NAME"
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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/3/23.
|
||||
*/
|
||||
@file:Suppress("unused")
|
||||
|
||||
package com.highcapable.hikage.compiler
|
||||
|
||||
import com.google.auto.service.AutoService
|
||||
import com.google.devtools.ksp.processing.Resolver
|
||||
import com.google.devtools.ksp.processing.SymbolProcessor
|
||||
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
|
||||
import com.google.devtools.ksp.processing.SymbolProcessorProvider
|
||||
import com.google.devtools.ksp.symbol.KSAnnotated
|
||||
import com.highcapable.hikage.compiler.subprocessor.HikageViewGenerator
|
||||
|
||||
@AutoService(SymbolProcessorProvider::class)
|
||||
class HikageProcessor : SymbolProcessorProvider {
|
||||
|
||||
override fun create(environment: SymbolProcessorEnvironment) = object : SymbolProcessor {
|
||||
|
||||
private val subProcessor = listOf(
|
||||
HikageViewGenerator(environment)
|
||||
)
|
||||
|
||||
override fun process(resolver: Resolver) = emptyList<KSAnnotated>().let { startProcess(resolver); it }
|
||||
|
||||
private fun startProcess(resolver: Resolver) {
|
||||
subProcessor.forEach {
|
||||
it.startProcess(resolver)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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/3/30.
|
||||
*/
|
||||
package com.highcapable.hikage.compiler.extension
|
||||
|
||||
import com.google.devtools.ksp.processing.Resolver
|
||||
import com.google.devtools.ksp.symbol.KSClassDeclaration
|
||||
import com.google.devtools.ksp.symbol.KSDeclaration
|
||||
import com.google.devtools.ksp.symbol.KSType
|
||||
import com.google.devtools.ksp.symbol.KSValueArgument
|
||||
import com.squareup.kotlinpoet.ClassName
|
||||
|
||||
fun KSDeclaration.getClassDeclaration(resolver: Resolver) =
|
||||
if (this is KSClassDeclaration) this else qualifiedName?.let { resolver.getClassDeclarationByName(it) }
|
||||
|
||||
fun KSDeclaration.getSimpleNameString(): String {
|
||||
val packageName = packageName.asString()
|
||||
return qualifiedName?.asString()?.replace("$packageName.", "") ?: simpleName.asString()
|
||||
}
|
||||
|
||||
fun KSClassDeclaration.isSubclassOf(superType: KSType?): Boolean {
|
||||
if (superType == null) return false
|
||||
if (this == superType.declaration) return true
|
||||
superTypes.forEach { parent ->
|
||||
val resolvedParent = parent.resolve()
|
||||
// Direct match.
|
||||
if (resolvedParent == superType) return true
|
||||
val parentDeclaration = resolvedParent.declaration as? KSClassDeclaration
|
||||
?: return@forEach
|
||||
// Recursively check the parent class.
|
||||
if (parentDeclaration.isSubclassOf(superType)) return true
|
||||
}; return false
|
||||
}
|
||||
|
||||
fun KSClassDeclaration.asType() = asType(emptyList())
|
||||
|
||||
inline fun <reified T> List<KSValueArgument>.getOrNull(name: String) =
|
||||
firstOrNull { it.name?.asString() == name }?.value as? T?
|
||||
|
||||
fun ClassName.getTypedSimpleName() = simpleName.replace(".", "_")
|
||||
|
||||
object ClassDetector {
|
||||
|
||||
private val javaKeywords = setOf(
|
||||
"abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class",
|
||||
"const", "continue", "default", "do", "double", "else", "enum", "extends", "final",
|
||||
"finally", "float", "for", "goto", "if", "implements", "import", "instanceof", "int",
|
||||
"interface", "long", "native", "new", "package", "private", "protected", "public",
|
||||
"return", "short", "static", "strictfp", "super", "switch", "synchronized", "this",
|
||||
"throw", "throws", "transient", "try", "void", "volatile", "while"
|
||||
)
|
||||
|
||||
private val invalidPattern = "^(\\d.*|.*[^A-Za-z0-9_\$].*)$".toRegex()
|
||||
|
||||
fun verify(name: String) = name !in javaKeywords && !invalidPattern.matches(name)
|
||||
}
|
@@ -0,0 +1,393 @@
|
||||
/*
|
||||
* 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/3/23.
|
||||
*/
|
||||
@file:Suppress("LocalVariableName")
|
||||
|
||||
package com.highcapable.hikage.compiler.subprocessor
|
||||
|
||||
import com.google.devtools.ksp.getClassDeclarationByName
|
||||
import com.google.devtools.ksp.processing.Dependencies
|
||||
import com.google.devtools.ksp.processing.KSPLogger
|
||||
import com.google.devtools.ksp.processing.Resolver
|
||||
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
|
||||
import com.google.devtools.ksp.symbol.ClassKind
|
||||
import com.google.devtools.ksp.symbol.KSAnnotation
|
||||
import com.google.devtools.ksp.symbol.KSClassDeclaration
|
||||
import com.google.devtools.ksp.symbol.KSType
|
||||
import com.highcapable.hikage.compiler.DeclaredSymbol
|
||||
import com.highcapable.hikage.compiler.extension.ClassDetector
|
||||
import com.highcapable.hikage.compiler.extension.asType
|
||||
import com.highcapable.hikage.compiler.extension.getClassDeclaration
|
||||
import com.highcapable.hikage.compiler.extension.getOrNull
|
||||
import com.highcapable.hikage.compiler.extension.getSimpleNameString
|
||||
import com.highcapable.hikage.compiler.extension.getTypedSimpleName
|
||||
import com.highcapable.hikage.compiler.extension.isSubclassOf
|
||||
import com.highcapable.hikage.compiler.subprocessor.base.BaseSymbolProcessor
|
||||
import com.highcapable.hikage.generated.HikageCompilerProperties
|
||||
import com.squareup.kotlinpoet.AnnotationSpec
|
||||
import com.squareup.kotlinpoet.ClassName
|
||||
import com.squareup.kotlinpoet.FileSpec
|
||||
import com.squareup.kotlinpoet.FunSpec
|
||||
import com.squareup.kotlinpoet.KModifier
|
||||
import com.squareup.kotlinpoet.ParameterSpec
|
||||
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
|
||||
import com.squareup.kotlinpoet.TypeVariableName
|
||||
import com.squareup.kotlinpoet.asTypeName
|
||||
import com.squareup.kotlinpoet.ksp.writeTo
|
||||
|
||||
class HikageViewGenerator(override val environment: SymbolProcessorEnvironment) : BaseSymbolProcessor(environment) {
|
||||
|
||||
private companion object {
|
||||
|
||||
private const val PACKAGE_NAME_PREFIX = "com.highcapable.hikage.widget"
|
||||
|
||||
val HikageableClass = ClassName(DeclaredSymbol.ANNOTATION_PACKAGE_NAME, DeclaredSymbol.HIKAGEABLE_ANNOTATION_CLASS_NAME)
|
||||
val HikageLparamClass = ClassName(DeclaredSymbol.HIKAGE_CORE_PACKAGE_NAME, DeclaredSymbol.HIKAGE_LAYOUT_PARAMS_CLASS_NAME)
|
||||
val ViewGroupLpClass = ClassName(DeclaredSymbol.ANDROID_VIEW_PACKAGE_NAME, DeclaredSymbol.ANDROID_LAYOUT_PARAMS_CLASS_NAME)
|
||||
val PerformerClass = ClassName(DeclaredSymbol.HIKAGE_CORE_PACKAGE_NAME, DeclaredSymbol.HIKAGE_PERFORMER_CLASS_NAME)
|
||||
val ViewLambdaClass = ClassName(DeclaredSymbol.HIKAGE_BASE_PACKAGE_NAME, DeclaredSymbol.HIKAGE_VIEW_LAMBDA_CLASS_NAME)
|
||||
val PerformerLambdaClass = ClassName(DeclaredSymbol.HIKAGE_BASE_PACKAGE_NAME, DeclaredSymbol.HIKAGE_PERFORMER_LAMBDA_CLASS_NAME)
|
||||
}
|
||||
|
||||
private val performers = mutableListOf<Performer>()
|
||||
|
||||
override fun startProcess(resolver: Resolver) {
|
||||
Processor.init(logger, resolver)
|
||||
val dependencies = Dependencies(aggregating = true, *resolver.getAllFiles().toList().toTypedArray())
|
||||
resolver.getSymbolsWithAnnotation(HikageViewSpec.CLASS)
|
||||
.filterIsInstance<KSClassDeclaration>()
|
||||
.distinctBy { it.qualifiedName }
|
||||
.forEach { ksClass ->
|
||||
ksClass.annotations.forEach {
|
||||
// Get annotation parameters.
|
||||
val (annotation, declaration) = HikageViewSpec.create(resolver, it, ksClass)
|
||||
performers += Performer(annotation, declaration)
|
||||
}
|
||||
}
|
||||
resolver.getSymbolsWithAnnotation(HikageViewDeclarationSpec.CLASS)
|
||||
.filterIsInstance<KSClassDeclaration>()
|
||||
.distinctBy { it.qualifiedName }
|
||||
.forEach { ksClass ->
|
||||
ksClass.annotations.forEach {
|
||||
// Get annotation parameters.
|
||||
val (annotation, declaration) = HikageViewDeclarationSpec.create(resolver, it, ksClass)
|
||||
performers += Performer(annotation, declaration)
|
||||
}
|
||||
}
|
||||
processPerformer(dependencies)
|
||||
}
|
||||
|
||||
private fun processPerformer(dependencies: Dependencies) {
|
||||
val duplicatedItems = performers.groupBy { it.declaration.key }.filter { it.value.size > 1 }.flatMap { it.value }
|
||||
require(duplicatedItems.isEmpty()) {
|
||||
"Discover duplicate @HikageView or @HikageViewDeclaration's class name or alias definitions, " +
|
||||
"you can re-specify the class name using the `alias` parameter.\n" +
|
||||
"Duplicated Items:\n" +
|
||||
duplicatedItems.joinToString("\n") { "${it.declaration}\n${it.declaration.locateDesc}" }
|
||||
}
|
||||
performers.forEach { generateCodeFile(dependencies, it) }
|
||||
}
|
||||
|
||||
private fun generateCodeFile(dependencies: Dependencies, performer: Performer) {
|
||||
val classNameSet = performer.declaration.alias ?: performer.declaration.className
|
||||
val fileName = "_$classNameSet"
|
||||
val viewClass = performer.declaration.toClassName().let {
|
||||
val packageName = it.packageName
|
||||
val simpleName = it.simpleName
|
||||
val topClassName = if (simpleName.contains(".")) simpleName.split(".")[0] else null
|
||||
// com.example.MyViewScope
|
||||
// com.example.MyViewScope.MyView
|
||||
topClassName?.let { name -> ClassName(packageName, name) } to it
|
||||
}
|
||||
val lparamsClass = performer.annotation.lparams?.let {
|
||||
val packageName = it.packageName.asString()
|
||||
val subClassName = it.getSimpleNameString()
|
||||
val simpleName = it.simpleName.asString()
|
||||
val topClassName = subClassName.replace(".$simpleName", "")
|
||||
// android.view.ViewGroup
|
||||
// android.view.ViewGroup.LayoutParams
|
||||
(if (topClassName != subClassName)
|
||||
ClassName(packageName, topClassName)
|
||||
else null) to ClassName(packageName, subClassName)
|
||||
}
|
||||
val packageName = "$PACKAGE_NAME_PREFIX.${performer.declaration.packageName}"
|
||||
val fileSpec = FileSpec.builder(packageName, fileName).apply {
|
||||
addFileComment(
|
||||
"""
|
||||
Hikage - ${HikageCompilerProperties.POM_DESCRIPTION}
|
||||
Copyright (C) 2019 HighCapable
|
||||
${HikageCompilerProperties.PROJECT_URL}
|
||||
|
||||
This file is auto generated by Hikage.
|
||||
**DO NOT EDIT THIS FILE MANUALLY**
|
||||
""".trimIndent()
|
||||
)
|
||||
addAnnotation(
|
||||
AnnotationSpec.builder(Suppress::class)
|
||||
.addMember("%S, %S, %S", "unused", "FunctionName", "DEPRECATION")
|
||||
.build()
|
||||
)
|
||||
addAnnotation(
|
||||
AnnotationSpec.builder(JvmName::class)
|
||||
.addMember("%S", "${classNameSet}Performer")
|
||||
.build()
|
||||
)
|
||||
addImport(DeclaredSymbol.ANDROID_VIEW_PACKAGE_NAME, DeclaredSymbol.ANDROID_VIEW_GROUP_CLASS_NAME)
|
||||
addImport(DeclaredSymbol.HIKAGE_CORE_PACKAGE_NAME, DeclaredSymbol.HIKAGE_CLASS_NAME)
|
||||
// Kotlin's import rule is to introduce the parent class that also needs to be introduced at the same time.
|
||||
// If a child class exists, it needs to import the parent class,
|
||||
// and kotlinpoet will not perform this operation automatically.
|
||||
viewClass.first?.let { addImport(it.packageName, it.simpleName) }
|
||||
lparamsClass?.first?.let { addImport(it.packageName, it.simpleName) }
|
||||
// May conflict with other [LayoutParams].
|
||||
lparamsClass?.second?.let { addAliasedImport(it, it.getTypedSimpleName()) }
|
||||
addAliasedImport(ViewGroupLpClass, ViewGroupLpClass.getTypedSimpleName())
|
||||
addAliasedImport(HikageLparamClass, HikageLparamClass.getTypedSimpleName())
|
||||
addFunction(FunSpec.builder(classNameSet).apply {
|
||||
addKdoc(
|
||||
"""
|
||||
Resolve the [${performer.declaration.className}].
|
||||
@see ${performer.declaration.className}
|
||||
@see Hikage.Performer.${if (lparamsClass == null) "View" else "ViewGroup"}
|
||||
@return [${performer.declaration.className}]
|
||||
""".trimIndent()
|
||||
)
|
||||
addAnnotation(HikageableClass)
|
||||
addModifiers(KModifier.INLINE)
|
||||
addTypeVariable(TypeVariableName(name = "LP", ViewGroupLpClass).copy(reified = true))
|
||||
receiver(PerformerClass.parameterizedBy(TypeVariableName("LP")))
|
||||
addParameter(
|
||||
ParameterSpec.builder(name = "lparams", HikageLparamClass.copy(nullable = true))
|
||||
.defaultValue("null")
|
||||
.build()
|
||||
)
|
||||
addParameter(
|
||||
ParameterSpec.builder(name = "id", String::class.asTypeName().copy(nullable = true))
|
||||
.defaultValue("null")
|
||||
.build()
|
||||
)
|
||||
addParameter(
|
||||
ParameterSpec.builder(
|
||||
name = "init",
|
||||
ViewLambdaClass.parameterizedBy(viewClass.second)
|
||||
).apply {
|
||||
if (!performer.annotation.requireInit) defaultValue("{}")
|
||||
}.build()
|
||||
)
|
||||
lparamsClass?.second?.let {
|
||||
addParameter(
|
||||
ParameterSpec.builder(
|
||||
name = "performer",
|
||||
PerformerLambdaClass.parameterizedBy(it)
|
||||
).apply {
|
||||
if (!performer.annotation.requirePerformer) defaultValue("{}")
|
||||
}.build()
|
||||
)
|
||||
addStatement("return ViewGroup<${performer.declaration.className}, ${it.simpleName}>(lparams, id, init, performer)")
|
||||
} ?: addStatement("return View<${performer.declaration.className}>(lparams, id, init)")
|
||||
returns(viewClass.second)
|
||||
}.build())
|
||||
}.build()
|
||||
// May already exists, no need to generate.
|
||||
runCatching {
|
||||
fileSpec.writeTo(codeGenerator, dependencies)
|
||||
}.onFailure {
|
||||
if (it !is FileAlreadyExistsException) throw it
|
||||
}
|
||||
}
|
||||
|
||||
private object Processor {
|
||||
|
||||
private lateinit var logger: KSPLogger
|
||||
|
||||
private lateinit var viewDeclaration: KSClassDeclaration
|
||||
private lateinit var viewGroupDeclaration: KSClassDeclaration
|
||||
private lateinit var lparamsDeclaration: KSClassDeclaration
|
||||
|
||||
fun init(logger: KSPLogger, resolver: Resolver) {
|
||||
this.logger = logger
|
||||
viewDeclaration = resolver.getClassDeclarationByName(DeclaredSymbol.ANDROID_VIEW_CLASS)!!
|
||||
viewGroupDeclaration = resolver.getClassDeclarationByName(DeclaredSymbol.ANDROID_VIEW_GROUP_CLASS)!!
|
||||
lparamsDeclaration = resolver.getClassDeclarationByName(DeclaredSymbol.ANDROID_LAYOUT_PARAMS_CLASS)!!
|
||||
}
|
||||
|
||||
fun resolvedLparamsDeclaration(
|
||||
tagName: String,
|
||||
resolver: Resolver,
|
||||
declaration: ViewDeclaration,
|
||||
lparams: KSType?
|
||||
): KSClassDeclaration? {
|
||||
val lparamsType = lparams?.takeIf { ksType ->
|
||||
ksType.declaration.qualifiedName?.asString() != Any::class.qualifiedName
|
||||
}
|
||||
var resolvedLparams = lparamsType?.declaration?.getClassDeclaration(resolver)
|
||||
when {
|
||||
// If the current view is not a view group but the lparams parameter is declared,
|
||||
// remove the lparams parameter.
|
||||
!declaration.isViewGroup && resolvedLparams != null -> {
|
||||
logger.warn(
|
||||
"Declares @$tagName's lparams \"${resolvedLparams.qualifiedName?.asString()}\" is invalid, " +
|
||||
"because the current view is not a view group.\n${declaration.locateDesc}"
|
||||
)
|
||||
resolvedLparams = null
|
||||
}
|
||||
// If the current view is a view group but the lparams parameter is not declared,
|
||||
// set the default type parameter for it.
|
||||
declaration.isViewGroup && resolvedLparams == null -> resolvedLparams = lparamsDeclaration
|
||||
}
|
||||
// Verify layout parameter class.
|
||||
if (resolvedLparams != null) require(resolvedLparams.isSubclassOf(lparamsDeclaration.asType())) {
|
||||
val resolvedLparamsName = resolvedLparams.qualifiedName?.asString()
|
||||
"Declares @$tagName's lparams \"$resolvedLparamsName\" must be subclass of " +
|
||||
"\"${DeclaredSymbol.ANDROID_LAYOUT_PARAMS_CLASS}\".\n${declaration.locateDesc}"
|
||||
}
|
||||
return resolvedLparams
|
||||
}
|
||||
|
||||
fun createViewDeclaration(
|
||||
tagName: String,
|
||||
alias: String?,
|
||||
ksClass: KSClassDeclaration,
|
||||
baseType: KSClassDeclaration = ksClass
|
||||
): ViewDeclaration {
|
||||
val packageName = ksClass.packageName.asString()
|
||||
val className = ksClass.getSimpleNameString()
|
||||
val isViewGroup = ksClass.isSubclassOf(viewGroupDeclaration.asType())
|
||||
var _alias = alias
|
||||
// If no alias name is set, if the class name contains a subclass,
|
||||
// replace it with an underscore and use it as an alias name.
|
||||
if (_alias.isNullOrBlank() && className.contains("."))
|
||||
_alias = className.replace(".", "_")
|
||||
_alias = _alias?.takeIf { it.isNotBlank() }
|
||||
val declaration = ViewDeclaration(packageName, className, _alias, isViewGroup, baseType)
|
||||
// Verify the legality of the class name.
|
||||
if (!_alias.isNullOrBlank()) require(ClassDetector.verify(_alias)) {
|
||||
"Declares @$tagName's alias \"$_alias\" is illegal.\n${declaration.locateDesc}"
|
||||
}
|
||||
// [ViewGroup] cannot be new instance.
|
||||
require(ksClass != viewGroupDeclaration) {
|
||||
"Declares @$tagName's class must not be a directly \"${DeclaredSymbol.ANDROID_VIEW_GROUP_CLASS}\".\n${declaration.locateDesc}"
|
||||
}
|
||||
// Annotations can only be modified on android view.
|
||||
require(ksClass.isSubclassOf(viewDeclaration.asType())) {
|
||||
"Declares @$tagName's class must be subclass of \"${DeclaredSymbol.ANDROID_VIEW_CLASS}\".\n${declaration.locateDesc}"
|
||||
}
|
||||
return declaration
|
||||
}
|
||||
}
|
||||
|
||||
private data class ViewDeclaration(
|
||||
val packageName: String,
|
||||
val className: String,
|
||||
val alias: String?,
|
||||
val isViewGroup: Boolean,
|
||||
val baseType: KSClassDeclaration
|
||||
) {
|
||||
|
||||
val key get() = "$packageName${alias ?: className}$isViewGroup"
|
||||
|
||||
val locateDesc by lazy { "Located: ${baseType.qualifiedName?.asString()}" }
|
||||
|
||||
fun toClassName() = ClassName(packageName, className)
|
||||
|
||||
override fun toString() = "{ package: $packageName, class: $className, alias: ${alias ?: "<unspec>"} }"
|
||||
}
|
||||
|
||||
private data class HikageViewSpec(
|
||||
override val lparams: KSClassDeclaration?,
|
||||
override val alias: String?,
|
||||
override val requireInit: Boolean,
|
||||
override val requirePerformer: Boolean
|
||||
) : HikageAnnotationSpec {
|
||||
|
||||
companion object {
|
||||
|
||||
const val CLASS = DeclaredSymbol.HIKAGE_VIEW_ANNOTATION_CLASS
|
||||
const val NAME = DeclaredSymbol.HIKAGE_VIEW_ANNOTATION_CLASS_NAME
|
||||
|
||||
fun create(
|
||||
resolver: Resolver,
|
||||
annotation: KSAnnotation,
|
||||
ksClass: KSClassDeclaration
|
||||
): Pair<HikageViewSpec, ViewDeclaration> {
|
||||
val lparams = annotation.arguments.getOrNull<KSType>("lparams")
|
||||
val alias = annotation.arguments.getOrNull<String>("alias")
|
||||
val requireInit = annotation.arguments.getOrNull<Boolean>("requireInit") ?: false
|
||||
val requirePerformer = annotation.arguments.getOrNull<Boolean>("requirePerformer") ?: false
|
||||
// Solve the actual content of the annotation parameters.
|
||||
val declaration = Processor.createViewDeclaration(NAME, alias, ksClass)
|
||||
val resolvedLparams = Processor.resolvedLparamsDeclaration(NAME, resolver, declaration, lparams)
|
||||
return HikageViewSpec(resolvedLparams, alias, requireInit, requirePerformer) to declaration
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class HikageViewDeclarationSpec(
|
||||
val view: KSClassDeclaration?,
|
||||
override val lparams: KSClassDeclaration?,
|
||||
override val alias: String?,
|
||||
override val requireInit: Boolean,
|
||||
override val requirePerformer: Boolean
|
||||
) : HikageAnnotationSpec {
|
||||
|
||||
companion object {
|
||||
|
||||
const val CLASS = DeclaredSymbol.HIKAGE_HIKAGE_VIEW_DECLARATION_ANNOTATION_CLASS
|
||||
const val NAME = DeclaredSymbol.HIKAGE_HIKAGE_VIEW_DECLARATION_ANNOTATION_CLASS_NAME
|
||||
|
||||
fun create(
|
||||
resolver: Resolver,
|
||||
annotation: KSAnnotation,
|
||||
ksClass: KSClassDeclaration
|
||||
): Pair<HikageViewDeclarationSpec, ViewDeclaration> {
|
||||
val view = annotation.arguments.getOrNull<KSType>("view")
|
||||
val lparams = annotation.arguments.getOrNull<KSType>("lparams")
|
||||
val alias = annotation.arguments.getOrNull<String>("alias")
|
||||
val requireInit = annotation.arguments.getOrNull<Boolean>("requireInit") ?: false
|
||||
val requirePerformer = annotation.arguments.getOrNull<Boolean>("requirePerformer") ?: false
|
||||
// Solve the actual content of the annotation parameters.
|
||||
val resolvedView = view?.declaration?.getClassDeclaration(resolver) ?: error("Internal error.")
|
||||
val declaration = Processor.createViewDeclaration(NAME, alias, resolvedView, ksClass)
|
||||
// Only object classes can be used as view declarations.
|
||||
require(ksClass.classKind == ClassKind.OBJECT) {
|
||||
"Declares @$NAME's class must be an object.\n${declaration.locateDesc}"
|
||||
}
|
||||
require(!ksClass.isCompanionObject) {
|
||||
"Declares @$NAME's class must not be a companion object.\n${declaration.locateDesc}"
|
||||
}
|
||||
val resolvedLparams = Processor.resolvedLparamsDeclaration(NAME, resolver, declaration, lparams)
|
||||
return HikageViewDeclarationSpec(resolvedView, resolvedLparams, alias, requireInit, requirePerformer) to declaration
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private interface HikageAnnotationSpec {
|
||||
val lparams: KSClassDeclaration?
|
||||
val alias: String?
|
||||
val requireInit: Boolean
|
||||
val requirePerformer: Boolean
|
||||
}
|
||||
|
||||
private data class Performer(
|
||||
val annotation: HikageAnnotationSpec,
|
||||
val declaration: ViewDeclaration
|
||||
)
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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/3/23.
|
||||
*/
|
||||
package com.highcapable.hikage.compiler.subprocessor.base
|
||||
|
||||
import com.google.devtools.ksp.processing.Resolver
|
||||
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
|
||||
|
||||
abstract class BaseSymbolProcessor(protected open val environment: SymbolProcessorEnvironment) {
|
||||
|
||||
protected val logger by lazy { environment.logger }
|
||||
protected val codeGenerator by lazy { environment.codeGenerator }
|
||||
|
||||
abstract fun startProcess(resolver: Resolver)
|
||||
}
|
46
hikage-core-lint/build.gradle.kts
Normal file
@@ -0,0 +1,46 @@
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
plugins {
|
||||
autowire(libs.plugins.kotlin.jvm)
|
||||
}
|
||||
|
||||
group = property.project.groupName
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(17)
|
||||
compilerOptions {
|
||||
freeCompilerArgs = listOf(
|
||||
"-Xno-param-assertions",
|
||||
"-Xno-call-assertions",
|
||||
"-Xno-receiver-assertions"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<KotlinCompile>().configureEach {
|
||||
compilerOptions {
|
||||
jvmTarget.set(JvmTarget.JVM_17)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named<Jar>("jar") {
|
||||
manifest {
|
||||
attributes(
|
||||
"Lint-Registry-V2" to property.project.hikage.core.lint.registry.v2.clazz
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(org.jetbrains.kotlin.kotlin.stdlib)
|
||||
compileOnly(com.android.tools.lint.lint.api)
|
||||
compileOnly(com.android.tools.lint.lint.checks)
|
||||
testImplementation(com.android.tools.lint.lint)
|
||||
testImplementation(com.android.tools.lint.lint.tests)
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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/3/17.
|
||||
*/
|
||||
package com.highcapable.hikage.core.lint
|
||||
|
||||
object DeclaredSymbol {
|
||||
|
||||
const val HIKAGEABLE_ANNOTATION_CLASS = "com.highcapable.hikage.annotation.Hikageable"
|
||||
const val HIKAGE_CLASS = "com.highcapable.hikage.core.Hikage"
|
||||
const val HIKAGE_PERFORMER_CLASS = "com.highcapable.hikage.core.Hikage.Performer"
|
||||
|
||||
val HIKAGE_VIEW_REGEX = "kotlin.jvm.functions.Function1<\\?\\s*super\\s+[^,]+,kotlin.Unit>".toRegex()
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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/3/14.
|
||||
*/
|
||||
@file:Suppress("unused")
|
||||
|
||||
package com.highcapable.hikage.core.lint
|
||||
|
||||
import com.android.tools.lint.client.api.IssueRegistry
|
||||
import com.android.tools.lint.client.api.Vendor
|
||||
import com.android.tools.lint.detector.api.CURRENT_API
|
||||
import com.highcapable.hikage.core.lint.detector.HikageSafeTypeCastDetector
|
||||
import com.highcapable.hikage.core.lint.detector.HikageableBeyondScopeDetector
|
||||
import com.highcapable.hikage.core.lint.detector.HikageableFunctionsDetector
|
||||
import com.highcapable.hikage.core.lint.detector.WidgetsUsageDetector
|
||||
import com.highcapable.hikage.generated.HikageCoreLintProperties
|
||||
|
||||
class HikageIssueRegistry : IssueRegistry() {
|
||||
|
||||
override val issues get() = listOf(
|
||||
HikageableBeyondScopeDetector.ISSUE,
|
||||
HikageableFunctionsDetector.ISSUE,
|
||||
HikageSafeTypeCastDetector.ISSUE,
|
||||
WidgetsUsageDetector.ISSUE
|
||||
)
|
||||
|
||||
override val minApi = HikageCoreLintProperties.PROJECT_HIKAGE_CORE_LINT_MIN_API
|
||||
override val api = CURRENT_API
|
||||
override val vendor = Vendor(
|
||||
vendorName = HikageCoreLintProperties.PROJECT_NAME,
|
||||
identifier = HikageCoreLintProperties.PROJECT_HIKAGE_CORE_LINT_IDENTIFIER,
|
||||
feedbackUrl = "${HikageCoreLintProperties.PROJECT_URL}/issues",
|
||||
contact = HikageCoreLintProperties.PROJECT_URL
|
||||
)
|
||||
}
|
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* 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/4/2.
|
||||
*/
|
||||
package com.highcapable.hikage.core.lint.detector
|
||||
|
||||
import com.android.tools.lint.client.api.UElementHandler
|
||||
import com.android.tools.lint.detector.api.Category
|
||||
import com.android.tools.lint.detector.api.Detector
|
||||
import com.android.tools.lint.detector.api.Implementation
|
||||
import com.android.tools.lint.detector.api.Issue
|
||||
import com.android.tools.lint.detector.api.JavaContext
|
||||
import com.android.tools.lint.detector.api.LintFix
|
||||
import com.android.tools.lint.detector.api.Scope
|
||||
import com.android.tools.lint.detector.api.Severity
|
||||
import com.highcapable.hikage.core.lint.DeclaredSymbol
|
||||
import org.jetbrains.uast.UArrayAccessExpression
|
||||
import org.jetbrains.uast.UBinaryExpressionWithType
|
||||
import org.jetbrains.uast.UParenthesizedExpression
|
||||
import org.jetbrains.uast.UQualifiedReferenceExpression
|
||||
|
||||
class HikageSafeTypeCastDetector : Detector(), Detector.UastScanner {
|
||||
|
||||
companion object {
|
||||
|
||||
val ISSUE = Issue.create(
|
||||
id = "UseHikageSafeTypeCast",
|
||||
briefDescription = "Hikage safe type cast usage.",
|
||||
explanation = "Recommended to use `hikage.get<YourView>(\"your_id\")` instead of `hikage[\"your_id\"] as YourView`.",
|
||||
category = Category.COMPLIANCE,
|
||||
priority = 5,
|
||||
severity = Severity.WARNING,
|
||||
implementation = Implementation(
|
||||
HikageSafeTypeCastDetector::class.java,
|
||||
Scope.JAVA_FILE_SCOPE
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun getApplicableUastTypes() = listOf(UQualifiedReferenceExpression::class.java, UBinaryExpressionWithType::class.java)
|
||||
|
||||
override fun createUastHandler(context: JavaContext) = object : UElementHandler() {
|
||||
|
||||
override fun visitQualifiedReferenceExpression(node: UQualifiedReferenceExpression) {
|
||||
if (node.selector !is UBinaryExpressionWithType) return
|
||||
val castExpr = node.selector as UBinaryExpressionWithType
|
||||
visitAndLint(context, castExpr, node)
|
||||
}
|
||||
|
||||
override fun visitBinaryExpressionWithType(node: UBinaryExpressionWithType) {
|
||||
visitAndLint(context, node)
|
||||
}
|
||||
|
||||
private fun visitAndLint(
|
||||
context: JavaContext,
|
||||
node: UBinaryExpressionWithType,
|
||||
parent: UQualifiedReferenceExpression? = null
|
||||
) {
|
||||
// Get the parent node, if it is wrapped with brackets will also be included.
|
||||
val locationNode = node.uastParent as? UParenthesizedExpression ?: node
|
||||
val receiver = parent?.receiver ?: node.operand
|
||||
val receiverType = (node.operand as? UArrayAccessExpression)?.receiver?.getExpressionType() ?: return
|
||||
val receiverClass = receiverType.canonicalText
|
||||
// Filter retains results that meet the conditions.
|
||||
if (receiverClass != DeclaredSymbol.HIKAGE_CLASS) return
|
||||
// Like `hikage["your_id"] as YourView`.
|
||||
val exprText = node.sourcePsi?.text ?: return
|
||||
// Like `hikage["your_id"]`.
|
||||
val receiverText = receiver.sourcePsi?.text ?: return
|
||||
// Like `hikage`.
|
||||
val receiverNameText = receiverText.split("[")[0]
|
||||
// Like `"your_id"`.
|
||||
val receiverContent = runCatching { receiverText.split("[")[1].split("]")[0] }.getOrNull() ?: return
|
||||
val isSafeCast = exprText.contains("as?") || exprText.endsWith("?")
|
||||
// Like `YourView`.
|
||||
val castTypeContent = node.typeReference?.sourcePsi?.text?.removeSuffix("?") ?: return
|
||||
val replacement = "$receiverNameText.${if (isSafeCast) "getOrNull" else "get"}<$castTypeContent>($receiverContent)"
|
||||
val replaceSuggestion = if (isSafeCast) "Hikage.getOrNull<$castTypeContent>" else "Hikage.get<$castTypeContent>"
|
||||
val location = context.getLocation(locationNode)
|
||||
val lintFix = LintFix.create()
|
||||
.name("Replace with '$replacement'")
|
||||
.replace()
|
||||
.range(location)
|
||||
.with(replacement)
|
||||
.reformat(true)
|
||||
.build()
|
||||
context.report(
|
||||
ISSUE, locationNode, location,
|
||||
message = "Can be replaced with safe type cast `$replaceSuggestion`.",
|
||||
quickfixData = lintFix
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* 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/3/17.
|
||||
*/
|
||||
package com.highcapable.hikage.core.lint.detector
|
||||
|
||||
import com.android.tools.lint.client.api.UElementHandler
|
||||
import com.android.tools.lint.detector.api.Category
|
||||
import com.android.tools.lint.detector.api.Detector
|
||||
import com.android.tools.lint.detector.api.Implementation
|
||||
import com.android.tools.lint.detector.api.Issue
|
||||
import com.android.tools.lint.detector.api.JavaContext
|
||||
import com.android.tools.lint.detector.api.LintFix
|
||||
import com.android.tools.lint.detector.api.Scope
|
||||
import com.android.tools.lint.detector.api.Severity
|
||||
import com.highcapable.hikage.core.lint.DeclaredSymbol
|
||||
import com.highcapable.hikage.core.lint.detector.entity.ReportDetail
|
||||
import com.highcapable.hikage.core.lint.detector.extension.hasHikageable
|
||||
import com.intellij.psi.PsiMethod
|
||||
import org.jetbrains.kotlin.psi.KtCallExpression
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.psi.KtLambdaArgument
|
||||
import org.jetbrains.kotlin.psi.KtLambdaExpression
|
||||
import org.jetbrains.kotlin.psi.KtValueArgument
|
||||
import org.jetbrains.uast.UCallExpression
|
||||
import org.jetbrains.uast.toUElementOfType
|
||||
|
||||
class HikageableBeyondScopeDetector : Detector(), Detector.UastScanner {
|
||||
|
||||
companion object {
|
||||
|
||||
val ISSUE = Issue.create(
|
||||
id = "HikageableBeyondScope",
|
||||
briefDescription = "Hikageable beyond scope.",
|
||||
explanation = "Functions marked with `@Hikageable` can only be passed in `Hikage.Performer`.",
|
||||
category = Category.COMPLIANCE,
|
||||
priority = 10,
|
||||
severity = Severity.ERROR,
|
||||
implementation = Implementation(
|
||||
HikageableBeyondScopeDetector::class.java,
|
||||
Scope.JAVA_FILE_SCOPE
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun getApplicableUastTypes() = listOf(UCallExpression::class.java)
|
||||
|
||||
override fun createUastHandler(context: JavaContext) = object : UElementHandler() {
|
||||
|
||||
private val reportedNodes = mutableSetOf<UCallExpression>()
|
||||
private val reports = mutableListOf<ReportDetail>()
|
||||
|
||||
override fun visitCallExpression(node: UCallExpression) {
|
||||
val callExpr = node.sourcePsi as? KtCallExpression ?: return
|
||||
val method = node.resolve() ?: return
|
||||
startLint(callExpr, method)
|
||||
organizeAndReport()
|
||||
}
|
||||
|
||||
private fun startLint(callExpr: KtCallExpression, method: PsiMethod) {
|
||||
val className = method.containingClass?.qualifiedName ?: ""
|
||||
val hasHikageable = method.hasHikageable()
|
||||
val hasLayoutParams = className == DeclaredSymbol.HIKAGE_PERFORMER_CLASS && method.name == "LayoutParams"
|
||||
if (hasHikageable || hasLayoutParams) visitAndLint(callExpr, method)
|
||||
}
|
||||
|
||||
private fun organizeAndReport() {
|
||||
reports.forEach {
|
||||
// Check if the call has been reported before reporting.
|
||||
if (reportedNodes.contains(it.callExpr)) return@forEach
|
||||
val location = context.getLocation(it.callExpr)
|
||||
val lintFix = LintFix.create()
|
||||
.name("Delete Call Expression")
|
||||
.replace()
|
||||
.range(location)
|
||||
// Delete the call expression.
|
||||
.with("")
|
||||
.build()
|
||||
context.report(ISSUE, it.callExpr, location, it.message, lintFix)
|
||||
reportedNodes.add(it.callExpr)
|
||||
}
|
||||
}
|
||||
|
||||
private fun visitAndLint(callExpr: KtCallExpression, method: PsiMethod) {
|
||||
val bodyBlocks = mutableMapOf<String, KtExpression>()
|
||||
val parameters = method.parameterList.parameters
|
||||
val valueArguments = callExpr.valueArgumentList?.arguments ?: emptyList()
|
||||
fun visitValueArg(arg: KtValueArgument) {
|
||||
val name = arg.getArgumentName()?.asName?.identifier ?: ""
|
||||
val expr = arg.getArgumentExpression()
|
||||
val parameter = parameters.firstOrNull { it.name == name }
|
||||
// If the last bit is a lambda expression, then `parameter` must have a lambda parameter defined by the last bit.
|
||||
?: if (arg is KtLambdaArgument) parameters.lastOrNull() else null
|
||||
val isMatched = parameter?.type?.canonicalText?.matches(DeclaredSymbol.HIKAGE_VIEW_REGEX) == true &&
|
||||
!parameter.type.canonicalText.contains(DeclaredSymbol.HIKAGE_PERFORMER_CLASS)
|
||||
if (expr is KtLambdaExpression && isMatched)
|
||||
expr.bodyExpression?.let { bodyBlocks[name] = it }
|
||||
}
|
||||
// Get the last lambda expression.
|
||||
val lastLambda = callExpr.lambdaArguments.lastOrNull()
|
||||
if (lastLambda != null) visitValueArg(lastLambda)
|
||||
valueArguments.forEach { arg -> visitValueArg(arg) }
|
||||
bodyBlocks.toList().flatMap { (_, value) -> value.children.filterIsInstance<KtCallExpression>() }.forEach {
|
||||
val expression = it.toUElementOfType<UCallExpression>() ?: return@forEach
|
||||
val sCallExpr = expression.sourcePsi as? KtCallExpression ?: return@forEach
|
||||
val sMethod = expression.resolve() ?: return@forEach
|
||||
if (sMethod.hasHikageable()) {
|
||||
val message = "Performers are not allowed to appear in `${method.name}` DSL creation process."
|
||||
reports.add(ReportDetail(message, expression))
|
||||
// Recursively to visit next level.
|
||||
visitAndLint(sCallExpr, sMethod)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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/3/17.
|
||||
*/
|
||||
package com.highcapable.hikage.core.lint.detector
|
||||
|
||||
import com.android.tools.lint.client.api.UElementHandler
|
||||
import com.android.tools.lint.detector.api.Category
|
||||
import com.android.tools.lint.detector.api.Detector
|
||||
import com.android.tools.lint.detector.api.Implementation
|
||||
import com.android.tools.lint.detector.api.Issue
|
||||
import com.android.tools.lint.detector.api.JavaContext
|
||||
import com.android.tools.lint.detector.api.LintFix
|
||||
import com.android.tools.lint.detector.api.Scope
|
||||
import com.android.tools.lint.detector.api.Severity
|
||||
import com.highcapable.hikage.core.lint.DeclaredSymbol
|
||||
import com.highcapable.hikage.core.lint.detector.extension.hasHikageable
|
||||
import com.intellij.psi.PsiMethod
|
||||
import org.jetbrains.uast.UBlockExpression
|
||||
import org.jetbrains.uast.UCallExpression
|
||||
import org.jetbrains.uast.UMethod
|
||||
import org.jetbrains.uast.UReturnExpression
|
||||
import org.jetbrains.uast.tryResolve
|
||||
|
||||
class HikageableFunctionsDetector : Detector(), Detector.UastScanner {
|
||||
|
||||
companion object {
|
||||
|
||||
val ISSUE = Issue.create(
|
||||
id = "HikageableFunctions",
|
||||
briefDescription = "Hikageable functions.",
|
||||
explanation = "Functions which invoke `@Hikageable` functions must be marked with the `@Hikageable` annotation.",
|
||||
category = Category.COMPLIANCE,
|
||||
priority = 10,
|
||||
severity = Severity.ERROR,
|
||||
implementation = Implementation(
|
||||
HikageableFunctionsDetector::class.java,
|
||||
Scope.JAVA_FILE_SCOPE
|
||||
)
|
||||
)
|
||||
|
||||
private val functionRegex = "(\\s?.+)?fun\\s?".toRegex()
|
||||
}
|
||||
|
||||
override fun getApplicableUastTypes() = listOf(UMethod::class.java)
|
||||
|
||||
override fun createUastHandler(context: JavaContext) = object : UElementHandler() {
|
||||
|
||||
override fun visitMethod(node: UMethod) {
|
||||
val uastBody = node.uastBody as? UBlockExpression ?: return
|
||||
val bodyHasHikageable = uastBody.expressions.any {
|
||||
when (it) {
|
||||
is UCallExpression -> it.resolve()?.hasHikageable() ?: false
|
||||
is UReturnExpression ->
|
||||
(it.returnExpression?.tryResolve() as? PsiMethod?)?.hasHikageable() ?: false
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
if (!node.hasHikageable() && bodyHasHikageable) {
|
||||
val location = context.getLocation(node)
|
||||
val nameLocation = context.getNameLocation(node)
|
||||
val message = "Function `${node.name}` must be marked with the `@Hikageable` annotation."
|
||||
val functionText = node.asSourceString()
|
||||
val hasDoubleSlash = functionText.startsWith("//")
|
||||
val replacement = functionRegex.replace(functionText) { result ->
|
||||
val functionBody = result.groupValues.getOrNull(0) ?: functionText
|
||||
val prefix = if (hasDoubleSlash) "\n" else ""
|
||||
"$prefix@Hikageable $functionBody"
|
||||
}
|
||||
val lintFix = LintFix.create()
|
||||
.name("Add '@Hikageable' to '${node.name}'")
|
||||
.replace()
|
||||
.range(location)
|
||||
.with(replacement)
|
||||
.imports(DeclaredSymbol.HIKAGEABLE_ANNOTATION_CLASS)
|
||||
.reformat(true)
|
||||
.build()
|
||||
context.report(ISSUE, node, nameLocation, message, lintFix)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* 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/3/17.
|
||||
*/
|
||||
package com.highcapable.hikage.core.lint.detector
|
||||
|
||||
import com.android.tools.lint.client.api.UElementHandler
|
||||
import com.android.tools.lint.detector.api.Category
|
||||
import com.android.tools.lint.detector.api.Detector
|
||||
import com.android.tools.lint.detector.api.Implementation
|
||||
import com.android.tools.lint.detector.api.Issue
|
||||
import com.android.tools.lint.detector.api.JavaContext
|
||||
import com.android.tools.lint.detector.api.LintFix
|
||||
import com.android.tools.lint.detector.api.Scope
|
||||
import com.android.tools.lint.detector.api.Severity
|
||||
import com.highcapable.hikage.core.lint.detector.extension.hasHikageable
|
||||
import com.intellij.psi.PsiMethod
|
||||
import org.jetbrains.kotlin.psi.KtCallExpression
|
||||
import org.jetbrains.uast.UCallExpression
|
||||
import org.jetbrains.uast.UElement
|
||||
import org.jetbrains.uast.UImportStatement
|
||||
import org.jetbrains.uast.toUElement
|
||||
|
||||
class WidgetsUsageDetector : Detector(), Detector.UastScanner {
|
||||
|
||||
companion object {
|
||||
|
||||
val ISSUE = Issue.create(
|
||||
id = "ReplaceWithHikageWidgets",
|
||||
briefDescription = "Hikage built-in widget usability.",
|
||||
explanation = "Use the built-in widget function component provided by Hikage like `TextView(...)` " +
|
||||
"without using a form like `View<TextView>(...)` to use the component.",
|
||||
category = Category.USABILITY,
|
||||
priority = 5,
|
||||
severity = Severity.WARNING,
|
||||
implementation = Implementation(
|
||||
WidgetsUsageDetector::class.java,
|
||||
Scope.JAVA_FILE_SCOPE
|
||||
)
|
||||
)
|
||||
|
||||
private const val BUILT_IN_WIDGETS_PACKAGE_PREFIX = "android.widget"
|
||||
private const val WIDGET_FUNCTION_PREFIX = "com.highcapable.hikage.widget.$BUILT_IN_WIDGETS_PACKAGE_PREFIX"
|
||||
private const val VIEW_CLASS_NAME = "android.view.View"
|
||||
private const val VIEW_GROUP_CLASS_NAME = "android.view.ViewGroup"
|
||||
|
||||
private val viewExpressionRegex = "(?:View|ViewGroup)<.*?>".toRegex()
|
||||
|
||||
private val builtInWidgets = listOf(
|
||||
"SeekBar",
|
||||
"LinearLayout",
|
||||
"ScrollView",
|
||||
"TextView",
|
||||
"EditText",
|
||||
"AutoCompleteTextView",
|
||||
"ExpandableListView",
|
||||
"ListView",
|
||||
"RatingBar",
|
||||
"ViewSwitcher",
|
||||
"ActionMenuView",
|
||||
"ImageView",
|
||||
"ViewAnimator",
|
||||
"HorizontalScrollView",
|
||||
"MediaController",
|
||||
"RelativeLayout",
|
||||
"TextClock",
|
||||
"CalendarView",
|
||||
"ToggleButton",
|
||||
"RadioGroup",
|
||||
"VideoView",
|
||||
"GridView",
|
||||
"QuickContactBadge",
|
||||
"TableLayout",
|
||||
"NumberPicker",
|
||||
"Toolbar",
|
||||
"ViewFlipper",
|
||||
"Chronometer",
|
||||
"ImageSwitcher",
|
||||
"Button",
|
||||
"CheckBox",
|
||||
"TabWidget",
|
||||
"TabHost",
|
||||
"SearchView",
|
||||
"Spinner",
|
||||
"TimePicker",
|
||||
"ImageButton",
|
||||
"TextSwitcher",
|
||||
"DatePicker",
|
||||
"RadioButton",
|
||||
"CheckedTextView",
|
||||
"FrameLayout",
|
||||
"Space",
|
||||
"GridLayout",
|
||||
"Switch",
|
||||
"ProgressBar",
|
||||
"TableRow"
|
||||
)
|
||||
}
|
||||
|
||||
data class ImportReference(val packagePrefix: String, val name: String, val alias: String? = null)
|
||||
|
||||
override fun getApplicableUastTypes(): List<Class<out UElement>> = listOf(UImportStatement::class.java, UCallExpression::class.java)
|
||||
|
||||
override fun createUastHandler(context: JavaContext) = object : UElementHandler() {
|
||||
|
||||
private val importReferences = mutableSetOf<ImportReference>()
|
||||
|
||||
override fun visitImportStatement(node: UImportStatement) {
|
||||
val imported = node.asSourceString().replace("import", "").let {
|
||||
when {
|
||||
it.contains("//") -> it.split("//")[0]
|
||||
it.contains("/*") -> it.split("/*")[0]
|
||||
else -> it
|
||||
}
|
||||
}.trim()
|
||||
val importRefs = imported.split(" as ")
|
||||
val alias = if (importRefs.size > 1)
|
||||
importRefs.getOrNull(1)?.trim()
|
||||
else null
|
||||
val importPrefix = importRefs[0]
|
||||
val hasPrefix = importPrefix.contains(".")
|
||||
val name = if (hasPrefix)
|
||||
importPrefix.split(".").last()
|
||||
else importPrefix
|
||||
val packagePrefix = importPrefix.replace(if (hasPrefix) ".$name" else name, "")
|
||||
val reference = ImportReference(packagePrefix, name, alias)
|
||||
importReferences.add(reference)
|
||||
}
|
||||
|
||||
override fun visitCallExpression(node: UCallExpression) {
|
||||
val callExpr = node.sourcePsi as? KtCallExpression ?: return
|
||||
val method = node.resolve() ?: return
|
||||
startLint(callExpr, method)
|
||||
}
|
||||
|
||||
private fun startLint(callExpr: KtCallExpression, method: PsiMethod) {
|
||||
val hasHikageable = method.hasHikageable()
|
||||
if (hasHikageable) visitAndReport(callExpr, method)
|
||||
}
|
||||
|
||||
private fun visitAndReport(callExpr: KtCallExpression, method: PsiMethod) {
|
||||
val typeParameters = method.typeParameterList?.typeParameters ?: emptyArray()
|
||||
val typeArguments = callExpr.typeArgumentList?.arguments ?: emptyList()
|
||||
val typeArgumentsText = typeArguments.mapNotNull { it.text }
|
||||
val typedViewFunctionIndex = typeParameters.indexOfFirst {
|
||||
it.extendsListTypes.any { type ->
|
||||
type.canonicalText == VIEW_CLASS_NAME ||
|
||||
type.canonicalText == VIEW_GROUP_CLASS_NAME
|
||||
}
|
||||
}
|
||||
val isTypedViewFunction = typedViewFunctionIndex >= 0
|
||||
val imports = typeArgumentsText.mapNotNull { typeName ->
|
||||
when {
|
||||
// Like `TextView`.
|
||||
!typeName.contains(".") -> importReferences.firstOrNull {
|
||||
it.packagePrefix == BUILT_IN_WIDGETS_PACKAGE_PREFIX &&
|
||||
(it.alias == typeName || it.name == typeName)
|
||||
}
|
||||
// Like `android.widget.TextView`.
|
||||
typeName.startsWith(BUILT_IN_WIDGETS_PACKAGE_PREFIX) ->
|
||||
ImportReference(BUILT_IN_WIDGETS_PACKAGE_PREFIX, typeName.replace("$BUILT_IN_WIDGETS_PACKAGE_PREFIX.", ""))
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
val matchedIndex = builtInWidgets.indexOfFirst { imports.any { e -> e.alias == it || e.name == it } }
|
||||
val isBuiltInWidget = matchedIndex >= 0
|
||||
if (isTypedViewFunction && isBuiltInWidget) {
|
||||
val widgetName = builtInWidgets[matchedIndex]
|
||||
val sourceLocation = context.getLocation(callExpr)
|
||||
val sourceText = callExpr.toUElement()?.asSourceString() ?: return
|
||||
val callExprElement = callExpr.toUElement() ?: return
|
||||
// Matchs '>' and like `View<TextView`'s length + 1.
|
||||
val callExprLength = sourceText.split(">")[0].trim().length + 1
|
||||
val nameLocation = context.getRangeLocation(callExprElement, fromDelta = 0, callExprLength)
|
||||
// Only replace the first one, because there may be multiple sub-functions in DSL.
|
||||
val replacement = sourceText.replaceFirst(viewExpressionRegex, widgetName)
|
||||
val lintFix = LintFix.create()
|
||||
.name("Replace with '$widgetName'")
|
||||
.replace()
|
||||
.range(sourceLocation)
|
||||
.with(replacement)
|
||||
.imports("$WIDGET_FUNCTION_PREFIX.$widgetName")
|
||||
.reformat(true)
|
||||
.build()
|
||||
val message = "Can be simplified to `$widgetName`."
|
||||
context.report(ISSUE, callExpr, nameLocation, message, lintFix)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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/3/17.
|
||||
*/
|
||||
package com.highcapable.hikage.core.lint.detector.entity
|
||||
|
||||
import org.jetbrains.uast.UCallExpression
|
||||
|
||||
data class ReportDetail(
|
||||
val message: String,
|
||||
val callExpr: UCallExpression
|
||||
)
|
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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/3/30.
|
||||
*/
|
||||
package com.highcapable.hikage.core.lint.detector.extension
|
||||
|
||||
import com.highcapable.hikage.core.lint.DeclaredSymbol
|
||||
import com.intellij.psi.PsiMethod
|
||||
|
||||
fun PsiMethod.hasHikageable() = hasAnnotation(DeclaredSymbol.HIKAGEABLE_ANNOTATION_CLASS)
|
54
hikage-core/build.gradle.kts
Normal file
@@ -0,0 +1,54 @@
|
||||
plugins {
|
||||
autowire(libs.plugins.android.library)
|
||||
autowire(libs.plugins.kotlin.android)
|
||||
autowire(libs.plugins.kotlin.dokka)
|
||||
autowire(libs.plugins.maven.publish)
|
||||
autowire(libs.plugins.kotlin.ksp)
|
||||
}
|
||||
|
||||
group = property.project.groupName
|
||||
version = property.project.hikage.core.version
|
||||
|
||||
android {
|
||||
namespace = property.project.hikage.core.namespace
|
||||
compileSdk = property.project.android.compileSdk
|
||||
|
||||
defaultConfig {
|
||||
minSdk = property.project.android.minSdk
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
freeCompilerArgs = listOf(
|
||||
"-opt-in=kotlin.ExperimentalStdlibApi",
|
||||
"-Xno-param-assertions",
|
||||
"-Xno-call-assertions",
|
||||
"-Xno-receiver-assertions"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
lintPublish(projects.hikageCoreLint)
|
||||
ksp(projects.hikageCompiler)
|
||||
implementation(org.lsposed.hiddenapibypass.hiddenapibypass)
|
||||
implementation(com.highcapable.yukireflection.api)
|
||||
api(com.highcapable.betterandroid.ui.extension)
|
||||
implementation(com.highcapable.betterandroid.system.extension)
|
||||
implementation(androidx.core.core.ktx)
|
||||
implementation(androidx.appcompat.appcompat)
|
||||
testImplementation(junit.junit)
|
||||
androidTestImplementation(androidx.test.ext.junit)
|
||||
androidTestImplementation(androidx.test.espresso.espresso.core)
|
||||
}
|
0
hikage-core/consumer-rules.pro
Normal file
21
hikage-core/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
@@ -0,0 +1,25 @@
|
||||
package com.highcapable.hikage
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.highcapable.hikage.test", appContext.packageName)
|
||||
}
|
||||
}
|
2
hikage-core/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest />
|
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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/3/23.
|
||||
*/
|
||||
package com.highcapable.hikage.annotation
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.highcapable.hikage.core.Hikage
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* Declare annotations to generate the [Hikage.Performer] function for the specified [View] at compile time.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```kotlin
|
||||
* @HikageView(lparams = FrameLayout.LayoutParams::class, alias = "MyView")
|
||||
* class MyView(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) {
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
* @param lparams the layout params class, only [ViewGroup] can be specified,
|
||||
* after specifying, the `performer` parameter will be generated for the function.
|
||||
* The parameters must be a class inherited from [ViewGroup.LayoutParams],
|
||||
* if the current [View] does not inherit from [ViewGroup], this parameter will be ignored and warned.
|
||||
* @param alias the view's class name alias will naming the function, default is the class name.
|
||||
* @param requireInit whether to force the `init` parameter to be called, default is false.
|
||||
* @param requirePerformer whether to force the `performer` parameter to be called, default is false,
|
||||
* this parameter will be ignored when no `performer` parameter is needed here.
|
||||
*/
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@MustBeDocumented
|
||||
annotation class HikageView(
|
||||
val lparams: KClass<*> = Any::class,
|
||||
val alias: String = "",
|
||||
val requireInit: Boolean = false,
|
||||
val requirePerformer: Boolean = false
|
||||
)
|
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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/3/31.
|
||||
*/
|
||||
package com.highcapable.hikage.annotation
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.highcapable.hikage.core.Hikage
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* Declare annotations to generate the [Hikage.Performer] function for the specified [view] at compile time.
|
||||
*
|
||||
* You can declare a class for [View] that does not belong to your own [View] or third-party library.
|
||||
*
|
||||
* The class name you define can be as good as you like, and notice it must to use the `object` keyword to modify it.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```kotlin
|
||||
* @HikageViewDeclaration(ThirdPartyView::class, FrameLayout.LayoutParams::class, alias = "ThirdPartyView")
|
||||
* object ThirdPartyViewDeclaration
|
||||
* ```
|
||||
* @param view the view class, only [View] can be specified.
|
||||
* @param lparams the layout params class, only [ViewGroup] can be specified,
|
||||
* after specifying, the `performer` parameter will be generated for the function.
|
||||
* The parameters must be a class inherited from [ViewGroup.LayoutParams],
|
||||
* if the current [View] does not inherit from [ViewGroup], this parameter will be ignored and warned.
|
||||
* @param alias the view's class name alias will naming the function, default is the [view] class name.
|
||||
* @param requireInit whether to force the `init` parameter to be called, default is false.
|
||||
* @param requirePerformer whether to force the `performer` parameter to be called, default is false,
|
||||
* this parameter will be ignored when no `performer` parameter is needed here.
|
||||
*/
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@MustBeDocumented
|
||||
annotation class HikageViewDeclaration(
|
||||
val view: KClass<*>,
|
||||
val lparams: KClass<*> = Any::class,
|
||||
val alias: String = "",
|
||||
val requireInit: Boolean = false,
|
||||
val requirePerformer: Boolean = false
|
||||
)
|
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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/3/23.
|
||||
*/
|
||||
package com.highcapable.hikage.annotation
|
||||
|
||||
import com.highcapable.hikage.core.Hikage
|
||||
|
||||
/**
|
||||
* Declare an implementation of the [Hikage.Performer] function that
|
||||
* will be used for infectious creation of sub-layouts.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```kotlin
|
||||
* @Hikageable
|
||||
* inline fun <reified LP : ViewGroup.LayoutParams> Hikage.Performer<LP>.MyView(
|
||||
* lparams: Hikage.LayoutParams? = null,
|
||||
* id: String? = null,
|
||||
* init: HikageView<MyView> = {}
|
||||
* ) = View<MyView>(id, init, lparams)
|
||||
* ```
|
||||
* @see Hikage.Performer
|
||||
*/
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
@MustBeDocumented
|
||||
annotation class Hikageable
|
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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/2/28.
|
||||
*/
|
||||
package com.highcapable.hikage.bypass
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import androidx.annotation.Keep
|
||||
|
||||
/**
|
||||
* Just a view for obtaining [AttributeSet].
|
||||
*
|
||||
* **DONT USE THIS VIEW IN YOUR LAYOUT.**
|
||||
*/
|
||||
@Keep
|
||||
class HikageAttrsView internal constructor(context: Context, internal val attrs: AttributeSet?) : View(context, attrs)
|
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* 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/3/5.
|
||||
*/
|
||||
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||
|
||||
package com.highcapable.hikage.bypass
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.res.XmlResourceParser
|
||||
import android.content.res.loader.AssetsProvider
|
||||
import android.content.res.loader.ResourcesProvider
|
||||
import android.util.AttributeSet
|
||||
import androidx.annotation.StyleRes
|
||||
import com.highcapable.betterandroid.system.extension.tool.SystemVersion
|
||||
import com.highcapable.betterandroid.ui.extension.view.inflateOrNull
|
||||
import com.highcapable.betterandroid.ui.extension.view.layoutInflater
|
||||
import com.highcapable.hikage.core.R
|
||||
import com.highcapable.yukireflection.factory.classOf
|
||||
import com.highcapable.yukireflection.factory.lazyClass
|
||||
import com.highcapable.yukireflection.type.android.AssetManagerClass
|
||||
import com.highcapable.yukireflection.type.java.BooleanType
|
||||
import com.highcapable.yukireflection.type.java.IntType
|
||||
import com.highcapable.yukireflection.type.java.LongType
|
||||
import com.highcapable.yukireflection.type.java.StringClass
|
||||
import org.lsposed.hiddenapibypass.HiddenApiBypass
|
||||
import android.R as Android_R
|
||||
|
||||
/**
|
||||
* Create a new XmlBlock with system api bypass reflection magic.
|
||||
*/
|
||||
internal object XmlBlockBypass {
|
||||
|
||||
/** The path used to load the apk assets represents an APK file. */
|
||||
private const val FORMAT_APK = 0
|
||||
|
||||
/** The path used to load the apk assets represents an idmap file. */
|
||||
private const val FORMAT_IDMAP = 1
|
||||
|
||||
/** The path used to load the apk assets represents an resources.arsc file. */
|
||||
private const val FORMAT_ARSC = 2
|
||||
|
||||
/** The path used to load the apk assets represents a directory. */
|
||||
private const val FORMAT_DIR = 3
|
||||
|
||||
/**
|
||||
* The apk assets contains framework resource values specified by the system.
|
||||
* This allows some functions to filter out this package when computing what
|
||||
* configurations/resources are available.
|
||||
*/
|
||||
private const val PROPERTY_SYSTEM = 1 shl 0
|
||||
|
||||
/**
|
||||
* The apk assets is a shared library or was loaded as a shared library by force.
|
||||
* The package ids of dynamic apk assets are assigned at runtime instead of compile time.
|
||||
*/
|
||||
private const val PROPERTY_DYNAMIC = 1 shl 1
|
||||
|
||||
/**
|
||||
* The apk assets has been loaded dynamically using a [ResourcesProvider].
|
||||
* Loader apk assets overlay resources like RROs except they are not backed by an idmap.
|
||||
*/
|
||||
private const val PROPERTY_LOADER = 1 shl 2
|
||||
|
||||
/**
|
||||
* The apk assets is a RRO.
|
||||
* An RRO overlays resource values of its target package.
|
||||
*/
|
||||
private const val PROPERTY_OVERLAY = 1 shl 3
|
||||
|
||||
/**
|
||||
* The apk assets is owned by the application running in this process and incremental crash
|
||||
* protections for this APK must be disabled.
|
||||
*/
|
||||
private const val PROPERTY_DISABLE_INCREMENTAL_HARDENING = 1 shl 4
|
||||
|
||||
/**
|
||||
* The apk assets only contain the overlayable declarations information.
|
||||
*/
|
||||
private const val PROPERTY_ONLY_OVERLAYABLES = 1 shl 5
|
||||
|
||||
/** The apk assets class. */
|
||||
private val ApkAssetsClass by lazyClass("android.content.res.ApkAssets")
|
||||
|
||||
/** The xml block class. */
|
||||
private val XmlBlockClass by lazyClass("android.content.res.XmlBlock")
|
||||
|
||||
/** Global pointer references object. */
|
||||
private var xmlBlock: Long? = null
|
||||
|
||||
/** Global pointer references object. */
|
||||
private var blockParser: AutoCloseable? = null
|
||||
|
||||
/** Whether the initialization is done once. */
|
||||
private var isInitOnce = false
|
||||
|
||||
/**
|
||||
* Initialize.
|
||||
* @param context the context.
|
||||
*/
|
||||
fun init(context: Context) {
|
||||
// Context may be loaded from the preview and other non-Android platforms, ignoring this.
|
||||
if (context.javaClass.name.endsWith("BridgeContext")) return
|
||||
init(context.applicationContext.applicationInfo)
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize.
|
||||
* @param info the application info.
|
||||
*/
|
||||
private fun init(info: ApplicationInfo) {
|
||||
if (SystemVersion.isLowOrEqualsTo(SystemVersion.P)) return
|
||||
if (isInitOnce) return
|
||||
val sourceDir = info.sourceDir
|
||||
xmlBlock = when {
|
||||
SystemVersion.isHighOrEqualsTo(SystemVersion.R) ->
|
||||
// private static native long nativeLoad(@FormatType int format, @NonNull String path,
|
||||
// @PropertyFlags int flags, @Nullable AssetsProvider asset) throws IOException;
|
||||
HiddenApiBypass.getDeclaredMethod(
|
||||
ApkAssetsClass, "nativeLoad",
|
||||
IntType, StringClass, IntType, classOf<AssetsProvider>()
|
||||
).apply { isAccessible = true }.invoke(null, FORMAT_APK, sourceDir, PROPERTY_SYSTEM, null)
|
||||
SystemVersion.isHighOrEqualsTo(SystemVersion.P) ->
|
||||
// private static native long nativeLoad(
|
||||
// @NonNull String path, boolean system, boolean forceSharedLib, boolean overlay)
|
||||
// throws IOException;
|
||||
HiddenApiBypass.getDeclaredMethod(
|
||||
ApkAssetsClass, "nativeLoad",
|
||||
StringClass, BooleanType, BooleanType, BooleanType
|
||||
).apply { isAccessible = true }.invoke(null, sourceDir, false, false, false)
|
||||
else -> error("Unsupported Android version.")
|
||||
} as? Long? ?: error("Failed to create ApkAssets.")
|
||||
blockParser = HiddenApiBypass.getDeclaredConstructor(XmlBlockClass, AssetManagerClass, LongType)
|
||||
.apply { isAccessible = true }
|
||||
.newInstance(null, xmlBlock) as? AutoCloseable? ?: error("Failed to create XmlBlock\$Parser.")
|
||||
isInitOnce = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new parser.
|
||||
* @param context the context.
|
||||
* @param resId the style resource id, default is [Android_R.style.Widget].
|
||||
* @return [XmlResourceParser]
|
||||
*/
|
||||
fun newParser(context: Context, @StyleRes resId: Int = Android_R.style.Widget): XmlResourceParser {
|
||||
/**
|
||||
* Create a view [AttributeSet].
|
||||
* @return [XmlResourceParser]
|
||||
*/
|
||||
fun createViewAttrs() = context.layoutInflater.inflateOrNull<HikageAttrsView>(R.layout.layout_hikage_attrs_view)?.attrs
|
||||
as? XmlResourceParser? ?: error("Failed to create AttributeSet.")
|
||||
return if (SystemVersion.isHighOrEqualsTo(SystemVersion.P)) {
|
||||
if (!isInitOnce) return createViewAttrs()
|
||||
require(blockParser != null) { "Hikage initialization failed." }
|
||||
HiddenApiBypass.getDeclaredMethod(XmlBlockClass, "newParser", IntType)
|
||||
.apply { isAccessible = true }
|
||||
.invoke(blockParser, resId) as? XmlResourceParser? ?: error("Failed to create parser.")
|
||||
} else createViewAttrs()
|
||||
}
|
||||
}
|
953
hikage-core/src/main/java/com/highcapable/hikage/core/Hikage.kt
Normal file
@@ -0,0 +1,953 @@
|
||||
/*
|
||||
* 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/2/25.
|
||||
*/
|
||||
@file:Suppress(
|
||||
"unused", "FunctionName", "PropertyName", "ConstPropertyName", "UNCHECKED_CAST",
|
||||
"MemberVisibilityCanBePrivate", "TOPLEVEL_TYPEALIASES_ONLY", "NON_PUBLIC_CALL_FROM_PUBLIC_INLINE"
|
||||
)
|
||||
|
||||
package com.highcapable.hikage.core
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.annotation.DimenRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.FontRes
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.highcapable.betterandroid.ui.extension.binding.ViewBinding
|
||||
import com.highcapable.betterandroid.ui.extension.component.base.DisplayDensity
|
||||
import com.highcapable.betterandroid.ui.extension.component.base.getColorCompat
|
||||
import com.highcapable.betterandroid.ui.extension.component.base.getColorStateListCompat
|
||||
import com.highcapable.betterandroid.ui.extension.component.base.getDrawableCompat
|
||||
import com.highcapable.betterandroid.ui.extension.component.base.getFontCompat
|
||||
import com.highcapable.betterandroid.ui.extension.component.base.toDp
|
||||
import com.highcapable.betterandroid.ui.extension.component.base.toPx
|
||||
import com.highcapable.betterandroid.ui.extension.view.LayoutParamsWrapContent
|
||||
import com.highcapable.betterandroid.ui.extension.view.ViewLayoutParams
|
||||
import com.highcapable.betterandroid.ui.extension.view.inflate
|
||||
import com.highcapable.betterandroid.ui.extension.view.layoutInflater
|
||||
import com.highcapable.hikage.annotation.Hikageable
|
||||
import com.highcapable.hikage.bypass.XmlBlockBypass
|
||||
import com.highcapable.hikage.core.Hikage.LayoutParamsBody
|
||||
import com.highcapable.hikage.core.base.HikageFactory
|
||||
import com.highcapable.hikage.core.base.HikageFactoryBuilder
|
||||
import com.highcapable.hikage.core.base.HikagePerformer
|
||||
import com.highcapable.hikage.core.base.HikageView
|
||||
import com.highcapable.hikage.core.base.PerformerException
|
||||
import com.highcapable.hikage.core.base.ProvideException
|
||||
import com.highcapable.hikage.core.extension.ResourcesScope
|
||||
import com.highcapable.yukireflection.factory.buildOf
|
||||
import com.highcapable.yukireflection.factory.classOf
|
||||
import com.highcapable.yukireflection.factory.constructor
|
||||
import com.highcapable.yukireflection.factory.current
|
||||
import com.highcapable.yukireflection.factory.notExtends
|
||||
import com.highcapable.yukireflection.type.android.AttributeSetClass
|
||||
import com.highcapable.yukireflection.type.android.ContextClass
|
||||
import com.highcapable.yukireflection.type.android.ViewGroup_LayoutParamsClass
|
||||
import com.highcapable.yukireflection.type.java.IntType
|
||||
import java.io.Serializable
|
||||
import java.lang.reflect.Constructor
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
/**
|
||||
* The Hikage core layout builder.
|
||||
*
|
||||
* A [Hikage] can have multiple levels of [Hikage.Performer].
|
||||
* @param factories the factories to customize the custom view in the initialization.
|
||||
*/
|
||||
class Hikage private constructor(private val factories: List<HikageFactory>) {
|
||||
|
||||
companion object {
|
||||
|
||||
/** The Android widget class prefix. */
|
||||
internal const val ANDROID_WIDGET_CLASS_PREFIX = "android.widget."
|
||||
|
||||
/** The unspecified layout params value. */
|
||||
private const val LayoutParamsUnspecified = LayoutParamsWrapContent - 1
|
||||
|
||||
/** The view constructors map. */
|
||||
private val viewConstructors = mutableMapOf<String, ViewConstructor>()
|
||||
|
||||
/** The view atomic id. */
|
||||
private val viewAtomicId = AtomicInteger(0x7F00000)
|
||||
|
||||
/** Returns nothing. */
|
||||
private fun noGetter(): Nothing = error("No getter available.")
|
||||
|
||||
/**
|
||||
* Automatically add [HikageFactory] to handle [LayoutInflater.Factory2].
|
||||
*
|
||||
* This [LayoutInflater] will be retrieved from the [Context] you passed in.
|
||||
*
|
||||
* This option is enabled by default and will add this feature when creating any [Hikage].
|
||||
* It can support the [View] autoboxing feature that supports libraries such as `androidx.appcompat`,
|
||||
* which can be disabled if you don't need it.
|
||||
*/
|
||||
var isAutoProcessWithFactory2 = true
|
||||
|
||||
/**
|
||||
* Create a new [Hikage].
|
||||
* @param context the context to create the layout.
|
||||
* @param parent the parent view group.
|
||||
* @param attachToParent whether to attach the layout to the parent when the [parent] is filled.
|
||||
* @param factory the [HikageFactory] builder.
|
||||
* @param performer the performer body.
|
||||
* @return [Hikage]
|
||||
*/
|
||||
@JvmName("createTyped")
|
||||
inline fun <reified LP : ViewGroup.LayoutParams> create(
|
||||
context: Context,
|
||||
parent: ViewGroup? = null,
|
||||
attachToParent: Boolean = parent != null,
|
||||
factory: HikageFactoryBuilder.() -> Unit = {},
|
||||
performer: HikagePerformer<LP>
|
||||
) = create(classOf<LP>(), context, parent, attachToParent, factory, performer)
|
||||
|
||||
/**
|
||||
* Create a new [Hikage].
|
||||
* @param context the context to create the layout.
|
||||
* @param parent the parent view group.
|
||||
* @param attachToParent whether to attach the layout to the parent when the [parent] is filled.
|
||||
* @param factory the [HikageFactory] builder.
|
||||
* @param performer the performer body.
|
||||
* @return [Hikage]
|
||||
*/
|
||||
inline fun create(
|
||||
context: Context,
|
||||
parent: ViewGroup? = null,
|
||||
attachToParent: Boolean = parent != null,
|
||||
factory: HikageFactoryBuilder.() -> Unit = {},
|
||||
performer: HikagePerformer<ViewGroup.LayoutParams>
|
||||
) = create(ViewGroup_LayoutParamsClass, context, parent, attachToParent, factory, performer)
|
||||
|
||||
/**
|
||||
* Create a new [Hikage].
|
||||
* @param lpClass the layout params type.
|
||||
* @param context the context to create the layout.
|
||||
* @param parent the parent view group.
|
||||
* @param attachToParent whether to attach the layout to the parent when the [parent] is filled.
|
||||
* @param factory the [HikageFactory] builder.
|
||||
* @param performer the performer body.
|
||||
* @return [Hikage]
|
||||
*/
|
||||
inline fun <LP : ViewGroup.LayoutParams> create(
|
||||
lpClass: Class<LP>,
|
||||
context: Context,
|
||||
parent: ViewGroup? = null,
|
||||
attachToParent: Boolean = parent != null,
|
||||
factory: HikageFactoryBuilder.() -> Unit = {},
|
||||
performer: HikagePerformer<LP>
|
||||
) = Hikage(HikageFactoryBuilder.create {
|
||||
if (isAutoProcessWithFactory2) add(HikageFactory(context.layoutInflater))
|
||||
factory()
|
||||
}.build()).apply {
|
||||
// If the parent view is specified and mark as attach to parent,
|
||||
// add it directly to the first position.
|
||||
if (parent != null && attachToParent) {
|
||||
val parentId = generateRandomViewId()
|
||||
provideView(parent, parentId)
|
||||
}; newPerformer(lpClass, parent, attachToParent, context).apply(performer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new [Hikage.Delegate].
|
||||
* @param factory the [HikageFactory] builder.
|
||||
* @param performer the performer body.
|
||||
* @return [Hikage.Delegate]<[LP]>
|
||||
*/
|
||||
@JvmName("buildTyped")
|
||||
inline fun <reified LP : ViewGroup.LayoutParams> build(
|
||||
noinline factory: HikageFactoryBuilder.() -> Unit = {},
|
||||
noinline performer: HikagePerformer<LP>
|
||||
) = build(classOf<LP>(), factory, performer)
|
||||
|
||||
/**
|
||||
* Create a new [Hikage.Delegate].
|
||||
* @param factory the [HikageFactory] builder.
|
||||
* @param performer the performer body.
|
||||
* @return [Hikage.Delegate]<[ViewGroup.LayoutParams]>
|
||||
*/
|
||||
fun build(
|
||||
factory: HikageFactoryBuilder.() -> Unit = {},
|
||||
performer: HikagePerformer<ViewGroup.LayoutParams>
|
||||
) = build(ViewGroup_LayoutParamsClass, factory, performer)
|
||||
|
||||
/**
|
||||
* Create a new [Hikage.Delegate].
|
||||
* @param lpClass the layout params type.
|
||||
* @param factory the [HikageFactory] builder.
|
||||
* @param performer the performer body.
|
||||
* @return [Hikage.Delegate]<[LP]>
|
||||
*/
|
||||
fun <LP : ViewGroup.LayoutParams> build(
|
||||
lpClass: Class<LP>,
|
||||
factory: HikageFactoryBuilder.() -> Unit = {},
|
||||
performer: HikagePerformer<LP>
|
||||
) = Delegate(lpClass, factory, performer)
|
||||
}
|
||||
|
||||
/** The [Hikage] layout params body type. */
|
||||
private typealias LayoutParamsBody<LP> = LP.() -> Unit
|
||||
|
||||
/**
|
||||
* The [Hikage.Performer] scope interface.
|
||||
*/
|
||||
private interface PerformerScope : DisplayDensity, ResourcesScope {
|
||||
|
||||
/**
|
||||
* Get the actual view id by [id].
|
||||
* @param id the view id.
|
||||
* @return [Int] or -1.
|
||||
*/
|
||||
fun actualViewId(id: String): Int
|
||||
}
|
||||
|
||||
/**
|
||||
* The view constructor class.
|
||||
* @param instance the constructor instance.
|
||||
* @param parameterCount the parameter count.
|
||||
*/
|
||||
private inner class ViewConstructor(
|
||||
private val instance: Constructor<*>,
|
||||
private val parameterCount: Int
|
||||
) {
|
||||
|
||||
/**
|
||||
* Build the view.
|
||||
* @param context the context.
|
||||
* @param attrs the attribute set.
|
||||
* @return [V] or null.
|
||||
*/
|
||||
fun <V : View> build(context: Context, attrs: AttributeSet) = when (parameterCount) {
|
||||
2 -> instance.newInstance(context, attrs)
|
||||
1 -> instance.newInstance(context)
|
||||
else -> null
|
||||
} as? V?
|
||||
}
|
||||
|
||||
/** The performer set. */
|
||||
private val performers = linkedSetOf<Performer<*>>()
|
||||
|
||||
/** The view map. */
|
||||
private val views = linkedMapOf<String, View>()
|
||||
|
||||
/** The view id map. */
|
||||
private val viewIds = mutableMapOf<String, Int>()
|
||||
|
||||
/**
|
||||
* Get the root view.
|
||||
* @return [View]
|
||||
*/
|
||||
val root get() = views.values.firstOrNull()
|
||||
?: throw PerformerException("No root view found, are you sure you have provided any view?")
|
||||
|
||||
/**
|
||||
* Get the root view [V].
|
||||
* @return [V]
|
||||
*/
|
||||
inline fun <reified V : View> root() = root as? V?
|
||||
?: throw PerformerException("Root view is not a type of ${classOf<V>().name}.")
|
||||
|
||||
/**
|
||||
* Get the view by [id].
|
||||
* @param id the view id.
|
||||
* @return [View]
|
||||
*/
|
||||
operator fun get(id: String) = views[id]
|
||||
?: throw PerformerException("View with id \"$id\" not found.")
|
||||
|
||||
/**
|
||||
* Get the view by [id].
|
||||
* @param id the view id.
|
||||
* @return [View] or null.
|
||||
*/
|
||||
fun getOrNull(id: String) = views[id]
|
||||
|
||||
/**
|
||||
* Get the view by [id] via [V].
|
||||
* @param id the view id.
|
||||
* @return [V]
|
||||
*/
|
||||
@JvmName("getTyped")
|
||||
inline fun <reified V : View> get(id: String) = get(id) as? V
|
||||
?: throw PerformerException("View with id \"$id\" is not a ${classOf<V>().name}.")
|
||||
|
||||
/**
|
||||
* Get the view by [id] via [V].
|
||||
* @param id the view id.
|
||||
* @return [V] or null.
|
||||
*/
|
||||
@JvmName("getOrNullTyped")
|
||||
inline fun <reified V : View> getOrNull(id: String) = getOrNull(id) as? V?
|
||||
|
||||
/**
|
||||
* Get the actual view id by [id].
|
||||
* @param id the view id.
|
||||
* @return [Int] or -1.
|
||||
*/
|
||||
fun getActualViewId(id: String) = viewIds[id] ?: -1
|
||||
|
||||
/**
|
||||
* Create a new [View] via [V].
|
||||
* @param viewClass the view class.
|
||||
* @param id the view id, generated by default.
|
||||
* @param context the context.
|
||||
* @return [V]
|
||||
*/
|
||||
private fun <V : View> createView(viewClass: Class<V>, id: String?, context: Context): V {
|
||||
val attrs = createAttributeSet(context)
|
||||
val view = createViewFromFactory(viewClass, id, context, attrs) ?: getViewConstructor(viewClass)?.build(context, attrs)
|
||||
if (view == null) throw PerformerException(
|
||||
"Create view of type ${viewClass.name} failed. " +
|
||||
"Please make sure the view class has a constructor with a single parameter of type Context."
|
||||
)
|
||||
provideView(view, id)
|
||||
return view
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the view constructor.
|
||||
* @param viewClass the view class.
|
||||
* @return [ViewConstructor] or null.
|
||||
*/
|
||||
private fun <V : View> getViewConstructor(viewClass: Class<V>) =
|
||||
viewConstructors[viewClass.name] ?: run {
|
||||
var parameterCount = 0
|
||||
val twoParams = viewClass.constructor {
|
||||
param(ContextClass, AttributeSetClass)
|
||||
}.ignored().give()
|
||||
val onceParam = viewClass.constructor {
|
||||
param(ContextClass)
|
||||
}.ignored().give()
|
||||
val constructor = onceParam?.apply { parameterCount = 1 }
|
||||
?: twoParams?.apply { parameterCount = 2 }
|
||||
val viewConstructor = constructor?.let { ViewConstructor(it, parameterCount) }
|
||||
if (viewConstructor != null) viewConstructors[viewClass.name] = viewConstructor
|
||||
viewConstructor
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new [View] from [HikageFactory].
|
||||
* @param viewClass the view class.
|
||||
* @param id the view id.
|
||||
* @param context the context.
|
||||
* @param attrs the attribute set.
|
||||
* @return [V] or null.
|
||||
*/
|
||||
private fun <V : View> createViewFromFactory(viewClass: Class<V>, id: String?, context: Context, attrs: AttributeSet): V? {
|
||||
val parent = performers.firstOrNull()?.parent
|
||||
var processed: V? = null
|
||||
factories.forEach { factory ->
|
||||
val params = PerformerParams(id, attrs, viewClass as Class<View>)
|
||||
val view = factory(parent, processed, context, params)
|
||||
if (view != null && view.javaClass notExtends viewClass) throw PerformerException(
|
||||
"HikageFactory cannot cast the created view type \"${view.javaClass}\" to \"${viewClass.name}\", " +
|
||||
"please confirm that the view type you created is correct."
|
||||
)
|
||||
if (view != null) processed = view as? V?
|
||||
}; return processed
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide an exists [View].
|
||||
* @param view the view instance.
|
||||
* @param id the view id.
|
||||
* @return [String]
|
||||
*/
|
||||
private fun provideView(view: View, id: String?): String {
|
||||
val (requireId, viewId) = generateViewId(id)
|
||||
view.id = viewId
|
||||
views[requireId] = view
|
||||
return requireId
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate view id from [id].
|
||||
* @param id the view id.
|
||||
* @return [Pair]<[String], [Int]>
|
||||
*/
|
||||
private fun generateViewId(id: String?): Pair<String, Int> {
|
||||
/**
|
||||
* Generate a new view id.
|
||||
* @param id the view id.
|
||||
* @return [Int]
|
||||
*/
|
||||
fun doGenerate(id: String): Int {
|
||||
val generateId = View.generateViewId()
|
||||
if (viewIds.contains(id)) throw PerformerException("View with id \"$id\" already exists.")
|
||||
viewIds[id] = generateId
|
||||
return generateId
|
||||
}
|
||||
val requireId = id ?: generateRandomViewId()
|
||||
val viewId = doGenerate(requireId)
|
||||
return requireId to viewId
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate random view id.
|
||||
* @return [String]
|
||||
*/
|
||||
private fun generateRandomViewId() = "anonymous@${viewAtomicId.getAndIncrement().toHexString()}"
|
||||
|
||||
/**
|
||||
* We just need a [AttributeSet] instance.
|
||||
* @param context the context.
|
||||
* @return [AttributeSet]
|
||||
*/
|
||||
private fun createAttributeSet(context: Context): AttributeSet = XmlBlockBypass.newParser(context)
|
||||
|
||||
/**
|
||||
* Start a new performer [LP].
|
||||
* @param parent the parent view group.
|
||||
* @param attachToParent whether to attach the layout to the parent when the [parent] is filled.
|
||||
* @param context the context, priority is given to [parent]'s context.
|
||||
* @return [Performer]
|
||||
*/
|
||||
private inline fun <reified LP : ViewGroup.LayoutParams> newPerformer(
|
||||
parent: ViewGroup? = null,
|
||||
attachToParent: Boolean = parent != null,
|
||||
context: Context? = null
|
||||
) = newPerformer(classOf<LP>(), parent, attachToParent, context)
|
||||
|
||||
/**
|
||||
* Start a new performer [LP].
|
||||
* @param lpClass the layout params type.
|
||||
* @param parent the parent view group.
|
||||
* @param attachToParent whether to attach the layout to the parent when the [parent] is filled.
|
||||
* @param context the context, priority is given to [parent]'s context.
|
||||
* @return [Performer]
|
||||
*/
|
||||
private fun <LP : ViewGroup.LayoutParams> newPerformer(
|
||||
lpClass: Class<LP>,
|
||||
parent: ViewGroup? = null,
|
||||
attachToParent: Boolean = parent != null,
|
||||
context: Context? = null
|
||||
) = Performer(lpClass, parent, attachToParent, context).apply {
|
||||
// Init [XmlBlockBypass] if the context is not null.
|
||||
context?.let { XmlBlockBypass.init(it) }
|
||||
performers.add(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Require no [Performer].
|
||||
* @param name the process name.
|
||||
* @param block the block.
|
||||
*/
|
||||
internal inline fun requireNoPerformers(name: String, block: () -> Unit) {
|
||||
val viewCount = views.size
|
||||
block()
|
||||
if (views.size != viewCount) throw PerformerException(
|
||||
"Performers are not allowed to appear in $name DSL creation process."
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Include a delegate.
|
||||
* @param delegate the delegate.
|
||||
* @param context the context.
|
||||
* @param embedded the embedded flag.
|
||||
* @return [Hikage]
|
||||
*/
|
||||
private fun include(delegate: Delegate<*>, context: Context, embedded: Boolean): Hikage {
|
||||
val hikage = delegate.create(context)
|
||||
if (!embedded) return hikage
|
||||
val duplicateId = hikage.viewIds.toList().firstOrNull { (k, _) -> viewIds.containsKey(k) }?.first
|
||||
if (duplicateId != null) throw PerformerException(
|
||||
"Embedded layout view IDs conflict, the view id \"$duplicateId\" is already exists."
|
||||
)
|
||||
viewIds.putAll(hikage.viewIds)
|
||||
views.putAll(hikage.views)
|
||||
performers.addAll(hikage.performers)
|
||||
return hikage
|
||||
}
|
||||
|
||||
/**
|
||||
* The core performer entity of layout build [LP].
|
||||
* @param parent the parent view group.
|
||||
* @param attachToParent whether to attach the layout to the parent.
|
||||
* @param lpClass the layout params type.
|
||||
* @param baseContext the context to create the layout, priority is given to [parent]'s context.
|
||||
* if [parent] is null, it must be set manually.
|
||||
*/
|
||||
inner class Performer<LP : ViewGroup.LayoutParams> internal constructor(
|
||||
private val lpClass: Class<LP>,
|
||||
internal val parent: ViewGroup?,
|
||||
private val attachToParent: Boolean,
|
||||
private val baseContext: Context? = null
|
||||
) : PerformerScope {
|
||||
|
||||
/** The current [Hikage]. */
|
||||
private val current get() = this@Hikage
|
||||
|
||||
/**
|
||||
* The context to create the layout.
|
||||
* @return [Context]
|
||||
*/
|
||||
private val context get() = parent?.context
|
||||
?: baseContext
|
||||
?: throw PerformerException("Parent layout is null or broken, Hikage.Performer need a Context to create the layout.")
|
||||
|
||||
override fun actualViewId(id: String) = getActualViewId(id)
|
||||
|
||||
override fun stringResource(@StringRes resId: Int, vararg formatArgs: Any) =
|
||||
if (formatArgs.isNotEmpty())
|
||||
context.getString(resId, *formatArgs)
|
||||
else context.getString(resId)
|
||||
override fun colorResource(@ColorRes resId: Int) = context.getColorCompat(resId)
|
||||
override fun stateColorResource(@ColorRes resId: Int) = context.getColorStateListCompat(resId)
|
||||
override fun drawableResource(@DrawableRes resId: Int) = context.getDrawableCompat(resId)
|
||||
override fun bitmapResource(@DrawableRes resId: Int) = context.getDrawableCompat(resId).toBitmap()
|
||||
override fun dimenResource(@DimenRes resId: Int) = context.resources.getDimension(resId)
|
||||
override fun fontResource(@FontRes resId: Int) = context.getFontCompat(resId)
|
||||
|
||||
override fun <N : Number> N.toPx() = toPx(context)
|
||||
override fun <N : Number> N.toDp() = toDp(context)
|
||||
|
||||
/** The count of providing views. */
|
||||
private var provideCount = 0
|
||||
|
||||
/**
|
||||
* Provide a new [View] instance [V].
|
||||
* @param lparams the view layout params.
|
||||
* @param id the view id, generated by default.
|
||||
* @param init the view initialization body.
|
||||
* @return [V]
|
||||
*/
|
||||
@Hikageable
|
||||
@JvmName("ViewTyped")
|
||||
inline fun <reified V : View> View(
|
||||
lparams: LayoutParams? = null,
|
||||
id: String? = null,
|
||||
init: HikageView<V> = {}
|
||||
): V {
|
||||
val lpDelegate = LayoutParams.from(current, lpClass, parent, lparams)
|
||||
val view = createView(classOf<V>(), id, context)
|
||||
view.layoutParams = lpDelegate.create()
|
||||
requireNoPerformers(classOf<V>().name) { view.init() }
|
||||
startProvide<V>(id)
|
||||
addToParentIfRequired(view)
|
||||
return view
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a new [View] instance.
|
||||
* @param lparams the view layout params.
|
||||
* @param id the view id, generated by default.
|
||||
* @param init the view initialization body.
|
||||
* @return [View]
|
||||
*/
|
||||
@Hikageable
|
||||
inline fun View(
|
||||
lparams: LayoutParams? = null,
|
||||
id: String? = null,
|
||||
init: HikageView<View> = {}
|
||||
) = View<View>(lparams, id, init)
|
||||
|
||||
/**
|
||||
* Provide a new [ViewGroup] instance [VG].
|
||||
*
|
||||
* Provide the new type of [ViewGroup.LayoutParams] down via [LP].
|
||||
*
|
||||
* - Note: The [VG] must be inherited from [ViewGroup].
|
||||
* @param lparams the view layout params.
|
||||
* @param id the view id, generated by default.
|
||||
* @param init the view initialization body.
|
||||
* @param performer the performer body.
|
||||
* @return [VG]
|
||||
*/
|
||||
@Hikageable
|
||||
@JvmName("ViewGroupLP")
|
||||
inline fun <reified VG : ViewGroup, reified LP : ViewGroup.LayoutParams> ViewGroup(
|
||||
lparams: LayoutParams? = null,
|
||||
id: String? = null,
|
||||
init: HikageView<VG> = {},
|
||||
performer: HikagePerformer<LP> = {}
|
||||
): VG {
|
||||
val lpDelegate = LayoutParams.from(current, lpClass, parent, lparams)
|
||||
val view = createView(classOf<VG>(), id, context)
|
||||
view.layoutParams = lpDelegate.create()
|
||||
requireNoPerformers(classOf<VG>().name) { view.init() }
|
||||
startProvide<VG>(id)
|
||||
addToParentIfRequired(view)
|
||||
newPerformer<LP>(view).apply(performer)
|
||||
return view
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a new [ViewGroup] instance [VG].
|
||||
*
|
||||
* - Note: The [VG] must be inherited from [ViewGroup].
|
||||
* @param lparams the view layout params.
|
||||
* @param id the view id, generated by default.
|
||||
* @param init the view initialization body.
|
||||
* @param performer the performer body.
|
||||
* @return [VG]
|
||||
*/
|
||||
@Hikageable
|
||||
inline fun <reified VG : ViewGroup> ViewGroup(
|
||||
lparams: LayoutParams? = null,
|
||||
id: String? = null,
|
||||
init: HikageView<VG> = {},
|
||||
performer: HikagePerformer<ViewGroup.LayoutParams> = {}
|
||||
) = ViewGroup<VG, ViewGroup.LayoutParams>(lparams, id, init, performer)
|
||||
|
||||
/**
|
||||
* Provide layout from [resId].
|
||||
* @param resId the layout resource id.
|
||||
* @param lparams the view layout params.
|
||||
* @param id the view id, generated by default.
|
||||
*/
|
||||
@Hikageable
|
||||
fun Layout(
|
||||
@LayoutRes resId: Int,
|
||||
lparams: LayoutParams? = null,
|
||||
id: String? = null
|
||||
): View {
|
||||
val view = context.layoutInflater.inflate(resId, parent, attachToRoot = false)
|
||||
startProvide<View>(id, view)
|
||||
lparams?.create()?.let { view.layoutParams = it }
|
||||
provideView(view, id)
|
||||
addToParentIfRequired(view)
|
||||
return view
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide layout from [ViewBinding].
|
||||
* @param lparams the view layout params.
|
||||
* @param id the view id, generated by default.
|
||||
* @return [VB]
|
||||
*/
|
||||
@Hikageable
|
||||
inline fun <reified VB : ViewBinding> Layout(
|
||||
lparams: LayoutParams? = null,
|
||||
id: String? = null
|
||||
): VB {
|
||||
val viewBinding = ViewBinding<VB>().inflate(context.layoutInflater, parent, attachToParent = false)
|
||||
val view = viewBinding.root
|
||||
startProvide<View>(id, view)
|
||||
if (view.parent != null) throw ProvideException(
|
||||
"The ViewBinding($view) already has a parent, " +
|
||||
"it may have been created using layout root node <merge> or <include>, cannot be provided."
|
||||
)
|
||||
lparams?.create()?.let { view.layoutParams = it }
|
||||
provideView(view, id)
|
||||
addToParentIfRequired(view)
|
||||
return viewBinding
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide layout from exists [View].
|
||||
* @param view the view instance.
|
||||
* @param lparams the view layout params.
|
||||
* @param id the view id, generated by default.
|
||||
* @return [View]
|
||||
*/
|
||||
@Hikageable
|
||||
fun Layout(
|
||||
view: View,
|
||||
lparams: LayoutParams? = null,
|
||||
id: String? = null
|
||||
): View {
|
||||
if (view.parent != null) throw ProvideException("The view $view already has a parent, cannot be provided.")
|
||||
startProvide<View>(id, view)
|
||||
val lpDelegate = LayoutParams.from(current = this@Hikage, lpClass, parent, lparams, view.layoutParams)
|
||||
view.layoutParams = lpDelegate.create()
|
||||
provideView(view, id)
|
||||
addToParentIfRequired(view)
|
||||
return view
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide layout from another [Hikage].
|
||||
*
|
||||
* - Note: [Hikage] view IDs will not copied to this layout
|
||||
* when provides a complete [Hikage] instead of [Delegate].
|
||||
*
|
||||
* ```kotlin
|
||||
* val first: Hikage = Hikageable(context) {
|
||||
* TextView(id = "textView")
|
||||
* }
|
||||
* val second: Hikage = Hikageable(context) {
|
||||
* TextView(id = "textView")
|
||||
* // The view ID "textView" will not copied to this layout,
|
||||
* // so there will be no problem of ID overlap.
|
||||
* Layout(first)
|
||||
* }
|
||||
* ```
|
||||
* @param hikage the hikage instance.
|
||||
* @param lparams the view layout params.
|
||||
* @param id the view id, generated by default.
|
||||
* @return [Hikage]
|
||||
*/
|
||||
@Hikageable
|
||||
fun Layout(
|
||||
hikage: Hikage,
|
||||
lparams: LayoutParams? = null,
|
||||
id: String? = null
|
||||
): Hikage {
|
||||
val view = hikage.root
|
||||
startProvide<View>(id, view)
|
||||
val lpDelegate = LayoutParams.from(current = this@Hikage, lpClass, parent, lparams, view.layoutParams)
|
||||
if (view.parent != null) throw ProvideException(
|
||||
"The Hikage layout root view $view already has a parent, cannot be provided."
|
||||
)
|
||||
view.layoutParams = lpDelegate.create()
|
||||
provideView(view, id)
|
||||
addToParentIfRequired(view)
|
||||
return hikage
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide layout from another [Delegate].
|
||||
*
|
||||
* - Note: If you use [embedded], you must make sure that the introduced view IDs
|
||||
* do not overlap with the view IDs in the current layout.
|
||||
* @param delegate the delegate instance.
|
||||
* @param lparams the view layout params.
|
||||
* @param id the view id, generated by default.
|
||||
* @param embedded whether to embed this layout (All view IDs and performers will be copied),
|
||||
* default is true.
|
||||
* @return [Hikage]
|
||||
*/
|
||||
@Hikageable
|
||||
fun Layout(
|
||||
delegate: Delegate<*>,
|
||||
lparams: LayoutParams? = null,
|
||||
id: String? = null,
|
||||
embedded: Boolean = true
|
||||
): Hikage {
|
||||
val hikage = include(delegate, context, embedded)
|
||||
return Layout(hikage, lparams, id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new layout params.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```kotlin
|
||||
* View(
|
||||
* lparams = LayoutParams {
|
||||
* // You code here.
|
||||
* }
|
||||
* )
|
||||
* ```
|
||||
* @see ViewLayoutParams
|
||||
* @param body the layout params body.
|
||||
* @return [LayoutParams]
|
||||
*/
|
||||
fun LayoutParams(
|
||||
width: Int = LayoutParamsUnspecified,
|
||||
height: Int = LayoutParamsUnspecified,
|
||||
matchParent: Boolean = false,
|
||||
widthMatchParent: Boolean = false,
|
||||
heightMatchParent: Boolean = false,
|
||||
body: LayoutParamsBody<LP> = {}
|
||||
) = LayoutParams.from(
|
||||
current, lpClass, parent,
|
||||
width, height, matchParent, widthMatchParent, heightMatchParent,
|
||||
body = body
|
||||
)
|
||||
|
||||
/** If required, add the [view] to the [parent]. */
|
||||
private fun addToParentIfRequired(view: View) {
|
||||
if (attachToParent) parent?.addView(view)
|
||||
}
|
||||
|
||||
/**
|
||||
* Call to start providing a new view.
|
||||
* @param id the view id.
|
||||
* @param view the view instance.
|
||||
*/
|
||||
private inline fun <reified V : View> startProvide(id: String?, view: V? = null) {
|
||||
provideCount++
|
||||
if (provideCount > 1 && (parent == null || !attachToParent)) throw ProvideException(
|
||||
"Provide view ${view?.javaClass ?: classOf<V>()}(${id?.let { "\"$it\""} ?: "<anonymous>"}) failed. ${
|
||||
if (parent == null) "No parent view group found"
|
||||
else "Parent view group declares attachToParent = false"
|
||||
}, you can only provide one view for the root view."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The parameters of the [Performer].
|
||||
* @param id the view ID.
|
||||
* @param attrs the attributes set.
|
||||
* @param viewClass the view class.
|
||||
*/
|
||||
data class PerformerParams internal constructor(
|
||||
val id: String?,
|
||||
val attrs: AttributeSet,
|
||||
val viewClass: Class<View>
|
||||
) : Serializable
|
||||
|
||||
/**
|
||||
* The [Hikage] layout params.
|
||||
* @see ViewLayoutParams
|
||||
* @param current the current [Hikage].
|
||||
* @param lpClass the layout params type.
|
||||
* @param parent the parent view group.
|
||||
*/
|
||||
class LayoutParams private constructor(
|
||||
private val current: Hikage,
|
||||
private val lpClass: Class<ViewGroup.LayoutParams>,
|
||||
private val parent: ViewGroup?
|
||||
) {
|
||||
|
||||
/**
|
||||
* Builder params of body.
|
||||
*/
|
||||
private class BodyBuilder(
|
||||
val width: Int,
|
||||
val height: Int,
|
||||
val matchParent: Boolean,
|
||||
val widthMatchParent: Boolean,
|
||||
val heightMatchParent: Boolean,
|
||||
val body: LayoutParamsBody<ViewGroup.LayoutParams>
|
||||
)
|
||||
|
||||
/**
|
||||
* Builder params of wrapper.
|
||||
*/
|
||||
private class WrapperBuilder(
|
||||
val delegate: LayoutParams?,
|
||||
val lparams: ViewGroup.LayoutParams?
|
||||
)
|
||||
|
||||
/** The layout params body. */
|
||||
private var bodyBuilder: BodyBuilder? = null
|
||||
|
||||
/** The layout params wrapper. */
|
||||
private var wrapperBuilder: WrapperBuilder? = null
|
||||
|
||||
internal companion object {
|
||||
|
||||
/**
|
||||
* Create a new [LayoutParams]
|
||||
* @see ViewLayoutParams
|
||||
* @param current the current [Hikage].
|
||||
* @param parent the parent view group.
|
||||
* @return [LayoutParams]
|
||||
*/
|
||||
fun <LP : ViewGroup.LayoutParams> from(
|
||||
current: Hikage,
|
||||
lpClass: Class<LP>,
|
||||
parent: ViewGroup?,
|
||||
width: Int,
|
||||
height: Int,
|
||||
matchParent: Boolean,
|
||||
widthMatchParent: Boolean,
|
||||
heightMatchParent: Boolean,
|
||||
body: LayoutParamsBody<LP>
|
||||
) = LayoutParams(current, lpClass as Class<ViewGroup.LayoutParams>, parent).apply {
|
||||
bodyBuilder = BodyBuilder(
|
||||
width, height, matchParent, widthMatchParent, heightMatchParent,
|
||||
body as LayoutParamsBody<ViewGroup.LayoutParams>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new [LayoutParams].
|
||||
* @param current the current [Hikage].
|
||||
* @param parent the parent view group.
|
||||
* @param delegate the delegate.
|
||||
* @param lparams the another layout params.
|
||||
* @return [LayoutParams]
|
||||
*/
|
||||
fun <LP : ViewGroup.LayoutParams> from(
|
||||
current: Hikage,
|
||||
lpClass: Class<LP>,
|
||||
parent: ViewGroup?,
|
||||
delegate: LayoutParams?,
|
||||
lparams: ViewGroup.LayoutParams? = null
|
||||
) = LayoutParams(current, lpClass as Class<ViewGroup.LayoutParams>, parent).apply {
|
||||
wrapperBuilder = WrapperBuilder(delegate, lparams)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a default layout params.
|
||||
* @return [ViewGroup.LayoutParams]
|
||||
*/
|
||||
private fun createDefaultLayoutParams(lparams: ViewGroup.LayoutParams? = null): ViewGroup.LayoutParams {
|
||||
val wrapped = lparams?.let {
|
||||
parent?.current(ignored = true)?.method {
|
||||
name = "generateLayoutParams"
|
||||
param(ViewGroup_LayoutParamsClass)
|
||||
superClass()
|
||||
}?.invoke<ViewGroup.LayoutParams?>(it)
|
||||
} ?: lparams
|
||||
return wrapped
|
||||
// Build a default.
|
||||
?: lpClass.buildOf<ViewGroup.LayoutParams>(LayoutParamsWrapContent, LayoutParamsWrapContent) {
|
||||
param(IntType, IntType)
|
||||
} ?: throw PerformerException("Create default layout params failed.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the layout params.
|
||||
* @return [ViewGroup.LayoutParams]
|
||||
*/
|
||||
fun create(): ViewGroup.LayoutParams {
|
||||
if (bodyBuilder == null && wrapperBuilder == null) throw PerformerException("No layout params builder found.")
|
||||
return bodyBuilder?.let {
|
||||
val lparams = ViewLayoutParams(lpClass, it.width, it.height, it.matchParent, it.widthMatchParent, it.heightMatchParent)
|
||||
current.requireNoPerformers(lparams.javaClass.name) { it.body(lparams) }
|
||||
lparams
|
||||
} ?: wrapperBuilder?.let {
|
||||
val lparams = it.delegate?.create() ?: it.lparams
|
||||
createDefaultLayoutParams(lparams)
|
||||
} ?: throw PerformerException("Internal error of build layout params.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The delegate for [Hikage].
|
||||
* @param lpClass the layout params type.
|
||||
* @param factory the [HikageFactory] builder.
|
||||
* @param performer the performer body.
|
||||
*/
|
||||
class Delegate<LP : ViewGroup.LayoutParams> internal constructor(
|
||||
private val lpClass: Class<LP>,
|
||||
private val factory: HikageFactoryBuilder.() -> Unit = {},
|
||||
private val performer: HikagePerformer<LP>
|
||||
) {
|
||||
|
||||
/**
|
||||
* Create a new [Hikage].
|
||||
* @param context the context to create the layout.
|
||||
* @param parent the parent view group.
|
||||
* @param attachToParent whether to attach the layout to the parent when the [parent] is filled.
|
||||
* @return [Hikage]
|
||||
*/
|
||||
|
||||
fun create(context: Context, parent: ViewGroup? = null, attachToParent: Boolean = parent != null) =
|
||||
create(lpClass, context, parent, attachToParent, factory, performer)
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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/2/26.
|
||||
*/
|
||||
@file:JvmName("ExceptionsUtils")
|
||||
|
||||
package com.highcapable.hikage.core.base
|
||||
|
||||
/**
|
||||
* The exception of performing view.
|
||||
* @param message the exception message.
|
||||
*/
|
||||
internal class PerformerException(message: String) : Exception(message)
|
||||
|
||||
/**
|
||||
* The exception of providing view.
|
||||
* @param message the exception message.
|
||||
*/
|
||||
internal class ProvideException(message: String) : Exception(message)
|