mirror of
https://github.com/BetterAndroid/Hikage.git
synced 2025-09-05 10:15:37 +08:00
Bump hikage-core, hikage-extension, hikage-extension-betterandroid, hikage-extension-compose, hikage-compiler, hikage-widget-androidx, hikage-widget-material version to 1.0.0
This commit is contained in:
54
hikage-core/build.gradle.kts
Normal file
54
hikage-core/build.gradle.kts
Normal file
@@ -0,0 +1,54 @@
|
||||
plugins {
|
||||
autowire(libs.plugins.android.library)
|
||||
autowire(libs.plugins.kotlin.android)
|
||||
autowire(libs.plugins.kotlin.dokka)
|
||||
autowire(libs.plugins.maven.publish)
|
||||
autowire(libs.plugins.kotlin.ksp)
|
||||
}
|
||||
|
||||
group = property.project.groupName
|
||||
version = property.project.hikage.core.version
|
||||
|
||||
android {
|
||||
namespace = property.project.hikage.core.namespace
|
||||
compileSdk = property.project.android.compileSdk
|
||||
|
||||
defaultConfig {
|
||||
minSdk = property.project.android.minSdk
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
freeCompilerArgs = listOf(
|
||||
"-opt-in=kotlin.ExperimentalStdlibApi",
|
||||
"-Xno-param-assertions",
|
||||
"-Xno-call-assertions",
|
||||
"-Xno-receiver-assertions"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
lintPublish(projects.hikageCoreLint)
|
||||
ksp(projects.hikageCompiler)
|
||||
implementation(org.lsposed.hiddenapibypass.hiddenapibypass)
|
||||
implementation(com.highcapable.yukireflection.api)
|
||||
api(com.highcapable.betterandroid.ui.extension)
|
||||
implementation(com.highcapable.betterandroid.system.extension)
|
||||
implementation(androidx.core.core.ktx)
|
||||
implementation(androidx.appcompat.appcompat)
|
||||
testImplementation(junit.junit)
|
||||
androidTestImplementation(androidx.test.ext.junit)
|
||||
androidTestImplementation(androidx.test.espresso.espresso.core)
|
||||
}
|
0
hikage-core/consumer-rules.pro
Normal file
0
hikage-core/consumer-rules.pro
Normal file
21
hikage-core/proguard-rules.pro
vendored
Normal file
21
hikage-core/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
@@ -0,0 +1,25 @@
|
||||
package com.highcapable.hikage
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.highcapable.hikage.test", appContext.packageName)
|
||||
}
|
||||
}
|
2
hikage-core/src/main/AndroidManifest.xml
Normal file
2
hikage-core/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest />
|
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Hikage - An Android responsive UI building tool.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/BetterAndroid/Hikage
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/3/23.
|
||||
*/
|
||||
package com.highcapable.hikage.annotation
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.highcapable.hikage.core.Hikage
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* Declare annotations to generate the [Hikage.Performer] function for the specified [View] at compile time.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```kotlin
|
||||
* @HikageView(lparams = FrameLayout.LayoutParams::class, alias = "MyView")
|
||||
* class MyView(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) {
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
* @param lparams the layout params class, only [ViewGroup] can be specified,
|
||||
* after specifying, the `performer` parameter will be generated for the function.
|
||||
* The parameters must be a class inherited from [ViewGroup.LayoutParams],
|
||||
* if the current [View] does not inherit from [ViewGroup], this parameter will be ignored and warned.
|
||||
* @param alias the view's class name alias will naming the function, default is the class name.
|
||||
* @param requireInit whether to force the `init` parameter to be called, default is false.
|
||||
* @param requirePerformer whether to force the `performer` parameter to be called, default is false,
|
||||
* this parameter will be ignored when no `performer` parameter is needed here.
|
||||
*/
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@MustBeDocumented
|
||||
annotation class HikageView(
|
||||
val lparams: KClass<*> = Any::class,
|
||||
val alias: String = "",
|
||||
val requireInit: Boolean = false,
|
||||
val requirePerformer: Boolean = false
|
||||
)
|
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Hikage - An Android responsive UI building tool.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/BetterAndroid/Hikage
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/3/31.
|
||||
*/
|
||||
package com.highcapable.hikage.annotation
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.highcapable.hikage.core.Hikage
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* Declare annotations to generate the [Hikage.Performer] function for the specified [view] at compile time.
|
||||
*
|
||||
* You can declare a class for [View] that does not belong to your own [View] or third-party library.
|
||||
*
|
||||
* The class name you define can be as good as you like, and notice it must to use the `object` keyword to modify it.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```kotlin
|
||||
* @HikageViewDeclaration(ThirdPartyView::class, FrameLayout.LayoutParams::class, alias = "ThirdPartyView")
|
||||
* object ThirdPartyViewDeclaration
|
||||
* ```
|
||||
* @param view the view class, only [View] can be specified.
|
||||
* @param lparams the layout params class, only [ViewGroup] can be specified,
|
||||
* after specifying, the `performer` parameter will be generated for the function.
|
||||
* The parameters must be a class inherited from [ViewGroup.LayoutParams],
|
||||
* if the current [View] does not inherit from [ViewGroup], this parameter will be ignored and warned.
|
||||
* @param alias the view's class name alias will naming the function, default is the [view] class name.
|
||||
* @param requireInit whether to force the `init` parameter to be called, default is false.
|
||||
* @param requirePerformer whether to force the `performer` parameter to be called, default is false,
|
||||
* this parameter will be ignored when no `performer` parameter is needed here.
|
||||
*/
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@MustBeDocumented
|
||||
annotation class HikageViewDeclaration(
|
||||
val view: KClass<*>,
|
||||
val lparams: KClass<*> = Any::class,
|
||||
val alias: String = "",
|
||||
val requireInit: Boolean = false,
|
||||
val requirePerformer: Boolean = false
|
||||
)
|
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Hikage - An Android responsive UI building tool.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/BetterAndroid/Hikage
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/3/23.
|
||||
*/
|
||||
package com.highcapable.hikage.annotation
|
||||
|
||||
import com.highcapable.hikage.core.Hikage
|
||||
|
||||
/**
|
||||
* Declare an implementation of the [Hikage.Performer] function that
|
||||
* will be used for infectious creation of sub-layouts.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```kotlin
|
||||
* @Hikageable
|
||||
* inline fun <reified LP : ViewGroup.LayoutParams> Hikage.Performer<LP>.MyView(
|
||||
* lparams: Hikage.LayoutParams? = null,
|
||||
* id: String? = null,
|
||||
* init: HikageView<MyView> = {}
|
||||
* ) = View<MyView>(id, init, lparams)
|
||||
* ```
|
||||
* @see Hikage.Performer
|
||||
*/
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
@MustBeDocumented
|
||||
annotation class Hikageable
|
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Hikage - An Android responsive UI building tool.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/BetterAndroid/Hikage
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/2/28.
|
||||
*/
|
||||
package com.highcapable.hikage.bypass
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import androidx.annotation.Keep
|
||||
|
||||
/**
|
||||
* Just a view for obtaining [AttributeSet].
|
||||
*
|
||||
* **DONT USE THIS VIEW IN YOUR LAYOUT.**
|
||||
*/
|
||||
@Keep
|
||||
class HikageAttrsView internal constructor(context: Context, internal val attrs: AttributeSet?) : View(context, attrs)
|
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Hikage - An Android responsive UI building tool.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/BetterAndroid/Hikage
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/3/5.
|
||||
*/
|
||||
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||
|
||||
package com.highcapable.hikage.bypass
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.res.XmlResourceParser
|
||||
import android.content.res.loader.AssetsProvider
|
||||
import android.content.res.loader.ResourcesProvider
|
||||
import android.util.AttributeSet
|
||||
import androidx.annotation.StyleRes
|
||||
import com.highcapable.betterandroid.system.extension.tool.SystemVersion
|
||||
import com.highcapable.betterandroid.ui.extension.view.inflateOrNull
|
||||
import com.highcapable.betterandroid.ui.extension.view.layoutInflater
|
||||
import com.highcapable.hikage.core.R
|
||||
import com.highcapable.yukireflection.factory.classOf
|
||||
import com.highcapable.yukireflection.factory.lazyClass
|
||||
import com.highcapable.yukireflection.type.android.AssetManagerClass
|
||||
import com.highcapable.yukireflection.type.java.BooleanType
|
||||
import com.highcapable.yukireflection.type.java.IntType
|
||||
import com.highcapable.yukireflection.type.java.LongType
|
||||
import com.highcapable.yukireflection.type.java.StringClass
|
||||
import org.lsposed.hiddenapibypass.HiddenApiBypass
|
||||
import android.R as Android_R
|
||||
|
||||
/**
|
||||
* Create a new XmlBlock with system api bypass reflection magic.
|
||||
*/
|
||||
internal object XmlBlockBypass {
|
||||
|
||||
/** The path used to load the apk assets represents an APK file. */
|
||||
private const val FORMAT_APK = 0
|
||||
|
||||
/** The path used to load the apk assets represents an idmap file. */
|
||||
private const val FORMAT_IDMAP = 1
|
||||
|
||||
/** The path used to load the apk assets represents an resources.arsc file. */
|
||||
private const val FORMAT_ARSC = 2
|
||||
|
||||
/** The path used to load the apk assets represents a directory. */
|
||||
private const val FORMAT_DIR = 3
|
||||
|
||||
/**
|
||||
* The apk assets contains framework resource values specified by the system.
|
||||
* This allows some functions to filter out this package when computing what
|
||||
* configurations/resources are available.
|
||||
*/
|
||||
private const val PROPERTY_SYSTEM = 1 shl 0
|
||||
|
||||
/**
|
||||
* The apk assets is a shared library or was loaded as a shared library by force.
|
||||
* The package ids of dynamic apk assets are assigned at runtime instead of compile time.
|
||||
*/
|
||||
private const val PROPERTY_DYNAMIC = 1 shl 1
|
||||
|
||||
/**
|
||||
* The apk assets has been loaded dynamically using a [ResourcesProvider].
|
||||
* Loader apk assets overlay resources like RROs except they are not backed by an idmap.
|
||||
*/
|
||||
private const val PROPERTY_LOADER = 1 shl 2
|
||||
|
||||
/**
|
||||
* The apk assets is a RRO.
|
||||
* An RRO overlays resource values of its target package.
|
||||
*/
|
||||
private const val PROPERTY_OVERLAY = 1 shl 3
|
||||
|
||||
/**
|
||||
* The apk assets is owned by the application running in this process and incremental crash
|
||||
* protections for this APK must be disabled.
|
||||
*/
|
||||
private const val PROPERTY_DISABLE_INCREMENTAL_HARDENING = 1 shl 4
|
||||
|
||||
/**
|
||||
* The apk assets only contain the overlayable declarations information.
|
||||
*/
|
||||
private const val PROPERTY_ONLY_OVERLAYABLES = 1 shl 5
|
||||
|
||||
/** The apk assets class. */
|
||||
private val ApkAssetsClass by lazyClass("android.content.res.ApkAssets")
|
||||
|
||||
/** The xml block class. */
|
||||
private val XmlBlockClass by lazyClass("android.content.res.XmlBlock")
|
||||
|
||||
/** Global pointer references object. */
|
||||
private var xmlBlock: Long? = null
|
||||
|
||||
/** Global pointer references object. */
|
||||
private var blockParser: AutoCloseable? = null
|
||||
|
||||
/** Whether the initialization is done once. */
|
||||
private var isInitOnce = false
|
||||
|
||||
/**
|
||||
* Initialize.
|
||||
* @param context the context.
|
||||
*/
|
||||
fun init(context: Context) {
|
||||
// Context may be loaded from the preview and other non-Android platforms, ignoring this.
|
||||
if (context.javaClass.name.endsWith("BridgeContext")) return
|
||||
init(context.applicationContext.applicationInfo)
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize.
|
||||
* @param info the application info.
|
||||
*/
|
||||
private fun init(info: ApplicationInfo) {
|
||||
if (SystemVersion.isLowOrEqualsTo(SystemVersion.P)) return
|
||||
if (isInitOnce) return
|
||||
val sourceDir = info.sourceDir
|
||||
xmlBlock = when {
|
||||
SystemVersion.isHighOrEqualsTo(SystemVersion.R) ->
|
||||
// private static native long nativeLoad(@FormatType int format, @NonNull String path,
|
||||
// @PropertyFlags int flags, @Nullable AssetsProvider asset) throws IOException;
|
||||
HiddenApiBypass.getDeclaredMethod(
|
||||
ApkAssetsClass, "nativeLoad",
|
||||
IntType, StringClass, IntType, classOf<AssetsProvider>()
|
||||
).apply { isAccessible = true }.invoke(null, FORMAT_APK, sourceDir, PROPERTY_SYSTEM, null)
|
||||
SystemVersion.isHighOrEqualsTo(SystemVersion.P) ->
|
||||
// private static native long nativeLoad(
|
||||
// @NonNull String path, boolean system, boolean forceSharedLib, boolean overlay)
|
||||
// throws IOException;
|
||||
HiddenApiBypass.getDeclaredMethod(
|
||||
ApkAssetsClass, "nativeLoad",
|
||||
StringClass, BooleanType, BooleanType, BooleanType
|
||||
).apply { isAccessible = true }.invoke(null, sourceDir, false, false, false)
|
||||
else -> error("Unsupported Android version.")
|
||||
} as? Long? ?: error("Failed to create ApkAssets.")
|
||||
blockParser = HiddenApiBypass.getDeclaredConstructor(XmlBlockClass, AssetManagerClass, LongType)
|
||||
.apply { isAccessible = true }
|
||||
.newInstance(null, xmlBlock) as? AutoCloseable? ?: error("Failed to create XmlBlock\$Parser.")
|
||||
isInitOnce = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new parser.
|
||||
* @param context the context.
|
||||
* @param resId the style resource id, default is [Android_R.style.Widget].
|
||||
* @return [XmlResourceParser]
|
||||
*/
|
||||
fun newParser(context: Context, @StyleRes resId: Int = Android_R.style.Widget): XmlResourceParser {
|
||||
/**
|
||||
* Create a view [AttributeSet].
|
||||
* @return [XmlResourceParser]
|
||||
*/
|
||||
fun createViewAttrs() = context.layoutInflater.inflateOrNull<HikageAttrsView>(R.layout.layout_hikage_attrs_view)?.attrs
|
||||
as? XmlResourceParser? ?: error("Failed to create AttributeSet.")
|
||||
return if (SystemVersion.isHighOrEqualsTo(SystemVersion.P)) {
|
||||
if (!isInitOnce) return createViewAttrs()
|
||||
require(blockParser != null) { "Hikage initialization failed." }
|
||||
HiddenApiBypass.getDeclaredMethod(XmlBlockClass, "newParser", IntType)
|
||||
.apply { isAccessible = true }
|
||||
.invoke(blockParser, resId) as? XmlResourceParser? ?: error("Failed to create parser.")
|
||||
} else createViewAttrs()
|
||||
}
|
||||
}
|
953
hikage-core/src/main/java/com/highcapable/hikage/core/Hikage.kt
Normal file
953
hikage-core/src/main/java/com/highcapable/hikage/core/Hikage.kt
Normal file
@@ -0,0 +1,953 @@
|
||||
/*
|
||||
* Hikage - An Android responsive UI building tool.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/BetterAndroid/Hikage
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/2/25.
|
||||
*/
|
||||
@file:Suppress(
|
||||
"unused", "FunctionName", "PropertyName", "ConstPropertyName", "UNCHECKED_CAST",
|
||||
"MemberVisibilityCanBePrivate", "TOPLEVEL_TYPEALIASES_ONLY", "NON_PUBLIC_CALL_FROM_PUBLIC_INLINE"
|
||||
)
|
||||
|
||||
package com.highcapable.hikage.core
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.annotation.DimenRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.FontRes
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.highcapable.betterandroid.ui.extension.binding.ViewBinding
|
||||
import com.highcapable.betterandroid.ui.extension.component.base.DisplayDensity
|
||||
import com.highcapable.betterandroid.ui.extension.component.base.getColorCompat
|
||||
import com.highcapable.betterandroid.ui.extension.component.base.getColorStateListCompat
|
||||
import com.highcapable.betterandroid.ui.extension.component.base.getDrawableCompat
|
||||
import com.highcapable.betterandroid.ui.extension.component.base.getFontCompat
|
||||
import com.highcapable.betterandroid.ui.extension.component.base.toDp
|
||||
import com.highcapable.betterandroid.ui.extension.component.base.toPx
|
||||
import com.highcapable.betterandroid.ui.extension.view.LayoutParamsWrapContent
|
||||
import com.highcapable.betterandroid.ui.extension.view.ViewLayoutParams
|
||||
import com.highcapable.betterandroid.ui.extension.view.inflate
|
||||
import com.highcapable.betterandroid.ui.extension.view.layoutInflater
|
||||
import com.highcapable.hikage.annotation.Hikageable
|
||||
import com.highcapable.hikage.bypass.XmlBlockBypass
|
||||
import com.highcapable.hikage.core.Hikage.LayoutParamsBody
|
||||
import com.highcapable.hikage.core.base.HikageFactory
|
||||
import com.highcapable.hikage.core.base.HikageFactoryBuilder
|
||||
import com.highcapable.hikage.core.base.HikagePerformer
|
||||
import com.highcapable.hikage.core.base.HikageView
|
||||
import com.highcapable.hikage.core.base.PerformerException
|
||||
import com.highcapable.hikage.core.base.ProvideException
|
||||
import com.highcapable.hikage.core.extension.ResourcesScope
|
||||
import com.highcapable.yukireflection.factory.buildOf
|
||||
import com.highcapable.yukireflection.factory.classOf
|
||||
import com.highcapable.yukireflection.factory.constructor
|
||||
import com.highcapable.yukireflection.factory.current
|
||||
import com.highcapable.yukireflection.factory.notExtends
|
||||
import com.highcapable.yukireflection.type.android.AttributeSetClass
|
||||
import com.highcapable.yukireflection.type.android.ContextClass
|
||||
import com.highcapable.yukireflection.type.android.ViewGroup_LayoutParamsClass
|
||||
import com.highcapable.yukireflection.type.java.IntType
|
||||
import java.io.Serializable
|
||||
import java.lang.reflect.Constructor
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
/**
|
||||
* The Hikage core layout builder.
|
||||
*
|
||||
* A [Hikage] can have multiple levels of [Hikage.Performer].
|
||||
* @param factories the factories to customize the custom view in the initialization.
|
||||
*/
|
||||
class Hikage private constructor(private val factories: List<HikageFactory>) {
|
||||
|
||||
companion object {
|
||||
|
||||
/** The Android widget class prefix. */
|
||||
internal const val ANDROID_WIDGET_CLASS_PREFIX = "android.widget."
|
||||
|
||||
/** The unspecified layout params value. */
|
||||
private const val LayoutParamsUnspecified = LayoutParamsWrapContent - 1
|
||||
|
||||
/** The view constructors map. */
|
||||
private val viewConstructors = mutableMapOf<String, ViewConstructor>()
|
||||
|
||||
/** The view atomic id. */
|
||||
private val viewAtomicId = AtomicInteger(0x7F00000)
|
||||
|
||||
/** Returns nothing. */
|
||||
private fun noGetter(): Nothing = error("No getter available.")
|
||||
|
||||
/**
|
||||
* Automatically add [HikageFactory] to handle [LayoutInflater.Factory2].
|
||||
*
|
||||
* This [LayoutInflater] will be retrieved from the [Context] you passed in.
|
||||
*
|
||||
* This option is enabled by default and will add this feature when creating any [Hikage].
|
||||
* It can support the [View] autoboxing feature that supports libraries such as `androidx.appcompat`,
|
||||
* which can be disabled if you don't need it.
|
||||
*/
|
||||
var isAutoProcessWithFactory2 = true
|
||||
|
||||
/**
|
||||
* Create a new [Hikage].
|
||||
* @param context the context to create the layout.
|
||||
* @param parent the parent view group.
|
||||
* @param attachToParent whether to attach the layout to the parent when the [parent] is filled.
|
||||
* @param factory the [HikageFactory] builder.
|
||||
* @param performer the performer body.
|
||||
* @return [Hikage]
|
||||
*/
|
||||
@JvmName("createTyped")
|
||||
inline fun <reified LP : ViewGroup.LayoutParams> create(
|
||||
context: Context,
|
||||
parent: ViewGroup? = null,
|
||||
attachToParent: Boolean = parent != null,
|
||||
factory: HikageFactoryBuilder.() -> Unit = {},
|
||||
performer: HikagePerformer<LP>
|
||||
) = create(classOf<LP>(), context, parent, attachToParent, factory, performer)
|
||||
|
||||
/**
|
||||
* Create a new [Hikage].
|
||||
* @param context the context to create the layout.
|
||||
* @param parent the parent view group.
|
||||
* @param attachToParent whether to attach the layout to the parent when the [parent] is filled.
|
||||
* @param factory the [HikageFactory] builder.
|
||||
* @param performer the performer body.
|
||||
* @return [Hikage]
|
||||
*/
|
||||
inline fun create(
|
||||
context: Context,
|
||||
parent: ViewGroup? = null,
|
||||
attachToParent: Boolean = parent != null,
|
||||
factory: HikageFactoryBuilder.() -> Unit = {},
|
||||
performer: HikagePerformer<ViewGroup.LayoutParams>
|
||||
) = create(ViewGroup_LayoutParamsClass, context, parent, attachToParent, factory, performer)
|
||||
|
||||
/**
|
||||
* Create a new [Hikage].
|
||||
* @param lpClass the layout params type.
|
||||
* @param context the context to create the layout.
|
||||
* @param parent the parent view group.
|
||||
* @param attachToParent whether to attach the layout to the parent when the [parent] is filled.
|
||||
* @param factory the [HikageFactory] builder.
|
||||
* @param performer the performer body.
|
||||
* @return [Hikage]
|
||||
*/
|
||||
inline fun <LP : ViewGroup.LayoutParams> create(
|
||||
lpClass: Class<LP>,
|
||||
context: Context,
|
||||
parent: ViewGroup? = null,
|
||||
attachToParent: Boolean = parent != null,
|
||||
factory: HikageFactoryBuilder.() -> Unit = {},
|
||||
performer: HikagePerformer<LP>
|
||||
) = Hikage(HikageFactoryBuilder.create {
|
||||
if (isAutoProcessWithFactory2) add(HikageFactory(context.layoutInflater))
|
||||
factory()
|
||||
}.build()).apply {
|
||||
// If the parent view is specified and mark as attach to parent,
|
||||
// add it directly to the first position.
|
||||
if (parent != null && attachToParent) {
|
||||
val parentId = generateRandomViewId()
|
||||
provideView(parent, parentId)
|
||||
}; newPerformer(lpClass, parent, attachToParent, context).apply(performer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new [Hikage.Delegate].
|
||||
* @param factory the [HikageFactory] builder.
|
||||
* @param performer the performer body.
|
||||
* @return [Hikage.Delegate]<[LP]>
|
||||
*/
|
||||
@JvmName("buildTyped")
|
||||
inline fun <reified LP : ViewGroup.LayoutParams> build(
|
||||
noinline factory: HikageFactoryBuilder.() -> Unit = {},
|
||||
noinline performer: HikagePerformer<LP>
|
||||
) = build(classOf<LP>(), factory, performer)
|
||||
|
||||
/**
|
||||
* Create a new [Hikage.Delegate].
|
||||
* @param factory the [HikageFactory] builder.
|
||||
* @param performer the performer body.
|
||||
* @return [Hikage.Delegate]<[ViewGroup.LayoutParams]>
|
||||
*/
|
||||
fun build(
|
||||
factory: HikageFactoryBuilder.() -> Unit = {},
|
||||
performer: HikagePerformer<ViewGroup.LayoutParams>
|
||||
) = build(ViewGroup_LayoutParamsClass, factory, performer)
|
||||
|
||||
/**
|
||||
* Create a new [Hikage.Delegate].
|
||||
* @param lpClass the layout params type.
|
||||
* @param factory the [HikageFactory] builder.
|
||||
* @param performer the performer body.
|
||||
* @return [Hikage.Delegate]<[LP]>
|
||||
*/
|
||||
fun <LP : ViewGroup.LayoutParams> build(
|
||||
lpClass: Class<LP>,
|
||||
factory: HikageFactoryBuilder.() -> Unit = {},
|
||||
performer: HikagePerformer<LP>
|
||||
) = Delegate(lpClass, factory, performer)
|
||||
}
|
||||
|
||||
/** The [Hikage] layout params body type. */
|
||||
private typealias LayoutParamsBody<LP> = LP.() -> Unit
|
||||
|
||||
/**
|
||||
* The [Hikage.Performer] scope interface.
|
||||
*/
|
||||
private interface PerformerScope : DisplayDensity, ResourcesScope {
|
||||
|
||||
/**
|
||||
* Get the actual view id by [id].
|
||||
* @param id the view id.
|
||||
* @return [Int] or -1.
|
||||
*/
|
||||
fun actualViewId(id: String): Int
|
||||
}
|
||||
|
||||
/**
|
||||
* The view constructor class.
|
||||
* @param instance the constructor instance.
|
||||
* @param parameterCount the parameter count.
|
||||
*/
|
||||
private inner class ViewConstructor(
|
||||
private val instance: Constructor<*>,
|
||||
private val parameterCount: Int
|
||||
) {
|
||||
|
||||
/**
|
||||
* Build the view.
|
||||
* @param context the context.
|
||||
* @param attrs the attribute set.
|
||||
* @return [V] or null.
|
||||
*/
|
||||
fun <V : View> build(context: Context, attrs: AttributeSet) = when (parameterCount) {
|
||||
2 -> instance.newInstance(context, attrs)
|
||||
1 -> instance.newInstance(context)
|
||||
else -> null
|
||||
} as? V?
|
||||
}
|
||||
|
||||
/** The performer set. */
|
||||
private val performers = linkedSetOf<Performer<*>>()
|
||||
|
||||
/** The view map. */
|
||||
private val views = linkedMapOf<String, View>()
|
||||
|
||||
/** The view id map. */
|
||||
private val viewIds = mutableMapOf<String, Int>()
|
||||
|
||||
/**
|
||||
* Get the root view.
|
||||
* @return [View]
|
||||
*/
|
||||
val root get() = views.values.firstOrNull()
|
||||
?: throw PerformerException("No root view found, are you sure you have provided any view?")
|
||||
|
||||
/**
|
||||
* Get the root view [V].
|
||||
* @return [V]
|
||||
*/
|
||||
inline fun <reified V : View> root() = root as? V?
|
||||
?: throw PerformerException("Root view is not a type of ${classOf<V>().name}.")
|
||||
|
||||
/**
|
||||
* Get the view by [id].
|
||||
* @param id the view id.
|
||||
* @return [View]
|
||||
*/
|
||||
operator fun get(id: String) = views[id]
|
||||
?: throw PerformerException("View with id \"$id\" not found.")
|
||||
|
||||
/**
|
||||
* Get the view by [id].
|
||||
* @param id the view id.
|
||||
* @return [View] or null.
|
||||
*/
|
||||
fun getOrNull(id: String) = views[id]
|
||||
|
||||
/**
|
||||
* Get the view by [id] via [V].
|
||||
* @param id the view id.
|
||||
* @return [V]
|
||||
*/
|
||||
@JvmName("getTyped")
|
||||
inline fun <reified V : View> get(id: String) = get(id) as? V
|
||||
?: throw PerformerException("View with id \"$id\" is not a ${classOf<V>().name}.")
|
||||
|
||||
/**
|
||||
* Get the view by [id] via [V].
|
||||
* @param id the view id.
|
||||
* @return [V] or null.
|
||||
*/
|
||||
@JvmName("getOrNullTyped")
|
||||
inline fun <reified V : View> getOrNull(id: String) = getOrNull(id) as? V?
|
||||
|
||||
/**
|
||||
* Get the actual view id by [id].
|
||||
* @param id the view id.
|
||||
* @return [Int] or -1.
|
||||
*/
|
||||
fun getActualViewId(id: String) = viewIds[id] ?: -1
|
||||
|
||||
/**
|
||||
* Create a new [View] via [V].
|
||||
* @param viewClass the view class.
|
||||
* @param id the view id, generated by default.
|
||||
* @param context the context.
|
||||
* @return [V]
|
||||
*/
|
||||
private fun <V : View> createView(viewClass: Class<V>, id: String?, context: Context): V {
|
||||
val attrs = createAttributeSet(context)
|
||||
val view = createViewFromFactory(viewClass, id, context, attrs) ?: getViewConstructor(viewClass)?.build(context, attrs)
|
||||
if (view == null) throw PerformerException(
|
||||
"Create view of type ${viewClass.name} failed. " +
|
||||
"Please make sure the view class has a constructor with a single parameter of type Context."
|
||||
)
|
||||
provideView(view, id)
|
||||
return view
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the view constructor.
|
||||
* @param viewClass the view class.
|
||||
* @return [ViewConstructor] or null.
|
||||
*/
|
||||
private fun <V : View> getViewConstructor(viewClass: Class<V>) =
|
||||
viewConstructors[viewClass.name] ?: run {
|
||||
var parameterCount = 0
|
||||
val twoParams = viewClass.constructor {
|
||||
param(ContextClass, AttributeSetClass)
|
||||
}.ignored().give()
|
||||
val onceParam = viewClass.constructor {
|
||||
param(ContextClass)
|
||||
}.ignored().give()
|
||||
val constructor = onceParam?.apply { parameterCount = 1 }
|
||||
?: twoParams?.apply { parameterCount = 2 }
|
||||
val viewConstructor = constructor?.let { ViewConstructor(it, parameterCount) }
|
||||
if (viewConstructor != null) viewConstructors[viewClass.name] = viewConstructor
|
||||
viewConstructor
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new [View] from [HikageFactory].
|
||||
* @param viewClass the view class.
|
||||
* @param id the view id.
|
||||
* @param context the context.
|
||||
* @param attrs the attribute set.
|
||||
* @return [V] or null.
|
||||
*/
|
||||
private fun <V : View> createViewFromFactory(viewClass: Class<V>, id: String?, context: Context, attrs: AttributeSet): V? {
|
||||
val parent = performers.firstOrNull()?.parent
|
||||
var processed: V? = null
|
||||
factories.forEach { factory ->
|
||||
val params = PerformerParams(id, attrs, viewClass as Class<View>)
|
||||
val view = factory(parent, processed, context, params)
|
||||
if (view != null && view.javaClass notExtends viewClass) throw PerformerException(
|
||||
"HikageFactory cannot cast the created view type \"${view.javaClass}\" to \"${viewClass.name}\", " +
|
||||
"please confirm that the view type you created is correct."
|
||||
)
|
||||
if (view != null) processed = view as? V?
|
||||
}; return processed
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide an exists [View].
|
||||
* @param view the view instance.
|
||||
* @param id the view id.
|
||||
* @return [String]
|
||||
*/
|
||||
private fun provideView(view: View, id: String?): String {
|
||||
val (requireId, viewId) = generateViewId(id)
|
||||
view.id = viewId
|
||||
views[requireId] = view
|
||||
return requireId
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate view id from [id].
|
||||
* @param id the view id.
|
||||
* @return [Pair]<[String], [Int]>
|
||||
*/
|
||||
private fun generateViewId(id: String?): Pair<String, Int> {
|
||||
/**
|
||||
* Generate a new view id.
|
||||
* @param id the view id.
|
||||
* @return [Int]
|
||||
*/
|
||||
fun doGenerate(id: String): Int {
|
||||
val generateId = View.generateViewId()
|
||||
if (viewIds.contains(id)) throw PerformerException("View with id \"$id\" already exists.")
|
||||
viewIds[id] = generateId
|
||||
return generateId
|
||||
}
|
||||
val requireId = id ?: generateRandomViewId()
|
||||
val viewId = doGenerate(requireId)
|
||||
return requireId to viewId
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate random view id.
|
||||
* @return [String]
|
||||
*/
|
||||
private fun generateRandomViewId() = "anonymous@${viewAtomicId.getAndIncrement().toHexString()}"
|
||||
|
||||
/**
|
||||
* We just need a [AttributeSet] instance.
|
||||
* @param context the context.
|
||||
* @return [AttributeSet]
|
||||
*/
|
||||
private fun createAttributeSet(context: Context): AttributeSet = XmlBlockBypass.newParser(context)
|
||||
|
||||
/**
|
||||
* Start a new performer [LP].
|
||||
* @param parent the parent view group.
|
||||
* @param attachToParent whether to attach the layout to the parent when the [parent] is filled.
|
||||
* @param context the context, priority is given to [parent]'s context.
|
||||
* @return [Performer]
|
||||
*/
|
||||
private inline fun <reified LP : ViewGroup.LayoutParams> newPerformer(
|
||||
parent: ViewGroup? = null,
|
||||
attachToParent: Boolean = parent != null,
|
||||
context: Context? = null
|
||||
) = newPerformer(classOf<LP>(), parent, attachToParent, context)
|
||||
|
||||
/**
|
||||
* Start a new performer [LP].
|
||||
* @param lpClass the layout params type.
|
||||
* @param parent the parent view group.
|
||||
* @param attachToParent whether to attach the layout to the parent when the [parent] is filled.
|
||||
* @param context the context, priority is given to [parent]'s context.
|
||||
* @return [Performer]
|
||||
*/
|
||||
private fun <LP : ViewGroup.LayoutParams> newPerformer(
|
||||
lpClass: Class<LP>,
|
||||
parent: ViewGroup? = null,
|
||||
attachToParent: Boolean = parent != null,
|
||||
context: Context? = null
|
||||
) = Performer(lpClass, parent, attachToParent, context).apply {
|
||||
// Init [XmlBlockBypass] if the context is not null.
|
||||
context?.let { XmlBlockBypass.init(it) }
|
||||
performers.add(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Require no [Performer].
|
||||
* @param name the process name.
|
||||
* @param block the block.
|
||||
*/
|
||||
internal inline fun requireNoPerformers(name: String, block: () -> Unit) {
|
||||
val viewCount = views.size
|
||||
block()
|
||||
if (views.size != viewCount) throw PerformerException(
|
||||
"Performers are not allowed to appear in $name DSL creation process."
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Include a delegate.
|
||||
* @param delegate the delegate.
|
||||
* @param context the context.
|
||||
* @param embedded the embedded flag.
|
||||
* @return [Hikage]
|
||||
*/
|
||||
private fun include(delegate: Delegate<*>, context: Context, embedded: Boolean): Hikage {
|
||||
val hikage = delegate.create(context)
|
||||
if (!embedded) return hikage
|
||||
val duplicateId = hikage.viewIds.toList().firstOrNull { (k, _) -> viewIds.containsKey(k) }?.first
|
||||
if (duplicateId != null) throw PerformerException(
|
||||
"Embedded layout view IDs conflict, the view id \"$duplicateId\" is already exists."
|
||||
)
|
||||
viewIds.putAll(hikage.viewIds)
|
||||
views.putAll(hikage.views)
|
||||
performers.addAll(hikage.performers)
|
||||
return hikage
|
||||
}
|
||||
|
||||
/**
|
||||
* The core performer entity of layout build [LP].
|
||||
* @param parent the parent view group.
|
||||
* @param attachToParent whether to attach the layout to the parent.
|
||||
* @param lpClass the layout params type.
|
||||
* @param baseContext the context to create the layout, priority is given to [parent]'s context.
|
||||
* if [parent] is null, it must be set manually.
|
||||
*/
|
||||
inner class Performer<LP : ViewGroup.LayoutParams> internal constructor(
|
||||
private val lpClass: Class<LP>,
|
||||
internal val parent: ViewGroup?,
|
||||
private val attachToParent: Boolean,
|
||||
private val baseContext: Context? = null
|
||||
) : PerformerScope {
|
||||
|
||||
/** The current [Hikage]. */
|
||||
private val current get() = this@Hikage
|
||||
|
||||
/**
|
||||
* The context to create the layout.
|
||||
* @return [Context]
|
||||
*/
|
||||
private val context get() = parent?.context
|
||||
?: baseContext
|
||||
?: throw PerformerException("Parent layout is null or broken, Hikage.Performer need a Context to create the layout.")
|
||||
|
||||
override fun actualViewId(id: String) = getActualViewId(id)
|
||||
|
||||
override fun stringResource(@StringRes resId: Int, vararg formatArgs: Any) =
|
||||
if (formatArgs.isNotEmpty())
|
||||
context.getString(resId, *formatArgs)
|
||||
else context.getString(resId)
|
||||
override fun colorResource(@ColorRes resId: Int) = context.getColorCompat(resId)
|
||||
override fun stateColorResource(@ColorRes resId: Int) = context.getColorStateListCompat(resId)
|
||||
override fun drawableResource(@DrawableRes resId: Int) = context.getDrawableCompat(resId)
|
||||
override fun bitmapResource(@DrawableRes resId: Int) = context.getDrawableCompat(resId).toBitmap()
|
||||
override fun dimenResource(@DimenRes resId: Int) = context.resources.getDimension(resId)
|
||||
override fun fontResource(@FontRes resId: Int) = context.getFontCompat(resId)
|
||||
|
||||
override fun <N : Number> N.toPx() = toPx(context)
|
||||
override fun <N : Number> N.toDp() = toDp(context)
|
||||
|
||||
/** The count of providing views. */
|
||||
private var provideCount = 0
|
||||
|
||||
/**
|
||||
* Provide a new [View] instance [V].
|
||||
* @param lparams the view layout params.
|
||||
* @param id the view id, generated by default.
|
||||
* @param init the view initialization body.
|
||||
* @return [V]
|
||||
*/
|
||||
@Hikageable
|
||||
@JvmName("ViewTyped")
|
||||
inline fun <reified V : View> View(
|
||||
lparams: LayoutParams? = null,
|
||||
id: String? = null,
|
||||
init: HikageView<V> = {}
|
||||
): V {
|
||||
val lpDelegate = LayoutParams.from(current, lpClass, parent, lparams)
|
||||
val view = createView(classOf<V>(), id, context)
|
||||
view.layoutParams = lpDelegate.create()
|
||||
requireNoPerformers(classOf<V>().name) { view.init() }
|
||||
startProvide<V>(id)
|
||||
addToParentIfRequired(view)
|
||||
return view
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a new [View] instance.
|
||||
* @param lparams the view layout params.
|
||||
* @param id the view id, generated by default.
|
||||
* @param init the view initialization body.
|
||||
* @return [View]
|
||||
*/
|
||||
@Hikageable
|
||||
inline fun View(
|
||||
lparams: LayoutParams? = null,
|
||||
id: String? = null,
|
||||
init: HikageView<View> = {}
|
||||
) = View<View>(lparams, id, init)
|
||||
|
||||
/**
|
||||
* Provide a new [ViewGroup] instance [VG].
|
||||
*
|
||||
* Provide the new type of [ViewGroup.LayoutParams] down via [LP].
|
||||
*
|
||||
* - Note: The [VG] must be inherited from [ViewGroup].
|
||||
* @param lparams the view layout params.
|
||||
* @param id the view id, generated by default.
|
||||
* @param init the view initialization body.
|
||||
* @param performer the performer body.
|
||||
* @return [VG]
|
||||
*/
|
||||
@Hikageable
|
||||
@JvmName("ViewGroupLP")
|
||||
inline fun <reified VG : ViewGroup, reified LP : ViewGroup.LayoutParams> ViewGroup(
|
||||
lparams: LayoutParams? = null,
|
||||
id: String? = null,
|
||||
init: HikageView<VG> = {},
|
||||
performer: HikagePerformer<LP> = {}
|
||||
): VG {
|
||||
val lpDelegate = LayoutParams.from(current, lpClass, parent, lparams)
|
||||
val view = createView(classOf<VG>(), id, context)
|
||||
view.layoutParams = lpDelegate.create()
|
||||
requireNoPerformers(classOf<VG>().name) { view.init() }
|
||||
startProvide<VG>(id)
|
||||
addToParentIfRequired(view)
|
||||
newPerformer<LP>(view).apply(performer)
|
||||
return view
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a new [ViewGroup] instance [VG].
|
||||
*
|
||||
* - Note: The [VG] must be inherited from [ViewGroup].
|
||||
* @param lparams the view layout params.
|
||||
* @param id the view id, generated by default.
|
||||
* @param init the view initialization body.
|
||||
* @param performer the performer body.
|
||||
* @return [VG]
|
||||
*/
|
||||
@Hikageable
|
||||
inline fun <reified VG : ViewGroup> ViewGroup(
|
||||
lparams: LayoutParams? = null,
|
||||
id: String? = null,
|
||||
init: HikageView<VG> = {},
|
||||
performer: HikagePerformer<ViewGroup.LayoutParams> = {}
|
||||
) = ViewGroup<VG, ViewGroup.LayoutParams>(lparams, id, init, performer)
|
||||
|
||||
/**
|
||||
* Provide layout from [resId].
|
||||
* @param resId the layout resource id.
|
||||
* @param lparams the view layout params.
|
||||
* @param id the view id, generated by default.
|
||||
*/
|
||||
@Hikageable
|
||||
fun Layout(
|
||||
@LayoutRes resId: Int,
|
||||
lparams: LayoutParams? = null,
|
||||
id: String? = null
|
||||
): View {
|
||||
val view = context.layoutInflater.inflate(resId, parent, attachToRoot = false)
|
||||
startProvide<View>(id, view)
|
||||
lparams?.create()?.let { view.layoutParams = it }
|
||||
provideView(view, id)
|
||||
addToParentIfRequired(view)
|
||||
return view
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide layout from [ViewBinding].
|
||||
* @param lparams the view layout params.
|
||||
* @param id the view id, generated by default.
|
||||
* @return [VB]
|
||||
*/
|
||||
@Hikageable
|
||||
inline fun <reified VB : ViewBinding> Layout(
|
||||
lparams: LayoutParams? = null,
|
||||
id: String? = null
|
||||
): VB {
|
||||
val viewBinding = ViewBinding<VB>().inflate(context.layoutInflater, parent, attachToParent = false)
|
||||
val view = viewBinding.root
|
||||
startProvide<View>(id, view)
|
||||
if (view.parent != null) throw ProvideException(
|
||||
"The ViewBinding($view) already has a parent, " +
|
||||
"it may have been created using layout root node <merge> or <include>, cannot be provided."
|
||||
)
|
||||
lparams?.create()?.let { view.layoutParams = it }
|
||||
provideView(view, id)
|
||||
addToParentIfRequired(view)
|
||||
return viewBinding
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide layout from exists [View].
|
||||
* @param view the view instance.
|
||||
* @param lparams the view layout params.
|
||||
* @param id the view id, generated by default.
|
||||
* @return [View]
|
||||
*/
|
||||
@Hikageable
|
||||
fun Layout(
|
||||
view: View,
|
||||
lparams: LayoutParams? = null,
|
||||
id: String? = null
|
||||
): View {
|
||||
if (view.parent != null) throw ProvideException("The view $view already has a parent, cannot be provided.")
|
||||
startProvide<View>(id, view)
|
||||
val lpDelegate = LayoutParams.from(current = this@Hikage, lpClass, parent, lparams, view.layoutParams)
|
||||
view.layoutParams = lpDelegate.create()
|
||||
provideView(view, id)
|
||||
addToParentIfRequired(view)
|
||||
return view
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide layout from another [Hikage].
|
||||
*
|
||||
* - Note: [Hikage] view IDs will not copied to this layout
|
||||
* when provides a complete [Hikage] instead of [Delegate].
|
||||
*
|
||||
* ```kotlin
|
||||
* val first: Hikage = Hikageable(context) {
|
||||
* TextView(id = "textView")
|
||||
* }
|
||||
* val second: Hikage = Hikageable(context) {
|
||||
* TextView(id = "textView")
|
||||
* // The view ID "textView" will not copied to this layout,
|
||||
* // so there will be no problem of ID overlap.
|
||||
* Layout(first)
|
||||
* }
|
||||
* ```
|
||||
* @param hikage the hikage instance.
|
||||
* @param lparams the view layout params.
|
||||
* @param id the view id, generated by default.
|
||||
* @return [Hikage]
|
||||
*/
|
||||
@Hikageable
|
||||
fun Layout(
|
||||
hikage: Hikage,
|
||||
lparams: LayoutParams? = null,
|
||||
id: String? = null
|
||||
): Hikage {
|
||||
val view = hikage.root
|
||||
startProvide<View>(id, view)
|
||||
val lpDelegate = LayoutParams.from(current = this@Hikage, lpClass, parent, lparams, view.layoutParams)
|
||||
if (view.parent != null) throw ProvideException(
|
||||
"The Hikage layout root view $view already has a parent, cannot be provided."
|
||||
)
|
||||
view.layoutParams = lpDelegate.create()
|
||||
provideView(view, id)
|
||||
addToParentIfRequired(view)
|
||||
return hikage
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide layout from another [Delegate].
|
||||
*
|
||||
* - Note: If you use [embedded], you must make sure that the introduced view IDs
|
||||
* do not overlap with the view IDs in the current layout.
|
||||
* @param delegate the delegate instance.
|
||||
* @param lparams the view layout params.
|
||||
* @param id the view id, generated by default.
|
||||
* @param embedded whether to embed this layout (All view IDs and performers will be copied),
|
||||
* default is true.
|
||||
* @return [Hikage]
|
||||
*/
|
||||
@Hikageable
|
||||
fun Layout(
|
||||
delegate: Delegate<*>,
|
||||
lparams: LayoutParams? = null,
|
||||
id: String? = null,
|
||||
embedded: Boolean = true
|
||||
): Hikage {
|
||||
val hikage = include(delegate, context, embedded)
|
||||
return Layout(hikage, lparams, id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new layout params.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```kotlin
|
||||
* View(
|
||||
* lparams = LayoutParams {
|
||||
* // You code here.
|
||||
* }
|
||||
* )
|
||||
* ```
|
||||
* @see ViewLayoutParams
|
||||
* @param body the layout params body.
|
||||
* @return [LayoutParams]
|
||||
*/
|
||||
fun LayoutParams(
|
||||
width: Int = LayoutParamsUnspecified,
|
||||
height: Int = LayoutParamsUnspecified,
|
||||
matchParent: Boolean = false,
|
||||
widthMatchParent: Boolean = false,
|
||||
heightMatchParent: Boolean = false,
|
||||
body: LayoutParamsBody<LP> = {}
|
||||
) = LayoutParams.from(
|
||||
current, lpClass, parent,
|
||||
width, height, matchParent, widthMatchParent, heightMatchParent,
|
||||
body = body
|
||||
)
|
||||
|
||||
/** If required, add the [view] to the [parent]. */
|
||||
private fun addToParentIfRequired(view: View) {
|
||||
if (attachToParent) parent?.addView(view)
|
||||
}
|
||||
|
||||
/**
|
||||
* Call to start providing a new view.
|
||||
* @param id the view id.
|
||||
* @param view the view instance.
|
||||
*/
|
||||
private inline fun <reified V : View> startProvide(id: String?, view: V? = null) {
|
||||
provideCount++
|
||||
if (provideCount > 1 && (parent == null || !attachToParent)) throw ProvideException(
|
||||
"Provide view ${view?.javaClass ?: classOf<V>()}(${id?.let { "\"$it\""} ?: "<anonymous>"}) failed. ${
|
||||
if (parent == null) "No parent view group found"
|
||||
else "Parent view group declares attachToParent = false"
|
||||
}, you can only provide one view for the root view."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The parameters of the [Performer].
|
||||
* @param id the view ID.
|
||||
* @param attrs the attributes set.
|
||||
* @param viewClass the view class.
|
||||
*/
|
||||
data class PerformerParams internal constructor(
|
||||
val id: String?,
|
||||
val attrs: AttributeSet,
|
||||
val viewClass: Class<View>
|
||||
) : Serializable
|
||||
|
||||
/**
|
||||
* The [Hikage] layout params.
|
||||
* @see ViewLayoutParams
|
||||
* @param current the current [Hikage].
|
||||
* @param lpClass the layout params type.
|
||||
* @param parent the parent view group.
|
||||
*/
|
||||
class LayoutParams private constructor(
|
||||
private val current: Hikage,
|
||||
private val lpClass: Class<ViewGroup.LayoutParams>,
|
||||
private val parent: ViewGroup?
|
||||
) {
|
||||
|
||||
/**
|
||||
* Builder params of body.
|
||||
*/
|
||||
private class BodyBuilder(
|
||||
val width: Int,
|
||||
val height: Int,
|
||||
val matchParent: Boolean,
|
||||
val widthMatchParent: Boolean,
|
||||
val heightMatchParent: Boolean,
|
||||
val body: LayoutParamsBody<ViewGroup.LayoutParams>
|
||||
)
|
||||
|
||||
/**
|
||||
* Builder params of wrapper.
|
||||
*/
|
||||
private class WrapperBuilder(
|
||||
val delegate: LayoutParams?,
|
||||
val lparams: ViewGroup.LayoutParams?
|
||||
)
|
||||
|
||||
/** The layout params body. */
|
||||
private var bodyBuilder: BodyBuilder? = null
|
||||
|
||||
/** The layout params wrapper. */
|
||||
private var wrapperBuilder: WrapperBuilder? = null
|
||||
|
||||
internal companion object {
|
||||
|
||||
/**
|
||||
* Create a new [LayoutParams]
|
||||
* @see ViewLayoutParams
|
||||
* @param current the current [Hikage].
|
||||
* @param parent the parent view group.
|
||||
* @return [LayoutParams]
|
||||
*/
|
||||
fun <LP : ViewGroup.LayoutParams> from(
|
||||
current: Hikage,
|
||||
lpClass: Class<LP>,
|
||||
parent: ViewGroup?,
|
||||
width: Int,
|
||||
height: Int,
|
||||
matchParent: Boolean,
|
||||
widthMatchParent: Boolean,
|
||||
heightMatchParent: Boolean,
|
||||
body: LayoutParamsBody<LP>
|
||||
) = LayoutParams(current, lpClass as Class<ViewGroup.LayoutParams>, parent).apply {
|
||||
bodyBuilder = BodyBuilder(
|
||||
width, height, matchParent, widthMatchParent, heightMatchParent,
|
||||
body as LayoutParamsBody<ViewGroup.LayoutParams>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new [LayoutParams].
|
||||
* @param current the current [Hikage].
|
||||
* @param parent the parent view group.
|
||||
* @param delegate the delegate.
|
||||
* @param lparams the another layout params.
|
||||
* @return [LayoutParams]
|
||||
*/
|
||||
fun <LP : ViewGroup.LayoutParams> from(
|
||||
current: Hikage,
|
||||
lpClass: Class<LP>,
|
||||
parent: ViewGroup?,
|
||||
delegate: LayoutParams?,
|
||||
lparams: ViewGroup.LayoutParams? = null
|
||||
) = LayoutParams(current, lpClass as Class<ViewGroup.LayoutParams>, parent).apply {
|
||||
wrapperBuilder = WrapperBuilder(delegate, lparams)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a default layout params.
|
||||
* @return [ViewGroup.LayoutParams]
|
||||
*/
|
||||
private fun createDefaultLayoutParams(lparams: ViewGroup.LayoutParams? = null): ViewGroup.LayoutParams {
|
||||
val wrapped = lparams?.let {
|
||||
parent?.current(ignored = true)?.method {
|
||||
name = "generateLayoutParams"
|
||||
param(ViewGroup_LayoutParamsClass)
|
||||
superClass()
|
||||
}?.invoke<ViewGroup.LayoutParams?>(it)
|
||||
} ?: lparams
|
||||
return wrapped
|
||||
// Build a default.
|
||||
?: lpClass.buildOf<ViewGroup.LayoutParams>(LayoutParamsWrapContent, LayoutParamsWrapContent) {
|
||||
param(IntType, IntType)
|
||||
} ?: throw PerformerException("Create default layout params failed.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the layout params.
|
||||
* @return [ViewGroup.LayoutParams]
|
||||
*/
|
||||
fun create(): ViewGroup.LayoutParams {
|
||||
if (bodyBuilder == null && wrapperBuilder == null) throw PerformerException("No layout params builder found.")
|
||||
return bodyBuilder?.let {
|
||||
val lparams = ViewLayoutParams(lpClass, it.width, it.height, it.matchParent, it.widthMatchParent, it.heightMatchParent)
|
||||
current.requireNoPerformers(lparams.javaClass.name) { it.body(lparams) }
|
||||
lparams
|
||||
} ?: wrapperBuilder?.let {
|
||||
val lparams = it.delegate?.create() ?: it.lparams
|
||||
createDefaultLayoutParams(lparams)
|
||||
} ?: throw PerformerException("Internal error of build layout params.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The delegate for [Hikage].
|
||||
* @param lpClass the layout params type.
|
||||
* @param factory the [HikageFactory] builder.
|
||||
* @param performer the performer body.
|
||||
*/
|
||||
class Delegate<LP : ViewGroup.LayoutParams> internal constructor(
|
||||
private val lpClass: Class<LP>,
|
||||
private val factory: HikageFactoryBuilder.() -> Unit = {},
|
||||
private val performer: HikagePerformer<LP>
|
||||
) {
|
||||
|
||||
/**
|
||||
* Create a new [Hikage].
|
||||
* @param context the context to create the layout.
|
||||
* @param parent the parent view group.
|
||||
* @param attachToParent whether to attach the layout to the parent when the [parent] is filled.
|
||||
* @return [Hikage]
|
||||
*/
|
||||
|
||||
fun create(context: Context, parent: ViewGroup? = null, attachToParent: Boolean = parent != null) =
|
||||
create(lpClass, context, parent, attachToParent, factory, performer)
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Hikage - An Android responsive UI building tool.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/BetterAndroid/Hikage
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/2/26.
|
||||
*/
|
||||
@file:JvmName("ExceptionsUtils")
|
||||
|
||||
package com.highcapable.hikage.core.base
|
||||
|
||||
/**
|
||||
* The exception of performing view.
|
||||
* @param message the exception message.
|
||||
*/
|
||||
internal class PerformerException(message: String) : Exception(message)
|
||||
|
||||
/**
|
||||
* The exception of providing view.
|
||||
* @param message the exception message.
|
||||
*/
|
||||
internal class ProvideException(message: String) : Exception(message)
|
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Hikage - An Android responsive UI building tool.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/BetterAndroid/Hikage
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/3/4.
|
||||
*/
|
||||
@file:Suppress("unused", "FunctionName")
|
||||
@file:JvmName("HikageFactory")
|
||||
|
||||
package com.highcapable.hikage.core.base
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.highcapable.hikage.core.Hikage
|
||||
|
||||
/**
|
||||
* The [Hikage] factory type.
|
||||
*
|
||||
* - `parent` the parent view group.
|
||||
* - `base` the base view (from previous [HikageFactory] processed, if not will null).
|
||||
* - `context` the view context.
|
||||
* - `params` the parameters.
|
||||
*/
|
||||
typealias HikageFactory = (parent: ViewGroup?, base: View?, context: Context, params: Hikage.PerformerParams) -> View?
|
||||
|
||||
/**
|
||||
* Create a [Hikage] factory.
|
||||
* @param createView the view creator.
|
||||
* @return [HikageFactory]
|
||||
*/
|
||||
@JvmSynthetic
|
||||
fun HikageFactory(createView: HikageFactory) = createView
|
||||
|
||||
/**
|
||||
* Create a [Hikage] factory from [LayoutInflater].
|
||||
*
|
||||
* This will proxy the function of [LayoutInflater.Factory2] to [Hikage].
|
||||
* @param inflater the layout inflater.
|
||||
* @return [HikageFactory]
|
||||
*/
|
||||
@JvmSynthetic
|
||||
fun HikageFactory(inflater: LayoutInflater) = HikageFactory { parent, base, context, params ->
|
||||
base ?: inflater.factory2?.onCreateView(parent, params.viewClass.name.let {
|
||||
if (it.startsWith(Hikage.ANDROID_WIDGET_CLASS_PREFIX))
|
||||
it.replace(Hikage.ANDROID_WIDGET_CLASS_PREFIX, "")
|
||||
else it
|
||||
}, context, params.attrs)
|
||||
}
|
||||
|
||||
/**
|
||||
* The [HikageFactory] builder.
|
||||
*/
|
||||
class HikageFactoryBuilder private constructor() {
|
||||
|
||||
internal companion object {
|
||||
|
||||
/**
|
||||
* Create a [HikageFactoryBuilder].
|
||||
* @param builder the builder.
|
||||
* @return [HikageFactoryBuilder]
|
||||
*/
|
||||
inline fun create(builder: HikageFactoryBuilder.() -> Unit) = HikageFactoryBuilder().apply(builder)
|
||||
}
|
||||
|
||||
/** Caches factories. */
|
||||
private val factories = mutableListOf<HikageFactory>()
|
||||
|
||||
/**
|
||||
* Add a factory.
|
||||
* @param factory the factory.
|
||||
*/
|
||||
fun add(factory: HikageFactory) {
|
||||
factories.add(factory)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add factories.
|
||||
* @param factories the factories.
|
||||
*/
|
||||
fun addAll(factories: List<HikageFactory>) {
|
||||
this.factories.addAll(factories)
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the factory.
|
||||
* @return <[List]>[HikageFactory]
|
||||
*/
|
||||
internal fun build() = factories.toList()
|
||||
}
|
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Hikage - An Android responsive UI building tool.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/BetterAndroid/Hikage
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/2/25.
|
||||
*/
|
||||
@file:Suppress("unused", "FunctionName", "NON_PUBLIC_CALL_FROM_PUBLIC_INLINE")
|
||||
@file:JvmName("HikageableUtils")
|
||||
|
||||
package com.highcapable.hikage.core.base
|
||||
|
||||
import android.content.Context
|
||||
import android.view.ViewGroup
|
||||
import com.highcapable.hikage.core.Hikage
|
||||
|
||||
/**
|
||||
* The [Hikage.Performer] type.
|
||||
*/
|
||||
typealias HikagePerformer<LP> = Hikage.Performer<LP>.() -> Unit
|
||||
|
||||
/**
|
||||
* The [Hikage] view scope type.
|
||||
*/
|
||||
typealias HikageView<V> = V.() -> Unit
|
||||
|
||||
/**
|
||||
* Start performing a [Hikage] layout [LP].
|
||||
* @param context the context to create the layout.
|
||||
* @param parent the parent view group.
|
||||
* @param attachToParent whether to attach the layout to the parent when the [parent] is filled.
|
||||
* @param factory the [HikageFactory] builder.
|
||||
* @param performer the performer body.
|
||||
* @return [Hikage]
|
||||
*/
|
||||
@JvmSynthetic
|
||||
@JvmName("HikageableTyped")
|
||||
inline fun <reified LP : ViewGroup.LayoutParams> Hikageable(
|
||||
context: Context,
|
||||
parent: ViewGroup? = null,
|
||||
attachToParent: Boolean = parent != null,
|
||||
factory: HikageFactoryBuilder.() -> Unit = {},
|
||||
performer: HikagePerformer<LP>
|
||||
) = Hikage.create(context, parent, attachToParent, factory, performer)
|
||||
|
||||
/**
|
||||
* Start performing a [Hikage] layout.
|
||||
* @param context the context to create the layout.
|
||||
* @param parent the parent view group.
|
||||
* @param attachToParent whether to attach the layout to the parent when the [parent] is filled.
|
||||
* @param factory the [HikageFactory] builder.
|
||||
* @param performer the performer body.
|
||||
* @return [Hikage]
|
||||
*/
|
||||
@JvmSynthetic
|
||||
inline fun Hikageable(
|
||||
context: Context,
|
||||
parent: ViewGroup? = null,
|
||||
attachToParent: Boolean = parent != null,
|
||||
factory: HikageFactoryBuilder.() -> Unit = {},
|
||||
performer: HikagePerformer<ViewGroup.LayoutParams>
|
||||
) = Hikage.create(context, parent, attachToParent, factory, performer)
|
||||
|
||||
/**
|
||||
* Start performing a [Hikage] layout [LP].
|
||||
* @param factory the [HikageFactory] builder.
|
||||
* @param performer the performer body.
|
||||
* @return [Hikage.Delegate]<[LP]>
|
||||
*/
|
||||
@JvmSynthetic
|
||||
@JvmName("HikageableTyped")
|
||||
inline fun <reified LP : ViewGroup.LayoutParams> Hikageable(
|
||||
noinline factory: HikageFactoryBuilder.() -> Unit = {},
|
||||
noinline performer: HikagePerformer<LP>
|
||||
) = Hikage.build<LP>(factory, performer)
|
||||
|
||||
/**
|
||||
* Start performing a [Hikage] layout.
|
||||
* @param factory the [HikageFactory] builder.
|
||||
* @param performer the performer body.
|
||||
* @return [Hikage.Delegate]<[ViewGroup.LayoutParams]>
|
||||
*/
|
||||
@JvmSynthetic
|
||||
fun Hikageable(
|
||||
factory: HikageFactoryBuilder.() -> Unit = {},
|
||||
performer: HikagePerformer<ViewGroup.LayoutParams>
|
||||
) = Hikage.build(factory, performer)
|
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Hikage - An Android responsive UI building tool.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/BetterAndroid/Hikage
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/3/6.
|
||||
*/
|
||||
@file:Suppress("unused")
|
||||
@file:JvmName("HikageBuilderUtils")
|
||||
|
||||
package com.highcapable.hikage.core.builder
|
||||
|
||||
import android.content.Context
|
||||
import android.view.ViewGroup
|
||||
import com.highcapable.hikage.core.Hikage
|
||||
|
||||
/**
|
||||
* Lazy initialize a [Hikage] layout.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```kotlin
|
||||
* object MainLayout : HikageBuilder {
|
||||
*
|
||||
* override fun build() = Hikageable {
|
||||
* LinearLayout(
|
||||
* lparams = LayoutParams(matchParent = true),
|
||||
* init = {
|
||||
* orientation = LinearLayout.VERTICAL
|
||||
* gravity = Gravity.CENTER
|
||||
* }
|
||||
* ) {
|
||||
* TextView {
|
||||
* text = "Hello, Hikage!"
|
||||
* textSize = 20f
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* class MainActivity : AppCompatActivity() {
|
||||
*
|
||||
* private val hikage by lazyHikage(MainLayout)
|
||||
*
|
||||
* override fun onCreate(savedInstanceState: Bundle?) {
|
||||
* super.onCreate(savedInstanceState)
|
||||
* setContentView(hikage)
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
* @receiver the context to create the layout.
|
||||
* @param builder the [HikageBuilder] instance.
|
||||
* @param parent the parent view group.
|
||||
* @param attachToParent whether to attach the layout to the parent when the [parent] is filled.
|
||||
* @return [Lazy]<[Hikage]>
|
||||
*/
|
||||
@JvmSynthetic
|
||||
fun Context.lazyHikage(
|
||||
builder: HikageBuilder,
|
||||
parent: ViewGroup? = null,
|
||||
attachToParent: Boolean = parent != null
|
||||
) = lazy { builder.build().create(context = this, parent, attachToParent) }
|
||||
|
||||
/**
|
||||
* A layout builder interface for [Hikage].
|
||||
*
|
||||
* Checking [build] for usage.
|
||||
*/
|
||||
interface HikageBuilder {
|
||||
|
||||
/**
|
||||
* Provide a [Hikage] for builder.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```kotlin
|
||||
* override fun build() = Hikageable {
|
||||
* TextView(
|
||||
* lparams = LayoutParams(
|
||||
* width = 100.dp,
|
||||
* height = 100.dp
|
||||
* )
|
||||
* ) {
|
||||
* text = "Hello, Hikage!"
|
||||
* textSize = 20f
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
* @return [Hikage.Delegate]
|
||||
*/
|
||||
fun build(): Hikage.Delegate<*>
|
||||
}
|
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Hikage - An Android responsive UI building tool.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/BetterAndroid/Hikage
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/3/12.
|
||||
*/
|
||||
package com.highcapable.hikage.core.extension
|
||||
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Typeface
|
||||
import android.graphics.drawable.Drawable
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.annotation.DimenRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.FontRes
|
||||
import androidx.annotation.StringRes
|
||||
import com.highcapable.hikage.core.Hikage
|
||||
|
||||
/**
|
||||
* An extension for [Hikage] view scope to provide resources.
|
||||
*/
|
||||
interface ResourcesScope {
|
||||
|
||||
/**
|
||||
* Get the string from [resId].
|
||||
* @param resId the resource id.
|
||||
* @param formatArgs the format arguments.
|
||||
* @return [String]
|
||||
*/
|
||||
fun stringResource(@StringRes resId: Int, vararg formatArgs: Any): String
|
||||
|
||||
/**
|
||||
* Get the color from [resId].
|
||||
* @param resId the resource id.
|
||||
* @return [Int]
|
||||
*/
|
||||
fun colorResource(@ColorRes resId: Int): Int
|
||||
|
||||
/**
|
||||
* Get the [ColorStateList] from [resId].
|
||||
* @param resId the resource id.
|
||||
* @return [Int]
|
||||
*/
|
||||
fun stateColorResource(@ColorRes resId: Int): ColorStateList
|
||||
|
||||
/**
|
||||
* Get the [Drawable] from [resId].
|
||||
* @param resId the resource id.
|
||||
* @return [Drawable]
|
||||
*/
|
||||
fun drawableResource(@DrawableRes resId: Int): Drawable
|
||||
|
||||
/**
|
||||
* Get the [Bitmap] from [resId].
|
||||
* @param resId the resource id.
|
||||
* @return [Bitmap]
|
||||
*/
|
||||
fun bitmapResource(@DrawableRes resId: Int): Bitmap
|
||||
|
||||
/**
|
||||
* Get the dimension from [resId].
|
||||
* @param resId the resource id.
|
||||
* @return [Float]
|
||||
*/
|
||||
fun dimenResource(@DimenRes resId: Int): Float
|
||||
|
||||
/**
|
||||
* Get the font from [resId].
|
||||
* @param resId the resource id.
|
||||
* @return [Typeface] or null.
|
||||
*/
|
||||
fun fontResource(@FontRes resId: Int): Typeface?
|
||||
}
|
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Hikage - An Android responsive UI building tool.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/BetterAndroid/Hikage
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/2/27.
|
||||
*/
|
||||
@file:Suppress("unused", "FunctionName")
|
||||
|
||||
package com.highcapable.hikage.core.preview
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import androidx.annotation.CallSuper
|
||||
import com.highcapable.hikage.core.Hikage
|
||||
import com.highcapable.hikage.core.builder.HikageBuilder
|
||||
|
||||
/**
|
||||
* A preview layout for [Hikage].
|
||||
*
|
||||
* - Note: This class should only be used in the layout preview mode by [View.isInEditMode].
|
||||
*/
|
||||
abstract class HikagePreview(context: Context, attrs: AttributeSet? = null) : FrameLayout(context, attrs), HikageBuilder {
|
||||
|
||||
init {
|
||||
require(isInEditMode) {
|
||||
"HikagePreview should only be used in the layout preview mode."
|
||||
}
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
removeAllViews()
|
||||
build().create(context, parent = this)
|
||||
}
|
||||
}
|
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
* Hikage - An Android responsive UI building tool.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/BetterAndroid/Hikage
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/2/26.
|
||||
*/
|
||||
@file:Suppress("unused", "DEPRECATION")
|
||||
@file:JvmName("WidgetsDeclaration")
|
||||
|
||||
package com.highcapable.hikage.widget.android
|
||||
|
||||
import android.widget.AbsListView
|
||||
import android.widget.ActionMenuView
|
||||
import android.widget.AutoCompleteTextView
|
||||
import android.widget.Button
|
||||
import android.widget.CalendarView
|
||||
import android.widget.CheckBox
|
||||
import android.widget.CheckedTextView
|
||||
import android.widget.Chronometer
|
||||
import android.widget.DatePicker
|
||||
import android.widget.EditText
|
||||
import android.widget.ExpandableListView
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.GridLayout
|
||||
import android.widget.GridView
|
||||
import android.widget.HorizontalScrollView
|
||||
import android.widget.ImageButton
|
||||
import android.widget.ImageSwitcher
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ListView
|
||||
import android.widget.MediaController
|
||||
import android.widget.NumberPicker
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.QuickContactBadge
|
||||
import android.widget.RadioButton
|
||||
import android.widget.RadioGroup
|
||||
import android.widget.RatingBar
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.ScrollView
|
||||
import android.widget.SearchView
|
||||
import android.widget.SeekBar
|
||||
import android.widget.Space
|
||||
import android.widget.Spinner
|
||||
import android.widget.Switch
|
||||
import android.widget.TabHost
|
||||
import android.widget.TabWidget
|
||||
import android.widget.TableLayout
|
||||
import android.widget.TableRow
|
||||
import android.widget.TextClock
|
||||
import android.widget.TextSwitcher
|
||||
import android.widget.TextView
|
||||
import android.widget.TimePicker
|
||||
import android.widget.ToggleButton
|
||||
import android.widget.Toolbar
|
||||
import android.widget.VideoView
|
||||
import android.widget.ViewAnimator
|
||||
import android.widget.ViewFlipper
|
||||
import android.widget.ViewSwitcher
|
||||
import com.highcapable.hikage.annotation.HikageViewDeclaration
|
||||
|
||||
@HikageViewDeclaration(SeekBar::class)
|
||||
private object SeekBarDeclaration
|
||||
|
||||
@HikageViewDeclaration(LinearLayout::class, LinearLayout.LayoutParams::class)
|
||||
private object LinearLayoutDeclaration
|
||||
|
||||
@HikageViewDeclaration(RelativeLayout::class, RelativeLayout.LayoutParams::class)
|
||||
private object RelativeLayoutDeclaration
|
||||
|
||||
@HikageViewDeclaration(FrameLayout::class, FrameLayout.LayoutParams::class)
|
||||
private object FrameLayoutDeclaration
|
||||
|
||||
@HikageViewDeclaration(ScrollView::class, FrameLayout.LayoutParams::class)
|
||||
private object ScrollViewDeclaration
|
||||
|
||||
@HikageViewDeclaration(ProgressBar::class)
|
||||
private object ProgressBarDeclaration
|
||||
|
||||
@HikageViewDeclaration(Chronometer::class)
|
||||
private object ChronometerDeclaration
|
||||
|
||||
@HikageViewDeclaration(Space::class)
|
||||
private object SpaceDeclaration
|
||||
|
||||
@HikageViewDeclaration(CheckedTextView::class)
|
||||
private object CheckedTextViewDeclaration
|
||||
|
||||
@HikageViewDeclaration(ExpandableListView::class, AbsListView.LayoutParams::class)
|
||||
private object ExpandableListViewDeclaration
|
||||
|
||||
@HikageViewDeclaration(Spinner::class)
|
||||
private object SpinnerDeclaration
|
||||
|
||||
@HikageViewDeclaration(RadioGroup::class, RadioGroup.LayoutParams::class)
|
||||
private object RadioGroupDeclaration
|
||||
|
||||
@HikageViewDeclaration(RadioButton::class)
|
||||
private object RadioButtonDeclaration
|
||||
|
||||
@HikageViewDeclaration(ToggleButton::class)
|
||||
private object ToggleButtonDeclaration
|
||||
|
||||
@HikageViewDeclaration(CheckBox::class)
|
||||
private object CheckBoxDeclaration
|
||||
|
||||
@HikageViewDeclaration(EditText::class)
|
||||
private object EditTextDeclaration
|
||||
|
||||
@HikageViewDeclaration(AutoCompleteTextView::class)
|
||||
private object AutoCompleteTextViewDeclaration
|
||||
|
||||
@HikageViewDeclaration(Button::class)
|
||||
private object ButtonDeclaration
|
||||
|
||||
@HikageViewDeclaration(ImageButton::class)
|
||||
private object ImageButtonDeclaration
|
||||
|
||||
@HikageViewDeclaration(TextView::class)
|
||||
private object TextViewDeclaration
|
||||
|
||||
@HikageViewDeclaration(TextClock::class)
|
||||
private object TextClockDeclaration
|
||||
|
||||
@HikageViewDeclaration(TextSwitcher::class, FrameLayout.LayoutParams::class)
|
||||
private object TextSwitcherDeclaration
|
||||
|
||||
@HikageViewDeclaration(ActionMenuView::class, ActionMenuView.LayoutParams::class)
|
||||
private object ActionMenuViewDeclaration
|
||||
|
||||
@HikageViewDeclaration(CalendarView::class, FrameLayout.LayoutParams::class)
|
||||
private object CalendarViewDeclaration
|
||||
|
||||
@HikageViewDeclaration(DatePicker::class, FrameLayout.LayoutParams::class)
|
||||
private object DatePickerDeclaration
|
||||
|
||||
@HikageViewDeclaration(TimePicker::class, FrameLayout.LayoutParams::class)
|
||||
private object TimePickerDeclaration
|
||||
|
||||
@HikageViewDeclaration(RatingBar::class)
|
||||
private object RatingBarDeclaration
|
||||
|
||||
@HikageViewDeclaration(HorizontalScrollView::class, FrameLayout.LayoutParams::class)
|
||||
private object HorizontalScrollViewDeclaration
|
||||
|
||||
@HikageViewDeclaration(QuickContactBadge::class)
|
||||
private object QuickContactBadgeDeclaration
|
||||
|
||||
@HikageViewDeclaration(ImageSwitcher::class, FrameLayout.LayoutParams::class)
|
||||
private object ImageSwitcherDeclaration
|
||||
|
||||
@HikageViewDeclaration(ViewSwitcher::class, FrameLayout.LayoutParams::class)
|
||||
private object ViewSwitcherDeclaration
|
||||
|
||||
@HikageViewDeclaration(ViewFlipper::class, FrameLayout.LayoutParams::class)
|
||||
private object ViewFlipperDeclaration
|
||||
|
||||
@HikageViewDeclaration(ViewAnimator::class, FrameLayout.LayoutParams::class)
|
||||
private object ViewAnimatorDeclaration
|
||||
|
||||
@HikageViewDeclaration(VideoView::class)
|
||||
private object VideoViewDeclaration
|
||||
|
||||
@HikageViewDeclaration(Toolbar::class, Toolbar.LayoutParams::class)
|
||||
private object ToolbarDeclaration
|
||||
|
||||
@HikageViewDeclaration(GridLayout::class, GridLayout.LayoutParams::class)
|
||||
private object GridLayoutDeclaration
|
||||
|
||||
@HikageViewDeclaration(GridView::class, AbsListView.LayoutParams::class)
|
||||
private object GridViewDeclaration
|
||||
|
||||
@HikageViewDeclaration(ListView::class, AbsListView.LayoutParams::class)
|
||||
private object ListViewDeclaration
|
||||
|
||||
@HikageViewDeclaration(ImageView::class)
|
||||
private object ImageViewDeclaration
|
||||
|
||||
@HikageViewDeclaration(MediaController::class, FrameLayout.LayoutParams::class)
|
||||
private object MediaControllerDeclaration
|
||||
|
||||
@HikageViewDeclaration(TableLayout::class, TableLayout.LayoutParams::class)
|
||||
private object TableLayoutDeclaration
|
||||
|
||||
@HikageViewDeclaration(TableRow::class, TableRow.LayoutParams::class)
|
||||
private object TableRowDeclaration
|
||||
|
||||
@HikageViewDeclaration(NumberPicker::class, LinearLayout.LayoutParams::class)
|
||||
private object NumberPickerDeclaration
|
||||
|
||||
@HikageViewDeclaration(SearchView::class, FrameLayout.LayoutParams::class)
|
||||
private object SearchViewDeclaration
|
||||
|
||||
@HikageViewDeclaration(Switch::class)
|
||||
private object SwitchDeclaration
|
||||
|
||||
@HikageViewDeclaration(TabHost::class, FrameLayout.LayoutParams::class)
|
||||
private object TabHostDeclaration
|
||||
|
||||
@HikageViewDeclaration(TabWidget::class, LinearLayout.LayoutParams::class)
|
||||
private object TabWidgetDeclaration
|
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.highcapable.hikage.bypass.HikageAttrsView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
@@ -0,0 +1,18 @@
|
||||
package com.highcapable.hikage
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user