直接上图
目标
将 TitleBar 封装起来,从页面上来说就是将 返回按钮 和 title 封装成一个组合控件
stackoverflow
how-to-add-custom-view-groups-to-anko-dsl
动手实践
难点有2 –>
1. kotlin 形式的 inflate view 写法
2. 如何将自定义 View 添加到 DSL 中使用
难点解决: kotlin 形式的 inflate view 写法
参考 github demo anko-example
上代码:
class KtTitleBar : FrameLayout {
private lateinit var image: ImageView
private lateinit var text: TextView
private lateinit var imageParent: LinearLayout
constructor(context: Context?) : super(context) {
initView()
}
private fun initView() = AnkoContext.createDelegate(this).apply {
relativeLayout {
gravity = Gravity.CENTER_VERTICAL
imageParent = verticalLayout {
image = imageView(R.drawable.icon).lparams(dip(21), dip(21)) {
padding = dip(12)
}
}
text = textView {
textSize = 18f
textColor = Color.parseColor("#333333")
}.lparams {
centerInParent()
}
}
}
}
(ps: 事实上,在上面的代码中 imageParent 这个父节点是有点多余的,但是,我在自定义 View 中发现使用 padding 的效果居然和 margin 的效果是等同的,所以我只能添加 imageParent。我觉得这是个坑,解释详见 Anko 我遇到的坑)
难点解决: 如何添加到 DSL 中使用
如上代码控件最基本的代码已经完成了,那么接下来就是怎么在 DSL 中使用,如果你仔细看过了 demo 中的 customView 代码,你就会发现完整的代码应该是这样的:
package kt.widget
import android.content.Context
import android.graphics.Color
import android.view.Gravity
import android.view.ViewManager
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import com.ibplus.client.R
import com.ibplus.client.Utils.RxUtil
import org.jetbrains.anko.*
import org.jetbrains.anko.custom.ankoView
/**
* Created by cjh on 2017/6/14.
*/
class KtTitleBar : FrameLayout {
private lateinit var image: ImageView
private lateinit var text: TextView
private lateinit var imageParent: LinearLayout
constructor(context: Context?) : super(context) {
initView()
}
private fun initView() = AnkoContext.createDelegate(this).apply {
relativeLayout {
gravity = Gravity.CENTER_VERTICAL
imageParent = verticalLayout {
image = imageView(R.drawable.icon).lparams(dip(21), dip(21)) {
padding = dip(12)
}
}
text = textView {
textSize = 18f
textColor = Color.parseColor("#333333")
}.lparams {
centerInParent()
}
}
}
}
inline fun ViewManager.ktTitleBar(theme: Int = 0) = ktTitleBar({}, theme)
inline fun ViewManager.ktTitleBar(init: ktTitleBar.() -> Unit, theme: Int = 0) = ankoView(::ktTitleBar, theme, init)
代码到这里,实际上已经可以在 DSL 中使用了,但是也仅仅只是引用的层次,比如我想动态的改变它的 backIcon,我想改变 title 的内容,其实主要理由是,写到这里,在 DSL 中只能这么用:
verticalLayout {
ktTitleBar()
}
连最基本的这样使用都不行:
verticalLayout {
ktTitleBar(){
}
}
那么记下来我就要来完善我的 TitleBar 了。
首先是 DSL 的使用优化,我非常希望这样使用:
verticalLayout {
ktTitleBar{
......
}
}
那么代码改动就是这样:
从 –>
inline fun ViewManager.ktTitleBar(theme: Int = 0) = ktTitleBar({}, theme)
inline fun ViewManager.ktTitleBar(init: ktTitleBar.() -> Unit, theme: Int = 0) = ankoView(::ktTitleBar, theme, init)
变成 –>
inline fun ViewManager.ktTitleBar() = ktTitleBar{}
inline fun ViewManager.ktTitleBar(init: KtTitleBar.() -> Unit) = ankoView(::KtTitleBar, 0, init)
继续深入自定义
以上的代码保证了基本的使用,但是在实际开发中,TitleBar 的返回按钮的图标和标题一定不会是一成不变的,它们随着页面的不同而改变,所以在实际开发中,我希望的使用代码模式是这样的:
verticalLayout {
ktTitleBar(backIcon, title){
......
}
}
以上代码的所表达的其实就是多变的项目使用。
那么最终我的 TitleBar 的完整代码变成了这样:
package kt.widget
import android.content.Context
import android.graphics.Color
import android.view.Gravity
import android.view.ViewManager
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import com.ibplus.client.R
import com.ibplus.client.Utils.RxUtil
import com.ibplus.viewtoimage.LogUtils
import org.jetbrains.anko.*
import org.jetbrains.anko.custom.ankoView
/**
* Created by cjh on 2017/6/14.
*/
class KtTitleBar : FrameLayout {
private lateinit var image: ImageView
private lateinit var text: TextView
private lateinit var imageParent: LinearLayout
constructor(context: Context?) : super(context) {
initView()
}
private fun initView() = AnkoContext.createDelegate(this).apply {
relativeLayout {
gravity = Gravity.CENTER_VERTICAL
imageParent = verticalLayout {
LogUtils.e("createDelegate")
image = imageView(R.drawable.menu_icon_back).lparams(dip(21), dip(21)) {
padding = dip(12)
}
}
text = textView {
textSize = 18f
textColor = Color.parseColor("#333333")
}.lparams {
centerInParent()
}
}
}
fun setBackIcon(res: Int) {
image?.setImageResource(res)
}
fun setTitle(title: String) {
text?.setText(title)
}
fun setBackIconClickFun(click: () -> Unit) {
RxUtil.click(imageParent, click)
}
}
inline fun ViewManager.ktTitleBar() = ktTitleBar {}
inline fun ViewManager.ktTitleBar(init: KtTitleBar.() -> Unit) = ankoView(::KtTitleBar, 0, init)
inline fun ViewManager.ktTitleBar(backIcon: Int, title: String, init: KtTitleBar.() -> Unit) = ktTitleBar {
setBackIcon(backIcon)
setTitle(title)
}
撇去添加的几个扩展功能性的函数 setBackIcon / setTitle / setBackIconClickFun,主要看控件的定义,我添加了:
inline fun ViewManager.ktTitleBar(backIcon: Int, title: String, init: KtTitleBar.() -> Unit) = ktTitleBar {
setBackIcon(backIcon)
setTitle(title)
}
这样我在 DSL 中使用的时候就变成如下的方式了:
val _ktTitleBar = ktTitleBar(backIcon, title) {
backgroundColor = Color.WHITE
}
_ktTitleBar.setBackIconClickFun { ui.owner.finish() }
至此,一个非常简单的 Anko TitleBar 就完成了,其实完成的比较曲折,因为官方对这一块的描述相当不完整,几乎没有,所以基本是一路 stackoverflow/github/看源码/猜测 走过来的。另外,写完之后,我发现 anko 真的是想和 xml 说 88,具体原因见:Anko 我遇到的坑。