參考:《第一行代碼:Android》第2版——郭霖
注1:本文為原創,例子可參考郭前輩著作:《第一行代碼:Android》第2版
注2:本文不贅述android開發的基本理論,不介紹入門知識,不介紹Android Studio基本安裝,開門見山,直接使用kotlin改寫郭前輩的《第一行代碼:Android》中的部分例子,有機會的話自己做一些新例子出來!
注3:本文嘗試用Google新官推語言kotlin改寫《第一行代碼:Android》中的案例,偶爾涉及java作為對比
注4:開發基於Android Studio 3.0,並且新建項目時勾選“support kotlin”
進入實戰——開發酷歐天氣(3)
14.6 手動更新天氣和切換城市(原書p532)
不知不覺已經接觸kotlin的第四天了,原書中的最后一個實踐項目“酷歐天氣”也改寫的差不多了,稍后會將源碼上傳至csdn!作為代碼樣本吧!
每日一圖做背景
雖然說現在我們已經把天氣界面編寫的非常不錯了,不過和市場上的一些天氣軟件的界面比起來,仍然還有一定差距的。(原書p526)
配置build.gradle
為了加載bing.com的每日一圖到本地緩存,我們需要添加一個外部類庫glide
關於glide:http://blog.csdn.net/fancylovejava/article/details/44747759
build.gradle:
dependencies {
......
compile "com.github.bumptech.glide:glide:3.8.0"
}
添加glide到gradle,sync一下
最終布局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">
<ImageView
android:id="@+id/bing_pic_img"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop" />
<android.support.v4.widget.DrawerLayout
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:id="@+id/weather_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never"
android:scrollbars="none">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:orientation="vertical">
<include layout="@layout/title" />
<include layout="@layout/now" />
<include layout="@layout/forecast" />
<include layout="@layout/aqi" />
<include layout="@layout/suggestion" />
</LinearLayout>
</ScrollView>
</android.support.v4.widget.SwipeRefreshLayout>
<fragment
android:id="@+id/choose_area_fragment"
android:name="cn.cslg.weatherkotlin.ChooseAreaFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start"
/>
</android.support.v4.widget.DrawerLayout>
</FrameLayout>
注意:可以看到新增了一個ImageView,他就是我們用來放背景的容器,由於他的外部是一個FrameLayout,所以他和他的兄弟節點的內容會靠左上角停放,那么這個ImageView將和其他的內容重疊,造出一種背景的效果
修改WeatherActivity.kt:
......
class WeatherActivity : AppCompatActivity() {
......
private var bingImg: ImageView? = null
......
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//狀態欄透明化
if (Build.VERSION.SDK_INT >= 21) {
val v = window.decorView
v.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
window.statusBarColor = Color.TRANSPARENT
}
bingImg = find<ImageView>(R.id.bing_pic_img)
......
}
......
//獲取每日一圖的API
private fun loadBingImg() {
val url = "http://guolin.tech/api/bing_pic"
async {
val s = URL(url).readText()
uiThread {
Glide.with(this@WeatherActivity).load(s).into(bingImg)
}
}
}
}
注意:我們添加了loadBingImg方法,專門加載每日一圖,其中多線程結束后,使用了Glide將圖片在進入ImageView當中變成背景。
我們還把狀態欄設置為透明,並且將他變成應用的一部分(應用全屏了),此時狀態欄可能會和下面的內容挨得太近了,需要在xml中設置:fitsSystemWindows="true",空出一段空間來
手動更新天氣
實現下拉刷新當前選定城市的天氣信息
布局文件activity_weather.xml上面已經給出最終版本
主要添加了一個SwipeRefreshLayout容器,這個容器可以使用下拉刷新功能,將需要刷新的頁面全部包含進去
修改WeatherActivity.kt:
......
class WeatherActivity : AppCompatActivity() {
......
var swipeRefresh: SwipeRefreshLayout? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_weather)
swipeRefresh = find<SwipeRefreshLayout>(R.id.swipe_refresh)
swipeRefresh!!.setColorSchemeResources(R.color.colorPrimary)
......
val weatherId = defaultSharedPreferences.getString("weather_id","")
weatherLayout!!.visibility = View.INVISIBLE
requestWeather(weatherId)
swipeRefresh!!.setOnRefreshListener {
//刷新當前的城市weather_id
requestWeather(defaultSharedPreferences.getString("weather_id",""))
}
}
//從服務器加載天氣信息
fun requestWeather(wid: String) {
val url = "http://guolin.tech/api/weather?cityid=" + wid + "&key=" + KEY
async {
val s = URL(url).readText()
uiThread {
val weather = Gson().fromJson(s, Weather::class.java)
//關閉下拉刷新
swipeRefresh!!.isRefreshing = false
Log.d("url",url)
Log.d("url",weather.toString())
showWeatherInfo(weather.HeWeather[0])
}
}
}
......
}
注意:獲取到了SwipeRefreshLayout控件,使用setColorSchemeResources設置了顏色,setOnRefreshListener設置了下拉刷新事件的監聽。
切換城市
將ChooseAreaFragment這個碎片放到了offcanvas(側滑)當中,實現側滑后選擇其他城市
在title.xml添加一個顯示offcanvas的按鈕:
<?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"
>
<Button
android:id="@+id/nav_button"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_alignParentLeft="true"
android:layout_centerInParent="true"
android:layout_marginLeft="10dp"
android:background="@android:drawable/ic_menu_sort_by_size" />
......
</RelativeLayout>
修改activity_weather.xml(詳見前面的最終布局)
主要添加了一個DrawerLayout,用於存放兩個直子控件,第一個是主屏幕顯示內容,第二個是側滑內容
修改WeatherActivity.kt:
......
class WeatherActivity : AppCompatActivity() {
......
private var navButton:Button?=null
var drawLayout:DrawerLayout?=null
......
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
.....
drawLayout = find<DrawerLayout>(R.id.drawer_layout)
navButton = find<Button>(R.id.nav_button)
navButton!!.setOnClickListener{
drawLayout!!.openDrawer(GravityCompat.START)
}
}
......
}
注意:僅僅是添加了按鈕觸發事件和獲取DrawerLayout的控件
修改:ChooseAreaFragment.kt
......
class ChooseAreaFragment : Fragment() {
......
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
//列表點擊監聽事件
listView!!.setOnItemClickListener {
_, _, position, _ ->
when (current_level) {
......
LEVEL_COUNTY -> {
selectedCounty = countyList[position]
defaultSharedPreferences.edit().putString("weather_id", selectedCounty!!.weather_id).apply()
if (activity is MainActivity) {
startActivity<WeatherActivity>()
activity.finish() //將MainActivity銷毀掉
} else if (activity is WeatherActivity) {
val act = activity as WeatherActivity
act.drawLayout!!.closeDrawers()
act.swipeRefresh!!.isRefreshing = true //顯示下拉刷新
act.requestWeather(selectedCounty!!.weather_id)
}
}
}
}
......
}
......
}
注意:在Fragment獲取activity中的控件,kotlin的anko庫可以直接使用activity,然后調用其它activity里的屬性,比如swipeRefresh和drawLayout屬性,當然前提是他們是public,不可以像其他僅僅在當前類下用的控件那樣設置為private!
當用戶觸發選擇了城市的事件后,將會去請求服務器的天氣信息,在這之前應該將選擇的城市的weather_id保存到SharedPreferences中,這樣用戶不必每次打開app時都要選擇城市,app可以自己根據上次的選擇請求天氣數據
kotlin中使用is來代替java中的instanceof,可以用來判斷當前實例所屬類
如果是MainActivity,則進入WeatherActivity中(其實這表示app是用戶第一次打開,還沒有選擇過城市)
anko庫提供了startActivity<>方法,直接啟動其他的活動
anko庫的一些輔助方法:http://www.tuicool.com/articles/VrIjIjq
如果是WeatherActivity則關閉側滑和下拉刷新,立即請求數據去!
效果
結語
至此,就把原書中的“酷歐天氣”的例子使用kotlin語言重寫了
代碼下載:http://download.csdn.net/detail/u014466109/9851378
雖然我在這之前從來沒有接觸過kotlin語言,甚至聞所未聞(希望不要說我孤陋寡聞,畢竟我在這之前連Android都沒寫過,我的專長算是web)
但此時我想我愛上了kotlin這門現代語言
總結一下相對java開發的一些優點:
- 100%兼容java所有類庫
- 每一行kotlin可以節約3-4行的java代碼
- anko庫簡直就是Android界的jQuery,簡化和封裝了許多原本很長參數很多的方法
- data class類讓你少些多少文件,你沒必要理會那些一個類一個文件的java pojo,也不需要自己寫get set方法
- 嚴格且安全的null類型
- val,var變量自動推斷變量類型
- val變量適合多線程,並發安全
- 沒有無聊的分號!
- 性能,有過之而無不及
- 清晰的lambda表達式,可代替難看復雜的匿名類