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


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

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

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

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

注4:開發基於Android Studio 3.0,並且新建項目時勾選“support kotlin”

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

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

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

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

注冊API

先去注冊一下郭神提供的weather api(通過他獲得在線的天氣信息)
http://console.heweather.com/register

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

獲得數據:
這里寫圖片描述

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

不過這個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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM