Android UI开发神兵利器—Kotlin Anko

作者: Taonce | 来源:发表于2019-01-04 12:40 被阅读25次

    Anko的简介

    引用Anko的GitHub主页上面的解释:

    Anko is a Kotlin library which makes Android application development faster and easier. It makes your code clean and easy to read, and lets you forget about rough edges of the Android SDK for Java.

    Anko是为了使Android开发程序更加简单和快速而生成的一个Kotlin库,它可以使您的代码清晰、易读,并且它可以让您忘记粗糙的Java Android SDK。

    Anko目前主要用于:Layout布局、SQLite数据库和Coroutines协程三个方面。

    接下来我们主要交流的是Layout方面的知识。

    引入Anko和遇到的问题

    添加Anko的依赖: implementation "org.jetbrains.anko:anko:$anko_version"

    这时发现有爆红的地方了:提示v7包和v4包版本不一致,这就很纳闷了,我都没用私自添加删除v4包,怎么会出现问题呢,接着我就去libraries找原因了,原来是Anko也引入了v4的包,而且版本是27.1.1,我新建工程的编译版本是28.0.0,小伙伴们要注意了。

    解决:排除anko包中的v4包

    implementation("org.jetbrains.anko:anko:$anko_version") {
            exclude module: 'support-v4'
    }
    
    

    使用Anko,从四个点介绍下如何使用Anko以及遇到的问题

    ① 实现一个简单的登录界面
    既然是使用Anko,那么当然是要抛弃xml布局文件咯,也就不用写setContentView()来绑定布局文件了,可以直接在onCreate()方法里面调用我们自己写的AnkoComponent类的setContentView()绑定activity就行了,这种写法是比较推荐的一种,还有一种就是直接把verticalLayout{}写在onCreate()里面,但是不推荐,这样会造成Activity类的代码冗余。下面来看看如何实现这么一个简单的布局:

    class AnkoActivity : AppCompatActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            // AnkoComponent和Activity相互绑定
            MainUI().setContentView(this@AnkoActivity)
        }
    }
    
    class MainUI : AnkoComponent<AnkoActivity> {
        override fun createView(ui: AnkoContext<AnkoActivity>) = with(ui) {
            verticalLayout {
                // 这个gravity对应的就是gravity,而在lparams闭包中,gravity对应的是layout_gravity
                gravity = Gravity.CENTER
                // 布局的属性params在闭包里面的lparams中设置,但是button、TextView等控件的属性params是在闭包外的lparams设置
                lparams(matchParent, matchParent)
                editText {
                    hint = "userName"
                    gravity = Gravity.CENTER
                    // 监听输入框输入情况
                    addTextChangedListener(object : TextWatcher {
                        override fun afterTextChanged(s: Editable?) {
                        }
    
                        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
                        }
    
                        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
                        }
                    })
                }.lparams(width = dip(250), height = 200)
    
                editText {
                    hint = "password"
                    top = 20
                    gravity = Gravity.CENTER
                }.lparams(width = dip(250), height = 200)
    
                button("list") {
                    backgroundColor = Color.parseColor("#FF9999")
                    alpha = 0.5f
                    // 点击事件
                    onClick {
                        // anko封装的intent携带值跳转
                        startActivity<ListActivity>("aulton" to "aulton")
                    }
                    // 长按事件
                    onLongClick {
                        toast("Long Click")
                    }
                }.lparams(dip(250), dip(50))
    
                button("setting") {
                    backgroundColor = Color.parseColor("#FF7777")
                    alpha = 0.5f
                    // 点击事件
                    onClick {
                        // anko封装的intent携带值跳转
                        startActivity<SettingActivity>("aulton" to "aulton")
                    }
                }.lparams(dip(250), dip(50)) {
                    topMargin = dip(16)
                }
    
                button("custom_view") {
                    backgroundColor = Color.parseColor("#FF7777")
                    alpha = 0.5f
                    // 点击事件
                    onClick {
                        // anko封装的intent携带值跳转
                        startActivity<CustomCircleActivity>("aulton" to "aulton")
                    }
                }.lparams(dip(250), dip(50)) {
                    topMargin = dip(16)
                }
            }
        }
    }
    
    
    • 这里我们用的都是大家常见的一些布局和控件,verticalLayout就是oritentation=verticalLinearLayout

    • 控件和布局的一些属性需要注意下,比如verticalLayout里面的gravity = Gravity.CENTER对应的就是xml中的gravity,如果出现在lparams闭包中的gravity = Gravity.CENTER指的就是layout_gravity属性了。千万要分清。

    • AnkoComponentcreateView()其实是有返回值的

      interface AnkoComponent<in T> {
          fun createView(ui: AnkoContext<T>): View
      }
      
      

      返回的是一个View对象,这里我使用with(ui)来实现自动返回。你也可以使用let()或者apply()等作用域函数。你可以从ui: AnkoContext<T>对象中拿到ContextActivityView三个对象,都是很重要的属性。

    效果如下:


    login

    ② 使用Anko实现RV列表

    要想使用RecyclerView你必须添加新的依赖:

    //    RecyclerView-v7
    implementation "org.jetbrains.anko:anko-recyclerview-v7:$anko_version"
    implementation "org.jetbrains.anko:anko-recyclerview-v7-coroutines:$anko_version"
    
    

    首先写出rv+swipeRefreshLayout布局:

    class ListUI : AnkoComponent<ListActivity> {
        override fun createView(ui: AnkoContext<ListActivity>) = with(ui) {
            // 下拉刷新控件
            swipeRefreshLayout {
                // 下拉监听事件
                setOnRefreshListener {
                    toast("refresh")
                    isRefreshing = false
                }
                // rv
                recyclerView {
                    layoutManager = LinearLayoutManager(ui.ctx)
                    lparams(width = matchParent, height = matchParent)
                    adapter = MyAdapter(ui.ctx, mutableListOf("1",
                            "2", "3", "4"))
                }
            }
        }
    }
    
    

    rv所有的属性:manageradapter都可以直接在闭包里面设置。

    接下来看看适配器中的ItemView如何用Anko实现:

    class MyAdapter(private val context: Context,
                    private val mData: MutableList<String>) : RecyclerView.Adapter<MyAdapter.MyHolder>() {
    
        // 创建Holder对象
        override fun onCreateViewHolder(p0: ViewGroup, p1: Int): MyHolder {
            // 根据anko生成itemView,并且给itemView的tag赋值,从而取得MyHolder
            return AdapterUI().createView(AnkoContext.create(context, p0)).tag as MyHolder
        }
    
        override fun getItemCount(): Int {
            return mData.size
        }
    
        // 绑定holder,呈现UI
        override fun onBindViewHolder(holder: MyHolder, p1: Int) {
            holder.tv_name.text = mData[p1]
        }
    
        class MyHolder(view: View, val tv_name: TextView) : RecyclerView.ViewHolder(view)
    
        class AdapterUI : AnkoComponent<ViewGroup> {
            override fun createView(ui: AnkoContext<ViewGroup>): View {
                var tv_name: TextView? = null
                val item_view = with(ui) {
                    relativeLayout {
                        lparams(width = matchParent, height = dip(50))
                        tv_name = textView {
                            textSize = 12f
                            textColor = Color.parseColor("#FF0000")
                            backgroundColor = Color.parseColor("#FFF0F5")
                            gravity = Gravity.CENTER
                        }.lparams(width = matchParent, height = dip(50)) {
                            // 设置外边距
                            topMargin = dip(10)
                        }
                    }
                }
                // 返回itemView,并且通过tag生成MyHolder
                item_view.tag = MyHolder(item_view, tv_name = tv_name!!)
                return item_view
            }
        }
    }
    
    

    其实这里主要使用到的就是View对象的tag属性,将ItemViewtagHolder绑定在一起,这样我们AnkoComponentcreateView()返回ItemView的同时也把Holder生成并返回了,就可以在AdapteronCreateViewHolder()方法中拿到Holder对象。

    效果如下:


    rv

    ③ 复用AnkoView

    在日常开发中我们会遇到这样的情形,类似于通用的设置界面,所有的条目都是很类似的,只不过文字或者icon不一样,如果我们用rv来实现,难免觉得条目太少,不划算,但是每个条目都是自己写一遍,又会觉得太繁琐,这时候Anko就会帮助我们简化很大的代码,下面一起来看看:

    fun myLinearLayout(viewManager: ViewManager,
                       itemHeight: Int = 40,
                       itemMarginTop: Int = 0,
                       itemMarginBottom: Int = 0,
                       headImageId: Int = 0,
                       headTextRes: String,
                       bottomImageId: Int = 0) = with(viewManager) {
        linearLayout {
            orientation = LinearLayout.HORIZONTAL
            leftPadding = dip(16)
            rightPadding = dip(16)
            backgroundColor = Color.parseColor("#FFFFFF")
            // 设置整体的宽高和外边距
            lparams(width = matchParent, height = dip(itemHeight)) {
                setMargins(0, itemMarginTop, 0, itemMarginBottom)
            }
            // 左边图片
            if (headImageId != 0) {
                imageView(headImageId) {
                    scaleType = ImageView.ScaleType.FIT_XY
                }.lparams(width = dip(30), height = dip(30)) {
                    gravity = Gravity.CENTER
                }
            }
            // 左边字体
            textView(headTextRes) {
                gravity = Gravity.CENTER_VERTICAL
            }.lparams(width = matchParent, height = matchParent, weight = 1f) {
                if (headImageId != 0) {
                    marginStart = dip(16)
                }
            }
            // 右边图片
            if (bottomImageId != 0) {
                imageView(bottomImageId) {
                    scaleType = ImageView.ScaleType.FIT_XY
                }.lparams(width = dip(30), height = dip(30)) {
                    gravity = Gravity.CENTER
                }
            }
        }
    }
    
    

    首先定义一个方法,方法包含了item的高度、上下外边距、头部icon、头部文字、尾部icon和ViewManager7个参数。方法的内部实现采用Anko的DSL(领域特定语言)语言实现。其中参数ViewManager是我们前面提到的AnkoComponent的父类,它是这个方法的主要参数,因为在Anko实现的一系列View都是ViewManager的扩展函数。想复用的话,直接调用这个方法就行了,ForExample:

    class SettingUI : AnkoComponent<SettingActivity> {
        override fun createView(ui: AnkoContext<SettingActivity>) = with(ui) {
            verticalLayout {
                myLinearLayout(viewManager = this,
                        headImageId = R.mipmap.setting,
                        headTextRes = "Setting",
                        bottomImageId = R.mipmap.arrow,
                        itemMarginBottom = 8,
                        itemMarginTop = 8)
                myLinearLayout(viewManager = this,
                        headTextRes = "MyInfo",
                        bottomImageId = R.mipmap.arrow,
                        itemMarginBottom = 8)
                myLinearLayout(this,
                        headTextRes = "Exit",
                        headImageId = R.mipmap.exit,
                        bottomImageId = R.mipmap.arrow)
            }
        }
    }
    
    

    效果如下:


    settin

    ④ 在Anko中使用自定义View

    有一天产品让你画一个比较奇特的圆弧,这个圆弧你必须用自定义View实现,在你实现了之后,你发现Anko中却不能使用,ViewManager并没有生成自定义View的方法,这时你傻眼了,辛辛苦苦写的View在Anko中用不了。别急,下面我们一起来学习下如何使用:

    第一步:自定义一个圆弧,这里用很简单的一个例子

    定义属性:这些属性都可以在Anko的闭包中直接赋值

    // 圆弧开始的角度
    var startAngle: Float = 0f
    // 圆弧结束的角度
    var endAngle: Float = 0f
    // 圆弧的背景颜色
    @ColorInt
    var arcBg: Int = 0
        set(value) {
            field = value
            circlePaint?.color = value
        }
    // 画笔的宽度
    var paintWidth: Float = 1f
        set(value) {
            field = value
            circlePaint?.strokeWidth = value
            rectPaint?.strokeWidth = value
        }
    
    

    RectArc:简单的实现下

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.drawRect(circleRect!!, rectPaint!!)
        canvas.drawArc(circleRect!!, startAngle, endAngle - startAngle, true, circlePaint!!)
    }
    
    

    第二步:实现扩展函数,扩展函数主要的还是靠返回的ankoView()来实现,我们看到的CustomCircle(it)中的it就是Context对象。这样就直接调用了自定义View的构造函数。

    /**
     * 以下都是为了在anko中实现自定义的CustomCircle,定义的一系列方法
     */
    inline fun ViewManager.customCircle(): CustomCircle = customCircle {}
    inline fun ViewManager.customCircle(theme: Int = 0, init: CustomCircle.() -> Unit): CustomCircle {
        return ankoView({ CustomCircle(it) }, theme, init)
    }
    
    inline fun Context.customCircle(): CustomCircle = customCircle {}
    inline fun Context.customCircle(theme: Int = 0, init: CustomCircle.() -> Unit): CustomCircle {
        return ankoView({ CustomCircle(it) }, theme, init)
    }
    
    inline fun Activity.customCircle(): CustomCircle = customCircle {}
    inline fun Activity.customCircle(theme: Int = 0, init: CustomCircle.() -> Unit): CustomCircle {
        return ankoView({ CustomCircle(it) }, theme, init)
    }
    
    

    第三步:调用,其实和buttontv没什么区别,看你自定义中的参数而已

    class CustomCircleUI : AnkoComponent<CustomCircleActivity> {
        override fun createView(ui: AnkoContext<CustomCircleActivity>) = with(ui) {
            linearLayout {
                orientation = LinearLayout.VERTICAL
                gravity = Gravity.CENTER
                lparams(matchParent, matchParent)
                verticalLayout {
                    lparams(width = dip(200), height = dip(200))
                    backgroundColor = Color.parseColor("#ff9999")
                    customCircle {
                        startAngle = 0f
                        endAngle = 180f
                        arcBg = Color.WHITE
                        paintWidth = 2f
                    }
                }
    
            }
        }
    }
    
    

    效果:


    custome

    Anko中Layout部分使用就介绍到这,有感兴趣的还希望可以去wiki文档仔细阅读,谢谢

    代码传送地:README文档中标明了每个知识点对应的代码所在地。

    写在最后

    每个人不是天生就强大,你若不努力,如何证明自己,加油!

    Thank You!

    --Taonce

    如果你觉得这篇文章对你有所帮助,那么就动动小手指,长按下方的二维码,关注一波吧~~非常期待大家的加入

    专注Kotlin和Android知识的公众号

    相关文章

      网友评论

        本文标题:Android UI开发神兵利器—Kotlin Anko

        本文链接:https://www.haomeiwen.com/subject/wgwulqtx.html