用kotlin的语法特性撸个声明式的UI框架,期望的效果是flutter式的UI写法。并无实际意义,仅作学习之用,实际上我们期待jetpack compose就好。闲话不多说,开始实现。
首先我们的Activity需要一个setContentView
方法
inline fun Activity.contentView(block: Activity.()->View){
this.setContentView(block())
}
contentView接收一个方法,这个方法返回View,并调用Activity的setContentView添加这个View
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
contentView {
}
}
}
接下来需要根布局了,先弄一个LinearLayout
inline fun Activity.linearLayout(block: LinearLayout.() -> Unit): View {
return LinearLayout(this).apply {
block()
}
}
实例化LinearLayout,lambda内部this指向LinearLayout
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
contentView {
linearLayout {
}
}
}
}
为ViewGroup添加创建TextView的扩展函数text
inline fun ViewGroup.text(block: TextView.() -> Unit) {
TextView(this.context).apply {
block()
addView(this)
}
}
实例化TextView并添加到调用者ViewGroup,方法参数lambda内部this指向TextView
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
contentView {
linearLayout {
text {
setTextColor(resources.getColor(android.R.color.holo_orange_light))
text = "kotlin DSL"
}
}
}
}
}
run一下看看效果
那么问题来了,我想初始化的时候设置TextView大小和一些布局属性,修改
text
方法
inline fun ViewGroup.text(width: Int = 0, height: Int = 0, block: TextView.() -> Unit) {
TextView(this.context).apply {
if (width != 0 && height != 0) {
val params = ViewGroup.MarginLayoutParams(width, height)
if (width != 0) params.width = width
if (height != 0) params.height = height
layoutParams = params
}
block()
addView(this)
}
}
TextView初始化时设置大小
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
contentView {
linearLayout {
orientation = LinearLayout.VERTICAL
gravity = Gravity.CENTER_HORIZONTAL
text(width = 500, height = 200) {
setBackgroundColor(resources.getColor(android.R.color.holo_orange_light))
setTextColor(resources.getColor(android.R.color.black))
gravity = Gravity.CENTER
text = "kotlin DSL"
}
}
}
}
}
还想给TextView设置Margin等布局属性
inline fun View.layoutParams(block: ViewGroup.MarginLayoutParams.() -> Unit) {
val params = layoutParams ?: ViewGroup.MarginLayoutParams(width, height)
(params as ViewGroup.MarginLayoutParams).block()
layoutParams = params
}
View统一添加扩展函数,操作layoutParams
布局属性
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
contentView {
linearLayout {
orientation = LinearLayout.VERTICAL
gravity = Gravity.CENTER_HORIZONTAL
text(width = 500, height = 200) {
setBackgroundColor(resources.getColor(android.R.color.holo_orange_light))
setTextColor(resources.getColor(android.R.color.black))
gravity = Gravity.CENTER
text = "kotlin DSL"
layoutParams {
width = 800
height = 300
topMargin = 100
}
}
}
}
}
}
这个时候你会想说,根布局我不想用LinearLayout,我想用RelativeLayout,需要搬砖式的为Activity添加一个relativeLayout扩展函数吗?当然没有必要,泛型实例化派上了用场。
修改Activity.linearLayout
inline fun <reified T : ViewGroup> Activity.viewGroup(block: T.() -> Unit): View {
val constructor = T::class.java.getConstructor(Context::class.java)
return constructor.newInstance(this).apply {
block()
}
}
拿到泛型类型,反射调用ViewGroup的单参数构造方法。如此一来,linearLayout
就可以修改为viewGroup
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
contentView {
viewGroup<RelativeLayout> {
gravity = Gravity.CENTER
text(width = 500, height = 200) {
setBackgroundColor(resources.getColor(android.R.color.holo_orange_light))
setTextColor(resources.getColor(android.R.color.black))
gravity = Gravity.CENTER
text = "kotlin DSL"
layoutParams {
width = 800
height = 300
topMargin = 100
}
}
}
}
}
}
当然呢,大家都会觉得反射比较消耗性能,那也可以选择搬砖式的单独添加构造函数,实际上每个ViewGroup肯定都有其自身特性,单独添加是有其必要的,这里只不过是为ViewGroup提供一个统一的入口。又有朋友会说我期望View一个lambda就能搞定所有属性,不希望再嵌套一层layoutParams去设置布局
fun View.setTopMargin(margin: Int) {
val params = layoutParams ?: ViewGroup.MarginLayoutParams(width, height)
(params as ViewGroup.MarginLayoutParams).topMargin = 300
layoutParams = params
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
contentView {
viewGroup<RelativeLayout> {
gravity = Gravity.CENTER_HORIZONTAL
text(width = 500, height = 200) {
setBackgroundColor(resources.getColor(android.R.color.holo_orange_light))
setTextColor(resources.getColor(android.R.color.black))
gravity = Gravity.CENTER
text = "kotlin DSL"
setTopMargin(100)
}
}
}
}
}
接下来就是搬砖活了,给View添加设置layoutParams的同名扩展方法。
刚刚给ViewGroup
统一添加了创建入口,View
当然也是需要的。
inline fun <reified T : View> ViewGroup.view(width: Int = 0, height: Int = 0, block: T.() -> Unit) {
val constructor = T::class.java.getConstructor(Context::class.java)
(constructor.newInstance(this.context) as T).apply {
if (width != 0 && height != 0) {
val params = ViewGroup.MarginLayoutParams(width, height)
if (width != 0) params.width = width
if (height != 0) params.height = height
layoutParams = params
}
block()
addView(this)
}
}
text
标签也可以修改为view
了
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
contentView {
viewGroup<RelativeLayout> {
gravity = Gravity.CENTER_HORIZONTAL
view<TextView>(width = 500, height = 200) {
setBackgroundColor(resources.getColor(android.R.color.holo_orange_light))
setTextColor(resources.getColor(android.R.color.black))
gravity = Gravity.CENTER
text = "kotlin DSL"
setTopMargin(100)
}
}
}
}
}
这样一来就是纯反射了,当然不合理,搬砖也不合理。实际上这里只是提供一个思路,很多细节都没考虑,写代码就是这样嘛,先得有思路然后再慢慢优化。