用kotlin方式打開《第一行代碼:Android》之開發酷歐天氣(2)

參考:《第一行代碼:Android》第2版——郭霖html

注1:本文爲原創,例子可參考郭前輩著做:《第一行代碼:Android》第2版java

注2:本文不贅述android開發的基本理論,不介紹入門知識,不介紹Android Studio基本安裝,開門見山,直接使用kotlin改寫郭前輩的《第一行代碼:Android》中的部分例子,有機會的話本身作一些新例子出來!android

注3:本文嘗試用Google新官推語言kotlin改寫《第一行代碼:Android》中的案例,偶爾涉及java做爲對比json

注4:開發基於Android Studio 3.0,而且新建項目時勾選「support kotlin」api

進入實戰——開發酷歐天氣(2)

本次博文,我將嘗試使用kotlin語言對郭前輩的《第一行代碼》中的最後那個實戰項目「酷歐天氣」進行重寫數組

原書:14.5顯示天氣信息(p509)服務器

上一章,咱們已經把省市縣數據「爬」到了app中,接下來咱們要作的是「爬」天氣預報信息,完成app剩下的功能app

註冊API

先去註冊一下郭神提供的weather api(經過他得到在線的天氣信息)
http://console.heweather.com/registerasync

註冊完成登錄後取得key,並測試訪問一下是否能得到天氣的json信息
http://guolin.tech/api/weather?cityid=CN101190402&key=e970db87b1f24fb8a00555c6b361e8d4
(訪問形式如上,參考原書:p522,地址中的參數cityid就是在上一章中County數據類裏的weather_id)ide

得到數據:
這裏寫圖片描述

這就說明已經能夠成功獲取最近的天氣數據了!

不過這個json信息量有點大啊!==|||

數據類

上一章已經「吹」過了kotlin強大的data class(數據類)了,這是一種比java pojo類更加方便精簡的類!

每個你創建的POJO類文件最後都轉換爲一行代碼而已

原做中,做者對gson實體類進行了分析(原書p509),可使用他分析後建立的實體類(只須要轉換成kotlin data class的寫法)

不過對於這麼複雜的json,我使用了在線json轉POJO,將剛剛得到的天氣json數據先轉換爲POJO類(這樣使得實體類定義更加完整而且比本身一個個手寫要塊多了,工具是個好東西!)

json轉POJO網址:http://www.bejson.com/json2javapojo/

注意從網站上獲得的只是java的POJO類,不是kotlin類(反正我尚未找到json直接轉kotlin數據類的工具==|||求推薦)

進入Android Studio 3.0,在項目中新建包:weatherapi->新建datas.kt

這裏寫圖片描述

爲何要新建一個包?
這個包是專門存放kotlin的天氣數據類的,由於我發現郭前輩那個api得到天氣json轉換後的數據類與以前的個人City這個數據類有衝突(那個json裏也有個City),故而將此次天氣的數據類放到一個單獨的包中,保證不會衝突,另外引入時須要注意,別引入了外面那個City類!

嘗試轉換

先新建一個City.java,這是一個POJO類文件,將json在線轉換好的POJO類代碼複製進來

這是那個在線json轉換出來的City java POJO類:

package cn.cslg.weatherkotlin.weatherapi;
public class City {
    private String aqi;

    private String pm10;

    private String pm25;

    private String qlty;

    public void setAqi(String aqi){
        this.aqi = aqi;
    }
    public String getAqi(){
        return this.aqi;
    }
    public void setPm10(String pm10){
        this.pm10 = pm10;
    }
    public String getPm10(){
        return this.pm10;
    }
    public void setPm25(String pm25){
        this.pm25 = pm25;
    }
    public String getPm25(){
        return this.pm25;
    }
    public void setQlty(String qlty){
        this.qlty = qlty;
    }
    public String getQlty(){
        return this.qlty;
    }

}

使用IDE能夠直接將java轉換爲kotlin代碼,這是kotlin類庫自帶的功能,咱們來轉這個java類爲kotlin類
點擊Android Studio 3.0上面的工具欄上的Code->Convert Java File to Kotlin File(在最後一個)

IDE轉換結束後,很遺憾,這不是一個kotlin的數據類,自行修修補補,改爲data class吧(求工具徹底轉換!)

如下是我自行修改後:City的dataclass

data class City(val aqi: String, val pm10: String, val pm25: String, val qlty: String)

我的推薦一種辦法:

  1. 先把那個網站轉換的全部的類一塊兒複製到一個java文件中,我知道這樣會報錯,java只能有一個public class
  2. 轉換爲kotlin
  3. 替換全部public,internal爲data
  4. 替換全部的{}爲()
  5. 刪除最後一個屬性的逗號
  6. 全部代碼合併成一行
  7. Code->Reformat Code

最終的datas.kt:

package cn.cslg.weatherkotlin.weatherapi

data class City(var aqi: String, var pm10: String, var pm25: String, var qlty: String)
data class Aqi(var city: City)
data class Update(var loc: String, var utc: String)
data class Basic(var city: String, var cnty: String, var id: String, var lat: String, var lon: String, var update: Update)
data class Now(var cond: Cond, var fl: String, var hum: String, var pcpn: String, var pres: String, var tmp: String, var vis: String, var wind: Wind)
data class Air(var brf: String, var txt: String)
data class Comf(var brf: String, var txt: String)
data class Cw(var brf: String, var txt: String)
data class Drsg(var brf: String, var txt: String)
data class Flu(var brf: String, var txt: String)
data class Sport(var brf: String, var txt: String)
data class Trav(var brf: String, var txt: String)
data class Uv(var brf: String, var txt: String)
data class Suggestion(var air: Air, var comf: Comf, var cw: Cw, var drsg: Drsg, var flu: Flu, var sport: Sport, var trav: Trav, var uv: Uv)
data class Astro(var mr: String, var ms: String, var sr: String, var ss: String)
data class Tmp(var max: String, var min: String)
data class Daily_forecast(var astro: Astro, var cond: DailyCond, var date: String, var hum: String, var pcpn: String, var pop: String, var pres: String, var tmp: Tmp, var uv: String, var vis: String, var wind: Wind)
data class Cond(var code: String, var txt: String)
data class DailyCond(var code_d:String,var code_n:String,var txt_d:String,var txt_n:String)
data class Wind(var deg: String, var dir: String, var sc: String, var spd: String)
data class Hourly_forecast(var cond: Cond, var date: String, var hum: String, var pop: String, var pres: String, var tmp: String, var wind: Wind)
data class HeWeather(var aqi: Aqi, var basic: Basic, var daily_forecast: List<Daily_forecast>, var hourly_forecast: List<Hourly_forecast>, var now: Now, var status: String, var suggestion: Suggestion)
data class Weather(var HeWeather:List<HeWeather>)

注意最後一個Weather類,這是我單獨添加出來的,由於我注意到API返回的json開頭就有一個HeWeathter對象而且對應的值是HeWeather數組,Gson映射的時候是以最外層{}開始轉換爲一個對象的,這個對象將會是這個Weather,而不是HeWeather!

另外注意到,那個工具網站生成的POJO類中有兩個Cond!仔細查看json數據格式,確實有兩個Cond,並且不同!因此我另開了一個DailyCond數據類做爲daily_forecast下的cond的實體模型

這裏寫圖片描述

這裏寫圖片描述

能夠樹形查看json格式的網站:http://www.qqe2.com/

至此數據類已經創建好了,後面能夠放心的用Gson映射爲實體對象了!

顯示天氣

原書:p520

接下來能夠開始書寫kt代碼了,我將會把原書中的java翻譯成爲簡單kotlin代碼

原書中有handleWeatherResponse,針對json進行處理,這個咱們不須要,我使用Gson映射爲數據類實體

WeatherActivity.kt:

package cn.cslg.weatherkotlin

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.widget.*
import cn.cslg.weatherkotlin.weatherapi.*
import com.google.gson.Gson
import org.jetbrains.anko.custom.async
import org.jetbrains.anko.find
import org.jetbrains.anko.uiThread
import java.net.URL

class WeatherActivity : AppCompatActivity() {

    private var weatherLayout: ScrollView? = null
    private var titleCity: TextView? = null
    private var titleUpdateTime: TextView? = null
    private var degreeText: TextView? = null
    private var weatherInfoText: TextView? = null
    private var forecastLayout: LinearLayout? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_weather)

        weatherLayout = find<ScrollView>(R.id.weather_layout)
        titleCity = find<TextView>(R.id.title_city)
        titleUpdateTime = find<TextView>(R.id.title_update_time)
        degreeText = find<TextView>(R.id.degree_text)
        weatherInfoText = find<TextView>(R.id.weather_info_text)
        forecastLayout = find<LinearLayout>(R.id.forecast_layout)

        val weatherId = intent.getStringExtra("weather_id");
        weatherLayout!!.visibility = View.INVISIBLE
        requestWeather(weatherId)
    }

    //從服務器加載天氣信息
    private fun requestWeather(wid:String){
        val url="http://guolin.tech/api/weather?cityid="+wid+"&key=e970db87b1f24fb8a00555c6b361e8d4"
        async {
            val s = URL(url).readText()

            uiThread {
                val weather = Gson().fromJson(s,Weather::class.java)
                showWeatherInfo(weather.HeWeather[0])
            }
        }
    }

    //顯示出天氣信息
    private fun showWeatherInfo(w: HeWeather){
        titleCity!!.text=w.basic.city
        titleUpdateTime!!.text= w.basic.update.loc
        degreeText!!.text=w.now.tmp+" ℃"
        weatherInfoText!!.text=w.now.cond.txt+"     "+w.now.wind.dir+"  "+w.now.wind.sc+"級"
        weatherLayout!!.visibility=View.VISIBLE
        forecastLayout!!.removeAllViews()
        for( d in w.daily_forecast){
            val v = LayoutInflater.from(this).inflate(R.layout.forecast_item,forecastLayout,false)
            val dateText = v.find<TextView>(R.id.date_text)
            val infoText = v.find<TextView>(R.id.info_text)
            val maxText = v.find<TextView>(R.id.max_text)
            val minText = v.find<TextView>(R.id.min_text)
            dateText.text = d.date
            maxText.text = d.tmp.max
            minText.text = d.tmp.min
            if(d.cond.code_d == d.cond.code_n){
                infoText.text = d.cond.txt_d
            }else{
                infoText.text = d.cond.txt_d+"->"+d.cond.txt_n
            }
            forecastLayout!!.addView(v)
        }
    }
}

一點分析

overwrite的onCreate方法很少解釋,主要是獲取R中的佈局轉成控件,隱藏了還沒有加載的地方,還從intent中得到了一個參數,用於選擇查詢的城市

requestWeather方法使用了async請求API的數據,傳入城市的weather_id
注意到,這裏使用Gson轉換的時候並無上一章的TypeToken,由於這一次得到json數據最外層不是數組,也就不須要List映射,因此直接傳入一個對象的類,kotlin使用Object::class.java便可!

showWeatherInfo方法將獲取的數據初始化到界面上,其中用到了kotlin的for( in )遍歷結構,很是簡單!

補充ChooseAreaFragment

ChooseAreaFragment.kt:

class ChooseAreaFragment : Fragment() {
......
override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)

        //列表點擊監聽事件
        listView!!.setOnItemClickListener {
          
                LEVEL_COUNTY -> {
                    selectedCounty = countyList[position]
                    val intent = Intent(activity,WeatherActivity::class.java)
                    intent.putExtra("weather_id", selectedCounty!!.weather_id)
                    startActivity(intent)
                    activity.finish()
.......
}

往裏面添加了:點擊縣跳轉到WeatherActivity的Intent

補充佈局

activity_weather.xml:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/colorPrimary">

    <ScrollView
        android:id="@+id/weather_layout"
        android:scrollbars="none"
        android:overScrollMode="never"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <include layout="@layout/title"/>
            <include layout="@layout/now"/>
            <include layout="@layout/forecast"/>
        </LinearLayout>
    </ScrollView>

</FrameLayout>

forecast.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:layout_margin="15dp"
    android:background="#8000"
    android:orientation="vertical">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="15dp"
        android:layout_marginRight="15dp"
        android:paddingTop="10sp"
        android:paddingBottom="10sp"
        android:text="三天預報"
        android:textColor="#fff"
        android:textSize="20sp"
        />
    <LinearLayout
        android:id="@+id/forecast_layout"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

    </LinearLayout>

</LinearLayout>

forecast_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:layout_margin="15dp"
    android:orientation="horizontal">
    <TextView
        android:id="@+id/date_text"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="2"
        android:layout_gravity="center_vertical"
        android:textColor="#fff"
        />
    <TextView
        android:id="@+id/info_text"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:layout_gravity="center_vertical"
        android:gravity="center"
        android:textColor="#fff"
        />
    <TextView
        android:id="@+id/max_text"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_weight="1"
        android:gravity="center"
        android:textColor="#fff"
        />
    <TextView
        android:id="@+id/min_text"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_weight="1"
        android:gravity="right"
        android:textColor="#fff"
        />

</LinearLayout>

now.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_margin="15dp"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">
    <TextView
        android:id="@+id/degree_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="end"
        android:textColor="#fff"
        android:textSize="60sp"
        />
    <TextView
        android:id="@+id/weather_info_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="end"
        android:textColor="#fff"
        android:textSize="20sp"
        />

</LinearLayout>

title.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="?attr/actionBarSize"
    >
    <TextView
        android:id="@+id/title_city"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:textColor="#fff"
        android:textSize="20sp"
        />
    <TextView
        android:id="@+id/title_update_time"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginRight="10sp"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:textColor="#fff"
        android:textSize="16sp"
        />

</RelativeLayout>

效果

這裏寫圖片描述

結尾

app的大致功能和樣式已經實現了,接下來須要對他作更多的美化和完善!

版權

轉載請註明出自:http://www.cnblogs.com/devilyouwei/p/6889804.html

相關文章
相關標籤/搜索