用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

阅读全文
0 0
原创粉丝点击