一款纯 Kotlin 编写的开源安卓应用 "Smile
For Your Smile
扫码下载最新版 apk
源码:https://github.com/yhaolpz/Smile
1.界面
版块一:段子鸡汤
版块二:花瓣福利
版块三:动态搞笑图
2.代码片段
1.数据类
相关的数据类可以写在同一个文件中,摒弃繁多的 get/set 、toString 等方法,简洁明了!
2.属性委托
对变量 mDefaultIndex 取值将直接从 SharedPreferences 中取出,对该变量赋值将直接存入 SharedPreferences 中,没有比这更简单的写法了!
什么时候用到 mClient 什么时候初始化它的值,实现延迟初始化如此简单!
3.代码简化神器 anko
一个单词实现线程切换,无需多言,nice !!!
更多内容请前往源码查看 ~
3.如何着手 Kotlin
我从对 Kotlin 一窍不通,到完整开发出此 App,共用了 9 天,其中有 4 天只在晚上开发,也就是说共计 5 天 + 4 个晚上。另外由于我对 ui 的挑剔,主题色的挑选及 logo 设计至少占去五分之一的时间。我并不是在炫耀我的自学能力,Smile 的内容并不算多,肯定有很多大牛比我快,我想表达的是这 9 天的学习及开发过程是刀过竹解、得心应手的。所以我想把这个学习过程分享给大家,只要能帮到一个人,那就是有价值的。
首先,选择一个完整的 kotlin 文档,从第一章开始阅读,尤其是最基础的基本类型、控制流等等,千万不能跳过,边读边把知识点记录下来,期间可以阅读一些博客了解一下别人对这些知识点的理解,但是学习文档是主线,一定不能偏离。这个过程我持续了三天,主要学习的文档为: Kotlin 语言中文站 ,期间每学到一个知识点我就记录下来,展示一部分:
这样的内容我记录了很多,直接分享出来吧,全文链接:https://github.com/yhaolpz/Smile/blob/master/kotlinNote.md
这个过程并不需要把所有的知识点都融汇贯通,对于你觉得比较复杂的内容,可以略读,但是不能直接跳过,你至少要知道这个知识点的存在,比如“泛型”章节我第一次阅读的时候真是云里雾里。
OK~ 这个过程完后你一定跃跃欲试了,这个委托功能真的这么酷吗!这个语法竟然这么简洁!如果这样的想法存在的话,那就说明前面的文档学习过程十分成功了!接下来就在 IDE 中尝试吧,仍然有 官方教程 为你铺路,除了创建 Kotlin 项目之外,还有 Kotlin 安卓扩展插件、Kotlin 集成第三方框架等教程,这些资料的参考顺序应该处于他人博文之前。
然后你就可以尽情的投入 Kotlin 的怀抱了,你会遇到代码简化神器 anko ,这里推荐一份 针对 anko 的 kotlin 教程,Smile 中的泛型 preference 委托就参考自这份文档,另外 anko 中的 doAsync 和 uiThread 表示强烈鄙视 Handler 及其他所有切换线程的写法 (⊙_⊙;)
4.开发心得
开发过程中不要急着集成第三方库
既然你的项目已经选择了 Kotlin,为何不多体验一下它的强大呢?拿请求网络来说,以往的开发习惯可能会促使你尽快把 retrofit 集成进来,暂时搁置它吧,体会一下 kotlin 中请求网络是多么简洁,就算你仍然打算在以后的项目中使用 retrofit,这也没关系,仅这次敲几行代码体会一下也是很美好的。Ok ~ talk is cheap ,下面看一下 Smile 中是如何请求网络数据的,比如一进去的“搞笑段子”,在其 Fragment 中是这样请求网络数据的:
doAsync {
val data = JokeService.getData(mPage)
uiThread {
mLoading = false
if (data == null) {
showSnackbar(view as ViewGroup, "加载失败")
return@uiThread
}
...
doAsync 中会自动切换到子线程,执行完 getData() 方法后,会自动切换到 ui 线程接着执行 uiThread{ } 中的代码,来看看 getData() 方法我是怎么实现的:
fun getData(page: Int, maxResult: Int = 10): List<Joke>? {
var forecastJsonStr: String? = null
try {
forecastJsonStr = URL(buildBaseUrl(page, maxResult)).readText()
} catch (e: Exception) {
return null
}
val data = Gson().fromJson(forecastJsonStr, JokeResult::class.java)
val jokes: List<Joke> = data.showapi_res_body.contentlist
return if (jokes.isNotEmpty()) jokes else null
}
注意,其中请求网络的代码仅仅一行:
forecastJsonStr = URL(buildBaseUrl(page, maxResult)).readText()
URL 仍然是原来 java.net 包的类,但 readText() 函数正是 kotlin 的杰作, Kotlin 支持扩展函数和扩展属性,readText() 就是 URL 类的扩展函数,除此之外还扩展了 readBytes() 函数,这代表着在 kotlin 中请求网络仅仅需要一行代码,再配合强大的 doAsync 、uiThread 方法,如此简单的实现了网络请求,在写法上是不是比以往的 rxJava+retrofit 更简洁呢?
回到 getData() 方法,看最后的返回语句:
return if (jokes.isNotEmpty()) jokes else null
在 Kotlin 中,if 语句是可以有一个返回值的,但是看起来似乎可以更简单的使用 java 中的三元操作符 ? : 实现,但是三元操作符 ? : 中是不能嵌套其他代码的,下面仍然是 Smile 中的代码:
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
if (keyCode == KeyEvent.KEYCODE_BACK) {
mBackPressedTime = if (mIsMenuOpen) {
drawerLayout.closeDrawers()
mBackPressedTime
} else {
System.currentTimeMillis()
}
}
return true
}
其中要实现的功能是:当按下返回键时,首先判断侧滑菜单是否打开,若打开就关闭之并执行 mBackPressedTime = mBackPressedTime 赋值语句;若处于关闭状态就执行 mBackPressedTime = System.currentTimeMillis() 赋值语句,其中使用 if 具有返回值这一特性很方便的实现了这一点。
为什么我要根据侧滑状态的打开状态分别给 mBackPressedTime 赋不同的值呢?
我想实现的功能是:在侧滑菜单关闭的状态下,快速按两次返回键,则退出应用。
通过监听 mBackPressedTime 值的变化可以实现,这里用到了 Kotlin 的属性委托:数据监听委托,来看怎么对 mBackPressedTime 实现数据变化监听的:
var mBackPressedTime by Delegates.observable(0L) {
_, old, new ->
if (new - old > 1000) {
showSnackbar(coordinatorLayout, getString(R.string.exit_message))
}
if (new - old in 1..1000) {
mDefaultIndex = mCurrentIndex
finish()
}
}
在声明变量的时候通过 by 关键字实现属性委托,Delegates.observable(0L) 是 Kotlin 自带的一种数据监听属性委托,代码中规定,当两次按下的时间间隔大于 1秒时显示提示框,小于 1 秒时退出应用。
最后再解释一下这段代码:
if (new - old in 1..1000) {
mDefaultIndex = mCurrentIndex
finish()
}
可以看到判断范围的写法与 Java 中不同, 在 Java 中我们是这样写的:
if (new - old >=1 && new - old<= 1000)
另外就是:
mDefaultIndex = mCurrentIndex
在本文开头贴出的代码片段中有提到过,对 mDefaultIndex 赋值意味着直接将值存入到 preference 文件中了。
这里就不再展示其他代码了,更多内容请前往源码查看 ~
通过截图无法感受到 Smile 的心意,下载试试吧: