Kotlin 使用 Anko 构建布局的那些事

欢迎访问个人博客:https://glumes.com

学习 Kotlin 应该都或多或少听过 Anko 这个开源库。

使用 Anko 来构建界面会更加简单、快捷。

毕竟以往的布局都是要从 XML 中解析出来,然后再到 LayoutInfalter 里面通过 Constructor.newInstance 反射创建出来的。而 Anko 则是直接创建 View,用代码构建布局,省去了解析 XML 的时间。

添加依赖

Anko 的 Github 仓库是:https://github.com/Kotlin/anko

在 Github 仓库的 README 上关于如何添加依赖已经写的很详细了,把要添加的选择性复制粘贴就好了。

Anko 包括四个部分内容:

  • Anko Commons
    • 轻量级的一些帮助类,比如 intent,dialog,logging 等等,其实就是对安卓一些类:Activity、Fragment、Intent 等添加扩展函数。
  • Anko Layouts
    • 动态布局用的最主要的库,将许多 Android 的控件 View 转换成了 Anko 加载的形式。
    • 由于 Android 还有其他的控件库,因此 Anko 也对那些库进行了拓展支持,可以选择添加对应的依赖库。
    • 当然,还可以根据需要对自定义 View 进行改造,让它们也支持 Anko 加载的形式。
  • Anko SQLite
    • 用于 Android SQLite 数据库的查询的库
  • Anko Coroutines
    • 基于 kotlinx.coroutines 协程的一个工具库。

创建简单布局

使用 Anko 创建布局很简单:

class AnkoActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        relativeLayout {
            button("button in center") {
                textSize = sp(18).toFloat()
                onClick {
                   longToast("you click button")
                }
            }.lparams {
                centerInParent()
            }
        }
    }
}

效果如下:

Anko 创建简单界面

relativeLayout 代码块里我们构建了当前的界面,并把它应用到了 Activity 中。

在这里,并没有使用熟悉的 setContentView 方法,这是因为 Anko 会自动将布局界面 View 设置到 Activity 中。

relativeLayout代码块就是 Anko 的主要使用方法。

relativeLayout 作为一个容器,在里面添加了一个 buttonbutton 控件的第一个大括号里设置了它的一些属性和事件,在 lparams 大括号里设置了它在相对于容器的一些参数。

就是这样简单的写法完成了界面布局,如同写 xml 文件一样,只要在父容器里面按照排列写好子控件的参数和位置就好。

多实践几次就可以熟练这种写法,通过 Anko 来创建一个登陆界面:

    verticalLayout {
            padding = dip(8)
            val account = textView("account") {
                id = R.id.account_text
                textSize = sp(12).toFloat()
                // 设置自己的属性
            }.lparams() {
                // 设置布局的参数
            }
            var name = editText() // 也可以什么都不设置,使用默认的设置
            textView("password") {
                id = R.id.password_text
                onClick {
                    account.setText("change account")
                }
            }
            var pwd = editText {
                hint = "input"
            }.lparams(
                    width = dip(100), 
                    height = ViewGroup.LayoutParams.WRAP_CONTENT
            )
            button("login") {
                onClick {
                    if (name.text.toString().equals("name") 
                        && pwd.text.toString().equals("pwd")) {
                        longToast("login....")
                    } else {
                        longToast("login failed")
                    }
                }
            }
        }

效果如下:

anko_login_ui

使用 AnkoComponent 接口创建界面

除了直接在 Activity 里面写布局,还可以使用 AnkoComponent 接口创建布局,这样就可以将界面代码和 Activity 的代码分离了。

class MyActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
        super.onCreate(savedInstanceState, persistentState)
        MyActivityUI().setContentView(this)
    }
}

class MyActivityUI : AnkoComponent<MyActivity> {
    override fun createView(ui: AnkoContext<MyActivity>) = with(ui) {
        verticalLayout {
            val name = editText()
            button("Say Hello") {
                onClick { ctx.toast("Hello, ${name.text}!") }
            }
        }
    }
}

需要创建我们的界面类,实现 AnkoComponent 接口,在 createView方法中返回我们的界面。

最后在 setContentView 方法中实际调用的也是 createView 方法,返回界面布局,然后再由上面提到的,Anko 会自动把布局填充到 Activity 中。

这里使用到了 Kotlin with 的语法糖,使用 with,则返回的是最后一行的内容,正好 verticalLayout 就是最后一行的内容。

也可以把它转换一下,使用 apply 的语法糖,最后返回的调用该方法的对象,再接着返回该对象的 view 就好了。

class MyActivityUI : AnkoComponent<MyActivity> {
    override fun createView(ui: AnkoContext<AnkoActivity>) = ui.apply {
        verticalLayout {
            val name = editText()
            button("Say Hello") {
                onClick { ctx.toast("Hello, ${name.text}!") }
            }
        }
    }.view
}

Fragment 中加载界面

在 Fragment 中添加界面稍有不同。

创建 Activity,将 Fragment 添加上来。

class AnkoFragmentActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        linearLayout {
            id = R.id.fragment_id
            supportFragmentManager.beginTransaction().replace(id, AnkoFragment.newInstance()).commit()
        }
    }
}
class AnkoFragment : Fragment() {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
//        return inflater.inflate(R.layout.fragment_anko, container, false)
        return UI {
            verticalLayout {
                editText()
                button("OK")
            }
        }.view
    }

    companion object {
        fun newInstance(): AnkoFragment {
            return AnkoFragment()
        }
    }
}

在 Fragment 的 onCreateView 方法中不在使用 inflater 来加载布局,而是直接使用 UI 函数来完成,返回最后的 View 即可。其中 UI 也是对 Fragment 的一个拓展函数。

自定义 View 的加载

除了 Anko 自带以及支持的控件之外,还可以让自定义的 View 也支持 Anko 的加载方式,在 Anko 的代码块中去更改自定义 View 的设置属性。

比如,自定义 View ,绘制一个矩形:

public class RectangleView extends View {
    public int size ;
    public Paint mPaint;
    public RectangleView(Context context) {
        super(context);
        mPaint = new Paint();
        mPaint.setColor(Color.RED);
        mPaint.setAntiAlias(true);
        mPaint.setStrokeWidth(2);
        mPaint.setStyle(Paint.Style.STROKE);
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        setBackgroundColor(Color.BLUE);
        canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, size, mPaint);
    }
}

其中,size 变量就是要改变的属性,它控制着圆的半径。

让自定义 View 支持 Anko 的加载方式,还需要添加如下的拓展函数:

inline fun ViewManager.rectangleView(init: RectangleView.() -> Unit): RectangleView {
    return ankoView({ RectangleView(it) }, theme = 0, init = init)
}

由该拓展函数来返回我们的 Rectangle View ,至于这其中是如何实现的,暂时不做深究,下篇文章再来探讨。

最后就可以像使用其他控件一样来添加到布局中了。

    //加载自定义的 View
        relativeLayout {
            var view = rectangleView {
                id = R.id.custom_view_id
                size = 300  // 自定义 View 的属性设置
            }.lparams {
                width = ViewGroup.LayoutParams.MATCH_PARENT
                height = dip(200)
                centerInParent()
            }

            button("change size") {
                onClick {
                    view.size = Random().nextInt(200) + 100
                    view.invalidate()
                }
            }.lparams {
                below(view)
                centerHorizontally()
            }
        }

效果如下:

anko_custom_view

点击按键来更改圆的半径大小。

Anko 配合 RecyclerView 的使用

使用 Anko 来构建一个下拉刷新的 RecyclerView 布局。

写法依旧简单:

       swipeRefreshLayout {
            setColorSchemeColors(Color.RED, Color.BLUE, Color.GREEN)
            onRefresh {
                mainHandler.postDelayed(Runnable {
                    isRefreshing = false
                }, 3000)
            }
            recyclerView {
                layoutManager = LinearLayoutManager(SampleApp.mContext)
                setHasFixedSize(true)
                adapter = Adapter(dataList)
            }
        }

直接在 recyclerView 布局里面设置好相应的 LayoutManager 和 Adapter 就好了。

同时还能够在 swipeRefreshLayout 里面处理刷新的事件,在三秒后更改刷新状态,从而停止刷新就好了。

源码参考 Github 地址:https://github.com/glumes/AndroidKotlinSample

不足

Anko 好是好,但是依旧不够完美。

在 XML 中能够设置的控件属性更多,更精确的控制布局状态,而 Anko 在构建简单界面的时候才显得快速、便捷。

而且 Anko 支持的控件有限,加载自定义的控件还得添加额外的代码,在更复杂的应用中应该不太会广泛的使用。

参考

1、https://github.com/Kotlin/anko/wiki/Anko-Layouts

一起交流学习,答疑解惑,有问题,我们星球见~~~


图形/图像/音视频交流

觉得文章还不错,可以关注一下微信公众号【纸上浅谈】

Wechat
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容