参考:《第一行代码: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表达式,可代替难看复杂的匿名类