参考:《第一行代码:Android》第2版——郭霖

注1:本文为原创,例子可参考郭前辈著作:《第一行代码:Android》第2版,转载请注明出处!

注2:本文不赘述android开发的基本理论,不介绍入门知识,不介绍Android Studio基本安装,开门见山,直接使用kotlin改写郭前辈的《第一行代码:Android》中的部分例子,有机会的话自己做一些新例子出来!

注3:本文基本以kotlin语言作为Android开发,偶尔涉及java作为对比

注4:开发基于Android Studio 3.0,并且新建项目时勾选“support kotlin”

进入实战——开发酷欧天气(1)

本次博文,我将尝试使用kotlin语言对郭前辈的《第一行代码》中的最后那个实战项目“酷欧天气”进行重写

我将跳过需求分析阶段,开门见山,进入正题(代码),详见书本:p486

14.4 遍历全国省市县数据(原书p499)

原书中在遍历了省市县数据后,郭神使用了litepal将数据写入到了sqlite表中,由于时间问题,跳过此步,采用每次都重新访问网址获取数据!

省市县网址:http://guolin.tech/api/china/

配置gradle

gradle.build(Module: app)

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
compile 'com.android.support:appcompat-v7:25.3.1'
testCompile 'junit:junit:4.12'
compile 'org.jetbrains.anko:anko-sdk15:0.9'
compile 'org.jetbrains.anko:anko-support-v4:0.9'
compile 'org.jetbrains.anko:anko-appcompat-v7:0.9'
compile "com.google.code.gson:gson:2.7"
}

可以看到我们依然使用了anko类库,这个类库简直就是android上的jquery!

另外添加了Google的Gson类库,用于对在线获取的json数据转换

manifest

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cn.cslg.weatherkotlin"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher"
......
</application> <uses-permission android:name="android.permission.INTERNET" />
</manifest>

打开网络权限!

layout布局文件结构

红色框出来的表示本次博文中必须用到的布局,当然其他的xml后面也要用到

注:详细xml布局文件和代码参考原书即可,布局文件我是照搬的!或者跳到本文最后!

最后打开res>valus>styles.xml

<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">

由于我们使用的是自己做的头部布局,所以去掉Android默认的actionBar,改为NoActionBar即可!

class文件布局(kotlin文件)

数据类

数据类是一种非常强大的类,它可以让你避免创建Java中的用于保存状态但又操作 非常简单的POJO的模版代码。

数据类往往和数据库或者某个实体模型属性一一对应的

比如例子中要用到的省,市,县,天气都是带有自身的属性,set,get方法,使用java你必须一一的定义他们,而kotlin的data class可以让你轻松构建这些Model,并且自带提供了用于访问它们属性的简单的getter 和setter!

这次我们需要三个数据类:省,市,县

根据json格式,例如:

[{"id":1,"name":"北京"},{"id":2,"name":"上海"},{"id":3,"name":"天津"},{"id":4,"name":"重庆"},{"id":5,"name":"香港"},{"id":6,"name":"澳门"},{"id":7,"name":"台湾"},{"id":8,"name":"黑龙江"},{"id":9,"name":"吉林"},{"id":10,"name":"辽宁"},{"id":11,"name":"内蒙古"},{"id":12,"name":"河北"},{"id":13,"name":"河南"},{"id":14,"name":"山西"},{"id":15,"name":"山东"},{"id":16,"name":"江苏"},{"id":17,"name":"浙江"},{"id":18,"name":"福建"},{"id":19,"name":"江西"},{"id":20,"name":"安徽"},{"id":21,"name":"湖北"},{"id":22,"name":"湖南"},{"id":23,"name":"广东"},{"id":24,"name":"广西"},{"id":25,"name":"海南"},{"id":26,"name":"贵州"},{"id":27,"name":"云南"},{"id":28,"name":"四川"},{"id":29,"name":"西藏"},{"id":30,"name":"陕西"},{"id":31,"name":"宁夏"},{"id":32,"name":"甘肃"},{"id":33,"name":"青海"},{"id":34,"name":"新疆"}]

建立:

data class Province(val id: Int, val name: String)

data class City(val id: Int, val name: String)

data class County(val id: Int, val name: String,val weather_id:String)

看着十分简单,这是根据返回的json数据定义的,json中有哪些键,就需要几个属性,这里json返回的是id,name,还有县的weather_id

这些类放在哪里?

你可以单独放在一个datas.kt文件中,注意kotlin可以将多个类放在同一个文件中,不像java那样一个文件只能有一个public类!

我将这些数据类放在了adapters.kt当中,因为布局适配器adapter当中要用到他们的List泛型

另外Gson转换服务器端的json时也需要用这三个数据类的Litst泛型做映射!生成数据类实体对象,这个后面再说

Adapter

做过安卓开发的,都知道ListView需要适配器Adapter将不同类型的数据适配到其中,做成列表样式布局,我们也可以通过继承已有的Adapter做自己的apdater

注:详细请翻阅原书p116作者的FruitAdapter例子

“酷欧天气”项目在原书中,作者对省市县ListView使用的是ArrayAdapter适配器,这个适配器系统自带,非常简易,只传入了一个dataList,dataList是一个List String类,这里我不想每次都将上面那三种数据类一一放到一个dataList,因为这样每次都要遍历他们的List,再一个个add到dataList当中(原书p504),故而我封装了三个数据类List泛型相应的Adapter(肯能有人觉这样更加繁琐,但我觉得这样看着少了很多循环语句,比较美观)

Adapters.kt

package cn.cslg.weatherkotlin

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.TextView
import org.jetbrains.anko.find /**
* Created by devil on 2017/5/20.
*/ //数据类
data class Province(val id: Int, val name: String)
data class City(val id: Int, val name: String)
data class County(val id: Int, val name: String,val weather_id:String) class ProvinceAdapter(context: Context?, resource: Int, objects: List<Province>) : ArrayAdapter<Province>(context, resource, objects) { val resId: Int = resource override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
val item: Province = getItem(position)
val view = LayoutInflater.from(context).inflate(resId, parent, false)
val itemName = view.find<TextView>(R.id.item_name)
itemName.text = item.name
return view
}
} class CityAdapter(context: Context?, resource: Int, objects: List<City>) : ArrayAdapter<City>(context, resource, objects) { val resId: Int = resource override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
val item: City = getItem(position)
val view = LayoutInflater.from(context).inflate(resId, parent, false)
val itemName = view.find<TextView>(R.id.item_name)
itemName.text = item.name
return view
}
} class CountyAdapter(context: Context?, resource: Int, objects: List<County>) : ArrayAdapter<County>(context, resource, objects) { val resId: Int = resource override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
val item: County = getItem(position)
val view = LayoutInflater.from(context).inflate(resId, parent, false)
val itemName = view.find<TextView>(R.id.item_name)
itemName.text = item.name
return view
}
}

同样的我将三个Adapter放在同一个文件中!如果使用的是java,就需要三个文件,或者用内部类!

注意开头是刚刚说的三个数据类Province,City,County,也一起放在了Adapters.kt中了。

ChooseAreaFragment

这是本文中最复杂的一个代码文件了,修改自原书的java代码:P502

ChooseAreaFragment.kt:

package cn.cslg.weatherkotlin

import android.app.ProgressDialog
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ListView
import android.widget.TextView
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import org.jetbrains.anko.custom.async
import org.jetbrains.anko.find
import org.jetbrains.anko.uiThread
import java.net.URL /**
* Created by devil on 2017/5/20.
*/
class ChooseAreaFragment : Fragment() {
private val LEVEL_PROVINCE = 0
private val LEVEL_CITY = 1
private val LEVEL_COUNTY = 2
private var current_level = 0 private val URL = "http://guolin.tech/api/china/" private var provinceList = ArrayList<Province>()
private var cityList = ArrayList<City>()
private var countyList = ArrayList<County>() private var selectedProvince: Province? = null
private var selectedCity: City? = null
private var selectedCounty: County? = null private var backBtn: Button? = null
private var titleText: TextView? = null
private var listView: ListView? = null override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View {
val view = inflater!!.inflate(R.layout.choose_area, container, false) titleText = view.find<TextView>(R.id.title_text)
listView = view.find<ListView>(R.id.list_view)
backBtn = view.find<Button>(R.id.back_button) return view
} override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) //列表点击监听事件
listView!!.setOnItemClickListener {
_, _, position, _ ->
when (current_level) {
LEVEL_PROVINCE -> {
selectedProvince = provinceList[position]
queryCity()
}
LEVEL_CITY -> {
selectedCity = cityList[position]
queryCounty()
}
LEVEL_COUNTY -> {
selectedCounty = countyList[position]
}
}
} //返回按钮监听事件
backBtn!!.setOnClickListener {
if (current_level == LEVEL_CITY)
queryProvince()
else if (current_level == LEVEL_COUNTY)
queryCity()
}
queryProvince()
} private fun queryProvince() {
titleText!!.text = "中国"
backBtn!!.visibility = View.INVISIBLE //隐藏返回键
showProgress() async {
val s = URL(URL).readText() uiThread {
closeProgress()
val t = object : TypeToken<List<Province>>() {}.type
provinceList = Gson().fromJson<List<Province>>(s, t) as ArrayList<Province>
val adapter = ProvinceAdapter(context, R.layout.list_city_item, provinceList)
listView!!.adapter = adapter
listView!!.setSelection(0)
current_level = LEVEL_PROVINCE
}
} } private fun queryCity() {
titleText!!.text = selectedProvince!!.name //标题为当前省份
backBtn!!.visibility = View.VISIBLE //显示返回键
showProgress() async {
val s = URL(URL + "/" + selectedProvince!!.id).readText() uiThread {
closeProgress()
val t = object : TypeToken<List<City>>() {}.type
cityList = Gson().fromJson<List<City>>(s, t) as ArrayList<City>
val adapter = CityAdapter(context, R.layout.list_city_item, cityList)
listView!!.adapter = adapter
listView!!.setSelection(0)
current_level = LEVEL_CITY
}
}
} private fun queryCounty() {
titleText!!.text = selectedCity!!.name //标题为当前市
backBtn!!.visibility = View.VISIBLE //显示返回键
showProgress() async {
val s = URL(URL + "/" + selectedProvince!!.id + "/" + selectedCity!!.id).readText() uiThread {
closeProgress()
val t = object : TypeToken<List<County>>() {}.type
countyList = Gson().fromJson<List<County>>(s, t) as ArrayList<County>
val adapter = CountyAdapter(context, R.layout.list_city_item, countyList)
listView!!.adapter = adapter
listView!!.setSelection(0)
current_level = LEVEL_COUNTY
}
}
} //进度条
private var progress: ProgressDialog? = null private fun showProgress(message: String = "加载中") {
if (progress == null) {
progress = ProgressDialog(activity)
progress!!.setMessage(message)
progress!!.setCancelable(false)
}
progress!!.show()
} private fun closeProgress() {
if (progress != null)
progress!!.dismiss()
} }

先看到类开头的一些类属性,他们的作用域是整个ChooseAreaFragment类

kotlin的属性自带get,set方法,你不需要单独写

注:在Kotlin中,一切都是对象。没有像Java中那样的原始基本类型。

和java不一样,kotlin可以自动推断数据类型,使用var(可变)和val(不可变)声明一个变量或属性,给他赋值同时就会在编译期间推断出数据类型,那如果我想像java一样开头声明几个类属性但不赋值呢?

不可以!

Kotlin不允许声明变量但不初始化

http://www.cnblogs.com/sw926/p/5870326.html

java可以这么做:

Province selectedProvince;

而kotlin需要使用如下形式:

var selectedProvince: Province? = null

随后overwrite了Fragment的两个方法:

“onCreateView”

“onActivityCreated”

在Fragment的生命周期中,onCreateView会更早执行,我将属性的赋值过程放在里面,让他获得控件

onActivityCreated方法中为返回按钮和ListView中的Item添加了点击事件,并根据点击时的情况分别执行不同的操作,当用户点击ListView的其中一个Item时会先获得用户点击的position(是哪一个Item),然后存储在selectedXXX属性中,接下来会在query中用到他,根据选定的数据对象的id请求他的子数据,比如根据选中的Province的id属性就可以获得Province下的所有CIty。

注意:由于ListView的Item的position是个编号(Int),刚好又和三个数据类的List的索引一一对应,故而可以根据这个position取出List里的一个Province对象,City和County同理,如下:

//列表点击监听事件
listView!!.setOnItemClickListener {
_, _, position, _ ->
when (current_level) {
LEVEL_PROVINCE -> {
selectedProvince = provinceList[position]
queryCity()
}
LEVEL_CITY -> {
selectedCity = cityList[position]
queryCounty()
}
LEVEL_COUNTY -> {
selectedCounty = countyList[position]
}
}
}

position使用kotlin的lambada表达式传入后面的代码块方法,用户点击时就会触发

注:when 就相当于java中的switch语句!

三个query中都有简单async异步请求,并在请求结束后回到主线程执行UI操作,我没有参照原书中单独将请求放在一个方法中,因为kotlin的Net扩展相当强大,访问相当简单,只有短短几行代码而已。

在uiThread中使用了Gson().fromJson把获取到的String(json)映射成为数据类的实体对象的List泛型,在这之前还需要TypeToken方法,如果不是List类型,可以在fromJson的最后一个参数直接使用xxx.class的形式映射,但如果使用List泛型则必须用这个方法进行List类型映射,注意代码形式:

val t = object : TypeToken<List<Province>>() {}.type

provinceList = Gson().fromJson<List<Province>>(s, t) as ArrayList<Province>

TypeToken前面添加“object :”后面还有“{}”

布局文件

基本与原书一致:

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<fragment
android:id="@+id/choose_area_fragment"
android:name="cn.cslg.weatherkotlin.ChooseAreaFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:ignore="InvalidId" /> </FrameLayout>

choose_area.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#fff"
>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorPrimary"
> <Button
android:id="@+id/back_button"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:layout_marginLeft="10dp"
android:background="@android:drawable/ic_menu_revert" />
<TextView
android:id="@+id/title_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textColor="#fff"
android:textSize="20sp"
/> </RelativeLayout> <ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"></ListView>
</LinearLayout>

list_city_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"> <TextView
android:id="@+id/item_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="10dp"
android:layout_marginLeft="10dp" /> </LinearLayout>

效果

结语

好了今天就写到此处,进入下一个阶段,编写天气和空气质量显示功能!

转载注明:出自:http://www.cnblogs.com/devilyouwei/p/6885052.html

用kotlin方式打开《第一行代码:Android》之开发酷欧天气(1)的更多相关文章

  1. 20172327 2018-2019-1 《第一行代码Android》第二章学习总结

    学号 2017-2018-2 <第一行代码Android>第二章学习总结 教材学习内容总结 - 活动是什么: 活动(Activity)是最容易吸引用户的地方,它是一种可以包含用户界面的组件 ...

  2. 20172327 2018-2019-1 《第一行代码Android》第一章学习总结

    学号 2018-2019-1 <第一行代码Android>第一章学习总结 教材学习内容总结 - Android系统架构: 1.Linux内核层 Android系统是基于Linux内核的,这 ...

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

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

  4. 晒订单赢图灵图书,《第一行代码——Android》福利活动劲爆来袭!

    版权声明:本文出自郭霖的博客,转载必须注明出处. https://blog.csdn.net/sinyu890807/article/details/28863515 (已结束) 我的著作<第一 ...

  5. 第一行代码Android(第3版).pdf下载

    2020年人民邮电出版社出版的图书 <第一行代码Android(第3版)>是2020年4月人民邮电出版社出版的图书,作者是郭霖. 封面: 内容简介: <第一行代码 Android 第 ...

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

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

  7. 历时一年,我的著作《第一行代码——Android》已出版!

    前言 事实上我当初决定開始写博客的想法挺简单的,认为自己搞技术这么多年了,总应该要留下点什么.既然没能写出什么出色的应用,那至少也要留下点文字分享给大家,以指引在我后面的开发人员们,毕竟我也从前辈们的 ...

  8. 第一行代码 Android 思维导图

    第一行代码 Android  思维导图

  9. 第一行代码 Android (郭霖 著)

    https://github.com/guolindev/booksource 第1章 开始启程----你的第一行Android代码 (已看) 第2章 先从看得到的入手----探究活动 (已看) 第3 ...

随机推荐

  1. 老李分享:jvm内存原型剖析

    老李分享:jvm内存原型剖析   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对课程感兴趣,请大家咨询qq:9088214 ...

  2. 性能调优案例分享:jvm crash的原因 1

    性能调优案例分享:jvm crash的原因   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对课程感兴趣,请大家咨询qq: ...

  3. Linux文件管理下

    文件操作 对于文件,我们可以读取(read),写入(write)和运行(execute).读取是从已经存在的文件中获得数据.写入是向新的文件或者旧的文件写入数据.如果文件储存的是可执行的二进制码,那么 ...

  4. python_21_线程+进程+协程

    python_线程_进程_协程 什么是线程? -- os能够进行运算调度的最小单位,被包含在进程之中,是一串指令的集合 -- 每个线程都是独立的,可以访问同一进程下所有的资源 什么是进程? -- 每个 ...

  5. 深入学习 DUBBO

    1.什么是 RPC 协议? RPC 的全称是 Remote Procedure Call 是一种进程间通信方式.它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显 ...

  6. [转]使用sklearn进行集成学习——理论

    转:http://www.cnblogs.com/jasonfreak/p/5657196.html 目录 1 前言2 集成学习是什么?3 偏差和方差 3.1 模型的偏差和方差是什么? 3.2 bag ...

  7. 线段树(hdu 1556)

    Problem Description: N个气球排成一排,从左到右依次编号为1,2,3....N.每次给定2个整数a b(a <= b),lele便为骑上他的"小飞鸽"牌电 ...

  8. MongoDB数据库安装及配置环境终极教程(windows10系统)

    本文是笔者花时间踩坑踩生气了写出来的!转载请注明出处@http://www.cnblogs.com/tim100/!请尊重我的劳动成果!谢谢! 今天,给大家说说在windows10系统下MongoDB ...

  9. SQL零星技术点:SQL中转换money类型数值转换为字符串问题

    --SQL中转换money类型数值转换为字符串问题,直接转换就转为两位了,所以需要做一下处理.具体请看下述sql实例. 1 create table #test(price money) insert ...

  10. 玩转Eclipse--如何使用eclipse可以更好的提高我们的工作效率

    工欲善其事必先利其器,更加了解我们的开发工具有利于提高开发效率,而合理使用快捷键可以使我们事半功倍,这里收集了eclipse中的几种常见设置,eclipse的优化以及非常全面的快捷键介绍,大家有用到的 ...