参考:《第一行代码: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. Python之路-字符编码&数据类型补充

    作业 三级菜单程序 menu = { '北京':{ '海淀':{ '五道口':{ 'soho':{ }, '网易':{ }, 'google':{ } }, '中关村':{ '爱奇艺':{}, '汽车 ...

  2. Hibernate基础学习(五)—对象-关系映射(下)

    一.单向n-1 单向n-1关联只需从n的一端可以访问1的一端. 域模型: 从Order到Customer的多对一单向关联.Order类中定义一个Customer属性,而在Customer类不用存放Or ...

  3. C#对文件操作(基本的读写以及压缩和解压)

    主要是针对单个文件进行读写操作和压缩操作:用到的主要C#类有FileStream.FileInfo.StreamWrite.StreamRead.GZipStream. 字符数组和字节数组的转换: ] ...

  4. python的MySQLdb模块在linux环境下的安装

    开始学习python数据库编程后,在了解了基本概念,打算上手试验一下时,卡在了MYSQLdb包的安装上,折腾了半天才解决.记录一下我在linux中安装此包遇到的问题.系统是ubuntn15.04. 1 ...

  5. (转)什么是P问题、NP问题和NPC问题

    这或许是众多OIer最大的误区之一.    你会经常看到网上出现"这怎么做,这不是NP问题吗"."这个只有搜了,这已经被证明是NP问题了"之类的话.你要知道,大 ...

  6. redis 链表

    redis 链表 前言 借鉴了 黄健宏 的 <<Redis 设计与实现>> 一书, 对 redis 源码进行学习 欢迎大家给予意见, 互相沟通学习 概述 redis 的链表结构 ...

  7. Unity Debug类

    静态变量 developerConsoleVisible 报告是否开发控制台是可见的.开发控制台不能出现使用: isDebugBuild 在构建设置对话框中有一个叫做"发展构建"复 ...

  8. Java排序算法之归并排序

    基本思想: 归并排序利用分治法,先将一个序列分成一个个子序列,然后对子序列进行排序,再把有序子序列合并为整体有序序列. 图片来自于http://www.cnblogs.com/shudonghe/p/ ...

  9. 浅析JS中的模块规范AMD和CMD

    一.AMD AMD就只有一个接口:define(id?,dependencies?,factory); 它要在声明模块的时候制定所有的依赖(dep),并且还要当做形参传到factory中,像这样: d ...

  10. EasyUI datagrid默认勾选checkbox时注意事项

    在使用easyui的datagrid默认选中复选框时遇到的一个问题:就是加载程序默认选中复选框时死活选不中,查了好多资料才知道是easyui的datagrid的singleSelect属性设置为‘tr ...