用kotlin方式打开《第一行代码:Android》之开发酷欧天气(1)
来源:互联网 发布:pc蛋蛋计算器单双算法 编辑:程序博客网 时间:2024/05/18 20:51
参考:《第一行代码: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.weatherkotlinimport android.content.Contextimport android.view.LayoutInflaterimport android.view.Viewimport android.view.ViewGroupimport android.widget.ArrayAdapterimport android.widget.TextViewimport 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.weatherkotlinimport android.app.ProgressDialogimport android.os.Bundleimport android.support.v4.app.Fragmentimport android.view.LayoutInflaterimport android.view.Viewimport android.view.ViewGroupimport android.widget.Buttonimport android.widget.ListViewimport android.widget.TextViewimport com.google.gson.Gsonimport com.google.gson.reflect.TypeTokenimport org.jetbrains.anko.custom.asyncimport org.jetbrains.anko.findimport org.jetbrains.anko.uiThreadimport 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>>() {}.typeprovinceList = 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)
- 用kotlin方式打开《第一行代码:Android》之开发酷欧天气(2)
- 用kotlin方式打开《第一行代码:Android》之开发酷欧天气(最终版)
- 用kotlin方式打开《第一行代码:Android》
- 《第一行代码》之开发欧酷天气学习笔记
- 第一行代码酷欧天气开发(一)
- 第一行代码酷欧天气开发(二)
- 第一行代码酷欧天气开发(三)
- 第一行代码酷欧天气开发(四)
- 第一行代码酷欧天气开发(五)
- 第一行代码酷欧天气开发详细总结
- kotlin版的酷欧天气 (郭林大神的第一行代码第二版)
- kotlin编《第一行代码》(1)
- Android 开发入门之《第一行代码》(1)
- Android《第一行代码》项目实战之酷我天气阅读笔记
- 第一行代码酷欧天气小升级!
- 阅读郭林《第一行代码》的笔记——第14章 进入实战,开发酷欧天气
- Kotlin的第一行代码
- volatile关键词
- IOS 代码优化之 整洁的UITableView
- nginx docker 镜像(带lua的1.11.2 与 1.12.0)
- 【 软考之再总结】
- USACO->Your Ride Is Here
- 用kotlin方式打开《第一行代码:Android》之开发酷欧天气(1)
- 谷歌的小弟自定义View篇笔记(杂)
- ContentProvider
- USB键盘协议程序之字符重复无数次
- hihoCoder 1524 : 逆序对 (树状数组)
- 实际工作中:----dubbo+zookeeper实现服务远程调用
- Quartz执行多作业,JobDataMap传值
- C语言--变量
- QQ聊天消息展示,提交评论实现