第14章 使用Kotlin 進行 Android 開發1

第14章 使用Kotlin 進行 Android 開發

Kotlin Android

根據Realm Report (2017-Q4,https://realm.io/realm-report...
) ,過去的一年在Android 端的開發:Java 從 95% 下降到 Java 85%, 而 Kotlin 從 5% 漲到 15% ,以下圖所示html

Kotlin is about to change the whole Android ecosystem

從這個趨勢來看,加上最新 Android Studio 3.0的發佈(內置 Kotlin 開發 Android 項目的支持),Kotlin 將會很快顛覆 Java 在 Android 領域的地位。java

本章將帶領你們快速入門使用 Kotlin 進行 Android 應用程序的開發。android

14.1 快速開始 Hello World

咱們從一個簡單的 Kotlin 版本的Hello World Android 應用程序開始。程序員

14.1.1 準備工做

首先準備好開發工具。Android 開發仍是用 Google 官方支持的 IDE Android Studio 好。編程

Android Studio 3.0 簡介

Google 在 2017-10-26 發佈了 Android 8.1 Oreo 開發者預覽版的同時還正式發佈了 Android Studio 3.0 ,爲其 IDE 引入了一系列新功能。android-studio

Android Studio 3.0 專一於加速 Android 應用開發,包含大量更新內容,其中最主要的功能之一就包括對 Kotlin 的支持。正如谷歌在 Google I / O 2017.5 所宣佈的那樣,Kotlin 已被官方支持用於 Android 開發。 Android Studio 3.0是第一個支持 Kotlin 語言的里程碑式版本(在此以前,可使用Android Studio 的 Kotlin 插件的方式)。緩存

在該版本中提供了許多方便實用的功能如代碼自動補全和語法高亮顯示,另外,Android Studio 內置轉換工具能夠很是方便地把 Java 代碼轉換成 Kotlin 代碼,以下圖所示app

原始的Java 代碼

Java 轉 Kotlin 工具

轉換以後的 Kotlin 代碼

安裝 Android Studio 3.0

Android Studio 是 Android 的官方 IDE。Android Studio 3.0的一個亮點就是內置了 Kotlin 的支持(https://developer.android.goo...)。正如 Google I/O 2017 所說的那樣, Kotlin 已成爲 Android 官方開發語言。框架

使用 Android Studio 3.0, 咱們能夠方便地把Java 源代碼自動轉換成 Kotlin 代碼,也能夠直接建立 Kotlin 語言開發的 Android 項目, 只須要在新建項目的時候勾選 Include Kotlin support 便可。ide

首先去官網下載安裝:https://developer.android.goo... 。筆者當前下載的安裝包版本是 android-studio-ide-171.4408382-mac.dmg ,下載完畢點擊 dmg 文件

安裝 android studio ide

拷貝至應用程序便可。

14.1.2 建立基於 Kotlin 的Android 項目

首先新建項目。若是您未打開項目,請在 Welcome to Android Studio 窗口中點擊 Start a new Android Studio project

Welcome to Android Studio 窗口

若是您已打開項目,請依次點擊 File > New > New Project ,以下圖所示

新建項目

進入 Create Android Project 對話框。在建立 Android 項目對話框中配置應用基本信息,注意勾選 Kotlin 支持選項,點擊 Next。以下圖所示

建立基於 Kotlin 的Android 項目

進入 Target Android Devices 配置應用運行 SDK 以及環境信息

Target Android Devices

咱們勾選 Phone and Tablet 選項,API 15:Android 4.0.3 ,點擊 Next 進入添加 Activity 界面

添加 Activity 界面

咱們選擇 Empty Activity,點擊 Next,進入配置 Activity 界面

配置 Activity 界面

配置好 Activity Name與 Layout Name 以後,點擊 Finish。咱們將獲得一個 Kotlin 版本的Hello World Android 應用程序。工程目錄以下

工程目錄

14.1.3 工程目錄文件說明

其中,在頂層的 Gradle 配置文件 build.gradle 中添加了 kotlin-gradle-plugin 插件的依賴

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 配置文件內容以下

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插件。這個插件是 Kotlin 專門針對 Android 擴展的插件,實現了與 Data-Binding、 Dagger等框架的功能。

佈局文件activity_main.xml內容以下

<?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 文件內容以下

<?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 安裝運行

點擊功能菜單欄中的運行按鈕

運行

會提示咱們選擇應用程序部署運行的目標設備

螢幕快照 2017-10-28 23.58.37.png

須要注意的是,手機要打開鏈接 USB 調試模式。點擊 OK,Android Studio 會爲咱們完成打包、安裝等事項。最終的運行效果以下

HelloWord.png

14.2 綜合項目實戰:開發一個電影指南應用程序

本節咱們將開發一個Android 應用程序, 列出流行/最高評級的電影, 顯示預告片和評論。

14.2.1 建立 Kotlin Android 項目

螢幕快照 2017-10-29 20.07.55.png

螢幕快照 2017-10-29 20.08.09.png

螢幕快照 2017-10-29 20.09.04.png

螢幕快照 2017-10-29 20.10.21.png

螢幕快照 2017-10-29 20.12.46.png

運行效果

MovieList.png

DetailActivity.png

14.2.2 工程說明

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 處的配置指定了應用程序的啓動Activity 爲 .ItemListActivity , 其中的點號 「.」 表示該類位於 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>

下面咱們來介紹應用程序的啓動主類 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>

對應的 UI 設計效果圖以下

 XML 代碼 activity_item_list.xml  設計效果圖

AppCompatActivity

在使用Android Studio開發Android應用的時候,建立項目時,自動繼承的是AppCompatActivity。這樣咱們能夠在自定義的 Activity 類中添加 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 也更新了,出現了AppCompatActivity。AppCompatActivity 是用來替代ActionBarActivity的 。 AppCompatActivity 的類圖繼承層次以下

AppCompatActivity 的類圖繼承層次

Activity 生命週期

Activity 生命週期以下圖所示(圖來自官網)

Activity 生命週期

相信很多朋友也已經看過這個流程圖了,關於Activity生命週期的幾個過程,咱們簡單說明以下

1.開始啓動Activity,系統會先調用onCreate方法,而後調用onStart方法,最後調用onResume,Activity進入運行狀態。

2.當前Activity被其餘Activity覆蓋其上或被鎖屏:系統會調用onPause方法,暫停當前Activity的執行。

3.當前Activity由被覆蓋狀態回到前臺或解鎖屏:系統會調用onResume方法,再次進入運行狀態。

4.當前Activity轉到新的Activity界面或按Home鍵回到主屏:系統會先調用onPause方法,而後調用onStop方法,進入中止狀態。

5.用戶後退回到此Activity:系統會先調用onRestart方法,而後調用onStart方法,最後調用onResume方法,再次進入運行狀態。

6.當前Activity處於被覆蓋狀態或者後臺不可見狀態,即第2步和第4步,系統內存不足,殺死當前Activity,然後用戶退回當前Activity:再次調用onCreate方法、onStart方法、onResume方法,進入運行狀態。

7.用戶退出當前Activity:系統先調用onPause方法,而後調用onStop方法,最後調用onDestory方法,結束當前Activity。

這個過程能夠用下面的狀態圖來簡單說明

Android Activity 生命週期狀態圖

Kotlin Android Extensions 插件

在上面的ItemListActivity.onCreate 函數中,其中的這行代碼

setSupportActionBar(toolbar)

是設置支持的 ActionBar方法。可是咱們發現,這裏並無使用 findViewById()方法來獲取這個 android:id="@+id/toolbar" Toolbar 的 View 對象,以前咱們可能都是這樣寫的

Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar);
setSupportActionBar(toolbar);

而這裏直接就使用了 toolbar 這個 Toolbar 的對象變量。這是怎麼作到的呢?其實這是經過 Kotlin Android Extensions 插件作到的。咱們在app 目錄下的 Gradle 配置文件 build.gradle 中添加了這個配置

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

有了這個插件咱們就能夠永遠跟 findViewById 說再見了。Kotlin Android Extensions 插件是 Kotlin 針對 Android 開發專門定製的通用插件, 經過它咱們可以以極簡的無縫方式實現從 Activity, Fragment 和 View 佈局組件中建立和獲取視圖 View 。使用 Kotlin 開發 Android 大大減小了咱們的樣板代碼。

就像上面的示例代碼同樣,咱們只要在代碼中直接使用這個佈局組件的 id 名稱做爲變量名便可,剩下的Kotlin 插件會幫咱們所有搞定。Kotlin Android Extensions 插件將會爲咱們生成一些額外的代碼,使得咱們能夠在佈局XML中直接經過 id 獲取到其 View 對象。另外,它還生成一個本地視圖緩存,當第一次使用屬性時,它將執行一個常規的findViewById。但在下一次使用屬性的時候,視圖將從緩存中恢復,所以訪問速度將更快。

只要佈局添加一個 View,在 Activity、View、Fragment 中均可以直接用 id 來引用這個 View,Kotlin 把 Android 編程極簡風格發揮得淋漓盡致。

咱們能夠經過Kotlin 對應的字節碼來更加本質深刻地理解 Kotlin 所作的事情。Android Studio 中跟 IDEA 同樣提供了 Kotlin 的工具箱。在菜單欄中依次選擇 Code > Kotlin > Show Kotlin Bytecode , 以下圖所示

Show Kotlin Bytecode

點擊 Show Kotlin Bytecode 以後,咱們將會看到以下圖所示的 Kotlin Bytecode 界面

Kotlin Bytecode 界面

其中這兩行代碼

setSupportActionBar(toolbar)
toolbar.title = title

對應的字節碼是

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

其實從字節碼中

GETSTATIC com/easy/kotlin/R$id.toolbar : I
    INVOKEVIRTUAL com/easy/kotlin/ItemListActivity._$_findCachedViewById

咱們已經看到了 Kotlin 爲咱們所作的事情了。反編譯成 Java 代碼可能會看的更加清楚

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 類中的 HashMap 類型的私有成員變量 _$_findViewCache 就是本地緩存。這裏其實反映出 Kotlin 語言設計的核心思想:經過更高一層的對 Java 的封裝,不只大大簡化了程序員的樣板化的代碼量,同時還根據一些特定的能夠優化的問題場景,順帶提供了更好的性能。

一樣的,上面的代碼中的 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 都是使用上面的方式。這樣代碼確實是大大精簡了許多。

嵌套 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 佈局 UI

而佈局 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>

item_list_content.xml 佈局 UI

ItemDetailActivity

這個是 Item 詳情頁的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>

打開item_detail.xml ,咱們能夠看到設計圖 UI 的效果

item_detail.xml UI 設計圖

咱們能夠看到詳情頁的佈局主要有3大塊:AppBarLayout 、NestedScrollView和 FloatingActionButton 。

在 ItemDetailActivity 的onCreate 函數裏的

setContentView(R.layout.activity_item_detail)

設置詳情頁 ItemDetailActivity 的顯示界面使用 activity_item_detail.xml 佈局文件進行佈局。

setSupportActionBar(detail_toolbar)

設置詳情頁的 android.support.v7.widget.Toolbar 控件佈局。

下面咱們來看在 ItemDetailActivity 中建立 ItemDetailFragment 的過程。代碼以下

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()
    }
}
  1. 首先咱們判斷當前 savedInstanceState 是否爲空。若是爲空, 執行步驟2 。
  2. 建立 ItemDetailFragment() 對象,並設置其 Bundle 信息(Fragment 中的成員變量 mArguments )
val arguments = Bundle()
arguments.putString(ItemDetailFragment.ARG_ITEM_ID,
        intent.getStringExtra(ItemDetailFragment.ARG_ITEM_ID))
val fragment = ItemDetailFragment()
fragment.arguments = arguments
  1. 經過 supportFragmentManager 添加 Fragment 與佈局空間的映射關係。
supportFragmentManager.beginTransaction()
        .add(R.id.item_detail_container, fragment)
        .commit()

其中,supportFragmentManager 用來獲取能管理和當前 Activity 有關聯的Fragment的 FragmentManager,使用supportFragmentManager 咱們能夠向Activity 狀態中添加一個Fragment 。

上面代碼中的 R.id.item_detail_container 對應的佈局是一個 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 界面設計效果以下圖所示

 R.id.item_detail_container 對應的佈局是一個 NestedScrollView

最後須要注意的是,若是當前 Activity 在前面已經保存了 Fragment 狀態數據,那麼 savedInstanceState 的值就是非空的, 這個時候咱們就不須要手動再去手工建立 Fragment 對象保存到當前的 Activity 中了。由於當咱們的 Activty 被異常銷燬時,Activity會對自身狀態進行保存(這裏麪包含了咱們添加的Fragment)。而在Activity被從新建立時,又會對咱們以前保存的 Fragment 進行恢復。

因此,添加 Fragment 前千萬要記得去檢查是否有保存的Activity狀態。若是沒有狀態保存,說明Acitvity是第1次被建立,咱們添加Fragment。若是有狀態保存,說明 Activity 剛剛出現過異常被銷燬過,以前的 Fragment 會被恢復,咱們再也不添加 Fragment。

FragmentTransaction

上面的代碼中,咱們使用了 FragmentTransaction 的 add 方法,該方法簽名以下

public abstract FragmentTransaction add(@IdRes int containerViewId, Fragment fragment);

其中參數 containerViewId 傳入 Activity 中某個視圖容器的 id。若是containerViewId 傳 0,則這個Fragment不會被放置在一個容器中。請注意,不要認爲 Fragment 沒添加進來,其實咱們只是添加了一個沒有視圖的Fragment 而已,這個Fragment能夠用來作一些相似於Service的後臺工做。

FragmentTransaction 經常使用的 API 以下表

API 方法 說明
add(int containerViewId, Fragment fragment, String tag) 向Activity state中添加一個Fragment。參數containerViewId通常會傳Activity中某個視圖容器的id。若是containerViewId傳0,則這個Fragment不會被放置在一個容器中。添加Fragment前檢查是否有保存的Activity狀態。
remove(Fragment fragment) 移除一個已經存在的Fragment。Fragment被remove後,Fragment的生命週期會一直執行完onDetach,以後Fragment的實例也會從FragmentManager中移除。
replace(int containerViewId, Fragment fragment) 替換一個已被添加進視圖容器的Fragment。以前添加的Fragment 會在 replace 時被視圖容器移除。
addToBackStack(String name) 記錄已提交的事務(transation),可用於回退操做。參數 name是此次回退操做的一個名稱(或標識),不須要能夠傳null。
show(Fragment fragment) 隱藏一個存在的Fragment。
hide(Fragment fragment) 顯示一個之前被隱藏過的Fragment。Fragment被hide/show,僅僅是隱藏/顯示Fragment的視圖,不會有任何生命週期方法的調用。在Fragment中重寫onHiddenChanged方法能夠對Fragment的hide和show狀態進行監聽。
attach(Fragment fragment) 從新關聯一個Fragment(當這個Fragment的detach執行以後)。當Fragment被detach後,執行attach操做,會讓Fragment從onCreateView開始執行,一直執行到onResume。attach沒法像add同樣單獨使用,單獨使用會拋異常。方法存在的意義是對detach後的Fragment進行界面恢復。
detach(Fragment fragment) 分離指定Fragment的UI視圖。當Fragment被detach後,Fragment的生命週期執行完onDestroyView就終止了,這意味着Fragment的實例並無被銷燬,只是UI界面被移除了(注意和remove是有區別的)。
setCustomAnimations(int enter, int exit) 爲Fragment的進入/退出設置指定的動畫資源。
commit() 提交事務。安排一個針對該事務的提交。提交併無馬上發生,會安排到在主線程下次準備好的時候來執行。
commitNow() 同步的提交這個事務。任何被添加的Fragment都將會被初始化,並將他們徹底帶入他們的生命週期狀態。 使用commitNow()時不能進行添加回退棧的操做,若是使用 addToBackStack(String)將會拋出一個 IllegalStateException的異常。
相關文章
相關標籤/搜索