第14章 使用Kotlin 進(jìn)行 Android 開(kāi)發(fā)(1)
根據(jù)Realm Report (2017-Q4团甲,https://realm.io/realm-report/2017-q4
) ,過(guò)去的一年在Android 端的開(kāi)發(fā):Java 從 95% 降低到 Java 85%嗅剖, 而 Kotlin 從 5% 漲到 15% 价匠,如下圖所示
從這個(gè)趨勢(shì)來(lái)看丘喻,加上最新 Android Studio 3.0的發(fā)布(內(nèi)置 Kotlin 開(kāi)發(fā) Android 項(xiàng)目的支持)久脯,Kotlin 將會(huì)很快顛覆 Java 在 Android 領(lǐng)域的地位。
本章將帶領(lǐng)大家快速入門(mén)使用 Kotlin 進(jìn)行 Android 應(yīng)用程序的開(kāi)發(fā)。
14.1 快速開(kāi)始 Hello World
我們從一個(gè)簡(jiǎn)單的 Kotlin 版本的Hello World Android 應(yīng)用程序開(kāi)始涂佃。
14.1.1 準(zhǔn)備工作
首先準(zhǔn)備好開(kāi)發(fā)工具。Android 開(kāi)發(fā)還是用 Google 官方支持的 IDE Android Studio 好蜈敢。
Android Studio 3.0 簡(jiǎn)介
Google 在 2017-10-26 發(fā)布了 Android 8.1 Oreo 開(kāi)發(fā)者預(yù)覽版的同時(shí)還正式發(fā)布了 Android Studio 3.0 辜荠,為其 IDE 引入了一系列新功能。
Android Studio 3.0 專(zhuān)注于加速 Android 應(yīng)用開(kāi)發(fā)抓狭,包含大量更新內(nèi)容伯病,其中最主要的功能之一就包括對(duì) Kotlin 的支持。正如谷歌在 Google I / O 2017.5 所宣布的那樣否过,Kotlin 已被官方支持用于 Android 開(kāi)發(fā)午笛。 Android Studio 3.0是第一個(gè)支持 Kotlin 語(yǔ)言的里程碑式版本(在此之前,可以使用Android Studio 的 Kotlin 插件的方式)苗桂。
在該版本中提供了許多方便實(shí)用的功能如代碼自動(dòng)補(bǔ)全和語(yǔ)法高亮顯示药磺,另外,Android Studio 內(nèi)置轉(zhuǎn)換工具可以非常方便地把 Java 代碼轉(zhuǎn)換成 Kotlin 代碼煤伟,如下圖所示
安裝 Android Studio 3.0
Android Studio 是 Android 的官方 IDE癌佩。Android Studio 3.0的一個(gè)亮點(diǎn)就是內(nèi)置了 Kotlin 的支持(https://developer.android.google.cn/kotlin/index.html)。正如 Google I/O 2017 所說(shuō)的那樣, Kotlin 已成為 Android 官方開(kāi)發(fā)語(yǔ)言便锨。
使用 Android Studio 3.0围辙, 我們可以方便地把Java 源代碼自動(dòng)轉(zhuǎn)換成 Kotlin 代碼,也可以直接創(chuàng)建 Kotlin 語(yǔ)言開(kāi)發(fā)的 Android 項(xiàng)目, 只需要在新建項(xiàng)目的時(shí)候勾選 Include Kotlin support 即可放案。
首先去官網(wǎng)下載安裝:https://developer.android.google.cn/studio/install.html 姚建。筆者當(dāng)前下載的安裝包版本是 android-studio-ide-171.4408382-mac.dmg ,下載完畢點(diǎn)擊 dmg 文件
拷貝至應(yīng)用程序即可卿叽。
14.1.2 創(chuàng)建基于 Kotlin 的Android 項(xiàng)目
首先新建項(xiàng)目桥胞。如果您未打開(kāi)項(xiàng)目,請(qǐng)?jiān)?Welcome to Android Studio 窗口中點(diǎn)擊 Start a new Android Studio project
如果您已打開(kāi)項(xiàng)目考婴,請(qǐng)依次點(diǎn)擊 File > New > New Project 贩虾,如下圖所示
進(jìn)入 Create Android Project 對(duì)話框。在創(chuàng)建 Android 項(xiàng)目對(duì)話框中配置應(yīng)用基本信息沥阱,注意勾選 Kotlin 支持選項(xiàng)缎罢,點(diǎn)擊 Next。如下圖所示
進(jìn)入 Target Android Devices 配置應(yīng)用運(yùn)行 SDK 以及環(huán)境信息
我們勾選 Phone and Tablet 選項(xiàng)考杉,API 15:Android 4.0.3 策精,點(diǎn)擊 Next 進(jìn)入添加 Activity 界面
我們選擇 Empty Activity,點(diǎn)擊 Next崇棠,進(jìn)入配置 Activity 界面
配置好 Activity Name與 Layout Name 之后咽袜,點(diǎn)擊 Finish。我們將得到一個(gè) Kotlin 版本的Hello World Android 應(yīng)用程序枕稀。工程目錄如下
14.1.3 工程目錄文件說(shuō)明
其中询刹,在頂層的 Gradle 配置文件 build.gradle 中添加了 kotlin-gradle-plugin 插件的依賴(lài)
buildscript {
ext.kotlin_version = '1.1.51'
...
dependencies {
classpath 'com.android.tools.build:gradle:3.0.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
...
app 目錄下的build.gradle 配置文件內(nèi)容如下
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
dependencies {
...
implementation"org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
...
}
其中的apply plugin: 'kotlin-android-extensions' 表示使用 Kotlin Android Extensions插件。這個(gè)插件是 Kotlin 專(zhuān)門(mén)針對(duì) Android 擴(kuò)展的插件萎坷,實(shí)現(xiàn)了與 Data-Binding凹联、 Dagger等框架的功能。
布局文件activity_main.xml內(nèi)容如下
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.easy.kotlin.myapplication.MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
MainActivity.kt 代碼如下
package com.easy.kotlin.myapplication
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
AndroidManifest.xml 文件內(nèi)容如下
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.easy.kotlin.myapplication">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
14.1.4 安裝運(yùn)行
點(diǎn)擊功能菜單欄中的運(yùn)行按鈕
會(huì)提示我們選擇應(yīng)用程序部署運(yùn)行的目標(biāo)設(shè)備
需要注意的是哆档,手機(jī)要打開(kāi)連接 USB 調(diào)試模式蔽挠。點(diǎn)擊 OK,Android Studio 會(huì)為我們完成打包瓜浸、安裝等事項(xiàng)澳淑。最終的運(yùn)行效果如下
14.2 綜合項(xiàng)目實(shí)戰(zhàn):開(kāi)發(fā)一個(gè)電影指南應(yīng)用程序
本節(jié)我們將開(kāi)發(fā)一個(gè)Android 應(yīng)用程序, 列出流行/最高評(píng)級(jí)的電影, 顯示預(yù)告片和評(píng)論。
14.2.1 創(chuàng)建 Kotlin Android 項(xiàng)目
運(yùn)行效果
14.2.2 工程說(shuō)明
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.easy.kotlin">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".ItemListActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ItemDetailActivity"
android:label="@string/title_item_detail"
android:parentActivityName=".ItemListActivity"
android:theme="@style/AppTheme.NoActionBar">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.easy.kotlin.ItemListActivity" />
</activity>
</application>
</manifest>
其中 android.intent.action.MAIN 處的配置指定了應(yīng)用程序的啟動(dòng)Activity 為 .ItemListActivity , 其中的點(diǎn)號(hào) “.” 表示該類(lèi)位于 package="com.easy.kotlin" 路徑下斟叼。
<activity
android:name=".ItemListActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
下面我們來(lái)介紹應(yīng)用程序的啟動(dòng)主類(lèi) ItemListActivity 偶惠。
ItemListActivity
Kotlin 代碼如下
package com.easy.kotlin
import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.RecyclerView
import android.support.design.widget.Snackbar
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import com.easy.kotlin.dummy.DummyContent
import kotlinx.android.synthetic.main.activity_item_list.*
import kotlinx.android.synthetic.main.item_list_content.view.*
import kotlinx.android.synthetic.main.item_list.*
/**
* An activity representing a list of Pings. This activity
* has different presentations for handset and tablet-size devices. On
* handsets, the activity presents a list of items, which when touched,
* lead to a [ItemDetailActivity] representing
* item details. On tablets, the activity presents the list of items and
* item details side-by-side using two vertical panes.
*/
class ItemListActivity : AppCompatActivity() {
/**
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
* device.
*/
private var mTwoPane: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_item_list)
setSupportActionBar(toolbar)
toolbar.title = title
fab.setOnClickListener { view ->
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show()
}
if (item_detail_container != null) {
// The detail container view will be present only in the
// large-screen layouts (res/values-w900dp).
// If this view is present, then the
// activity should be in two-pane mode.
mTwoPane = true
}
setupRecyclerView(item_list)
}
private fun setupRecyclerView(recyclerView: RecyclerView) {
recyclerView.adapter = SimpleItemRecyclerViewAdapter(this, DummyContent.ITEMS, mTwoPane)
}
class SimpleItemRecyclerViewAdapter(private val mParentActivity: ItemListActivity,
private val mValues: List<DummyContent.DummyItem>,
private val mTwoPane: Boolean) :
RecyclerView.Adapter<SimpleItemRecyclerViewAdapter.ViewHolder>() {
private val mOnClickListener: View.OnClickListener
init {
mOnClickListener = View.OnClickListener { v ->
val item = v.tag as DummyContent.DummyItem
if (mTwoPane) {
val fragment = ItemDetailFragment().apply {
arguments = Bundle()
arguments.putString(ItemDetailFragment.ARG_ITEM_ID, item.id)
}
mParentActivity.supportFragmentManager
.beginTransaction()
.replace(R.id.item_detail_container, fragment)
.commit()
} else {
val intent = Intent(v.context, ItemDetailActivity::class.java).apply {
putExtra(ItemDetailFragment.ARG_ITEM_ID, item.id)
}
v.context.startActivity(intent)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_list_content, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = mValues[position]
holder.mIdView.text = item.id
holder.mContentView.text = item.content
with(holder.itemView) {
tag = item
setOnClickListener(mOnClickListener)
}
}
override fun getItemCount(): Int {
return mValues.size
}
inner class ViewHolder(mView: View) : RecyclerView.ViewHolder(mView) {
val mIdView: TextView = mView.id_text
val mContentView: TextView = mView.content
}
}
}
布局文件 XML 代碼 activity_item_list.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="com.easy.kotlin.ItemListActivity">
<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<FrameLayout
android:id="@+id/frameLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<include layout="@layout/item_list" />
</FrameLayout>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
app:srcCompat="@android:drawable/ic_dialog_email" />
</android.support.design.widget.CoordinatorLayout>
對(duì)應(yīng)的 UI 設(shè)計(jì)效果圖如下
AppCompatActivity
在使用Android Studio開(kāi)發(fā)Android應(yīng)用的時(shí)候,創(chuàng)建項(xiàng)目時(shí)朗涩,自動(dòng)繼承的是AppCompatActivity忽孽。這樣我們可以在自定義的 Activity 類(lèi)中添加 android.support.v7.app.ActionBar( API level 7 +)。例如activity_item_list.xml 布局中的
<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
Activity 中添加 Toolbar 的代碼是
class ItemListActivity : AppCompatActivity() {
/**
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
* device.
*/
private var mTwoPane: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_item_list)
setSupportActionBar(toolbar)
toolbar.title = title
fab.setOnClickListener { view ->
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show()
}
if (item_detail_container != null) {
// The detail container view will be present only in the
// large-screen layouts (res/values-w900dp).
// If this view is present, then the
// activity should be in two-pane mode.
mTwoPane = true
}
setupRecyclerView(item_list)
}
}
AppCompatActivity 背后也是繼承的 Activity 谢床。推出Android 5.0之后兄一,提供了很多新功能,于是 support v7 也更新了识腿,出現(xiàn)了AppCompatActivity出革。AppCompatActivity 是用來(lái)替代ActionBarActivity的 。 AppCompatActivity 的類(lèi)圖繼承層次如下
Activity 生命周期
Activity 生命周期如下圖所示(圖來(lái)自官網(wǎng))
相信不少朋友也已經(jīng)看過(guò)這個(gè)流程圖了渡讼,關(guān)于Activity生命周期的幾個(gè)過(guò)程骂束,我們簡(jiǎn)單說(shuō)明如下
1.開(kāi)始啟動(dòng)Activity耳璧,系統(tǒng)會(huì)先調(diào)用onCreate方法,然后調(diào)用onStart方法展箱,最后調(diào)用onResume旨枯,Activity進(jìn)入運(yùn)行狀態(tài)。
2.當(dāng)前Activity被其他Activity覆蓋其上或被鎖屏:系統(tǒng)會(huì)調(diào)用onPause方法混驰,暫停當(dāng)前Activity的執(zhí)行攀隔。
3.當(dāng)前Activity由被覆蓋狀態(tài)回到前臺(tái)或解鎖屏:系統(tǒng)會(huì)調(diào)用onResume方法,再次進(jìn)入運(yùn)行狀態(tài)栖榨。
4.當(dāng)前Activity轉(zhuǎn)到新的Activity界面或按Home鍵回到主屏:系統(tǒng)會(huì)先調(diào)用onPause方法昆汹,然后調(diào)用onStop方法,進(jìn)入停止?fàn)顟B(tài)婴栽。
5.用戶后退回到此Activity:系統(tǒng)會(huì)先調(diào)用onRestart方法满粗,然后調(diào)用onStart方法,最后調(diào)用onResume方法居夹,再次進(jìn)入運(yùn)行狀態(tài)败潦。
6.當(dāng)前Activity處于被覆蓋狀態(tài)或者后臺(tái)不可見(jiàn)狀態(tài),即第2步和第4步准脂,系統(tǒng)內(nèi)存不足劫扒,殺死當(dāng)前Activity,而后用戶退回當(dāng)前Activity:再次調(diào)用onCreate方法狸膏、onStart方法沟饥、onResume方法,進(jìn)入運(yùn)行狀態(tài)湾戳。
7.用戶退出當(dāng)前Activity:系統(tǒng)先調(diào)用onPause方法贤旷,然后調(diào)用onStop方法,最后調(diào)用onDestory方法砾脑,結(jié)束當(dāng)前Activity幼驶。
這個(gè)過(guò)程可以用下面的狀態(tài)圖來(lái)簡(jiǎn)單說(shuō)明
Kotlin Android Extensions 插件
在上面的ItemListActivity.onCreate 函數(shù)中,其中的這行代碼
setSupportActionBar(toolbar)
是設(shè)置支持的 ActionBar方法韧衣。但是我們發(fā)現(xiàn)盅藻,這里并沒(méi)有使用 findViewById()方法來(lái)獲取這個(gè) android:id="@+id/toolbar" Toolbar 的 View 對(duì)象,之前我們可能都是這樣寫(xiě)的
Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
而這里直接就使用了 toolbar 這個(gè) Toolbar 的對(duì)象變量畅铭。這是怎么做到的呢氏淑?其實(shí)這是通過(guò) Kotlin Android Extensions 插件做到的。我們?cè)赼pp 目錄下的 Gradle 配置文件 build.gradle 中添加了這個(gè)配置
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
有了這個(gè)插件我們就可以永遠(yuǎn)跟 findViewById 說(shuō)再見(jiàn)了硕噩。Kotlin Android Extensions 插件是 Kotlin 針對(duì) Android 開(kāi)發(fā)專(zhuān)門(mén)定制的通用插件, 通過(guò)它我們能夠以極簡(jiǎn)的無(wú)縫方式實(shí)現(xiàn)從 Activity, Fragment 和 View 布局組件中創(chuàng)建和獲取視圖 View 假残。使用 Kotlin 開(kāi)發(fā) Android 大大減少了我們的樣板代碼。
就像上面的示例代碼一樣炉擅,我們只要在代碼中直接使用這個(gè)布局組件的 id 名稱(chēng)作為變量名即可辉懒,剩下的Kotlin 插件會(huì)幫我們?nèi)扛愣ㄑ羧恰otlin Android Extensions 插件將會(huì)為我們生成一些額外的代碼,使得我們可以在布局XML中直接通過(guò) id 獲取到其 View 對(duì)象眶俩。另外穆端,它還生成一個(gè)本地視圖緩存,當(dāng)?shù)谝淮问褂脤傩詴r(shí)仿便,它將執(zhí)行一個(gè)常規(guī)的findViewById。但在下一次使用屬性的時(shí)候攒巍,視圖將從緩存中恢復(fù)嗽仪,因此訪問(wèn)速度將更快。
只要布局添加一個(gè) View柒莉,在 Activity闻坚、View、Fragment 中都可以直接用 id 來(lái)引用這個(gè) View兢孝,Kotlin 把 Android 編程極簡(jiǎn)風(fēng)格發(fā)揮得淋漓盡致窿凤。
我們可以通過(guò)Kotlin 對(duì)應(yīng)的字節(jié)碼來(lái)更加本質(zhì)深入地理解 Kotlin 所做的事情。Android Studio 中跟 IDEA 一樣提供了 Kotlin 的工具箱跨蟹。在菜單欄中依次選擇 Code > Kotlin > Show Kotlin Bytecode , 如下圖所示
點(diǎn)擊 Show Kotlin Bytecode 之后雳殊,我們將會(huì)看到如下圖所示的 Kotlin Bytecode 界面
其中這兩行代碼
setSupportActionBar(toolbar)
toolbar.title = title
對(duì)應(yīng)的字節(jié)碼是
LINENUMBER 39 L2
ALOAD 0
ALOAD 0
GETSTATIC com/easy/kotlin/R$id.toolbar : I
INVOKEVIRTUAL com/easy/kotlin/ItemListActivity._$_findCachedViewById (I)Landroid/view/View;
CHECKCAST android/support/v7/widget/Toolbar
INVOKEVIRTUAL com/easy/kotlin/ItemListActivity.setSupportActionBar (Landroid/support/v7/widget/Toolbar;)V
L3
LINENUMBER 40 L3
ALOAD 0
GETSTATIC com/easy/kotlin/R$id.toolbar : I
INVOKEVIRTUAL com/easy/kotlin/ItemListActivity._$_findCachedViewById (I)Landroid/view/View;
CHECKCAST android/support/v7/widget/Toolbar
ALOAD 0
INVOKEVIRTUAL com/easy/kotlin/ItemListActivity.getTitle ()Ljava/lang/CharSequence;
INVOKEVIRTUAL android/support/v7/widget/Toolbar.setTitle (Ljava/lang/CharSequence;)V
L4
其實(shí)從字節(jié)碼中
GETSTATIC com/easy/kotlin/R$id.toolbar : I
INVOKEVIRTUAL com/easy/kotlin/ItemListActivity._$_findCachedViewById
我們已經(jīng)看到了 Kotlin 為我們所做的事情了。反編譯成 Java 代碼可能會(huì)看的更加清楚
public final class ItemListActivity extends AppCompatActivity {
private boolean mTwoPane;
private HashMap _$_findViewCache;
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(2131361820);
this.setSupportActionBar((Toolbar)this._$_findCachedViewById(id.toolbar));
((Toolbar)this._$_findCachedViewById(id.toolbar)).setTitle(this.getTitle());
...
}
public View _$_findCachedViewById(int var1) {
if(this._$_findViewCache == null) {
this._$_findViewCache = new HashMap();
}
View var2 = (View)this._$_findViewCache.get(Integer.valueOf(var1));
if(var2 == null) {
var2 = this.findViewById(var1);
this._$_findViewCache.put(Integer.valueOf(var1), var2);
}
return var2;
}
...
}
其中的ItemListActivity 類(lèi)中的 HashMap 類(lèi)型的私有成員變量 _$_findViewCache 就是本地緩存窗轩。這里其實(shí)反映出 Kotlin 語(yǔ)言設(shè)計(jì)的核心思想:通過(guò)更高一層的對(duì) Java 的封裝夯秃,不僅大大簡(jiǎn)化了程序員的樣板化的代碼量,同時(shí)還根據(jù)一些特定的可以?xún)?yōu)化的問(wèn)題場(chǎng)景痢艺,順帶提供了更好的性能仓洼。
同樣的,上面的代碼中的 fab 變量
fab.setOnClickListener { view ->
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show()
}
也是直接使用的布局 XML 中的 android:id="@+id/fab"
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
app:srcCompat="@android:drawable/ic_dialog_email" />
item_detail_container 堤舒、setupRecyclerView(item_list)中的 item_list 都是使用上面的方式色建。這樣代碼確實(shí)是大大精簡(jiǎn)了許多。
嵌套 Layout 布局
上面的 activity_item_list.xml 布局中嵌套的 FrameLayout 布局配置如下
<FrameLayout
android:id="@+id/frameLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<include layout="@layout/item_list" />
</FrameLayout>
里面的 <include layout="@layout/item_list" /> 表示引用 layout 文件夾下面的 item_list.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/item_list"
android:name="com.easy.kotlin.ItemListFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
app:layoutManager="LinearLayoutManager"
tools:context="com.easy.kotlin.ItemListActivity"
tools:listitem="@layout/item_list_content" />
而布局 item_list.xml 中的 tools:listitem="@layout/item_list_content" 表示又引用了layout 文件夾下面的 item_list_content.xml 布局文件舌缤。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/id_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:textAppearance="?attr/textAppearanceListItem" />
<TextView
android:id="@+id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:textAppearance="?attr/textAppearanceListItem" />
</LinearLayout>
ItemDetailActivity
這個(gè)是 Item 詳情頁(yè)的Activity 箕戳。Kotlin 代碼如下
package com.easy.kotlin
import android.content.Intent
import android.os.Bundle
import android.support.design.widget.Snackbar
import android.support.v7.app.AppCompatActivity
import android.view.MenuItem
import kotlinx.android.synthetic.main.activity_item_detail.*
/**
* An activity representing a single Item detail screen. This
* activity is only used on narrow width devices. On tablet-size devices,
* item details are presented side-by-side with a list of items
* in a [ItemListActivity].
*/
class ItemDetailActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_item_detail)
setSupportActionBar(detail_toolbar)
fab.setOnClickListener { view ->
Snackbar.make(view, "Replace with your own detail action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show()
}
// Show the Up button in the action bar.
supportActionBar?.setDisplayHomeAsUpEnabled(true)
// savedInstanceState is non-null when there is fragment state
// saved from previous configurations of this activity
// (e.g. when rotating the screen from portrait to landscape).
// In this case, the fragment will automatically be re-added
// to its container so we don't need to manually add it.
// For more information, see the Fragments API guide at:
//
// http://developer.android.com/guide/components/fragments.html
//
if (savedInstanceState == null) {
// Create the detail fragment and add it to the activity
// using a fragment transaction.
val arguments = Bundle()
arguments.putString(ItemDetailFragment.ARG_ITEM_ID,
intent.getStringExtra(ItemDetailFragment.ARG_ITEM_ID))
val fragment = ItemDetailFragment()
fragment.arguments = arguments
supportFragmentManager.beginTransaction()
.add(R.id.item_detail_container, fragment)
.commit()
}
}
override fun onOptionsItemSelected(item: MenuItem) =
when (item.itemId) {
android.R.id.home -> {
// This ID represents the Home or Up button. In the case of this
// activity, the Up button is shown. For
// more details, see the Navigation pattern on Android Design:
//
// http://developer.android.com/design/patterns/navigation.html#up-vs-back
navigateUpTo(Intent(this, ItemListActivity::class.java))
true
}
else -> super.onOptionsItemSelected(item)
}
}
UI 布局 XML 文件 item_detail.xml 如下
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="com.easy.kotlin.ItemDetailActivity"
tools:ignore="MergeRootFrame">
<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/app_bar_height"
android:fitsSystemWindows="true"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:toolbarId="@+id/toolbar">
<android.support.v7.widget.Toolbar
android:id="@+id/detail_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
android:id="@+id/item_detail_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|start"
android:layout_margin="@dimen/fab_margin"
app:layout_anchor="@+id/item_detail_container"
app:layout_anchorGravity="top|end"
app:srcCompat="@android:drawable/stat_notify_chat" />
</android.support.design.widget.CoordinatorLayout>
打開(kāi)item_detail.xml ,我們可以看到設(shè)計(jì)圖 UI 的效果
我們可以看到詳情頁(yè)的布局主要有3大塊:AppBarLayout 友驮、NestedScrollView和 FloatingActionButton 漂羊。
在 ItemDetailActivity 的onCreate 函數(shù)里的
setContentView(R.layout.activity_item_detail)
設(shè)置詳情頁(yè) ItemDetailActivity 的顯示界面使用 activity_item_detail.xml 布局文件進(jìn)行布局。
setSupportActionBar(detail_toolbar)
設(shè)置詳情頁(yè)的 android.support.v7.widget.Toolbar 控件布局卸留。
下面我們來(lái)看在 ItemDetailActivity 中創(chuàng)建 ItemDetailFragment 的過(guò)程走越。代碼如下
override fun onCreate(savedInstanceState: Bundle?) {
...
if (savedInstanceState == null) {
// Create the detail fragment and add it to the activity
// using a fragment transaction.
val arguments = Bundle()
arguments.putString(ItemDetailFragment.ARG_ITEM_ID,
intent.getStringExtra(ItemDetailFragment.ARG_ITEM_ID))
val fragment = ItemDetailFragment()
fragment.arguments = arguments
supportFragmentManager.beginTransaction()
.add(R.id.item_detail_container, fragment)
.commit()
}
}
- 首先我們判斷當(dāng)前 savedInstanceState 是否為空。如果為空耻瑟, 執(zhí)行步驟2 旨指。
- 創(chuàng)建 ItemDetailFragment() 對(duì)象赏酥,并設(shè)置其 Bundle 信息(Fragment 中的成員變量 mArguments )
val arguments = Bundle()
arguments.putString(ItemDetailFragment.ARG_ITEM_ID,
intent.getStringExtra(ItemDetailFragment.ARG_ITEM_ID))
val fragment = ItemDetailFragment()
fragment.arguments = arguments
- 通過(guò) supportFragmentManager 添加 Fragment 與布局空間的映射關(guān)系。
supportFragmentManager.beginTransaction()
.add(R.id.item_detail_container, fragment)
.commit()
其中谆构,supportFragmentManager 用來(lái)獲取能管理和當(dāng)前 Activity 有關(guān)聯(lián)的Fragment的 FragmentManager裸扶,使用supportFragmentManager 我們可以向Activity 狀態(tài)中添加一個(gè)Fragment 。
上面代碼中的 R.id.item_detail_container 對(duì)應(yīng)的布局是一個(gè) NestedScrollView 搬素,代碼如下
<android.support.v4.widget.NestedScrollView
android:id="@+id/item_detail_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
UI 界面設(shè)計(jì)效果如下圖所示
最后需要注意的是呵晨,如果當(dāng)前 Activity 在前面已經(jīng)保存了 Fragment 狀態(tài)數(shù)據(jù),那么 savedInstanceState 的值就是非空的熬尺, 這個(gè)時(shí)候我們就不需要手動(dòng)再去手工創(chuàng)建 Fragment 對(duì)象保存到當(dāng)前的 Activity 中了摸屠。因?yàn)楫?dāng)我們的 Activty 被異常銷(xiāo)毀時(shí),Activity會(huì)對(duì)自身狀態(tài)進(jìn)行保存(這里面包含了我們添加的Fragment)粱哼。而在Activity被重新創(chuàng)建時(shí)季二,又會(huì)對(duì)我們之前保存的 Fragment 進(jìn)行恢復(fù)。
所以揭措,添加 Fragment 前千萬(wàn)要記得去檢查是否有保存的Activity狀態(tài)胯舷。如果沒(méi)有狀態(tài)保存,說(shuō)明Acitvity是第1次被創(chuàng)建绊含,我們添加Fragment桑嘶。如果有狀態(tài)保存,說(shuō)明 Activity 剛剛出現(xiàn)過(guò)異常被銷(xiāo)毀過(guò)躬充,之前的 Fragment 會(huì)被恢復(fù)不翩,我們不再添加 Fragment。
FragmentTransaction
上面的代碼中麻裳,我們使用了 FragmentTransaction 的 add 方法口蝠,該方法簽名如下
public abstract FragmentTransaction add(@IdRes int containerViewId, Fragment fragment);
其中參數(shù) containerViewId 傳入 Activity 中某個(gè)視圖容器的 id。如果containerViewId 傳 0津坑,則這個(gè)Fragment不會(huì)被放置在一個(gè)容器中妙蔗。請(qǐng)注意,不要認(rèn)為 Fragment 沒(méi)添加進(jìn)來(lái)疆瑰,其實(shí)我們只是添加了一個(gè)沒(méi)有視圖的Fragment 而已眉反,這個(gè)Fragment可以用來(lái)做一些類(lèi)似于Service的后臺(tái)工作。
FragmentTransaction 常用的 API 如下表
API 方法 | 說(shuō)明 |
---|---|
add(int containerViewId, Fragment fragment, String tag) | 向Activity state中添加一個(gè)Fragment穆役。參數(shù)containerViewId一般會(huì)傳Activity中某個(gè)視圖容器的id寸五。如果containerViewId傳0,則這個(gè)Fragment不會(huì)被放置在一個(gè)容器中耿币。添加Fragment前檢查是否有保存的Activity狀態(tài)梳杏。 |
remove(Fragment fragment) | 移除一個(gè)已經(jīng)存在的Fragment。Fragment被remove后,F(xiàn)ragment的生命周期會(huì)一直執(zhí)行完onDetach十性,之后Fragment的實(shí)例也會(huì)從FragmentManager中移除叛溢。 |
replace(int containerViewId, Fragment fragment) | 替換一個(gè)已被添加進(jìn)視圖容器的Fragment。之前添加的Fragment 會(huì)在 replace 時(shí)被視圖容器移除劲适。 |
addToBackStack(String name) | 記錄已提交的事務(wù)(transation)楷掉,可用于回退操作。參數(shù) name是這次回退操作的一個(gè)名稱(chēng)(或標(biāo)識(shí))霞势,不需要可以傳null烹植。 |
show(Fragment fragment) | 隱藏一個(gè)存在的Fragment。 |
hide(Fragment fragment) | 顯示一個(gè)以前被隱藏過(guò)的Fragment愕贡。Fragment被hide/show刊橘,僅僅是隱藏/顯示Fragment的視圖,不會(huì)有任何生命周期方法的調(diào)用颂鸿。在Fragment中重寫(xiě)onHiddenChanged方法可以對(duì)Fragment的hide和show狀態(tài)進(jìn)行監(jiān)聽(tīng)。 |
attach(Fragment fragment) | 重新關(guān)聯(lián)一個(gè)Fragment(當(dāng)這個(gè)Fragment的detach執(zhí)行之后)攒庵。當(dāng)Fragment被detach后嘴纺,執(zhí)行attach操作,會(huì)讓Fragment從onCreateView開(kāi)始執(zhí)行浓冒,一直執(zhí)行到onResume栽渴。attach無(wú)法像add一樣單獨(dú)使用,單獨(dú)使用會(huì)拋異常稳懒。方法存在的意義是對(duì)detach后的Fragment進(jìn)行界面恢復(fù)闲擦。 |
detach(Fragment fragment) | 分離指定Fragment的UI視圖。當(dāng)Fragment被detach后场梆,F(xiàn)ragment的生命周期執(zhí)行完onDestroyView就終止了墅冷,這意味著Fragment的實(shí)例并沒(méi)有被銷(xiāo)毀,只是UI界面被移除了(注意和remove是有區(qū)別的)或油。 |
setCustomAnimations(int enter, int exit) | 為Fragment的進(jìn)入/退出設(shè)置指定的動(dòng)畫(huà)資源寞忿。 |
commit() | 提交事務(wù)。安排一個(gè)針對(duì)該事務(wù)的提交顶岸。提交并沒(méi)有立刻發(fā)生腔彰,會(huì)安排到在主線程下次準(zhǔn)備好的時(shí)候來(lái)執(zhí)行。 |
commitNow() | 同步的提交這個(gè)事務(wù)辖佣。任何被添加的Fragment都將會(huì)被初始化霹抛,并將他們完全帶入他們的生命周期狀態(tài)。 使用commitNow()時(shí)不能進(jìn)行添加回退棧的操作卷谈,如果使用 addToBackStack(String)將會(huì)拋出一個(gè) IllegalStateException的異常杯拐。 |
下面我們來(lái)介紹 ItemDetailFragment 。
ItemDetailFragment
這個(gè) ItemDetailFragment 表示單個(gè) Item 詳細(xì)信息。此片段在雙窗格模式 (在平板電腦上) 包含在 ItemListActivity 中藕施,在手機(jī)上則是包含在ItemDetailActivity中寇损。其 Kotlin 代碼如下
package com.easy.kotlin
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.easy.kotlin.dummy.DummyContent
import kotlinx.android.synthetic.main.activity_item_detail.*
import kotlinx.android.synthetic.main.item_detail.view.*
class ItemDetailFragment : Fragment() {
/**
* 測(cè)試數(shù)據(jù) dummy content this fragment is presenting.
*/
private var mItem: DummyContent.DummyItem? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (arguments.containsKey(ARG_ITEM_ID)) {
// 加載數(shù)據(jù)
mItem = DummyContent.ITEM_MAP[arguments.getString(ARG_ITEM_ID)]
mItem?.let {
// 給 toolbar_layout 布局設(shè)置標(biāo)題
activity.toolbar_layout?.title = it.content
}
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val rootView = inflater.inflate(R.layout.item_detail, container, false)
// 在 TextView 中顯示測(cè)試數(shù)據(jù)文本
mItem?.let {
rootView.item_detail.text = it.details
}
return rootView
}
companion object {
/**
* The fragment argument representing the item ID that this fragment
* represents.
*/
const val ARG_ITEM_ID = "item_id"
}
}
在 onCreate 中,activity.toolbar_layout?.title = it.content 這行代碼是給詳情頁(yè)ToolBar 的大標(biāo)題賦值
<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/app_bar_height"
android:fitsSystemWindows="true"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:toolbarId="@+id/toolbar">
<android.support.v7.widget.Toolbar
android:id="@+id/detail_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
對(duì)應(yīng)的 UI 如下圖
在 onCreateView中裳食, rootView.item_detail.text = it.details 該行代碼對(duì)應(yīng)的布局是單個(gè) Item 的詳情展示 TextView 視圖矛市,其布局 XML 代碼 item_detail.xml 如下
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/item_detail"
style="?android:attr/textAppearanceLarge"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
android:textIsSelectable="true"
tools:context="com.easy.kotlin.ItemDetailFragment" />
UI 效果圖是
Fragment 生命周期
Fragment 必須嵌入在 Activity 中才能生存,其生命周期也直接受宿主 Activity 的生命周期的影響诲祸。比如浊吏,若宿主 Activity 處于 pause 狀態(tài),它所管轄的 Fragment 也將進(jìn)入 pause 狀態(tài)救氯。而當(dāng) Activity 處于 resume 狀態(tài)的時(shí)候找田,您可以獨(dú)立地控制每一個(gè) Fragment,如添加或刪除等着憨。為了創(chuàng)建Fragment饭宾,需要繼承一個(gè) Fragment 類(lèi),并實(shí)現(xiàn) Fragment 的生命周期回調(diào)方法货抄,如 onCreate(), onStart(), onPause(), onStop() 等沾瓦。事實(shí)上,若需要在一個(gè)應(yīng)用中加入 Fragment准谚,只需要將原來(lái)的 Activity 替換為 Fragment挫剑,并將 Activity 的生命周期回調(diào)方法簡(jiǎn)單地改為 Fragment 的生命周期回調(diào)方法即可。Fragment 的生命周期如下所示:
另外柱衔, Fragment 與 Activity 的生命周期的對(duì)比圖如下
1.當(dāng)一個(gè)fragment被創(chuàng)建的時(shí)候樊破,它會(huì)經(jīng)歷以下?tīng)顟B(tài).
onAttach()
onCreate()
onCreateView()
onActivityCreated()
2.當(dāng)這個(gè)fragment對(duì)用戶可見(jiàn)的時(shí)候,它會(huì)經(jīng)歷以下?tīng)顟B(tài)
onStart()
onResume()
3.當(dāng)這個(gè)fragment進(jìn)入“后臺(tái)模式”的時(shí)候唆铐,它會(huì)經(jīng)歷以下?tīng)顟B(tài)
onPause()
onStop()
4.當(dāng)這個(gè)Fragment被銷(xiāo)毀了(或者持有它的activity被銷(xiāo)毀了)哲戚,它會(huì)經(jīng)歷以下?tīng)顟B(tài)
onPause()
onStop()
onDestroyView()
onDetach()
5.就像 Activity 一樣,在以下的狀態(tài)中艾岂,可以使用Bundle對(duì)象保存一個(gè)Fragment的對(duì)象
onCreate()
onCreateView()
onActivityCreated()
6.Fragments的大部分狀態(tài)都和 Activity 很相似惫恼,但 Fragment 有一些新的狀態(tài)
onAttached() —— 當(dāng)fragment和activity關(guān)聯(lián)之后,調(diào)用這個(gè)方法澳盐。
onCreateView() —— 創(chuàng)建fragment中的視圖的時(shí)候祈纯,調(diào)用這個(gè)方法。
onActivityCreated() —— 當(dāng)activity的onCreate()方法被返回之后叼耙,調(diào)用這個(gè)方法腕窥。
onDestroyView() —— 當(dāng)fragment中的視圖被移除的時(shí)候,調(diào)用這個(gè)方法筛婉。
onDetach() —— 當(dāng)fragment和activity分離的時(shí)候簇爆,調(diào)用這個(gè)方法癞松。
一般來(lái)說(shuō),在 Fragment 中應(yīng)至少重寫(xiě)下面3個(gè)生命周期方法:
- onCreate()
當(dāng)創(chuàng)建 Fragment 實(shí)例時(shí)入蛆,系統(tǒng)回調(diào)的方法响蓉。在該方法中,需要對(duì)一些必要的組件進(jìn)行初始化哨毁,以保證這個(gè)組件的實(shí)例在 Fragment 處于 pause或stop 狀態(tài)時(shí)仍然存在枫甲。
- onCreateView()
當(dāng)?shù)谝淮卧?Fragment 上繪制UI時(shí),系統(tǒng)回調(diào)的方法扼褪。該方法返回一個(gè) View 對(duì)象想幻,該對(duì)象表示 Fragment 的根視圖;若 Fragment 不需要展示視圖话浇,則該方法可以返回 null脏毯。
- onPause()
當(dāng)用戶離開(kāi) Fragment 時(shí)回調(diào)的方法(并不意味著該 Fragment 被銷(xiāo)毀)。在該方法中幔崖,可以對(duì) Fragment 的數(shù)據(jù)信息做一些持久化的保存工作食店,因?yàn)橛脩艨赡懿辉俜祷剡@個(gè) Fragment。
大多數(shù)情況下赏寇,需要重寫(xiě)上述三個(gè)方法吉嫩,有時(shí)還需要重寫(xiě)其他生命周期方法。
當(dāng)執(zhí)行一個(gè) Fragment 事務(wù)時(shí)蹋订,也可以將該 Fragment 加入到一個(gè)由宿主 Activity 管轄的后退棧中,并由 Activity 記錄加入到后退棧的 Fragment 信息刻伊,按下后退鍵可以將 Fragment 從后退棧中一次彈出露戒。
將 Fragment 添加至 Activity 的視圖布局中有兩種方式:一種是使用fragment標(biāo)簽加入,F(xiàn)ragment的父視圖應(yīng)是一個(gè)ViewGroup捶箱;另一種使用代碼動(dòng)態(tài)加入智什,并將一個(gè)ViewGroup作為Fragment的容器。在某些情況下丁屎,F(xiàn)ragment并不作為Activity視圖展示的一部分荠锭,它可能只是用來(lái)作為非顯示性的功能。
Fragment 是 Android 3.0 (API level 11) 新加入的API晨川,主要的設(shè)置目的是為了使UI在不同的屏幕上表現(xiàn)得更加靈活证九。由于平板比手機(jī)屏幕大的多,因此平板上可以呈現(xiàn)更多的內(nèi)容共虑,而 Fragment 可以實(shí)現(xiàn)同一視圖布局在不同大小的屏幕上顯示不同的效果愧怜,將 Fragment 加入到 Activity 的 Layout 中,可以在運(yùn)行時(shí)動(dòng)態(tài)替換 Fragment 并將 Fragment 保存至 Activity 管轄的“后退椔璋瑁”中拥坛。另外,同樣的界面Activity占用內(nèi)存比Fragment要多,在中低端手機(jī)上Fragment 比 Activity 都響應(yīng)速度要快很多猜惋。
為了方便起見(jiàn)丸氛,繼承下面這些特殊的Fragment可以簡(jiǎn)化其初始化過(guò)程:
DialogFragment:可展示一個(gè)懸浮的對(duì)話框。使用該類(lèi)創(chuàng)建的對(duì)話框可以很好地替換由 Activity 類(lèi)中的方法創(chuàng)建的對(duì)話框著摔,因?yàn)槟梢韵窆芾砥渌?Fragment 一樣管理 DialogFragment——它們都被壓入由宿主 Activity 管理的 Fragment 棧中缓窜,這可以很方便的找回已被壓入棧中的 Fragment。
ListFragment:可以展示一個(gè)內(nèi)置的 AdapterView梨撞,該 AdapterView 由一個(gè) Adapter 管理著雹洗,如 SimpleCursorAdapter。ListFragment 類(lèi)似于 ListActivity卧波,它提供了大量的用于管理 ListView 的方法时肿,比如回調(diào)方法 onListItemClick(),它用于處理點(diǎn)擊項(xiàng)事件港粱。
PreferenceFragment:可以展示層級(jí)嵌套的 Preference 對(duì)象列表螃成。PreferenceFragment 類(lèi)似于 PreferenceActivity,該類(lèi)一般用于為應(yīng)用程序編寫(xiě)設(shè)置頁(yè)面查坪。
Fragment 綁定 UI 布局必須重寫(xiě) onCreateView() 方法寸宏,為 Fragment 綁定布局,該方法返回的 View 就是 Fragment 的根視圖
class ItemDetailFragment : Fragment() {
private var mItem: DummyContent.DummyItem? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val rootView = inflater.inflate(R.layout.item_detail, container, false)
// Show the dummy content as text in a TextView.
mItem?.let {
rootView.item_detail.text = it.details
}
return rootView
}
}
其中偿曙,val rootView = inflater.inflate(R.layout.item_detail, container, false) 這一行代碼中的 inflater.inflate 是用于填充布局的, 這是布局填充器 LayoutInflater 類(lèi)的方法氮凝。通常我們加載布局的任務(wù)都是在 Activity 中調(diào)用 setContentView() 方法來(lái)完成的。其實(shí) setContentView() 方法的內(nèi)部也是使用LayoutInflater 來(lái)加載布局的望忆,相關(guān)的代碼在 android.support.v7.app.AppCompatDelegateImplV9 中
@Override
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);
mOriginalWindowCallback.onContentChanged();
}
在實(shí)際開(kāi)發(fā)中 LayoutInflater 這個(gè)類(lèi)還是非常有用的罩阵,它的作用類(lèi)似于findViewById()。不同點(diǎn)是LayoutInflater 是用來(lái)找 res/layout/ 下的 xml 布局文件并實(shí)例化(填充布局)启摄;而 findViewById() 是找 xml 布局文件下的具體widget控件(如Button稿壁、TextView等) 并實(shí)例化。
具體作用說(shuō)明如下:
1歉备、對(duì)于一個(gè)沒(méi)有被載入或者想要?jiǎng)討B(tài)載入的界面傅是,都需要使用LayoutInflater.inflate()來(lái)載入;
2蕾羊、對(duì)于一個(gè)已經(jīng)載入的界面喧笔,就可以使用Activiyt.findViewById()方法來(lái)獲得其中的界面元素。
注意:若繼承的 Fragment 是 ListFragment龟再,onCreateView() 方法已默認(rèn)返回了 ListView 對(duì)象溃斋,故無(wú)需再重寫(xiě)該方法。
有關(guān) Fragment 的更多信息吸申,請(qǐng)參見(jiàn)“Fragment API指南” (http://developer.android.com/guide/components/fragments.html)梗劫。
DummyContent
這個(gè)類(lèi)中構(gòu)造了我們?cè)?ListActivity 中展示的測(cè)試數(shù)據(jù)享甸。代碼如下
package com.easy.kotlin.dummy
import java.util.ArrayList
import java.util.HashMap
object DummyContent {
val ITEMS: MutableList<DummyItem> = ArrayList()
val ITEM_MAP: MutableMap<String, DummyItem> = HashMap()
private val COUNT = 25
init {
// Add some sample items.
for (i in 1..COUNT) {
addItem(createDummyItem(i))
}
}
private fun addItem(item: DummyItem) {
ITEMS.add(item)
ITEM_MAP.put(item.id, item)
}
private fun createDummyItem(position: Int): DummyItem {
return DummyItem(position.toString(), "Item " + position, makeDetails(position))
}
private fun makeDetails(position: Int): String {
val builder = StringBuilder()
builder.append("Details about Item: ").append(position)
for (i in 0..position - 1) {
builder.append("\nMore details information here.")
}
return builder.toString()
}
data class DummyItem(val id: String, val content: String, val details: String) {
override fun toString(): String = content
}
}
至此,我們已經(jīng)了解了怎樣使用 Android Studio 3.0 創(chuàng)建一個(gè)帶 ListActivity 和Fragment 的列表及其詳情頁(yè)展示梳侨,同時(shí)學(xué)習(xí)了 Activity 和 Fragment 的基本用法蛉威。下面我們來(lái)實(shí)現(xiàn)后端 API 的接入與數(shù)據(jù)的展現(xiàn)。
Kotlin 開(kāi)發(fā)者社區(qū)
國(guó)內(nèi)第一Kotlin 開(kāi)發(fā)者社區(qū)公眾號(hào)走哺,主要分享蚯嫌、交流 Kotlin 編程語(yǔ)言、Spring Boot丙躏、Android择示、React.js/Node.js、函數(shù)式編程晒旅、編程思想等相關(guān)主題栅盲。