看过《第一行代码》的朋友应该知道“酷欧天气”,作者郭神用整整一章的内容来讲述其从无到有的过程。

最近正好看完该书的第二版(也有人称“第二行代码”),尝试着将项目中的Java代码用Kotlin实现。

原项目获取点这里

Kotlin实现点这里

除了将Java转为Kotlin外,界面与资源的定义也略微做了调整,但是功能上没有变化(这部分后续进行完善,书中有提到可以改进的地方)。

1. 界面调整

1.1 将“预告”和“空气质量”两个模块的位置互换,“空气质量”和“温度/天气状态”放在一起感觉会比较直观。

1.2 将模块布局的背景色调整为主题色,与标题栏背景色保持一致。

效果图如下:

   

    

2. 资源定义

2.1 将dimen、color及string由直接使用字面值改为先将资源值定义在相应的文件中,然后在布局文件中用@type/value的形式引用。

2.2 将表示左/右方向的xxxLeft、xxxRight改为xxxStart、xxxEnd,适配Android高版本设备左/右方向问题。

具体细节可以下载代码进行查看,这样做的目的是加强应用的适配性与可维护性。

3. Java->Kotlin

Kotlin的基础知识可以阅读官方文档或者我之前的文章进行了解,下面只描述和原项目代码结构上的调整,或者Java转为Kotlin值得注意的地方。

3.1 Android Studio 3.0开始已经集成Kotlin(2.0需要手动安装插件),不过有两种情况:

a 在非Kotlin项目的基础上进行Kotlin编码,得先在Project和Module的build.gradle文件中引入依赖;

b 新建支持Kotlin的项目,那么依赖是默认引入的,直接编码即可;

3.2 将“散落”在多个文件中的常量提取到HttpUtil类中:

 object HttpUtil {
val Url = "http://guolin.tech/api/weather"
val Key = "key=bc0418b57b2d4918819d3974ac1285d9"
val Bing = "http://guolin.tech/api/bing_pic"
val China = "http://guolin.tech/api/china"
...
}

Kotlin中推荐用object修饰工具类,里面的成员默认是static的。常量用val,变量用var。

但是在使用HttpUtil.Url时并不像Java那样是引用类所属的静态变量,而是通过单例对象(lazy-init机制,第一次使用时进行创建)来引用成员变量,了解更多点这里

3.3 for (int i = start; i < end; ++i) {}改为for (i in start..end - 1) {}:

 for (i in start..end - 1) {
...
}

in和..的组合遍历的区间是[start, end-1],即最后一个元素是包含在内的,不注意容易引起下标越界问题。变量i是自动推断类型,不需要显式声明。

3.4 将只有数据的类由class改为data class,如城市类的定义:

 data class City(var id: Int = 0,
var cityName: String? = null,
var cityCode: Int = 0,
var provinceId: Int = 0) : DataSupport() {}

这种类一般称为Bean类(只拥有数据而没有操作),编译器会根据声明的属性自动生成对应的equal()、hashCode()方法及pair,了解更多点这里

3.5 Kotlin中==和equals效果是相同的(比较的是对象的值),比较引用的是===。那么我们在比较字串的值时可以简化代码了:

 if ("ok" == it.status) {
...
}

将常量字面值写在前面也是一种好习惯,因为str.equals("ok")的形式,当str为空时会报空指针异常。还有,基本类型比较时需要显式转换,否则也会报错,如1 == 1L需要写成1.toLong() == 1L。了解更多点这里

3.6 打开应用时会进行天气缓存信息的读取,如果之前使用过应用,那么就直接显示上次的天气信息;如果没有则进行城市的选择,并进行对应天气的获取。这里通过?.let语法来代替下一步操作前的null判断:

 val prefs = PreferenceManager.getDefaultSharedPreferences(this@MainActivity)
val weather = prefs.getString("weather", null)
weather?.let {
  val intent = Intent(this, WeatherActivity::class.java)
  startActivity(intent)
  finish()
}

如果weather变量值为null,那么就不会往下执行,类似的用法还有获取字串长度str?.length,当str为null时不往后取length值就能避免空指针异常。

3.7 by lazy延迟加载

 val drawerLayout by lazy {
findViewById(R.id.drawer_layout) as DrawerLayout
} val swipeRefresh by lazy {
findViewById(R.id.swipe_refresh) as SwipeRefreshLayout
}

之前的做法一般是先定义变量,然后在onCreate等重载方法中利用findViewById将变量和id进行绑定。现在Kotlin中提供了延迟加载机制,只有在布局环境初始化完成后,才会建立变量和id的联系。不管是绑定的时机,还是这个过程的代码实现,都变得一目了然。

3.8 when、_和is

 list_view.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ ->
when (currentLevel) {
LEVEL_PROVINCE -> {
selectedProvince = provinceList!![position]
queryCities()
}
LEVEL_CITY -> {
selectedCity = cityList!![position]
queryCounties()
}
LEVEL_COUNTY -> {
val weatherId = countyList!![position].weatherId
weatherId?.let {
when (activity) {
is MainActivity -> {
val intent = Intent(activity, WeatherActivity::class.java)
intent.putExtra("weather_id", weatherId)
startActivity(intent)
activity.finish()
}
is WeatherActivity -> {
val activity = activity as WeatherActivity
activity.drawerLayout.closeDrawers()
activity.swipeRefresh.isRefreshing = true
activity.requestWeather(weatherId)
}
else -> {}
}
}
}
else -> {}
}
}

这段代码的功能是给列表项添加点击响应,也比以往的写法简化很多,提一下代码中用到的三个点。

a when用来替换switch case,省去了case:、break及default,毕竟每一个分支结尾处都要记得写上break还是不爽的;

b 方法参数_,当后面不会使用的情况下可以写成下划线,优点是过多不用的参数可以简写,缺点是可读性不好,后续如果参数被使用还是需要加上名称;

c is和as,前者是判断类型是否匹配,后者是类型转换。

3.9 map映射和it

 provinceList?.let {
if (it.isNotEmpty()) {
dataList.clear()
it.map {
dataList.add(it.provinceName!!)
}
adapter.notifyDataSetChanged()
list_view.setSelection()
currentLevel = LEVEL_PROVINCE
return
}
}

之前提到过let的用法,其实和之搭配的还有it,代表let前面的变量,比如it.isNotEmpty()就是判断列表变量provinceList是否为空。而it.map的作用是遍历列表/数组,元素又可以用it表示,有点像it的嵌套,Kotlin会自动区分it代表的对象。

3.10 @SerializedName注解(Java中概念),用于将json内容中的属性名和对应类的成员变量名进行一一对应,毕竟json中字段名的可读性是不能保证的。用法如下:

 class Now {
@SerializedName("tmp")
var temperature: String? = null
...
}

3.11 $name和${name}获取变量值

val address = "${HttpUtil.China}/$provinceCode"

这种用法在字串拼接时特别适用,不用再通过“+”号写一长串的代码来进行各字串的拼接,直接将值的获取放到字串中。

$name当name是普通变量时使用,${name}当name是类属性或者数组/列表元素时使用,还可以这样使用:
 val a =
println("a > 10 is ${if (a > 10) "true" else "false"}")

输出结果为false,将变量值的判断和结果都放在${}中。

4. 总结

上面仅仅介绍了关于Kotlin语言的部分用法,项目中还用到了Glide、Litepal、OkHttp及Gson等流行库,以及在Service中用AlarmManager来后台定期更新天气数据。感兴趣的话建议查看相关源码,会有收获的。

Kotlin实现《第一行代码》案例“酷欧天气”的更多相关文章

  1. 用kotlin方式打开《第一行代码:Android》之开发酷欧天气(1)

    参考:<第一行代码:Android>第2版--郭霖 注1:本文为原创,例子可参考郭前辈著作:<第一行代码:Android>第2版 注2:本文不赘述android开发的基本理论, ...

  2. 用kotlin方式打开《第一行代码:Android》

    参考:<第一行代码:Android>第2版--郭霖 注1:本文为原创,例子可参考郭前辈著作:<第一行代码:Android> 注2:本文不赘述android开发的基本理论,不介绍 ...

  3. 《第一行代码——Android》

    <第一行代码——Android> 基本信息 作者: 郭霖 丛书名: 图灵原创 出版社:人民邮电出版社 ISBN:9787115362865 上架时间:2014-7-14 出版日期:2014 ...

  4. 第一行代码 Android 第二版到货啦

    今日android第一行代码[第二版]已到,收获的季节到了 先看一下封面 书签: 以后就把空闲时间送给它吧 先来看一下本书的目录: 第1章 开始启程--你的第1行Android代码 第2章 先从看得到 ...

  5. Android Studio 单刷《第一行代码》系列 02 —— 日志工具 LogCat

    前情提要(Previously) 本系列将使用 Android Studio 将<第一行代码>(书中讲解案例使用Eclipse)刷一遍,旨在为想入坑 Android 开发,并选择 Andr ...

  6. Android Studio 单刷《第一行代码》系列 01 —— 第一战 HelloWorld

    前言(Prologue) 本系列将使用 Android Studio 将<第一行代码>(书中讲解案例使用Eclipse)刷一遍,旨在为想入坑 Android 开发,并选择 Android ...

  7. Android Studio 单刷《第一行代码》系列目录

    前言(Prologue) 本系列将使用 Android Studio 将<第一行代码>(书中讲解案例使用Eclipse)刷一遍,旨在为想入坑 Android 开发,并选择 Android ...

  8. Android Studio 单刷《第一行代码》系列 07 —— Broadcast 广播

    前情提要(Previously) 本系列将使用 Android Studio 将<第一行代码>(书中讲解案例使用Eclipse)刷一遍,旨在为想入坑 Android 开发,并选择 Andr ...

  9. Android Studio 单刷《第一行代码》系列 06 —— Fragment 生命周期

    前情提要(Previously) 本系列将使用 Android Studio 将<第一行代码>(书中讲解案例使用Eclipse)刷一遍,旨在为想入坑 Android 开发,并选择 Andr ...

随机推荐

  1. 高性能消息队列 CKafka 核心原理介绍(上)

    欢迎大家前往腾讯云技术社区,获取更多腾讯海量技术实践干货哦~ 作者:闫燕飞 1.背景 Ckafka是基础架构部开发的高性能.高可用消息中间件,其主要用于消息传输.网站活动追踪.运营监控.日志聚合.流式 ...

  2. h5的video标签

    在video标签中,我们可以使用属性:videoWidth & videoHeight,它获取的是video的宽度和高度(媒体本身). 虽然不能直接使用,但是可以通过计算宽高比得到 video ...

  3. Hash表分析

    http://baike.baidu.com/link?url=Ua74895uGf1NuPxB4pawmuAXedi427jJvM6aSLh_V1-23ptlMc7XIrr_cylIBn5d

  4. Http get方式url参数长度以及大小

    详见: http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp69 众所周知, 传递小量参数(在没有其他原因,例如隐藏参数值的情况下)推 ...

  5. 【Java学习笔记之三十三】详解Java中try,catch,finally的用法及分析

    这一篇我们将会介绍java中try,catch,finally的用法 以下先给出try,catch用法: try { //需要被检测的异常代码 } catch(Exception e) { //异常处 ...

  6. stable_sort()与sort

    stable_sort与sort()都是c++库函数,调用<algorithm>库,但区别是sort是不稳定的排序,而stable_sort是稳定的,有时候stable_sort比sort ...

  7. 第二次项目冲刺(Beta阶段)5.20

    1.提供当天站立式会议照片一张 会议内容: ①检查前一天的任务情况,心得分享以及困难分析. ②制定新一轮的任务计划. 2.每个人的工作 (1)工作安排 队员 今日进展 明日安排 王婧 #42文件分类改 ...

  8. 201521123047 《Java程序设计》第4周学习总结

    1. 本周学习总结 1.1 尝试使用思维导图总结有关继承的知识点. 1.2 使用常规方法总结其他上课内容. 答: - 只能有一个父类,即单继承,子类继承父类的全部成员(属性和方法),并可能有自己特有的 ...

  9. 201521123098 《Java程序设计》第2周学习总结

    1. 本周学习总结 1. 熟悉了一些码云中储存eclipse中代码的操作,利于随时储存代码,避免U盘丢失导致代码丢失的问题: 2. 了解了如何从码云中提取已储存的代码: 3. 学会了如何创建动态数组, ...

  10. java第一次作业0

    lsl321 java第一次作业 #1. 本章学习总结 你对于本章知识的学习总结 本章我们学习了各种java相关文件的使用,以及码云,博客,pat等程序辅助软件,这些对于我们专业的学习有非常大的帮助, ...