Compare commits

...

16 Commits

Author SHA1 Message Date
ddab346bf6 chore: bump dependencies 2025-09-06 23:32:23 +08:00
8ee3ae17ba docs: update reference link 2025-08-24 02:50:00 +08:00
0d99806004 Bump hikage-core version to 1.0.2, hikage-extension, hikage-extension-betterandroid, hikage-compiler, hikage-widget-androidx, hikage-widget-material version to 1.0.1 2025-08-24 02:39:05 +08:00
1cb95849ab chore: downgrade lint and lock version 2025-08-24 02:35:07 +08:00
714c8552ab feat: add SurfaceView, WebView in Widgets 2025-08-24 00:07:57 +08:00
18ccf196b6 refactor: adjust some components that are not allow to add performer param 2025-08-23 23:48:05 +08:00
76df8fa06c feat: add final param in HikageView, HikageViewDeclaration 2025-08-23 22:39:35 +08:00
8e25430d68 feat: add MotionLayout, ImageFilterButton, ImageFilterView, MockView, MotionButton, MotionLabel, MotionTelltales in ConstraintLayout 2025-08-23 21:36:28 +08:00
0529d6a2b6 feat: add LP generic function in ViewGroup 2025-08-23 21:22:05 +08:00
51a59b672c chore: update jdk to 21 2025-08-19 15:48:34 +08:00
59d09b9611 chore: bump gradle to 8.14.3 2025-08-19 15:48:00 +08:00
b6c46b7240 chore: bump dependencies 2025-08-19 15:45:58 +08:00
646e5e5056 chore: bump dependencies
Some checks failed
Deploy to GitHub pages / docs (push) Has been cancelled
2025-08-16 01:48:55 +08:00
14ba71cc2b chore: update target sdk to 36 2025-08-03 23:30:02 +08:00
ee97222bcb refactor: merge to BetterAndroid new usage 2025-08-03 23:27:46 +08:00
8990e03e97 chore: update project files 2025-08-03 03:56:26 +08:00
61 changed files with 481 additions and 190 deletions

View File

@@ -29,10 +29,10 @@ jobs:
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 18 node-version: 18
- name: Prepare Java 17 - name: Prepare Java 21
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
java-version: 17 java-version: 21
java-package: jdk java-package: jdk
distribution: 'temurin' distribution: 'temurin'
cache: 'gradle' cache: 'gradle'

View File

@@ -0,0 +1 @@
2019 HighCapable

View File

@@ -0,0 +1,13 @@
* 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.

View File

@@ -0,0 +1,13 @@
/*
* #parse("project-name") - #parse("project-description")
* Copyright (C) #parse("copyright-name")
* #parse("project-url")
*
#parse("license-content")
*
* This file is created by $USER on $DATE.
*/

View File

@@ -0,0 +1 @@
An Android responsive UI building tool.

View File

@@ -0,0 +1 @@
Hikage

View File

@@ -0,0 +1 @@
https://github.com/BetterAndroid/Hikage

View File

@@ -0,0 +1,6 @@
#parse("open-source-license-header")
#if (${PACKAGE_NAME} != "")package ${PACKAGE_NAME}
#end
annotation class ${NAME}

View File

@@ -0,0 +1,7 @@
#parse("open-source-license-header")
#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME}
#end
class ${NAME} {
}

View File

@@ -0,0 +1,6 @@
#parse("open-source-license-header")
#if (${PACKAGE_NAME} != "")package ${PACKAGE_NAME}
#end
data class ${NAME}()

View File

@@ -0,0 +1,7 @@
#parse("open-source-license-header")
#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME}
#end
enum class ${NAME} {
}

View File

@@ -0,0 +1,5 @@
#parse("open-source-license-header")
#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME}
#end

View File

@@ -0,0 +1,7 @@
#parse("open-source-license-header")
#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME}
#end
interface ${NAME} {
}

View File

@@ -0,0 +1,7 @@
#parse("open-source-license-header")
#if (${PACKAGE_NAME} != "")package ${PACKAGE_NAME}
#end
object ${NAME} {
}

View File

@@ -2,6 +2,8 @@ import com.android.build.gradle.LibraryExtension
import com.vanniktech.maven.publish.AndroidSingleVariantLibrary import com.vanniktech.maven.publish.AndroidSingleVariantLibrary
import com.vanniktech.maven.publish.MavenPublishBaseExtension import com.vanniktech.maven.publish.MavenPublishBaseExtension
import org.jetbrains.dokka.gradle.DokkaTask import org.jetbrains.dokka.gradle.DokkaTask
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
plugins { plugins {
autowire(libs.plugins.android.application) apply false autowire(libs.plugins.android.application) apply false
@@ -65,6 +67,20 @@ libraryProjects {
} }
} }
allprojects {
tasks.withType<KotlinJvmCompile>().configureEach {
compilerOptions {
jvmTarget = JvmTarget.JVM_17
freeCompilerArgs.addAll(
"-opt-in=kotlin.ExperimentalStdlibApi",
"-Xno-param-assertions",
"-Xno-call-assertions",
"-Xno-receiver-assertions"
)
}
}
}
fun libraryProjects(action: Action<in Project>) { fun libraryProjects(action: Action<in Project>) {
val libraries = listOf( val libraries = listOf(
Libraries.HIKAGE_CORE, Libraries.HIKAGE_CORE,

View File

@@ -18,7 +18,16 @@ Time zone of version release date: **UTC+8**
## hikage-core ## hikage-core
### 1.0.1 | 2025.05.06 &ensp;<Badge type="tip" text="latest" vertical="middle" /> ### 1.0.2 | 2025.08.24 &ensp;<Badge type="tip" text="latest" vertical="middle" />
- Migrated Java reflection related behaviors from [YukiReflection](https://github.com/HighCapable/YukiReflection) to [KavaRef](https://github.com/HighCapable/KavaRef)
- Adapted to Android 16 (API 36), fixed the `XmlBlock` crash issue on Android 16
- Optimized layout performance, removed unnecessary inline operations, added caching for reflection operations
- Added `final` parameter to `HikageView` and `HikageViewDeclaration` to support new features in `hikage-compiler`
- Added `SurfaceView` and `WebView` built-in components to `Widgets`
- Adjusted some components in `Widgets` to be `final`
### 1.0.1 | 2025.05.06 &ensp;<Badge type="warning" text="stale" vertical="middle" />
- Fixed the issue where the KSP source code was not successfully released - Fixed the issue where the KSP source code was not successfully released
- Added states management feature - Added states management feature
@@ -29,19 +38,32 @@ Time zone of version release date: **UTC+8**
## hikage-compiler ## hikage-compiler
### 1.0.0 | 2025.04.20 &ensp;<Badge type="tip" text="latest" vertical="middle" /> ### 1.0.1 | 2025.08.24 &ensp;<Badge type="tip" text="latest" vertical="middle" />
- Added support for the `final` parameter of `HikageView` and `HikageViewDeclaration`, please refer to the relevant usage in the documentation
### 1.0.0 | 2025.04.20 &ensp;<Badge type="warning" text="stale" vertical="middle" />
- The first version is submitted to Maven - The first version is submitted to Maven
## hikage-extension ## hikage-extension
### 1.0.0 | 2025.04.20 &ensp;<Badge type="tip" text="latest" vertical="middle" /> ### 1.0.1 | 2025.08.24 &ensp;<Badge type="tip" text="latest" vertical="middle" />
- Migrated Java reflection related behaviors from [YukiReflection](https://github.com/HighCapable/YukiReflection) to [KavaRef](https://github.com/HighCapable/KavaRef)
- Added generic `ViewGroup.LayoutParams` support for `addView` in `ViewGroup`
### 1.0.0 | 2025.04.20 &ensp;<Badge type="warning" text="stale" vertical="middle" />
- The first version is submitted to Maven - The first version is submitted to Maven
## hikage-extension-betterandroid ## hikage-extension-betterandroid
### 1.0.0 | 2025.04.20 &ensp;<Badge type="tip" text="latest" vertical="middle" /> ### 1.0.1 | 2025.08.24 &ensp;<Badge type="tip" text="latest" vertical="middle" />
- Adapted to decoupled `ui-component` and `ui-component-adapter` in `BetterAndroid`
### 1.0.0 | 2025.04.20 &ensp;<Badge type="warning" text="stale" vertical="middle" />
- The first version is submitted to Maven - The first version is submitted to Maven
@@ -53,12 +75,21 @@ Time zone of version release date: **UTC+8**
## hikage-widget-androidx ## hikage-widget-androidx
### 1.0.0 | 2025.04.20 &ensp;<Badge type="tip" text="latest" vertical="middle" /> ### 1.0.1 | 2025.08.24 &ensp;<Badge type="tip" text="latest" vertical="middle" />
- Added `MotionLayout`, `ImageFilterButton`, `ImageFilterView`, `MockView`, `MotionButton`, `MotionLabel`, `MotionTelltales` components to `ConstraintLayout`
- Adjusted some components to be `final`
### 1.0.0 | 2025.04.20 &ensp;<Badge type="warning" text="stale" vertical="middle" />
- The first version is submitted to Maven - The first version is submitted to Maven
## hikage-widget-material ## hikage-widget-material
### 1.0.0 | 2025.04.20 &ensp;<Badge type="tip" text="latest" vertical="middle" /> ### 1.0.1 | 2025.08.24 &ensp;<Badge type="tip" text="latest" vertical="middle" />
- Adjusted some components to be `final`
### 1.0.0 | 2025.04.20 &ensp;<Badge type="warning" text="stale" vertical="middle" />
- The first version is submitted to Maven - The first version is submitted to Maven

View File

@@ -139,12 +139,13 @@ Hikage can automatically generate the `Hikageable` function corresponding to the
You can add the `HikageView` annotation on your custom `View` to mark it as a Hikage layout component. You can add the `HikageView` annotation on your custom `View` to mark it as a Hikage layout component.
| Parameter Name | Description | | 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 | | `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 | | `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 | | `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` | | `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` |
| `final` | Whether to declare the layout as "final layout", the default is false, that is, whether this layout is `ViewGroup` or its subclasses will not generate the `performer` method block. After set to `true`, `lparams` and `requirePerformer` will no longer be valid. |
> The following example > The following example
@@ -177,13 +178,14 @@ Hikageable {
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. 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 | | Parameter Name | Description |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `view` | Class object of layout component that needs to be declared | | `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 | | `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 | | `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 | | `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` | | `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` |
| `final` | Whether to declare the layout as "final layout", the default is false, that is, whether this layout is `ViewGroup` or its subclasses will not generate the `performer` method block. After set to `true`, `lparams` and `requirePerformer` will no longer be valid. |
> The following example > The following example

View File

@@ -65,7 +65,7 @@ You can view the KDoc [click here](kdoc://hikage-extension-betterandroid).
### Adapter Extension ### Adapter Extension
Hikage provides layout extension function for BetterAndroid's [Adapter](https://betterandroid.github.io/BetterAndroid/en/library/ui-component#adapter), 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. 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. It uses the `ViewHolderDelegate` provided by BetterAndroid to create extension methods.
@@ -76,9 +76,9 @@ Here is a simple example based on `RecyclerView`.
```kotlin ```kotlin
// Assume this is the dataset you need to bind to. // Assume this is the dataset you need to bind to.
val listData = ArrayList<CustomBean>() val listData = ArrayList<MyEntity>()
// Create and bind to a custom RecyclerView.Adapter. // Create and bind to a custom RecyclerView.Adapter.
val adapter = recyclerView.bindAdapter<CustomBean> { val adapter = recyclerView.bindAdapter<MyEntity> {
onBindData { listData } onBindData { listData }
onBindItemView( onBindItemView(
Hikageable = { Hikageable = {
@@ -87,8 +87,8 @@ val adapter = recyclerView.bindAdapter<CustomBean> {
textSize = 16f textSize = 16f
} }
} }
) { hikage, bean, position -> ) { hikage, entity, position ->
hikage.get<TextView>("text_view").text = bean.name hikage.get<TextView>("text_view").text = entity.name
} }
} }
``` ```

View File

@@ -219,7 +219,7 @@ Or, use in a custom `View`.
class CustomView(context: Context, attrs: AttributeSet? = null) : FrameLayout(context, attrs) { class CustomView(context: Context, attrs: AttributeSet? = null) : FrameLayout(context, attrs) {
init { init {
addView { addView<FrameLayout.LayoutParams> {
TextView { TextView {
text = "Hello, World!" text = "Hello, World!"
textSize = 16f textSize = 16f

View File

@@ -10,7 +10,16 @@
## hikage-core ## hikage-core
### 1.0.1 | 2025.05.06 &ensp;<Badge type="tip" text="最新" vertical="middle" /> ### 1.0.2 | 2025.08.24 &ensp;<Badge type="tip" text="最新" vertical="middle" />
- 将 Java 反射相关行为由 [YukiReflection](https://github.com/HighCapable/YukiReflection) 迁移至 [KavaRef](https://github.com/HighCapable/KavaRef)
- 适配 Android 16 (API 36),解决了 Android 16 上 `XmlBlock` 的崩溃问题
- 优化布局性能,移除了不必要的内联操作,对反射操作增加缓存
- `HikageView``HikageViewDeclaration` 新增 `final` 参数以配合 `hikage-compiler` 实现新功能
- `Widgets` 新增 `SurfaceView``WebView` 内置组件
- `Widgets` 调整部分组件为 `final`
### 1.0.1 | 2025.05.06 &ensp;<Badge type="warning" text="过旧" vertical="middle" />
- 修复 KSP 源码没有成功发布的问题 - 修复 KSP 源码没有成功发布的问题
- 新增状态管理功能 - 新增状态管理功能
@@ -21,19 +30,32 @@
## hikage-compiler ## hikage-compiler
### 1.0.0 | 2025.04.20 &ensp;<Badge type="tip" text="最新" vertical="middle" /> ### 1.0.1 | 2025.08.24 &ensp;<Badge type="tip" text="最新" vertical="middle" />
- 新增对 `HikageView``HikageViewDeclaration``final` 参数的支持,详情请参考文档的相关用法
### 1.0.0 | 2025.04.20 &ensp;<Badge type="warning" text="过旧" vertical="middle" />
- 首个版本提交至 Maven - 首个版本提交至 Maven
## hikage-extension ## hikage-extension
### 1.0.0 | 2025.04.20 &ensp;<Badge type="tip" text="最新" vertical="middle" /> ### 1.0.1 | 2025.08.24 &ensp;<Badge type="tip" text="最新" vertical="middle" />
- 将 Java 反射相关行为由 [YukiReflection](https://github.com/HighCapable/YukiReflection) 迁移至 [KavaRef](https://github.com/HighCapable/KavaRef)
- `ViewGroup` 新增对 `addView` 的泛型 `ViewGroup.LayoutParams` 支持
### 1.0.0 | 2025.04.20 &ensp;<Badge type="warning" text="过旧" vertical="middle" />
- 首个版本提交至 Maven - 首个版本提交至 Maven
## hikage-extension-betterandroid ## hikage-extension-betterandroid
### 1.0.0 | 2025.04.20 &ensp;<Badge type="tip" text="最新" vertical="middle" /> ### 1.0.1 | 2025.08.24 &ensp;<Badge type="tip" text="最新" vertical="middle" />
- 适配了 `BetterAndroid` 解耦合后的 `ui-component``ui-component-adapter`
### 1.0.0 | 2025.04.20 &ensp;<Badge type="warning" text="过旧" vertical="middle" />
- 首个版本提交至 Maven - 首个版本提交至 Maven
@@ -45,12 +67,21 @@
## hikage-widget-androidx ## hikage-widget-androidx
### 1.0.0 | 2025.04.20 &ensp;<Badge type="tip" text="最新" vertical="middle" /> ### 1.0.1 | 2025.08.24 &ensp;<Badge type="tip" text="最新" vertical="middle" />
- `ConstraintLayout` 新增 `MotionLayout``ImageFilterButton``ImageFilterView``MockView``MotionButton``MotionLabel``MotionTelltales` 组件
- 调整部分组件为 `final`
### 1.0.0 | 2025.04.20 &ensp;<Badge type="warning" text="过旧" vertical="middle" />
- 首个版本提交至 Maven - 首个版本提交至 Maven
## hikage-widget-material ## hikage-widget-material
### 1.0.0 | 2025.04.20 &ensp;<Badge type="tip" text="最新" vertical="middle" /> ### 1.0.1 | 2025.08.24 &ensp;<Badge type="tip" text="最新" vertical="middle" />
- 调整部分组件为 `final`
### 1.0.0 | 2025.04.20 &ensp;<Badge type="warning" text="过旧" vertical="middle" />
- 首个版本提交至 Maven - 首个版本提交至 Maven

View File

@@ -136,12 +136,13 @@ Hikage 可以在编译时为指定的布局组件自动生成布局组件对应
你可以在你的自定义 `View` 上加入 `HikageView` 注解,以标记它生成为 Hikage 布局组件。 你可以在你的自定义 `View` 上加入 `HikageView` 注解,以标记它生成为 Hikage 布局组件。
| 参数名称 | 描述 | | 参数名称 | 描述 |
| ------------------ | --------------------------------------------------------------------------------------------------------------------- | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `lparams` | 布局参数 `ViewGroup.LayoutParams` Class 对象,如果你的自定义 `View``ViewGroup` 的子类,则可以声明或留空使用默认值 | | `lparams` | 布局参数 `ViewGroup.LayoutParams` Class 对象,如果你的自定义 `View``ViewGroup` 的子类,则可以声明或留空使用默认值 |
| `alias` | 布局组件的别名,即要生成的函数名称,默认获取当前 Class 的名称 | | `alias` | 布局组件的别名,即要生成的函数名称,默认获取当前 Class 的名称 |
| `requireInit` | 是否要求填写布局的初始化方法块,默认为可省略的参数 | | `requireInit` | 是否要求填写布局的初始化方法块,默认为可省略的参数 |
| `requirePerformer` | 是否要求填写布局的 `performer` 方法块,默认为可省略的参数,仅在你的自定义 `View``ViewGroup` 的子类时生效 | | `requirePerformer` | 是否要求填写布局的 `performer` 方法块,默认为可省略的参数,仅在你的自定义 `View``ViewGroup` 的子类时生效 |
| `final` | 是否将布局声明为 “最终布局”,默认否,即此布局是否是 `ViewGroup` 还是从其继承都将不会生成 `performer` 方法块,设置为 `true` 之后,`lparams``requirePerformer` 将不再有效。 |
> 示例如下 > 示例如下
@@ -174,13 +175,14 @@ Hikageable {
Hikage 同样可以为第三方提供的 `View` 组件自动生成布局组件函数,你可以使用 `HikageViewDeclaration` 注解来完成。 Hikage 同样可以为第三方提供的 `View` 组件自动生成布局组件函数,你可以使用 `HikageViewDeclaration` 注解来完成。
| 参数名称 | 描述 | | 参数名称 | 描述 |
| ------------------ | --------------------------------------------------------------------------------------------------------------------- | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `view` | 需要声明的布局组件的 Class 对象 | | `view` | 需要声明的布局组件的 Class 对象 |
| `lparams` | 布局参数 `ViewGroup.LayoutParams` Class 对象,如果你的自定义 `View``ViewGroup` 的子类,则可以声明或留空使用默认值 | | `lparams` | 布局参数 `ViewGroup.LayoutParams` Class 对象,如果你的自定义 `View``ViewGroup` 的子类,则可以声明或留空使用默认值 |
| `alias` | 布局组件的别名,即要生成的函数名称,默认获取 `view` Class 的名称 | | `alias` | 布局组件的别名,即要生成的函数名称,默认获取 `view` Class 的名称 |
| `requireInit` | 是否要求填写布局的初始化方法块,默认为可省略的参数 | | `requireInit` | 是否要求填写布局的初始化方法块,默认为可省略的参数 |
| `requirePerformer` | 是否要求填写布局的 `performer` 方法块,默认为可省略的参数,仅在你的自定义 `View``ViewGroup` 的子类时生效 | | `requirePerformer` | 是否要求填写布局的 `performer` 方法块,默认为可省略的参数,仅在你的自定义 `View``ViewGroup` 的子类时生效 |
| `final` | 是否将布局声明为 “最终布局”,默认否,即此布局是否是 `ViewGroup` 还是从其继承都将不会生成 `performer` 方法块,设置为 `true` 之后,`lparams``requirePerformer` 将不再有效。 |
> 示例如下 > 示例如下

View File

@@ -65,7 +65,7 @@ implementation("com.highcapable.hikage:hikage-extension-betterandroid:<version>"
### 适配器 (Adapter) 扩展 ### 适配器 (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 提供的 [适配器](https://betterandroid.github.io/BetterAndroid/zh-cn/library/ui-component-adapter)
提供了布局扩展功能,你可以直接在适配器的原始扩展方法上使用 Hikage 布局。 提供了布局扩展功能,你可以直接在适配器的原始扩展方法上使用 Hikage 布局。
它使用了 BetterAndroid 提供的 `ViewHolderDelegate` 来创建扩展方法。 它使用了 BetterAndroid 提供的 `ViewHolderDelegate` 来创建扩展方法。
@@ -76,9 +76,9 @@ Hikage 为 BetterAndroid 提供的 [适配器](https://betterandroid.github.io/B
```kotlin ```kotlin
// 假设这就是你需要绑定的数据集 // 假设这就是你需要绑定的数据集
val listData = ArrayList<CustomBean>() val listData = ArrayList<MyEntity>()
// 创建并绑定到自定义的 RecyclerView.Adapter // 创建并绑定到自定义的 RecyclerView.Adapter
val adapter = recyclerView.bindAdapter<CustomBean> { val adapter = recyclerView.bindAdapter<MyEntity> {
onBindData { listData } onBindData { listData }
onBindItemView( onBindItemView(
Hikageable = { Hikageable = {
@@ -87,8 +87,8 @@ val adapter = recyclerView.bindAdapter<CustomBean> {
textSize = 16f textSize = 16f
} }
} }
) { hikage, bean, position -> ) { hikage, entity, position ->
hikage.get<TextView>("text_view").text = bean.name hikage.get<TextView>("text_view").text = entity.name
} }
} }
``` ```

View File

@@ -217,7 +217,7 @@ root.addView {
class CustomView(context: Context, attrs: AttributeSet? = null) : FrameLayout(context, attrs) { class CustomView(context: Context, attrs: AttributeSet? = null) : FrameLayout(context, attrs) {
init { init {
addView { addView<FrameLayout.LayoutParams> {
TextView { TextView {
text = "Hello, World!" text = "Hello, World!"
textSize = 16f textSize = 16f

View File

@@ -3,35 +3,34 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
android.useAndroidX=true android.useAndroidX=true
android.nonTransitiveRClass=true android.nonTransitiveRClass=true
kotlin.code.style=official kotlin.code.style=official
kotlin.incremental.useClasspathSnapshot=true
# Project Configuration # Project Configuration
project.name=Hikage project.name=Hikage
project.url=https://github.com/BetterAndroid/Hikage project.url=https://github.com/BetterAndroid/Hikage
project.groupName=com.highcapable.hikage project.groupName=com.highcapable.hikage
project.android.compileSdk=35 project.android.compileSdk=36
project.android.minSdk=21 project.android.minSdk=21
project.android.targetSdk=35 project.android.targetSdk=36
project.samples-app.packageName=com.highcapable.hikage.demo project.samples-app.packageName=com.highcapable.hikage.demo
project.samples-app.versionName=universal project.samples-app.versionName=universal
project.samples-app.versionCode=1 project.samples-app.versionCode=1
project.hikage-core.namespace=${project.groupName}.core project.hikage-core.namespace=${project.groupName}.core
project.hikage-core.version="1.0.1" project.hikage-core.version="1.0.2"
project.hikage-core-lint.namespace=${project.groupName}.core.lint 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.identifier=${project.groupName}:hikage-core:${project.hikage-core.version}
project.hikage-core-lint.min-api=9 project.hikage-core-lint.min-api=9
project.hikage-core-lint.registry-v2-class="${project.hikage-core-lint.namespace}.HikageIssueRegistry" project.hikage-core-lint.registry-v2-class="${project.hikage-core-lint.namespace}.HikageIssueRegistry"
project.hikage-extension.namespace=${project.groupName}.extension project.hikage-extension.namespace=${project.groupName}.extension
project.hikage-extension.version="1.0.0" project.hikage-extension.version="1.0.1"
project.hikage-extension-betterandroid.namespace=${project.groupName}.extension.betterandroid project.hikage-extension-betterandroid.namespace=${project.groupName}.extension.betterandroid
project.hikage-extension-betterandroid.version="1.0.0" project.hikage-extension-betterandroid.version="1.0.1"
project.hikage-extension-compose.namespace=${project.groupName}.extension.androidx.compose project.hikage-extension-compose.namespace=${project.groupName}.extension.androidx.compose
project.hikage-extension-compose.version="1.0.0" project.hikage-extension-compose.version="1.0.0"
project.hikage-compiler.namespace="${project.groupName}.compiler" project.hikage-compiler.namespace="${project.groupName}.compiler"
project.hikage-compiler.version="1.0.0" project.hikage-compiler.version="1.0.1"
project.hikage-widget-androidx.namespace=${project.groupName}.widget.androidx project.hikage-widget-androidx.namespace=${project.groupName}.widget.androidx
project.hikage-widget-androidx.version="1.0.0" project.hikage-widget-androidx.version="1.0.1"
project.hikage-widget-material.namespace=${project.groupName}.widget.google.material project.hikage-widget-material.namespace=${project.groupName}.widget.google.material
project.hikage-widget-material.version="1.0.0" project.hikage-widget-material.version="1.0.1"
# Maven Publish Configuration # Maven Publish Configuration
SONATYPE_HOST=CENTRAL_PORTAL SONATYPE_HOST=CENTRAL_PORTAL
RELEASE_SIGNING_ENABLED=true RELEASE_SIGNING_ENABLED=true

View File

@@ -24,7 +24,7 @@ plugins:
auto-update: false auto-update: false
com.android.application: com.android.application:
alias: android-application alias: android-application
version: 8.9.3 version: 8.13.0
com.android.library: com.android.library:
alias: android-library alias: android-library
version-ref: android-application version-ref: android-application
@@ -37,7 +37,7 @@ plugins:
auto-update: false auto-update: false
com.vanniktech.maven.publish: com.vanniktech.maven.publish:
alias: maven-publish alias: maven-publish
version: 0.33.0 version: 0.34.0
libraries: libraries:
org.jetbrains.kotlin: org.jetbrains.kotlin:
@@ -59,11 +59,13 @@ libraries:
version-ref: <this>::kotlinpoet version-ref: <this>::kotlinpoet
com.highcapable.betterandroid: com.highcapable.betterandroid:
ui-component: ui-component:
version: 1.0.7 version: 1.0.8
ui-component-adapter:
version: 1.0.0
ui-extension: ui-extension:
version: 1.0.6 version: 1.0.7
system-extension: system-extension:
version: 1.0.2 version: 1.0.3
org.lsposed.hiddenapibypass: org.lsposed.hiddenapibypass:
hiddenapibypass: hiddenapibypass:
version: 6.1 version: 6.1
@@ -74,10 +76,10 @@ libraries:
version: 1.0.1 version: 1.0.1
com.highcapable.pangutext: com.highcapable.pangutext:
pangutext-android: pangutext-android:
version: 1.0.2 version: 1.0.4
androidx.core: androidx.core:
core: core:
version: 1.16.0 version: 1.17.0
core-ktx: core-ktx:
version-ref: <this>::core version-ref: <this>::core
androidx.appcompat: androidx.appcompat:
@@ -117,19 +119,20 @@ libraries:
version: 1.4.0 version: 1.4.0
androidx.compose.ui: androidx.compose.ui:
ui: ui:
version: 1.8.3 version: 1.9.0
junit: junit:
junit: junit:
version: 4.13.2 version: 4.13.2
androidx.test.ext: androidx.test.ext:
junit: junit:
version: 1.2.1 version: 1.3.0
androidx.test.espresso: androidx.test.espresso:
espresso-core: espresso-core:
version: 3.6.1 version: 3.7.0
com.android.tools.lint: com.android.tools.lint:
lint: lint:
version: 31.11.0 version: 31.9.0
auto-update: false
lint-api: lint-api:
version-ref: <this>::lint version-ref: <this>::lint
lint-checks: lint-checks:

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

View File

@@ -39,15 +39,20 @@ fun KSDeclaration.getSimpleNameString(): String {
fun KSClassDeclaration.isSubclassOf(superType: KSType?): Boolean { fun KSClassDeclaration.isSubclassOf(superType: KSType?): Boolean {
if (superType == null) return false if (superType == null) return false
if (this == superType.declaration) return true if (this == superType.declaration) return true
superTypes.forEach { parent -> superTypes.forEach { parent ->
val resolvedParent = parent.resolve() val resolvedParent = parent.resolve()
// Direct match. // Direct match.
if (resolvedParent == superType) return true if (resolvedParent == superType) return true
val parentDeclaration = resolvedParent.declaration as? KSClassDeclaration val parentDeclaration = resolvedParent.declaration as? KSClassDeclaration
?: return@forEach ?: return@forEach
// Recursively check the parent class. // Recursively check the parent class.
if (parentDeclaration.isSubclassOf(superType)) return true if (parentDeclaration.isSubclassOf(superType)) return true
}; return false }
return false
} }
fun KSClassDeclaration.asType() = asType(emptyList()) fun KSClassDeclaration.asType() = asType(emptyList())

View File

@@ -71,6 +71,7 @@ class HikageViewGenerator(override val environment: SymbolProcessorEnvironment)
override fun startProcess(resolver: Resolver) { override fun startProcess(resolver: Resolver) {
Processor.init(logger, resolver) Processor.init(logger, resolver)
val dependencies = Dependencies(aggregating = true, *resolver.getAllFiles().toList().toTypedArray()) val dependencies = Dependencies(aggregating = true, *resolver.getAllFiles().toList().toTypedArray())
resolver.getSymbolsWithAnnotation(HikageViewSpec.CLASS) resolver.getSymbolsWithAnnotation(HikageViewSpec.CLASS)
.filterIsInstance<KSClassDeclaration>() .filterIsInstance<KSClassDeclaration>()
@@ -79,9 +80,11 @@ class HikageViewGenerator(override val environment: SymbolProcessorEnvironment)
ksClass.annotations.forEach { ksClass.annotations.forEach {
// Get annotation parameters. // Get annotation parameters.
val (annotation, declaration) = HikageViewSpec.create(resolver, it, ksClass) val (annotation, declaration) = HikageViewSpec.create(resolver, it, ksClass)
performers += Performer(annotation, declaration) performers += Performer(annotation, declaration)
} }
} }
resolver.getSymbolsWithAnnotation(HikageViewDeclarationSpec.CLASS) resolver.getSymbolsWithAnnotation(HikageViewDeclarationSpec.CLASS)
.filterIsInstance<KSClassDeclaration>() .filterIsInstance<KSClassDeclaration>()
.distinctBy { it.qualifiedName } .distinctBy { it.qualifiedName }
@@ -89,14 +92,17 @@ class HikageViewGenerator(override val environment: SymbolProcessorEnvironment)
ksClass.annotations.forEach { ksClass.annotations.forEach {
// Get annotation parameters. // Get annotation parameters.
val (annotation, declaration) = HikageViewDeclarationSpec.create(resolver, it, ksClass) val (annotation, declaration) = HikageViewDeclarationSpec.create(resolver, it, ksClass)
performers += Performer(annotation, declaration) performers += Performer(annotation, declaration)
} }
} }
processPerformer(dependencies) processPerformer(dependencies)
} }
private fun processPerformer(dependencies: Dependencies) { private fun processPerformer(dependencies: Dependencies) {
val duplicatedItems = performers.groupBy { it.declaration.key }.filter { it.value.size > 1 }.flatMap { it.value } val duplicatedItems = performers.groupBy { it.declaration.key }.filter { it.value.size > 1 }.flatMap { it.value }
require(duplicatedItems.isEmpty()) { require(duplicatedItems.isEmpty()) {
"Discover duplicate @HikageView or @HikageViewDeclaration's class name or alias definitions, " + "Discover duplicate @HikageView or @HikageViewDeclaration's class name or alias definitions, " +
"you can re-specify the class name using the `alias` parameter.\n" + "you can re-specify the class name using the `alias` parameter.\n" +
@@ -109,25 +115,30 @@ class HikageViewGenerator(override val environment: SymbolProcessorEnvironment)
private fun generateCodeFile(dependencies: Dependencies, performer: Performer) { private fun generateCodeFile(dependencies: Dependencies, performer: Performer) {
val classNameSet = performer.declaration.alias ?: performer.declaration.className val classNameSet = performer.declaration.alias ?: performer.declaration.className
val fileName = "_$classNameSet" val fileName = "_$classNameSet"
val viewClass = performer.declaration.toClassName().let { val viewClass = performer.declaration.toClassName().let {
val packageName = it.packageName val packageName = it.packageName
val simpleName = it.simpleName val simpleName = it.simpleName
val topClassName = if (simpleName.contains(".")) simpleName.split(".")[0] else null val topClassName = if (simpleName.contains(".")) simpleName.split(".")[0] else null
// com.example.MyViewScope // com.example.MyViewScope
// com.example.MyViewScope.MyView // com.example.MyViewScope.MyView
topClassName?.let { name -> ClassName(packageName, name) } to it topClassName?.let { name -> ClassName(packageName, name) } to it
} }
val lparamsClass = performer.annotation.lparams?.let { val lparamsClass = performer.annotation.lparams?.let {
val packageName = it.packageName.asString() val packageName = it.packageName.asString()
val subClassName = it.getSimpleNameString() val subClassName = it.getSimpleNameString()
val simpleName = it.simpleName.asString() val simpleName = it.simpleName.asString()
val topClassName = subClassName.replace(".$simpleName", "") val topClassName = subClassName.replace(".$simpleName", "")
// android.view.ViewGroup // android.view.ViewGroup
// android.view.ViewGroup.LayoutParams // android.view.ViewGroup.LayoutParams
(if (topClassName != subClassName) (if (topClassName != subClassName)
ClassName(packageName, topClassName) ClassName(packageName, topClassName)
else null) to ClassName(packageName, subClassName) else null) to ClassName(packageName, subClassName)
} }
val packageName = "$PACKAGE_NAME_PREFIX.${performer.declaration.packageName}" val packageName = "$PACKAGE_NAME_PREFIX.${performer.declaration.packageName}"
val fileSpec = FileSpec.builder(packageName, fileName).apply { val fileSpec = FileSpec.builder(packageName, fileName).apply {
addFileComment( addFileComment(
@@ -140,6 +151,7 @@ class HikageViewGenerator(override val environment: SymbolProcessorEnvironment)
**DO NOT EDIT THIS FILE MANUALLY** **DO NOT EDIT THIS FILE MANUALLY**
""".trimIndent() """.trimIndent()
) )
addAnnotation( addAnnotation(
AnnotationSpec.builder(Suppress::class) AnnotationSpec.builder(Suppress::class)
.addMember("%S, %S, %S", "unused", "FunctionName", "DEPRECATION") .addMember("%S, %S, %S", "unused", "FunctionName", "DEPRECATION")
@@ -150,17 +162,22 @@ class HikageViewGenerator(override val environment: SymbolProcessorEnvironment)
.addMember("%S", "${classNameSet}Performer") .addMember("%S", "${classNameSet}Performer")
.build() .build()
) )
addImport(DeclaredSymbol.ANDROID_VIEW_PACKAGE_NAME, DeclaredSymbol.ANDROID_VIEW_GROUP_CLASS_NAME) addImport(DeclaredSymbol.ANDROID_VIEW_PACKAGE_NAME, DeclaredSymbol.ANDROID_VIEW_GROUP_CLASS_NAME)
addImport(DeclaredSymbol.HIKAGE_CORE_PACKAGE_NAME, DeclaredSymbol.HIKAGE_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. // 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, // If a child class exists, it needs to import the parent class,
// and kotlinpoet will not perform this operation automatically. // and kotlinpoet will not perform this operation automatically.
viewClass.first?.let { addImport(it.packageName, it.simpleName) } viewClass.first?.let { addImport(it.packageName, it.simpleName) }
lparamsClass?.first?.let { addImport(it.packageName, it.simpleName) } lparamsClass?.first?.let { addImport(it.packageName, it.simpleName) }
// May conflict with other [LayoutParams]. // May conflict with other [LayoutParams].
lparamsClass?.second?.let { addAliasedImport(it, it.getTypedSimpleName()) } lparamsClass?.second?.let { addAliasedImport(it, it.getTypedSimpleName()) }
addAliasedImport(ViewGroupLpClass, ViewGroupLpClass.getTypedSimpleName()) addAliasedImport(ViewGroupLpClass, ViewGroupLpClass.getTypedSimpleName())
addAliasedImport(HikageLparamClass, HikageLparamClass.getTypedSimpleName()) addAliasedImport(HikageLparamClass, HikageLparamClass.getTypedSimpleName())
addFunction(FunSpec.builder(classNameSet).apply { addFunction(FunSpec.builder(classNameSet).apply {
addKdoc( addKdoc(
""" """
@@ -170,10 +187,12 @@ class HikageViewGenerator(override val environment: SymbolProcessorEnvironment)
@return [${performer.declaration.className}] @return [${performer.declaration.className}]
""".trimIndent() """.trimIndent()
) )
addAnnotation(HikageableClass) addAnnotation(HikageableClass)
addModifiers(KModifier.INLINE) addModifiers(KModifier.INLINE)
addTypeVariable(TypeVariableName(name = "LP", ViewGroupLpClass).copy(reified = true)) addTypeVariable(TypeVariableName(name = "LP", ViewGroupLpClass).copy(reified = true))
receiver(PerformerClass.parameterizedBy(TypeVariableName("LP"))) receiver(PerformerClass.parameterizedBy(TypeVariableName("LP")))
addParameter( addParameter(
ParameterSpec.builder(name = "lparams", HikageLparamClass.copy(nullable = true)) ParameterSpec.builder(name = "lparams", HikageLparamClass.copy(nullable = true))
.defaultValue("null") .defaultValue("null")
@@ -192,7 +211,7 @@ class HikageViewGenerator(override val environment: SymbolProcessorEnvironment)
if (!performer.annotation.requireInit) defaultValue("{}") if (!performer.annotation.requireInit) defaultValue("{}")
}.build() }.build()
) )
lparamsClass?.second?.let { lparamsClass?.second?.takeIf { !performer.annotation.final }?.let {
addParameter( addParameter(
ParameterSpec.builder( ParameterSpec.builder(
name = "performer", name = "performer",
@@ -203,9 +222,11 @@ class HikageViewGenerator(override val environment: SymbolProcessorEnvironment)
) )
addStatement("return ViewGroup<${performer.declaration.className}, ${it.simpleName}>(lparams, id, init, performer)") addStatement("return ViewGroup<${performer.declaration.className}, ${it.simpleName}>(lparams, id, init, performer)")
} ?: addStatement("return View<${performer.declaration.className}>(lparams, id, init)") } ?: addStatement("return View<${performer.declaration.className}>(lparams, id, init)")
returns(viewClass.second) returns(viewClass.second)
}.build()) }.build())
}.build() }.build()
// May already exists, no need to generate. // May already exists, no need to generate.
runCatching { runCatching {
fileSpec.writeTo(codeGenerator, dependencies) fileSpec.writeTo(codeGenerator, dependencies)
@@ -239,6 +260,7 @@ class HikageViewGenerator(override val environment: SymbolProcessorEnvironment)
ksType.declaration.qualifiedName?.asString() != Any::class.qualifiedName ksType.declaration.qualifiedName?.asString() != Any::class.qualifiedName
} }
var resolvedLparams = lparamsType?.declaration?.getClassDeclaration(resolver) var resolvedLparams = lparamsType?.declaration?.getClassDeclaration(resolver)
when { when {
// If the current view is not a view group but the lparams parameter is declared, // If the current view is not a view group but the lparams parameter is declared,
// remove the lparams parameter. // remove the lparams parameter.
@@ -253,6 +275,7 @@ class HikageViewGenerator(override val environment: SymbolProcessorEnvironment)
// set the default type parameter for it. // set the default type parameter for it.
declaration.isViewGroup && resolvedLparams == null -> resolvedLparams = lparamsDeclaration declaration.isViewGroup && resolvedLparams == null -> resolvedLparams = lparamsDeclaration
} }
// Verify layout parameter class. // Verify layout parameter class.
if (resolvedLparams != null) require(resolvedLparams.isSubclassOf(lparamsDeclaration.asType())) { if (resolvedLparams != null) require(resolvedLparams.isSubclassOf(lparamsDeclaration.asType())) {
val resolvedLparamsName = resolvedLparams.qualifiedName?.asString() val resolvedLparamsName = resolvedLparams.qualifiedName?.asString()
@@ -271,17 +294,21 @@ class HikageViewGenerator(override val environment: SymbolProcessorEnvironment)
val packageName = ksClass.packageName.asString() val packageName = ksClass.packageName.asString()
val className = ksClass.getSimpleNameString() val className = ksClass.getSimpleNameString()
val isViewGroup = ksClass.isSubclassOf(viewGroupDeclaration.asType()) val isViewGroup = ksClass.isSubclassOf(viewGroupDeclaration.asType())
var _alias = alias var _alias = alias
// If no alias name is set, if the class name contains a subclass, // 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. // replace it with an underscore and use it as an alias name.
if (_alias.isNullOrBlank() && className.contains(".")) if (_alias.isNullOrBlank() && className.contains("."))
_alias = className.replace(".", "_") _alias = className.replace(".", "_")
_alias = _alias?.takeIf { it.isNotBlank() } _alias = _alias?.takeIf { it.isNotBlank() }
val declaration = ViewDeclaration(packageName, className, _alias, isViewGroup, baseType) val declaration = ViewDeclaration(packageName, className, _alias, isViewGroup, baseType)
// Verify the legality of the class name. // Verify the legality of the class name.
if (!_alias.isNullOrBlank()) require(ClassDetector.verify(_alias)) { if (!_alias.isNullOrBlank()) require(ClassDetector.verify(_alias)) {
"Declares @$tagName's alias \"$_alias\" is illegal.\n${declaration.locateDesc}" "Declares @$tagName's alias \"$_alias\" is illegal.\n${declaration.locateDesc}"
} }
// [ViewGroup] cannot be new instance. // [ViewGroup] cannot be new instance.
require(ksClass != viewGroupDeclaration) { require(ksClass != viewGroupDeclaration) {
"Declares @$tagName's class must not be a directly \"${DeclaredSymbol.ANDROID_VIEW_GROUP_CLASS}\".\n${declaration.locateDesc}" "Declares @$tagName's class must not be a directly \"${DeclaredSymbol.ANDROID_VIEW_GROUP_CLASS}\".\n${declaration.locateDesc}"
@@ -290,6 +317,7 @@ class HikageViewGenerator(override val environment: SymbolProcessorEnvironment)
require(ksClass.isSubclassOf(viewDeclaration.asType())) { require(ksClass.isSubclassOf(viewDeclaration.asType())) {
"Declares @$tagName's class must be subclass of \"${DeclaredSymbol.ANDROID_VIEW_CLASS}\".\n${declaration.locateDesc}" "Declares @$tagName's class must be subclass of \"${DeclaredSymbol.ANDROID_VIEW_CLASS}\".\n${declaration.locateDesc}"
} }
return declaration return declaration
} }
} }
@@ -315,7 +343,8 @@ class HikageViewGenerator(override val environment: SymbolProcessorEnvironment)
override val lparams: KSClassDeclaration?, override val lparams: KSClassDeclaration?,
override val alias: String?, override val alias: String?,
override val requireInit: Boolean, override val requireInit: Boolean,
override val requirePerformer: Boolean override val requirePerformer: Boolean,
override val final: Boolean
) : HikageAnnotationSpec { ) : HikageAnnotationSpec {
companion object { companion object {
@@ -332,10 +361,13 @@ class HikageViewGenerator(override val environment: SymbolProcessorEnvironment)
val alias = annotation.arguments.getOrNull<String>("alias") val alias = annotation.arguments.getOrNull<String>("alias")
val requireInit = annotation.arguments.getOrNull<Boolean>("requireInit") ?: false val requireInit = annotation.arguments.getOrNull<Boolean>("requireInit") ?: false
val requirePerformer = annotation.arguments.getOrNull<Boolean>("requirePerformer") ?: false val requirePerformer = annotation.arguments.getOrNull<Boolean>("requirePerformer") ?: false
val final = annotation.arguments.getOrNull<Boolean>("final") ?: false
// Solve the actual content of the annotation parameters. // Solve the actual content of the annotation parameters.
val declaration = Processor.createViewDeclaration(NAME, alias, ksClass) val declaration = Processor.createViewDeclaration(NAME, alias, ksClass)
val resolvedLparams = Processor.resolvedLparamsDeclaration(NAME, resolver, declaration, lparams) val resolvedLparams = Processor.resolvedLparamsDeclaration(NAME, resolver, declaration, lparams)
return HikageViewSpec(resolvedLparams, alias, requireInit, requirePerformer) to declaration
return HikageViewSpec(resolvedLparams, alias, requireInit, requirePerformer, final) to declaration
} }
} }
} }
@@ -345,7 +377,8 @@ class HikageViewGenerator(override val environment: SymbolProcessorEnvironment)
override val lparams: KSClassDeclaration?, override val lparams: KSClassDeclaration?,
override val alias: String?, override val alias: String?,
override val requireInit: Boolean, override val requireInit: Boolean,
override val requirePerformer: Boolean override val requirePerformer: Boolean,
override val final: Boolean
) : HikageAnnotationSpec { ) : HikageAnnotationSpec {
companion object { companion object {
@@ -363,9 +396,12 @@ class HikageViewGenerator(override val environment: SymbolProcessorEnvironment)
val alias = annotation.arguments.getOrNull<String>("alias") val alias = annotation.arguments.getOrNull<String>("alias")
val requireInit = annotation.arguments.getOrNull<Boolean>("requireInit") ?: false val requireInit = annotation.arguments.getOrNull<Boolean>("requireInit") ?: false
val requirePerformer = annotation.arguments.getOrNull<Boolean>("requirePerformer") ?: false val requirePerformer = annotation.arguments.getOrNull<Boolean>("requirePerformer") ?: false
val final = annotation.arguments.getOrNull<Boolean>("final") ?: false
// Solve the actual content of the annotation parameters. // Solve the actual content of the annotation parameters.
val resolvedView = view?.declaration?.getClassDeclaration(resolver) ?: error("Internal error.") val resolvedView = view?.declaration?.getClassDeclaration(resolver) ?: error("Internal error.")
val declaration = Processor.createViewDeclaration(NAME, alias, resolvedView, ksClass) val declaration = Processor.createViewDeclaration(NAME, alias, resolvedView, ksClass)
// Only object classes can be used as view declarations. // Only object classes can be used as view declarations.
require(ksClass.classKind == ClassKind.OBJECT) { require(ksClass.classKind == ClassKind.OBJECT) {
"Declares @$NAME's class must be an object.\n${declaration.locateDesc}" "Declares @$NAME's class must be an object.\n${declaration.locateDesc}"
@@ -373,8 +409,9 @@ class HikageViewGenerator(override val environment: SymbolProcessorEnvironment)
require(!ksClass.isCompanionObject) { require(!ksClass.isCompanionObject) {
"Declares @$NAME's class must not be a companion object.\n${declaration.locateDesc}" "Declares @$NAME's class must not be a companion object.\n${declaration.locateDesc}"
} }
val resolvedLparams = Processor.resolvedLparamsDeclaration(NAME, resolver, declaration, lparams) val resolvedLparams = Processor.resolvedLparamsDeclaration(NAME, resolver, declaration, lparams)
return HikageViewDeclarationSpec(resolvedView, resolvedLparams, alias, requireInit, requirePerformer) to declaration return HikageViewDeclarationSpec(resolvedView, resolvedLparams, alias, requireInit, requirePerformer, final) to declaration
} }
} }
} }
@@ -384,6 +421,7 @@ class HikageViewGenerator(override val environment: SymbolProcessorEnvironment)
val alias: String? val alias: String?
val requireInit: Boolean val requireInit: Boolean
val requirePerformer: Boolean val requirePerformer: Boolean
val final: Boolean
} }
private data class Performer( private data class Performer(

View File

@@ -60,6 +60,7 @@ class HikageSafeTypeCastDetector : Detector(), Detector.UastScanner {
override fun visitQualifiedReferenceExpression(node: UQualifiedReferenceExpression) { override fun visitQualifiedReferenceExpression(node: UQualifiedReferenceExpression) {
if (node.selector !is UBinaryExpressionWithType) return if (node.selector !is UBinaryExpressionWithType) return
val castExpr = node.selector as UBinaryExpressionWithType val castExpr = node.selector as UBinaryExpressionWithType
visitAndLint(context, castExpr, node) visitAndLint(context, castExpr, node)
} }
@@ -75,11 +76,14 @@ class HikageSafeTypeCastDetector : Detector(), Detector.UastScanner {
) { ) {
// Get the parent node, if it is wrapped with brackets will also be included. // Get the parent node, if it is wrapped with brackets will also be included.
val locationNode = node.uastParent as? UParenthesizedExpression ?: node val locationNode = node.uastParent as? UParenthesizedExpression ?: node
val receiver = parent?.receiver ?: node.operand val receiver = parent?.receiver ?: node.operand
val receiverType = (node.operand as? UArrayAccessExpression)?.receiver?.getExpressionType() ?: return val receiverType = (node.operand as? UArrayAccessExpression)?.receiver?.getExpressionType() ?: return
val receiverClass = receiverType.canonicalText val receiverClass = receiverType.canonicalText
// Filter retains results that meet the conditions. // Filter retains results that meet the conditions.
if (receiverClass != DeclaredSymbol.HIKAGE_CLASS) return if (receiverClass != DeclaredSymbol.HIKAGE_CLASS) return
// Like `hikage["your_id"] as YourView`. // Like `hikage["your_id"] as YourView`.
val exprText = node.sourcePsi?.text ?: return val exprText = node.sourcePsi?.text ?: return
// Like `hikage["your_id"]`. // Like `hikage["your_id"]`.
@@ -88,11 +92,15 @@ class HikageSafeTypeCastDetector : Detector(), Detector.UastScanner {
val receiverNameText = receiverText.split("[")[0] val receiverNameText = receiverText.split("[")[0]
// Like `"your_id"`. // Like `"your_id"`.
val receiverContent = runCatching { receiverText.split("[")[1].split("]")[0] }.getOrNull() ?: return val receiverContent = runCatching { receiverText.split("[")[1].split("]")[0] }.getOrNull() ?: return
val isSafeCast = exprText.contains("as?") || exprText.endsWith("?") val isSafeCast = exprText.contains("as?") || exprText.endsWith("?")
// Like `YourView`. // Like `YourView`.
val castTypeContent = node.typeReference?.sourcePsi?.text?.removeSuffix("?") ?: return val castTypeContent = node.typeReference?.sourcePsi?.text?.removeSuffix("?") ?: return
val replacement = "$receiverNameText.${if (isSafeCast) "getOrNull" else "get"}<$castTypeContent>($receiverContent)" val replacement = "$receiverNameText.${if (isSafeCast) "getOrNull" else "get"}<$castTypeContent>($receiverContent)"
val replaceSuggestion = if (isSafeCast) "Hikage.getOrNull<$castTypeContent>" else "Hikage.get<$castTypeContent>" val replaceSuggestion = if (isSafeCast) "Hikage.getOrNull<$castTypeContent>" else "Hikage.get<$castTypeContent>"
val location = context.getLocation(locationNode) val location = context.getLocation(locationNode)
val lintFix = LintFix.create() val lintFix = LintFix.create()
.name("Replace with '$replacement'") .name("Replace with '$replacement'")
@@ -101,6 +109,7 @@ class HikageSafeTypeCastDetector : Detector(), Detector.UastScanner {
.with(replacement) .with(replacement)
.reformat(true) .reformat(true)
.build() .build()
context.report( context.report(
ISSUE, locationNode, location, ISSUE, locationNode, location,
message = "Can be replaced with safe type cast `$replaceSuggestion`.", message = "Can be replaced with safe type cast `$replaceSuggestion`.",

View File

@@ -70,6 +70,7 @@ class HikageableBeyondScopeDetector : Detector(), Detector.UastScanner {
override fun visitCallExpression(node: UCallExpression) { override fun visitCallExpression(node: UCallExpression) {
val callExpr = node.sourcePsi as? KtCallExpression ?: return val callExpr = node.sourcePsi as? KtCallExpression ?: return
val method = node.resolve() ?: return val method = node.resolve() ?: return
startLint(callExpr, method) startLint(callExpr, method)
organizeAndReport() organizeAndReport()
} }
@@ -78,6 +79,7 @@ class HikageableBeyondScopeDetector : Detector(), Detector.UastScanner {
val className = method.containingClass?.qualifiedName ?: "" val className = method.containingClass?.qualifiedName ?: ""
val hasHikageable = method.hasHikageable() val hasHikageable = method.hasHikageable()
val hasLayoutParams = className == DeclaredSymbol.HIKAGE_PERFORMER_CLASS && method.name == "LayoutParams" val hasLayoutParams = className == DeclaredSymbol.HIKAGE_PERFORMER_CLASS && method.name == "LayoutParams"
if (hasHikageable || hasLayoutParams) visitAndLint(callExpr, method) if (hasHikageable || hasLayoutParams) visitAndLint(callExpr, method)
} }
@@ -85,6 +87,7 @@ class HikageableBeyondScopeDetector : Detector(), Detector.UastScanner {
reports.forEach { reports.forEach {
// Check if the call has been reported before reporting. // Check if the call has been reported before reporting.
if (reportedNodes.contains(it.callExpr)) return@forEach if (reportedNodes.contains(it.callExpr)) return@forEach
val location = context.getLocation(it.callExpr) val location = context.getLocation(it.callExpr)
val lintFix = LintFix.create() val lintFix = LintFix.create()
.name("Delete Call Expression") .name("Delete Call Expression")
@@ -93,6 +96,7 @@ class HikageableBeyondScopeDetector : Detector(), Detector.UastScanner {
// Delete the call expression. // Delete the call expression.
.with("") .with("")
.build() .build()
context.report(ISSUE, it.callExpr, location, it.message, lintFix) context.report(ISSUE, it.callExpr, location, it.message, lintFix)
reportedNodes.add(it.callExpr) reportedNodes.add(it.callExpr)
} }
@@ -102,28 +106,37 @@ class HikageableBeyondScopeDetector : Detector(), Detector.UastScanner {
val bodyBlocks = mutableMapOf<String, KtExpression>() val bodyBlocks = mutableMapOf<String, KtExpression>()
val parameters = method.parameterList.parameters val parameters = method.parameterList.parameters
val valueArguments = callExpr.valueArgumentList?.arguments ?: emptyList() val valueArguments = callExpr.valueArgumentList?.arguments ?: emptyList()
fun visitValueArg(arg: KtValueArgument) { fun visitValueArg(arg: KtValueArgument) {
val name = arg.getArgumentName()?.asName?.identifier ?: "" val name = arg.getArgumentName()?.asName?.identifier ?: ""
val expr = arg.getArgumentExpression() val expr = arg.getArgumentExpression()
val parameter = parameters.firstOrNull { it.name == name } 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 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 ?: if (arg is KtLambdaArgument) parameters.lastOrNull() else null
val isMatched = parameter?.type?.canonicalText?.matches(DeclaredSymbol.HIKAGE_VIEW_REGEX) == true && val isMatched = parameter?.type?.canonicalText?.matches(DeclaredSymbol.HIKAGE_VIEW_REGEX) == true &&
!parameter.type.canonicalText.contains(DeclaredSymbol.HIKAGE_PERFORMER_CLASS) !parameter.type.canonicalText.contains(DeclaredSymbol.HIKAGE_PERFORMER_CLASS)
if (expr is KtLambdaExpression && isMatched) if (expr is KtLambdaExpression && isMatched)
expr.bodyExpression?.let { bodyBlocks[name] = it } expr.bodyExpression?.let { bodyBlocks[name] = it }
} }
// Get the last lambda expression. // Get the last lambda expression.
val lastLambda = callExpr.lambdaArguments.lastOrNull() val lastLambda = callExpr.lambdaArguments.lastOrNull()
if (lastLambda != null) visitValueArg(lastLambda) if (lastLambda != null) visitValueArg(lastLambda)
valueArguments.forEach { arg -> visitValueArg(arg) } valueArguments.forEach { arg -> visitValueArg(arg) }
bodyBlocks.toList().flatMap { (_, value) -> value.children.filterIsInstance<KtCallExpression>() }.forEach { bodyBlocks.toList().flatMap { (_, value) -> value.children.filterIsInstance<KtCallExpression>() }.forEach {
val expression = it.toUElementOfType<UCallExpression>() ?: return@forEach val expression = it.toUElementOfType<UCallExpression>() ?: return@forEach
val sCallExpr = expression.sourcePsi as? KtCallExpression ?: return@forEach val sCallExpr = expression.sourcePsi as? KtCallExpression ?: return@forEach
val sMethod = expression.resolve() ?: return@forEach val sMethod = expression.resolve() ?: return@forEach
if (sMethod.hasHikageable()) { if (sMethod.hasHikageable()) {
val message = "Performers are not allowed to appear in `${method.name}` DSL creation process." val message = "Performers are not allowed to appear in `${method.name}` DSL creation process."
reports.add(ReportDetail(message, expression)) reports.add(ReportDetail(message, expression))
// Recursively to visit next level. // Recursively to visit next level.
visitAndLint(sCallExpr, sMethod) visitAndLint(sCallExpr, sMethod)
} }

View File

@@ -65,6 +65,7 @@ class HikageableFunctionsDetector : Detector(), Detector.UastScanner {
override fun visitMethod(node: UMethod) { override fun visitMethod(node: UMethod) {
val uastBody = node.uastBody as? UBlockExpression ?: return val uastBody = node.uastBody as? UBlockExpression ?: return
val bodyHasHikageable = uastBody.expressions.any { val bodyHasHikageable = uastBody.expressions.any {
when (it) { when (it) {
is UCallExpression -> it.resolve()?.hasHikageable() ?: false is UCallExpression -> it.resolve()?.hasHikageable() ?: false
@@ -73,17 +74,22 @@ class HikageableFunctionsDetector : Detector(), Detector.UastScanner {
else -> false else -> false
} }
} }
if (!node.hasHikageable() && bodyHasHikageable) { if (!node.hasHikageable() && bodyHasHikageable) {
val location = context.getLocation(node) val location = context.getLocation(node)
val nameLocation = context.getNameLocation(node) val nameLocation = context.getNameLocation(node)
val message = "Function `${node.name}` must be marked with the `@Hikageable` annotation." val message = "Function `${node.name}` must be marked with the `@Hikageable` annotation."
val functionText = node.asSourceString() val functionText = node.asSourceString()
val hasDoubleSlash = functionText.startsWith("//") val hasDoubleSlash = functionText.startsWith("//")
val replacement = functionRegex.replace(functionText) { result -> val replacement = functionRegex.replace(functionText) { result ->
val functionBody = result.groupValues.getOrNull(0) ?: functionText val functionBody = result.groupValues.getOrNull(0) ?: functionText
val prefix = if (hasDoubleSlash) "\n" else "" val prefix = if (hasDoubleSlash) "\n" else ""
"$prefix@Hikageable $functionBody" "$prefix@Hikageable $functionBody"
} }
val lintFix = LintFix.create() val lintFix = LintFix.create()
.name("Add '@Hikageable' to '${node.name}'") .name("Add '@Hikageable' to '${node.name}'")
.replace() .replace()
@@ -92,6 +98,7 @@ class HikageableFunctionsDetector : Detector(), Detector.UastScanner {
.imports(DeclaredSymbol.HIKAGEABLE_ANNOTATION_CLASS) .imports(DeclaredSymbol.HIKAGEABLE_ANNOTATION_CLASS)
.reformat(true) .reformat(true)
.build() .build()
context.report(ISSUE, node, nameLocation, message, lintFix) context.report(ISSUE, node, nameLocation, message, lintFix)
} }
} }

View File

@@ -135,18 +135,23 @@ class WidgetsUsageDetector : Detector(), Detector.UastScanner {
importRefs.getOrNull(1)?.trim() importRefs.getOrNull(1)?.trim()
else null else null
val importPrefix = importRefs[0] val importPrefix = importRefs[0]
val hasPrefix = importPrefix.contains(".") val hasPrefix = importPrefix.contains(".")
val name = if (hasPrefix) val name = if (hasPrefix)
importPrefix.split(".").last() importPrefix.split(".").last()
else importPrefix else importPrefix
val packagePrefix = importPrefix.replace(if (hasPrefix) ".$name" else name, "") val packagePrefix = importPrefix.replace(if (hasPrefix) ".$name" else name, "")
val reference = ImportReference(packagePrefix, name, alias) val reference = ImportReference(packagePrefix, name, alias)
importReferences.add(reference) importReferences.add(reference)
} }
override fun visitCallExpression(node: UCallExpression) { override fun visitCallExpression(node: UCallExpression) {
val callExpr = node.sourcePsi as? KtCallExpression ?: return val callExpr = node.sourcePsi as? KtCallExpression ?: return
val method = node.resolve() ?: return val method = node.resolve() ?: return
startLint(callExpr, method) startLint(callExpr, method)
} }
@@ -165,7 +170,9 @@ class WidgetsUsageDetector : Detector(), Detector.UastScanner {
type.canonicalText == VIEW_GROUP_CLASS_NAME type.canonicalText == VIEW_GROUP_CLASS_NAME
} }
} }
val isTypedViewFunction = typedViewFunctionIndex >= 0 val isTypedViewFunction = typedViewFunctionIndex >= 0
val imports = typeArgumentsText.mapNotNull { typeName -> val imports = typeArgumentsText.mapNotNull { typeName ->
when { when {
// Like `TextView`. // Like `TextView`.
@@ -179,18 +186,24 @@ class WidgetsUsageDetector : Detector(), Detector.UastScanner {
else -> null else -> null
} }
} }
val matchedIndex = builtInWidgets.indexOfFirst { imports.any { e -> e.alias == it || e.name == it } } val matchedIndex = builtInWidgets.indexOfFirst { imports.any { e -> e.alias == it || e.name == it } }
val isBuiltInWidget = matchedIndex >= 0 val isBuiltInWidget = matchedIndex >= 0
if (isTypedViewFunction && isBuiltInWidget) { if (isTypedViewFunction && isBuiltInWidget) {
val widgetName = builtInWidgets[matchedIndex] val widgetName = builtInWidgets[matchedIndex]
val sourceLocation = context.getLocation(callExpr) val sourceLocation = context.getLocation(callExpr)
val sourceText = callExpr.toUElement()?.asSourceString() ?: return val sourceText = callExpr.toUElement()?.asSourceString() ?: return
val callExprElement = callExpr.toUElement() ?: return val callExprElement = callExpr.toUElement() ?: return
// Matchs '>' and like `View<TextView`'s length + 1. // Matchs '>' and like `View<TextView`'s length + 1.
val callExprLength = sourceText.split(">")[0].trim().length + 1 val callExprLength = sourceText.split(">")[0].trim().length + 1
val nameLocation = context.getRangeLocation(callExprElement, fromDelta = 0, callExprLength) val nameLocation = context.getRangeLocation(callExprElement, fromDelta = 0, callExprLength)
// Only replace the first one, because there may be multiple sub-functions in DSL. // Only replace the first one, because there may be multiple sub-functions in DSL.
val replacement = sourceText.replaceFirst(viewExpressionRegex, widgetName) val replacement = sourceText.replaceFirst(viewExpressionRegex, widgetName)
val lintFix = LintFix.create() val lintFix = LintFix.create()
.name("Replace with '$widgetName'") .name("Replace with '$widgetName'")
.replace() .replace()
@@ -199,6 +212,7 @@ class WidgetsUsageDetector : Detector(), Detector.UastScanner {
.imports("$WIDGET_FUNCTION_PREFIX.$widgetName") .imports("$WIDGET_FUNCTION_PREFIX.$widgetName")
.reformat(true) .reformat(true)
.build() .build()
val message = "Can be simplified to `$widgetName`." val message = "Can be simplified to `$widgetName`."
context.report(ISSUE, callExpr, nameLocation, message, lintFix) context.report(ISSUE, callExpr, nameLocation, message, lintFix)
} }

View File

@@ -28,15 +28,6 @@ android {
sourceCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = 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 { dependencies {

View File

@@ -45,6 +45,9 @@ import kotlin.reflect.KClass
* @param requireInit whether to force the `init` parameter to be called, default is false. * @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, * @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. * this parameter will be ignored when no `performer` parameter is needed here.
* @param final whether to declare this layout as "final layout", default is false, that is,
* whether this layout inherits from or is [ViewGroup], the `performer` parameter will not be generated.
* After set to `true`, [lparams] and [requirePerformer] will no longer be valid.
*/ */
@Retention(AnnotationRetention.BINARY) @Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.CLASS) @Target(AnnotationTarget.CLASS)
@@ -53,5 +56,6 @@ annotation class HikageView(
val lparams: KClass<*> = Any::class, val lparams: KClass<*> = Any::class,
val alias: String = "", val alias: String = "",
val requireInit: Boolean = false, val requireInit: Boolean = false,
val requirePerformer: Boolean = false val requirePerformer: Boolean = false,
val final: Boolean = false
) )

View File

@@ -48,6 +48,9 @@ import kotlin.reflect.KClass
* @param requireInit whether to force the `init` parameter to be called, default is false. * @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, * @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. * this parameter will be ignored when no `performer` parameter is needed here.
* @param final whether to declare this layout as "final layout", default is false, that is,
* whether this layout inherits from or is [ViewGroup], the `performer` parameter will not be generated.
* After set to `true`, [lparams] and [requirePerformer] will no longer be valid.
*/ */
@Retention(AnnotationRetention.BINARY) @Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.CLASS) @Target(AnnotationTarget.CLASS)
@@ -57,5 +60,6 @@ annotation class HikageViewDeclaration(
val lparams: KClass<*> = Any::class, val lparams: KClass<*> = Any::class,
val alias: String = "", val alias: String = "",
val requireInit: Boolean = false, val requireInit: Boolean = false,
val requirePerformer: Boolean = false val requirePerformer: Boolean = false,
val final: Boolean = false
) )

View File

@@ -31,7 +31,7 @@ import android.content.res.loader.AssetsProvider
import android.content.res.loader.ResourcesProvider import android.content.res.loader.ResourcesProvider
import android.util.AttributeSet import android.util.AttributeSet
import androidx.annotation.StyleRes import androidx.annotation.StyleRes
import com.highcapable.betterandroid.system.extension.tool.SystemVersion import com.highcapable.betterandroid.system.extension.tool.AndroidVersion
import com.highcapable.betterandroid.ui.extension.view.inflateOrNull import com.highcapable.betterandroid.ui.extension.view.inflateOrNull
import com.highcapable.betterandroid.ui.extension.view.layoutInflater import com.highcapable.betterandroid.ui.extension.view.layoutInflater
import com.highcapable.hikage.core.R import com.highcapable.hikage.core.R
@@ -116,12 +116,12 @@ internal object XmlBlockBypass {
private val resolver = object : MemberProcessor.Resolver() { private val resolver = object : MemberProcessor.Resolver() {
override fun <T : Any> getDeclaredConstructors(declaringClass: Class<T>): List<Constructor<T>> = override fun <T : Any> getDeclaredConstructors(declaringClass: Class<T>): List<Constructor<T>> =
SystemVersion.require(SystemVersion.P, super.getDeclaredConstructors(declaringClass)) { AndroidVersion.require(AndroidVersion.P, super.getDeclaredConstructors(declaringClass)) {
HiddenApiBypass.getDeclaredMethods(declaringClass).filterIsInstance<Constructor<T>>().toList() HiddenApiBypass.getDeclaredMethods(declaringClass).filterIsInstance<Constructor<T>>().toList()
} }
override fun <T : Any> getDeclaredMethods(declaringClass: Class<T>): List<Method> = override fun <T : Any> getDeclaredMethods(declaringClass: Class<T>): List<Method> =
SystemVersion.require(SystemVersion.P, super.getDeclaredMethods(declaringClass)) { AndroidVersion.require(AndroidVersion.P, super.getDeclaredMethods(declaringClass)) {
HiddenApiBypass.getDeclaredMethods(declaringClass).filterIsInstance<Method>().toList() HiddenApiBypass.getDeclaredMethods(declaringClass).filterIsInstance<Method>().toList()
} }
} }
@@ -143,6 +143,7 @@ internal object XmlBlockBypass {
fun init(context: Context) { fun init(context: Context) {
// Context may be loaded from the preview and other non-Android platforms, ignoring this. // Context may be loaded from the preview and other non-Android platforms, ignoring this.
if (context.javaClass.name.endsWith("BridgeContext")) return if (context.javaClass.name.endsWith("BridgeContext")) return
init(context.applicationContext.applicationInfo) init(context.applicationContext.applicationInfo)
} }
@@ -151,11 +152,12 @@ internal object XmlBlockBypass {
* @param info the application info. * @param info the application info.
*/ */
private fun init(info: ApplicationInfo) { private fun init(info: ApplicationInfo) {
if (SystemVersion.isLowOrEqualsTo(SystemVersion.P)) return if (AndroidVersion.isAtMost(AndroidVersion.P)) return
if (isInitOnce) return if (isInitOnce) return
val sourceDir = info.sourceDir val sourceDir = info.sourceDir
xmlBlock = when { xmlBlock = when {
SystemVersion.isHighOrEqualsTo(SystemVersion.R) -> AndroidVersion.isAtLeast(AndroidVersion.R) ->
// private static native long nativeLoad(@FormatType int format, @NonNull String path, // private static native long nativeLoad(@FormatType int format, @NonNull String path,
// @PropertyFlags int flags, @Nullable AssetsProvider asset) throws IOException; // @PropertyFlags int flags, @Nullable AssetsProvider asset) throws IOException;
ApkAssetsClass.resolve() ApkAssetsClass.resolve()
@@ -166,7 +168,7 @@ internal object XmlBlockBypass {
parameters(Int::class, String::class, Int::class, AssetsProvider::class) parameters(Int::class, String::class, Int::class, AssetsProvider::class)
modifiers(Modifiers.NATIVE) modifiers(Modifiers.NATIVE)
}?.invokeQuietly(FORMAT_APK, sourceDir, PROPERTY_SYSTEM, null) }?.invokeQuietly(FORMAT_APK, sourceDir, PROPERTY_SYSTEM, null)
SystemVersion.isHighOrEqualsTo(SystemVersion.P) -> AndroidVersion.isAtLeast(AndroidVersion.P) ->
// private static native long nativeLoad( // private static native long nativeLoad(
// @NonNull String path, boolean system, boolean forceSharedLib, boolean overlay) // @NonNull String path, boolean system, boolean forceSharedLib, boolean overlay)
// throws IOException; // throws IOException;
@@ -180,18 +182,20 @@ internal object XmlBlockBypass {
}?.invokeQuietly(sourceDir, false, false, false) }?.invokeQuietly(sourceDir, false, false, false)
else -> error("Unsupported Android version.") else -> error("Unsupported Android version.")
} as? Long? ?: error("Failed to create ApkAssets.") } as? Long? ?: error("Failed to create ApkAssets.")
blockParser = XmlBlockClass.resolve() blockParser = XmlBlockClass.resolve()
.processor(resolver) .processor(resolver)
.optional() .optional()
.firstConstructorOrNull { .firstConstructorOrNull {
if (SystemVersion.isHighOrEqualsTo(36)) if (AndroidVersion.isAtLeast(AndroidVersion.BAKLAVA))
parameters(AssetManager::class, Long::class, Boolean::class) parameters(AssetManager::class, Long::class, Boolean::class)
else parameters(AssetManager::class, Long::class) else parameters(AssetManager::class, Long::class)
}?.let { }?.let {
if (SystemVersion.isHighOrEqualsTo(36)) if (AndroidVersion.isAtLeast(AndroidVersion.BAKLAVA))
it.createQuietly(null, xmlBlock, false) it.createQuietly(null, xmlBlock, false)
else it.createQuietly(null, xmlBlock) else it.createQuietly(null, xmlBlock)
} ?: error("Failed to create XmlBlock\$Parser.") } ?: error("Failed to create XmlBlock\$Parser.")
isInitOnce = true isInitOnce = true
} }
@@ -208,8 +212,10 @@ internal object XmlBlockBypass {
*/ */
fun createViewAttrs() = context.layoutInflater.inflateOrNull<HikageAttrsView>(R.layout.layout_hikage_attrs_view)?.attrs fun createViewAttrs() = context.layoutInflater.inflateOrNull<HikageAttrsView>(R.layout.layout_hikage_attrs_view)?.attrs
as? XmlResourceParser? ?: error("Failed to create AttributeSet.") as? XmlResourceParser? ?: error("Failed to create AttributeSet.")
return if (SystemVersion.isHighOrEqualsTo(SystemVersion.P)) {
return if (AndroidVersion.isAtLeast(AndroidVersion.P)) {
if (!isInitOnce) return createViewAttrs() if (!isInitOnce) return createViewAttrs()
require(blockParser != null) { "Hikage initialization failed." } require(blockParser != null) { "Hikage initialization failed." }
newParser?.copy()?.of(blockParser) newParser?.copy()?.of(blockParser)
?.invokeQuietly<XmlResourceParser>(resId) ?.invokeQuietly<XmlResourceParser>(resId)

View File

@@ -53,7 +53,6 @@ import com.highcapable.betterandroid.ui.extension.view.inflate
import com.highcapable.betterandroid.ui.extension.view.layoutInflater import com.highcapable.betterandroid.ui.extension.view.layoutInflater
import com.highcapable.hikage.annotation.Hikageable import com.highcapable.hikage.annotation.Hikageable
import com.highcapable.hikage.bypass.XmlBlockBypass 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.HikageFactory
import com.highcapable.hikage.core.base.HikageFactoryBuilder import com.highcapable.hikage.core.base.HikageFactoryBuilder
import com.highcapable.hikage.core.base.HikagePerformer import com.highcapable.hikage.core.base.HikagePerformer
@@ -167,7 +166,9 @@ class Hikage private constructor(private val factories: List<HikageFactory>) {
if (parent != null && attachToParent) { if (parent != null && attachToParent) {
val parentId = generateRandomViewId() val parentId = generateRandomViewId()
provideView(parent, parentId) provideView(parent, parentId)
}; newPerformer(lpClass, parent, attachToParent, context).apply(performer) }
newPerformer(lpClass, parent, attachToParent, context).apply(performer)
} }
/** /**
@@ -317,11 +318,13 @@ class Hikage private constructor(private val factories: List<HikageFactory>) {
*/ */
private fun <V : View> createView(viewClass: Class<V>, id: String?, context: Context): V { private fun <V : View> createView(viewClass: Class<V>, id: String?, context: Context): V {
val attrs = createAttributeSet(context) val attrs = createAttributeSet(context)
val view = createViewFromFactory(viewClass, id, context, attrs) ?: getViewConstructor(viewClass)?.build(context, attrs) val view = createViewFromFactory(viewClass, id, context, attrs) ?: getViewConstructor(viewClass)?.build(context, attrs)
if (view == null) throw PerformerException( if (view == null) throw PerformerException(
"Create view of type ${viewClass.name} failed. " + "Create view of type ${viewClass.name} failed. " +
"Please make sure the view class has a constructor with a single parameter of type Context." "Please make sure the view class has a constructor with a single parameter of type Context."
) )
provideView(view, id) provideView(view, id)
return view return view
} }
@@ -334,16 +337,20 @@ class Hikage private constructor(private val factories: List<HikageFactory>) {
private fun <V : View> getViewConstructor(viewClass: Class<V>) = private fun <V : View> getViewConstructor(viewClass: Class<V>) =
viewConstructors[viewClass.name] as? ViewConstructor<V>? ?: run { viewConstructors[viewClass.name] as? ViewConstructor<V>? ?: run {
var parameterCount = 0 var parameterCount = 0
val twoParams = viewClass.resolve() val twoParams = viewClass.resolve()
.optional(silent = true) .optional(silent = true)
.firstConstructorOrNull { parameters(Context::class, AttributeSet::class) } .firstConstructorOrNull { parameters(Context::class, AttributeSet::class) }
val onceParam = viewClass.resolve() val onceParam = viewClass.resolve()
.optional(silent = true) .optional(silent = true)
.firstConstructorOrNull { parameters(Context::class) } .firstConstructorOrNull { parameters(Context::class) }
val constructor = onceParam?.apply { parameterCount = 1 } val constructor = onceParam?.apply { parameterCount = 1 }
?: twoParams?.apply { parameterCount = 2 } ?: twoParams?.apply { parameterCount = 2 }
val viewConstructor = constructor?.let { ViewConstructor(it, parameterCount) } val viewConstructor = constructor?.let { ViewConstructor(it, parameterCount) }
if (viewConstructor != null) viewConstructors[viewClass.name] = viewConstructor if (viewConstructor != null) viewConstructors[viewClass.name] = viewConstructor
viewConstructor viewConstructor
} }
@@ -358,15 +365,20 @@ class Hikage private constructor(private val factories: List<HikageFactory>) {
private fun <V : View> createViewFromFactory(viewClass: Class<V>, id: String?, context: Context, attrs: AttributeSet): V? { private fun <V : View> createViewFromFactory(viewClass: Class<V>, id: String?, context: Context, attrs: AttributeSet): V? {
val parent = performers.firstOrNull()?.parent val parent = performers.firstOrNull()?.parent
var processed: V? = null var processed: V? = null
factories.forEach { factory -> factories.forEach { factory ->
val params = PerformerParams(id, attrs, viewClass as Class<View>) val params = PerformerParams(id, attrs, viewClass as Class<View>)
val view = factory(parent, processed, context, params) val view = factory(parent, processed, context, params)
if (view != null && view.javaClass isNotSubclassOf viewClass) throw PerformerException( if (view != null && view.javaClass isNotSubclassOf viewClass) throw PerformerException(
"HikageFactory cannot cast the created view type \"${view.javaClass}\" to \"${viewClass.name}\", " + "HikageFactory cannot cast the created view type \"${view.javaClass}\" to \"${viewClass.name}\", " +
"please confirm that the view type you created is correct." "please confirm that the view type you created is correct."
) )
if (view != null) processed = view as? V? if (view != null) processed = view as? V?
}; return processed }
return processed
} }
/** /**
@@ -379,6 +391,7 @@ class Hikage private constructor(private val factories: List<HikageFactory>) {
val (requireId, viewId) = generateViewId(id) val (requireId, viewId) = generateViewId(id)
view.id = viewId view.id = viewId
views[requireId] = view views[requireId] = view
return requireId return requireId
} }
@@ -395,12 +408,16 @@ class Hikage private constructor(private val factories: List<HikageFactory>) {
*/ */
fun doGenerate(id: String): Int { fun doGenerate(id: String): Int {
val generateId = View.generateViewId() val generateId = View.generateViewId()
if (viewIds.contains(id)) throw PerformerException("View with id \"$id\" already exists.") if (viewIds.contains(id)) throw PerformerException("View with id \"$id\" already exists.")
viewIds[id] = generateId viewIds[id] = generateId
return generateId return generateId
} }
val requireId = id ?: generateRandomViewId() val requireId = id ?: generateRandomViewId()
val viewId = doGenerate(requireId) val viewId = doGenerate(requireId)
return requireId to viewId return requireId to viewId
} }
@@ -456,6 +473,7 @@ class Hikage private constructor(private val factories: List<HikageFactory>) {
*/ */
internal inline fun requireNoPerformers(name: String, block: () -> Unit) { internal inline fun requireNoPerformers(name: String, block: () -> Unit) {
val viewCount = views.size val viewCount = views.size
block() block()
if (views.size != viewCount) throw PerformerException( if (views.size != viewCount) throw PerformerException(
"Performers are not allowed to appear in $name DSL creation process." "Performers are not allowed to appear in $name DSL creation process."
@@ -472,13 +490,16 @@ class Hikage private constructor(private val factories: List<HikageFactory>) {
private fun include(delegate: Delegate<*>, context: Context, embedded: Boolean): Hikage { private fun include(delegate: Delegate<*>, context: Context, embedded: Boolean): Hikage {
val hikage = delegate.create(context) val hikage = delegate.create(context)
if (!embedded) return hikage if (!embedded) return hikage
val duplicateId = hikage.viewIds.toList().firstOrNull { (k, _) -> viewIds.containsKey(k) }?.first val duplicateId = hikage.viewIds.toList().firstOrNull { (k, _) -> viewIds.containsKey(k) }?.first
if (duplicateId != null) throw PerformerException( if (duplicateId != null) throw PerformerException(
"Embedded layout view IDs conflict, the view id \"$duplicateId\" is already exists." "Embedded layout view IDs conflict, the view id \"$duplicateId\" is already exists."
) )
viewIds.putAll(hikage.viewIds) viewIds.putAll(hikage.viewIds)
views.putAll(hikage.views) views.putAll(hikage.views)
performers.addAll(hikage.performers) performers.addAll(hikage.performers)
return hikage return hikage
} }
@@ -544,9 +565,11 @@ class Hikage private constructor(private val factories: List<HikageFactory>) {
val lpDelegate = LayoutParams.from(current, lpClass, parent, lparams) val lpDelegate = LayoutParams.from(current, lpClass, parent, lparams)
val view = createView(classOf<V>(), id, context) val view = createView(classOf<V>(), id, context)
view.layoutParams = lpDelegate.create() view.layoutParams = lpDelegate.create()
requireNoPerformers(classOf<V>().name) { view.init() } requireNoPerformers(classOf<V>().name) { view.init() }
startProvide(id, classOf<V>()) startProvide(id, classOf<V>())
addToParentIfRequired(view) addToParentIfRequired(view)
return view return view
} }
@@ -587,10 +610,12 @@ class Hikage private constructor(private val factories: List<HikageFactory>) {
val lpDelegate = LayoutParams.from(current, lpClass, parent, lparams) val lpDelegate = LayoutParams.from(current, lpClass, parent, lparams)
val view = createView(classOf<VG>(), id, context) val view = createView(classOf<VG>(), id, context)
view.layoutParams = lpDelegate.create() view.layoutParams = lpDelegate.create()
requireNoPerformers(classOf<VG>().name) { view.init() } requireNoPerformers(classOf<VG>().name) { view.init() }
startProvide(id, classOf<VG>()) startProvide(id, classOf<VG>())
addToParentIfRequired(view) addToParentIfRequired(view)
newPerformer<LP>(view).apply(performer) newPerformer<LP>(view).apply(performer)
return view return view
} }
@@ -625,10 +650,12 @@ class Hikage private constructor(private val factories: List<HikageFactory>) {
id: String? = null id: String? = null
): View { ): View {
val view = context.layoutInflater.inflate(resId, parent, attachToRoot = false) val view = context.layoutInflater.inflate(resId, parent, attachToRoot = false)
startProvide(id, view.javaClass, view) startProvide(id, view.javaClass, view)
lparams?.create()?.let { view.layoutParams = it } lparams?.create()?.let { view.layoutParams = it }
provideView(view, id) provideView(view, id)
addToParentIfRequired(view) addToParentIfRequired(view)
return view return view
} }
@@ -645,14 +672,17 @@ class Hikage private constructor(private val factories: List<HikageFactory>) {
): VB { ): VB {
val viewBinding = ViewBinding<VB>().inflate(context.layoutInflater, parent, attachToParent = false) val viewBinding = ViewBinding<VB>().inflate(context.layoutInflater, parent, attachToParent = false)
val view = viewBinding.root val view = viewBinding.root
startProvide(id, view.javaClass, view) startProvide(id, view.javaClass, view)
if (view.parent != null) throw ProvideException( if (view.parent != null) throw ProvideException(
"The ViewBinding($view) already has a parent, " + "The ViewBinding($view) already has a parent, " +
"it may have been created using layout root node <merge> or <include>, cannot be provided." "it may have been created using layout root node <merge> or <include>, cannot be provided."
) )
lparams?.create()?.let { view.layoutParams = it } lparams?.create()?.let { view.layoutParams = it }
provideView(view, id) provideView(view, id)
addToParentIfRequired(view) addToParentIfRequired(view)
return viewBinding return viewBinding
} }
@@ -670,11 +700,14 @@ class Hikage private constructor(private val factories: List<HikageFactory>) {
id: String? = null id: String? = null
): View { ): View {
if (view.parent != null) throw ProvideException("The view $view already has a parent, cannot be provided.") if (view.parent != null) throw ProvideException("The view $view already has a parent, cannot be provided.")
startProvide(id, view.javaClass, view) startProvide(id, view.javaClass, view)
val lpDelegate = LayoutParams.from(current = this@Hikage, lpClass, parent, lparams, view.layoutParams) val lpDelegate = LayoutParams.from(current = this@Hikage, lpClass, parent, lparams, view.layoutParams)
view.layoutParams = lpDelegate.create() view.layoutParams = lpDelegate.create()
provideView(view, id) provideView(view, id)
addToParentIfRequired(view) addToParentIfRequired(view)
return view return view
} }
@@ -708,13 +741,17 @@ class Hikage private constructor(private val factories: List<HikageFactory>) {
): Hikage { ): Hikage {
val view = hikage.root val view = hikage.root
startProvide(id, view.javaClass, view) startProvide(id, view.javaClass, view)
val lpDelegate = LayoutParams.from(current = this@Hikage, lpClass, parent, lparams, view.layoutParams) val lpDelegate = LayoutParams.from(current = this@Hikage, lpClass, parent, lparams, view.layoutParams)
if (view.parent != null) throw ProvideException( if (view.parent != null) throw ProvideException(
"The Hikage layout root view $view already has a parent, cannot be provided." "The Hikage layout root view $view already has a parent, cannot be provided."
) )
view.layoutParams = lpDelegate.create() view.layoutParams = lpDelegate.create()
provideView(view, id) provideView(view, id)
addToParentIfRequired(view) addToParentIfRequired(view)
return hikage return hikage
} }
@@ -783,6 +820,7 @@ class Hikage private constructor(private val factories: List<HikageFactory>) {
*/ */
private fun <V : View> startProvide(id: String?, viewClass: Class<V>, view: V? = null) { private fun <V : View> startProvide(id: String?, viewClass: Class<V>, view: V? = null) {
provideCount++ provideCount++
if (provideCount > 1 && (parent == null || !attachToParent)) throw ProvideException( if (provideCount > 1 && (parent == null || !attachToParent)) throw ProvideException(
"Provide view ${view?.javaClass ?: viewClass}(${id?.let { "\"$it\""} ?: "<anonymous>"}) failed. ${ "Provide view ${view?.javaClass ?: viewClass}(${id?.let { "\"$it\""} ?: "<anonymous>"}) failed. ${
if (parent == null) "No parent view group found" if (parent == null) "No parent view group found"
@@ -900,6 +938,7 @@ class Hikage private constructor(private val factories: List<HikageFactory>) {
superclass() superclass()
}?.invokeQuietly<ViewGroup.LayoutParams>(it) }?.invokeQuietly<ViewGroup.LayoutParams>(it)
} ?: lparams } ?: lparams
return wrapped return wrapped
// Build a default. // Build a default.
?: lpClass.createInstanceOrNull(LayoutParamsWrapContent, LayoutParamsWrapContent) ?: lpClass.createInstanceOrNull(LayoutParamsWrapContent, LayoutParamsWrapContent)
@@ -912,12 +951,15 @@ class Hikage private constructor(private val factories: List<HikageFactory>) {
*/ */
fun create(): ViewGroup.LayoutParams { fun create(): ViewGroup.LayoutParams {
if (bodyBuilder == null && wrapperBuilder == null) throw PerformerException("No layout params builder found.") if (bodyBuilder == null && wrapperBuilder == null) throw PerformerException("No layout params builder found.")
return bodyBuilder?.let { return bodyBuilder?.let {
val lparams = ViewLayoutParams(lpClass, it.width, it.height, it.matchParent, it.widthMatchParent, it.heightMatchParent) val lparams = ViewLayoutParams(lpClass, it.width, it.height, it.matchParent, it.widthMatchParent, it.heightMatchParent)
current.requireNoPerformers(lparams.javaClass.name) { it.body(lparams) } current.requireNoPerformers(lparams.javaClass.name) { it.body(lparams) }
lparams lparams
} ?: wrapperBuilder?.let { } ?: wrapperBuilder?.let {
val lparams = it.delegate?.create() ?: it.lparams val lparams = it.delegate?.create() ?: it.lparams
createDefaultLayoutParams(lparams) createDefaultLayoutParams(lparams)
} ?: throw PerformerException("Internal error of build layout params.") } ?: throw PerformerException("Internal error of build layout params.")
} }

View File

@@ -47,6 +47,7 @@ abstract class HikagePreview(context: Context, attrs: AttributeSet? = null) : Fr
@CallSuper @CallSuper
override fun onAttachedToWindow() { override fun onAttachedToWindow() {
super.onAttachedToWindow() super.onAttachedToWindow()
removeAllViews() removeAllViews()
build().create(context, parent = this) build().create(context, parent = this)
} }

View File

@@ -24,7 +24,8 @@
package com.highcapable.hikage.widget.android package com.highcapable.hikage.widget.android
import android.widget.AbsListView import android.view.SurfaceView
import android.webkit.WebView
import android.widget.ActionMenuView import android.widget.ActionMenuView
import android.widget.AutoCompleteTextView import android.widget.AutoCompleteTextView
import android.widget.Button import android.widget.Button
@@ -101,7 +102,7 @@ private object SpaceDeclaration
@HikageViewDeclaration(CheckedTextView::class) @HikageViewDeclaration(CheckedTextView::class)
private object CheckedTextViewDeclaration private object CheckedTextViewDeclaration
@HikageViewDeclaration(ExpandableListView::class, AbsListView.LayoutParams::class) @HikageViewDeclaration(ExpandableListView::class, final = true)
private object ExpandableListViewDeclaration private object ExpandableListViewDeclaration
@HikageViewDeclaration(Spinner::class) @HikageViewDeclaration(Spinner::class)
@@ -143,13 +144,13 @@ private object TextSwitcherDeclaration
@HikageViewDeclaration(ActionMenuView::class, ActionMenuView.LayoutParams::class) @HikageViewDeclaration(ActionMenuView::class, ActionMenuView.LayoutParams::class)
private object ActionMenuViewDeclaration private object ActionMenuViewDeclaration
@HikageViewDeclaration(CalendarView::class, FrameLayout.LayoutParams::class) @HikageViewDeclaration(CalendarView::class, final = true)
private object CalendarViewDeclaration private object CalendarViewDeclaration
@HikageViewDeclaration(DatePicker::class, FrameLayout.LayoutParams::class) @HikageViewDeclaration(DatePicker::class, final = true)
private object DatePickerDeclaration private object DatePickerDeclaration
@HikageViewDeclaration(TimePicker::class, FrameLayout.LayoutParams::class) @HikageViewDeclaration(TimePicker::class, final = true)
private object TimePickerDeclaration private object TimePickerDeclaration
@HikageViewDeclaration(RatingBar::class) @HikageViewDeclaration(RatingBar::class)
@@ -173,19 +174,25 @@ private object ViewFlipperDeclaration
@HikageViewDeclaration(ViewAnimator::class, FrameLayout.LayoutParams::class) @HikageViewDeclaration(ViewAnimator::class, FrameLayout.LayoutParams::class)
private object ViewAnimatorDeclaration private object ViewAnimatorDeclaration
@HikageViewDeclaration(SurfaceView::class)
private object SurfaceVieweclaration
@HikageViewDeclaration(VideoView::class) @HikageViewDeclaration(VideoView::class)
private object VideoViewDeclaration private object VideoViewDeclaration
@HikageViewDeclaration(Toolbar::class, Toolbar.LayoutParams::class) @HikageViewDeclaration(WebView::class, final = true)
private object WebViewDeclaration
@HikageViewDeclaration(Toolbar::class, final = true)
private object ToolbarDeclaration private object ToolbarDeclaration
@HikageViewDeclaration(GridLayout::class, GridLayout.LayoutParams::class) @HikageViewDeclaration(GridLayout::class, GridLayout.LayoutParams::class)
private object GridLayoutDeclaration private object GridLayoutDeclaration
@HikageViewDeclaration(GridView::class, AbsListView.LayoutParams::class) @HikageViewDeclaration(GridView::class, final = true)
private object GridViewDeclaration private object GridViewDeclaration
@HikageViewDeclaration(ListView::class, AbsListView.LayoutParams::class) @HikageViewDeclaration(ListView::class, final = true)
private object ListViewDeclaration private object ListViewDeclaration
@HikageViewDeclaration(ImageView::class) @HikageViewDeclaration(ImageView::class)
@@ -200,10 +207,10 @@ private object TableLayoutDeclaration
@HikageViewDeclaration(TableRow::class, TableRow.LayoutParams::class) @HikageViewDeclaration(TableRow::class, TableRow.LayoutParams::class)
private object TableRowDeclaration private object TableRowDeclaration
@HikageViewDeclaration(NumberPicker::class, LinearLayout.LayoutParams::class) @HikageViewDeclaration(NumberPicker::class, final = true)
private object NumberPickerDeclaration private object NumberPickerDeclaration
@HikageViewDeclaration(SearchView::class, FrameLayout.LayoutParams::class) @HikageViewDeclaration(SearchView::class, final = true)
private object SearchViewDeclaration private object SearchViewDeclaration
@HikageViewDeclaration(Switch::class) @HikageViewDeclaration(Switch::class)
@@ -212,5 +219,5 @@ private object SwitchDeclaration
@HikageViewDeclaration(TabHost::class, FrameLayout.LayoutParams::class) @HikageViewDeclaration(TabHost::class, FrameLayout.LayoutParams::class)
private object TabHostDeclaration private object TabHostDeclaration
@HikageViewDeclaration(TabWidget::class, LinearLayout.LayoutParams::class) @HikageViewDeclaration(TabWidget::class, final = true)
private object TabWidgetDeclaration private object TabWidgetDeclaration

View File

@@ -27,14 +27,6 @@ android {
sourceCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17
} }
kotlinOptions {
jvmTarget = "17"
freeCompilerArgs = listOf(
"-Xno-param-assertions",
"-Xno-call-assertions",
"-Xno-receiver-assertions"
)
}
} }
dependencies { dependencies {
@@ -42,6 +34,7 @@ dependencies {
implementation(com.highcapable.kavaref.kavaref.core) implementation(com.highcapable.kavaref.kavaref.core)
implementation(com.highcapable.kavaref.kavaref.extension) implementation(com.highcapable.kavaref.kavaref.extension)
implementation(com.highcapable.betterandroid.ui.component) implementation(com.highcapable.betterandroid.ui.component)
implementation(com.highcapable.betterandroid.ui.component.adapter)
implementation(com.highcapable.betterandroid.ui.extension) implementation(com.highcapable.betterandroid.ui.extension)
implementation(com.highcapable.betterandroid.system.extension) implementation(com.highcapable.betterandroid.system.extension)
implementation(androidx.core.core.ktx) implementation(androidx.core.core.ktx)

View File

@@ -25,7 +25,7 @@
package com.highcapable.hikage.extension.betterandroid.ui.component.adapter package com.highcapable.hikage.extension.betterandroid.ui.component.adapter
import android.view.ViewGroup import android.view.ViewGroup
import com.highcapable.betterandroid.ui.component.adapter.CommonAdapterBuilder import com.highcapable.betterandroid.ui.component.adapter.BaseAdapterBuilder
import com.highcapable.hikage.core.Hikage import com.highcapable.hikage.core.Hikage
import com.highcapable.hikage.core.base.HikagePerformer import com.highcapable.hikage.core.base.HikagePerformer
import com.highcapable.hikage.core.base.Hikageable import com.highcapable.hikage.core.base.Hikageable
@@ -51,26 +51,26 @@ import com.highcapable.hikage.extension.betterandroid.ui.component.adapter.viewh
* hikage.get<TextView>("text").text = "Item ${entity.name} of ${position + 1}" * hikage.get<TextView>("text").text = "Item ${entity.name} of ${position + 1}"
* } * }
* ``` * ```
* @see CommonAdapterBuilder.onBindItemView * @see BaseAdapterBuilder.onBindItemView
* @receiver [CommonAdapterBuilder]<[E]> * @receiver [BaseAdapterBuilder]<[E]>
* @param Hikageable the performer body. * @param Hikageable the performer body.
* @return [CommonAdapterBuilder]<[E]> * @return [BaseAdapterBuilder]<[E]>
*/ */
@JvmOverloads @JvmOverloads
fun <E> CommonAdapterBuilder<E>.onBindItemView( fun <E> BaseAdapterBuilder<E>.onBindItemView(
Hikageable: HikagePerformer<ViewGroup.LayoutParams>, Hikageable: HikagePerformer<ViewGroup.LayoutParams>,
viewHolder: (hikage: Hikage, entity: E, position: Int) -> Unit = { _, _, _ -> } viewHolder: (hikage: Hikage, entity: E, position: Int) -> Unit = { _, _, _ -> }
) = onBindItemView(Hikageable(performer = Hikageable), viewHolder) ) = onBindItemView(Hikageable(performer = Hikageable), viewHolder)
/** /**
* Create and add view holder from [Hikage.Delegate]. * Create and add view holder from [Hikage.Delegate].
* @see CommonAdapterBuilder.onBindItemView * @see BaseAdapterBuilder.onBindItemView
* @receiver [CommonAdapterBuilder]<[E]> * @receiver [BaseAdapterBuilder]<[E]>
* @param delegate the delegate. * @param delegate the delegate.
* @return [CommonAdapterBuilder]<[E]> * @return [BaseAdapterBuilder]<[E]>
*/ */
@JvmOverloads @JvmOverloads
fun <E> CommonAdapterBuilder<E>.onBindItemView( fun <E> BaseAdapterBuilder<E>.onBindItemView(
delegate: Hikage.Delegate<*>, delegate: Hikage.Delegate<*>,
viewHolder: (hikage: Hikage, entity: E, position: Int) -> Unit = { _, _, _ -> } viewHolder: (hikage: Hikage, entity: E, position: Int) -> Unit = { _, _, _ -> }
) = onBindItemView(HikageHolderDelegate(delegate), viewHolder) ) = onBindItemView(HikageHolderDelegate(delegate), viewHolder)

View File

@@ -28,14 +28,6 @@ android {
sourceCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17
} }
kotlinOptions {
jvmTarget = "17"
freeCompilerArgs = listOf(
"-Xno-param-assertions",
"-Xno-call-assertions",
"-Xno-receiver-assertions"
)
}
} }
dependencies { dependencies {

View File

@@ -27,14 +27,6 @@ android {
sourceCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17
} }
kotlinOptions {
jvmTarget = "17"
freeCompilerArgs = listOf(
"-Xno-param-assertions",
"-Xno-call-assertions",
"-Xno-receiver-assertions"
)
}
} }
dependencies { dependencies {

View File

@@ -30,6 +30,18 @@ import com.highcapable.hikage.core.base.HikageFactoryBuilder
import com.highcapable.hikage.core.base.HikagePerformer import com.highcapable.hikage.core.base.HikagePerformer
import com.highcapable.hikage.core.base.Hikageable import com.highcapable.hikage.core.base.Hikageable
/**
* @see ViewGroup.addView
* @see Hikageable
* @return [Hikage]
*/
@JvmName("addViewTyped")
inline fun <reified LP : ViewGroup.LayoutParams> ViewGroup.addView(
index: Int = -1,
factory: HikageFactoryBuilder.() -> Unit = {},
performer: HikagePerformer<LP>
) = Hikageable<LP>(context = context, factory = factory, performer = performer).apply { addView(root, index) }
/** /**
* @see ViewGroup.addView * @see ViewGroup.addView
* @see Hikageable * @see Hikageable
@@ -39,7 +51,7 @@ inline fun ViewGroup.addView(
index: Int = -1, index: Int = -1,
factory: HikageFactoryBuilder.() -> Unit = {}, factory: HikageFactoryBuilder.() -> Unit = {},
performer: HikagePerformer<ViewGroup.LayoutParams> performer: HikagePerformer<ViewGroup.LayoutParams>
) = Hikageable(context = context, factory = factory, performer = performer).apply { addView(root, index) } ) = addView<ViewGroup.LayoutParams>(index, factory, performer)
/** /**
* @see ViewGroup.addView * @see ViewGroup.addView

View File

@@ -27,14 +27,6 @@ android {
sourceCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17
} }
kotlinOptions {
jvmTarget = "17"
freeCompilerArgs = listOf(
"-Xno-param-assertions",
"-Xno-call-assertions",
"-Xno-receiver-assertions"
)
}
} }
dependencies { dependencies {

View File

@@ -78,7 +78,7 @@ private object AppCompatTextViewDeclaration
@HikageViewDeclaration(AppCompatToggleButton::class) @HikageViewDeclaration(AppCompatToggleButton::class)
private object AppCompatToggleButtonDeclaration private object AppCompatToggleButtonDeclaration
@HikageViewDeclaration(AppCompatToolbar::class, AppCompatToolbar.LayoutParams::class) @HikageViewDeclaration(AppCompatToolbar::class, final = true)
private object AppCompatToolbarDeclaration private object AppCompatToolbarDeclaration
@HikageViewDeclaration(AppCompatCheckedTextView::class) @HikageViewDeclaration(AppCompatCheckedTextView::class)
@@ -87,7 +87,7 @@ private object AppCompatCheckedTextViewDeclaration
@HikageViewDeclaration(SwitchCompat::class) @HikageViewDeclaration(SwitchCompat::class)
private object SwitchCompatDeclaration private object SwitchCompatDeclaration
@HikageViewDeclaration(AppCompatSearchView::class) @HikageViewDeclaration(AppCompatSearchView::class, final = true)
private object AppCompatSearchViewDeclaration private object AppCompatSearchViewDeclaration
@HikageViewDeclaration(LinearLayoutCompat::class, LinearLayoutCompat.LayoutParams::class) @HikageViewDeclaration(LinearLayoutCompat::class, LinearLayoutCompat.LayoutParams::class)

View File

@@ -24,8 +24,36 @@
package com.highcapable.hikage.widget.androidx.constraintlayout package com.highcapable.hikage.widget.androidx.constraintlayout
import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.constraintlayout.utils.widget.ImageFilterButton
import androidx.constraintlayout.utils.widget.ImageFilterView
import androidx.constraintlayout.utils.widget.MockView
import androidx.constraintlayout.utils.widget.MotionButton
import androidx.constraintlayout.utils.widget.MotionLabel
import androidx.constraintlayout.utils.widget.MotionTelltales
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import com.highcapable.hikage.annotation.HikageViewDeclaration import com.highcapable.hikage.annotation.HikageViewDeclaration
@HikageViewDeclaration(ConstraintLayout::class, ConstraintLayout.LayoutParams::class) @HikageViewDeclaration(ConstraintLayout::class, ConstraintLayout.LayoutParams::class)
private object ConstraintLayoutDeclaration private object ConstraintLayoutDeclaration
@HikageViewDeclaration(MotionLayout::class, ConstraintLayout.LayoutParams::class)
private object MotionLayoutDeclaration
@HikageViewDeclaration(ImageFilterButton::class)
private object ImageFilterButtonDeclaration
@HikageViewDeclaration(ImageFilterView::class)
private object ImageFilterViewDeclaration
@HikageViewDeclaration(MockView::class)
private object MockViewDeclaration
@HikageViewDeclaration(MotionButton::class)
private object MotionButtonDeclaration
@HikageViewDeclaration(MotionLabel::class)
private object MotionLabelDeclaration
@HikageViewDeclaration(MotionTelltales::class)
private object MotionTelltalesDeclaration

View File

@@ -27,5 +27,5 @@ package com.highcapable.hikage.widget.androidx.recyclerview
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.highcapable.hikage.annotation.HikageViewDeclaration import com.highcapable.hikage.annotation.HikageViewDeclaration
@HikageViewDeclaration(RecyclerView::class, RecyclerView.LayoutParams::class) @HikageViewDeclaration(RecyclerView::class, final = true)
private object RecyclerViewDeclaration private object RecyclerViewDeclaration

View File

@@ -28,8 +28,8 @@ import androidx.viewpager.widget.ViewPager
import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2
import com.highcapable.hikage.annotation.HikageViewDeclaration import com.highcapable.hikage.annotation.HikageViewDeclaration
@HikageViewDeclaration(ViewPager::class, ViewPager.LayoutParams::class) @HikageViewDeclaration(ViewPager::class, final = true)
private object ViewPagerDeclaration private object ViewPagerDeclaration
@HikageViewDeclaration(ViewPager2::class) @HikageViewDeclaration(ViewPager2::class, final = true)
private object ViewPager2Declaration private object ViewPager2Declaration

View File

@@ -27,14 +27,6 @@ android {
sourceCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17
} }
kotlinOptions {
jvmTarget = "17"
freeCompilerArgs = listOf(
"-Xno-param-assertions",
"-Xno-call-assertions",
"-Xno-receiver-assertions"
)
}
} }
dependencies { dependencies {

View File

@@ -24,7 +24,6 @@
package com.highcapable.hikage.widget.google.material.appbar package com.highcapable.hikage.widget.google.material.appbar
import android.widget.Toolbar
import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.CollapsingToolbarLayout import com.google.android.material.appbar.CollapsingToolbarLayout
import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.appbar.MaterialToolbar
@@ -33,7 +32,7 @@ import com.highcapable.hikage.annotation.HikageViewDeclaration
@HikageViewDeclaration(AppBarLayout::class, AppBarLayout.LayoutParams::class) @HikageViewDeclaration(AppBarLayout::class, AppBarLayout.LayoutParams::class)
private object AppBarLayoutDeclaration private object AppBarLayoutDeclaration
@HikageViewDeclaration(MaterialToolbar::class, Toolbar.LayoutParams::class) @HikageViewDeclaration(MaterialToolbar::class, final = true)
private object MaterialToolbarDeclaration private object MaterialToolbarDeclaration
@HikageViewDeclaration(CollapsingToolbarLayout::class, CollapsingToolbarLayout.LayoutParams::class) @HikageViewDeclaration(CollapsingToolbarLayout::class, CollapsingToolbarLayout.LayoutParams::class)

View File

@@ -24,9 +24,8 @@
package com.highcapable.hikage.widget.google.material.bottomappbar package com.highcapable.hikage.widget.google.material.bottomappbar
import androidx.appcompat.widget.Toolbar
import com.google.android.material.bottomappbar.BottomAppBar import com.google.android.material.bottomappbar.BottomAppBar
import com.highcapable.hikage.annotation.HikageViewDeclaration import com.highcapable.hikage.annotation.HikageViewDeclaration
@HikageViewDeclaration(BottomAppBar::class, Toolbar.LayoutParams::class) @HikageViewDeclaration(BottomAppBar::class, final = true)
private object BottomAppBarDeclaration private object BottomAppBarDeclaration

View File

@@ -24,9 +24,8 @@
package com.highcapable.hikage.widget.google.material.bottomnavigation package com.highcapable.hikage.widget.google.material.bottomnavigation
import android.widget.FrameLayout
import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.bottomnavigation.BottomNavigationView
import com.highcapable.hikage.annotation.HikageViewDeclaration import com.highcapable.hikage.annotation.HikageViewDeclaration
@HikageViewDeclaration(BottomNavigationView::class, FrameLayout.LayoutParams::class) @HikageViewDeclaration(BottomNavigationView::class, final = true)
private object BottomNavigationViewDeclaration private object BottomNavigationViewDeclaration

View File

@@ -24,9 +24,8 @@
package com.highcapable.hikage.widget.google.material.navigation package com.highcapable.hikage.widget.google.material.navigation
import android.widget.FrameLayout
import com.google.android.material.navigation.NavigationView import com.google.android.material.navigation.NavigationView
import com.highcapable.hikage.annotation.HikageViewDeclaration import com.highcapable.hikage.annotation.HikageViewDeclaration
@HikageViewDeclaration(NavigationView::class, FrameLayout.LayoutParams::class) @HikageViewDeclaration(NavigationView::class, final = true)
private object NavigationViewDeclaration private object NavigationViewDeclaration

View File

@@ -24,9 +24,8 @@
package com.highcapable.hikage.widget.google.material.navigationrail package com.highcapable.hikage.widget.google.material.navigationrail
import android.widget.FrameLayout
import com.google.android.material.navigationrail.NavigationRailView import com.google.android.material.navigationrail.NavigationRailView
import com.highcapable.hikage.annotation.HikageViewDeclaration import com.highcapable.hikage.annotation.HikageViewDeclaration
@HikageViewDeclaration(NavigationRailView::class, FrameLayout.LayoutParams::class) @HikageViewDeclaration(NavigationRailView::class, final = true)
private object NavigationRailViewDeclaration private object NavigationRailViewDeclaration

View File

@@ -25,12 +25,11 @@
package com.highcapable.hikage.widget.google.material.search package com.highcapable.hikage.widget.google.material.search
import android.widget.FrameLayout import android.widget.FrameLayout
import androidx.appcompat.widget.Toolbar
import com.google.android.material.search.SearchBar import com.google.android.material.search.SearchBar
import com.google.android.material.search.SearchView import com.google.android.material.search.SearchView
import com.highcapable.hikage.annotation.HikageViewDeclaration import com.highcapable.hikage.annotation.HikageViewDeclaration
@HikageViewDeclaration(SearchBar::class, Toolbar.LayoutParams::class) @HikageViewDeclaration(SearchBar::class, final = true)
private object SearchBarDeclaration private object SearchBarDeclaration
@HikageViewDeclaration(SearchView::class, FrameLayout.LayoutParams::class) @HikageViewDeclaration(SearchView::class, FrameLayout.LayoutParams::class)

View File

@@ -27,14 +27,6 @@ android {
sourceCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17
} }
kotlinOptions {
jvmTarget = "17"
freeCompilerArgs = listOf(
"-Xno-param-assertions",
"-Xno-call-assertions",
"-Xno-receiver-assertions"
)
}
buildFeatures { buildFeatures {
buildConfig = true buildConfig = true
viewBinding = true viewBinding = true

View File

@@ -28,6 +28,7 @@ class DemoApp : Application() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
DynamicColors.applyToActivitiesIfAvailable(this) DynamicColors.applyToActivitiesIfAvailable(this)
} }
} }

View File

@@ -52,9 +52,11 @@ class MainActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView { setContentView {
var username = "" var username = ""
var password = "" var password = ""
CoordinatorLayout( CoordinatorLayout(
lparams = LayoutParams(matchParent = true) lparams = LayoutParams(matchParent = true)
) { ) {

View File

@@ -31,6 +31,7 @@ abstract class BaseActivity : AppViewsActivity() {
@CallSuper @CallSuper
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
PanguTextFactory2.inject(this) PanguTextFactory2.inject(this)
} }
} }

View File

@@ -8,7 +8,7 @@ pluginManagement {
} }
plugins { plugins {
id("com.highcapable.sweetdependency") version "1.0.4" id("com.highcapable.sweetdependency") version "1.0.4"
id("com.highcapable.sweetproperty") version "1.0.5" id("com.highcapable.sweetproperty") version "1.0.8"
} }
sweetProperty { sweetProperty {
rootProject { all { isEnable = false } } rootProject { all { isEnable = false } }