参考:《第一行代码: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. SpringMVC基础学习(一)—初识SpringMVC

    一.HelloWorld 1.导入SpringMVC所需的jar包        2.配置web.xml      配置DispatcherServlet.DispatcherServlet默认加载/ ...

  2. linux cut命令详解

    cut是一个选取命令,就是将一段数据经过分析,取出我们想要的.一般来说,选取信息通常是针对"行"来进行分析的,并不是整篇信息分析的. (1)其语法格式为:cut  [-bn] [f ...

  3. effective c++ Item 48 了解模板元编程

    1. TMP是什么? 模板元编程(template metaprogramming TMP)是实现基于模板的C++程序的过程,它能够在编译期执行.你可以想一想:一个模板元程序是用C++实现的并且可以在 ...

  4. oh-my-zsh 安装和使用

    oh-my-zsh是github用户robbyrussell的一款为简化zsh配置而开发的开源项目. 其github地址:https://github.com/robbyrussell/oh-my-z ...

  5. 拦截器的四种拦截方式以及Filter的执行顺序(17/4/8)

    一:拦截方式 需要在配置文件web.xml配置 在对应filter-mapping节点下 如下 <filter-mapping> <filter-name>BFilter< ...

  6. hadoop2.8和spark2.1完全分布式搭建

    一.前期准备工作: 1.安装包的准备: VMware(10.0版本以上) : 官方网站:https://www.vmware.com/cn.html 官方下载地址:http://www.vmware. ...

  7. 又拍云SSL证书全新上线,提供一站式HTTPS安全解决方案

    互联网快速发展,云服务早已融入每一个人的日常生活,而互联网安全与互联网的发展息息相关,这其中涉及到信息的保密性.完整性.可用性.真实性和可控性.又拍云上线了与多家国际顶级 CA 机构合作的数款OV & ...

  8. ELK菜鸟手记 (三) - X-Pack权限控制之给Kibana加上登录控制以及index_not_found_exception问题解决

    0. 背景 我们在使用ELK进行日志记录的时候,通过网址在Kibana中查看我们的应用程序(eg: Java Web)记录的日志, 但是默认是任何客户端都可以访问Kibana的, 这样就会造成很不安全 ...

  9. bzoj4810 [Ynoi2017]由乃的玉米田

    Description 由乃在自己的农田边散步,她突然发现田里的一排玉米非常的不美.这排玉米一共有N株,它们的高度参差不齐. 由乃认为玉米田不美,所以她决定出个数据结构题   这个题是这样的: 给你一 ...

  10. Java实现压缩文件与解压缩文件

    由于工作需要,需要将zip的压缩文件进行解压,经过调查发现,存在两个开源的工具包,一个是Apache的ant工具包,另一个就是Java api自带的工具包:但是Java自带的工具包存在问题:如果压缩或 ...