《第一行代碼》第三版之UI開發入門(五)

       本章我們將介紹UI開發的相關知識。常用控件包括了TextView、Button、EditText、ImageView、PrograssBar和AlertDialog等;三種佈局包括了LinearLayout、RelativeLayout以及FrameLayout;ListView的簡單用法、基於圖片文字的ListView、利用ConvertView和ViewHolder去提升效率以及setOnItemClickListener點擊事件響應;RecyclerView的基本用法、適配器、橫向滾動、瀑布流佈局以及View的點擊事件處理;隨後進行了實踐(基於nine-patch圖片編寫聊天界面);最後是延遲初始化lateinit和密封類sealed class。
4.1.如何編寫程序界面?
        主要通過編寫XML來實現,另外Google近些年推出了ConstrainLayout,它是通過拖拽控件來對界面進行操作。在本章我們僅介紹xml。
4.2.常用控件使用方法
4.2.1.TextView

     TextView的功能是顯示一段文本信息。

<?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="match_parent"
    android:orientation="vertical">
    <!--layout_width指明寬度,layout_height指明高度。三個值可供選擇:match_parent(當前控件大小與父佈局相同)、
    wrap_content(剛好包住)和固定值dp(與屏幕密度無關,不同分辨率下儘可能一致)-->
    <!--android:gravity指明文字對齊方式,可選值有top、bottom、top等。可以用|來同時指定多個值。譬如center等價於center_vertical|center_horizontal-->
    <!--android:textColor指明瞭文字顏色,android:textSize文字大小,文字大小以sp爲單位-->
    <TextView
        android:id="@+id/text_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:textColor="#00ff00"
        android:textSize="24sp"
        android:text="Hello World!" />
</LinearLayout>

4.2.2.Button
       與用戶進行交互的一個重要控件。先編寫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="match_parent"
    android:orientation="vertical">
    .....
   <!--Android默認將英文字母轉換成大寫,android:textAllCaps設置爲false保留指定原始文件內容-->
    <Button
        android:id="@+id/btn_click01"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textAllCaps="false"
        android:text="Button" />
</LinearLayout>

        其次,點擊事件可以採用函數式API進行事件響應,也可以通過實現接口的方式來進行註冊。

package com.example.myapplication

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity(), View.OnClickListener {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //第一種調用方法:匿名內部類,利用Java單抽象方法接口特性,使用函數式API來寫監聽事件。
//        btn_click01.setOnClickListener{
//            //在此添加處理邏輯
//        }
        //第二種調用方法:實現接口方法進行註冊。讓MainActivity實現了View.OnClickListener接口,並重寫onClick方法
        //setOnClickListener將MainActivity實例傳了進去
        btn_click01.setOnClickListener(this)
    }
    //重寫的方法,很簡單。
    override fun onClick(v: View?) {
        when (v?.id) {
            R.id.btn_click01 -> {
                //在此添加邏輯
            }
        }
    }
}

4.2.3.EditText
      用戶在該控件中輸入和編輯文本。應用場景譬如發微博、聊QQ等。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="match_parent"
     .....
    <!--android:hint指定提示性的文本-->
    <!--android:maxLines指明最大行數爲2行,若超過,文本向上滾動,EditText不會再拉伸。-->
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/edit_text01"
        android:maxLines="2"
        android:hint="type sth please"/>
</LinearLayout>

   點擊按鈕獲取EditText裏面的內容,代碼如下:

    //重寫的方法,很簡單。
    override fun onClick(v: View?) {
        when (v?.id) {
            R.id.btn_click01 -> {
                //點擊按鈕完成顯式EditText文本內容的功能
                //edit_text01.text通過語法糖調用了getText方法,編寫代碼直接調用實際方法即可,getText可以自動轉換爲text。
                val inputText = edit_text01.text.toString()
                Toast.makeText(this, inputText, Toast.LENGTH_SHORT).show()
            }
        }
}

4.2.4.ImageView
       ImageView用於在界面顯示圖片,圖片通常放在Drawable開頭的目錄,並且要附上具體分辨率。目前主流屏幕分辨率是xxhdpi的,所以在res目錄再建一個drawable-xxhdpi目錄,將事先準備好的照片複製到該目錄。Xml如下:

  <!--android:src爲ImageView指定了一張圖片,由於寬高未知,因此使用兩個wrap_content使得其能顯示出來-->
    <ImageView
        android:id="@+id/image_view01"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/image_1" />

    相應的響應事件如下:

 //通過代碼動態更改ImageView圖片
   R.id.btn_click02 ->{
          image_view01.setImageResource(R.drawable.image_2)
   }

4.2.5.PrograssBar
      PrograssBar是顯示一個進度條,表示正在加載數據。Xml代碼如下:

   <!-- 默認是圓形樣式,通過style屬性將其變爲水平進度條,修改其中的代碼-->
    <!--android:max給進度條設置一個最大值,在代碼中動態的更改進度條進度。-->
    <ProgressBar
        android:id="@+id/progress_bar01"
        style="?android:attr/progressBarStyleHorizontal"
        android:max="100"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

      Kotlin代碼中有相應的響應事件和進度條遞增代碼。

            //android:visibility有三個可選值:visiable(控件可見,默認)、invisible(空間不可見,仍佔據原來未知)
            // 和gone(控件不僅不可見,也不再佔用之前的屏幕),在使用setVisibility使用View.可選值
            R.id.btn_click03 -> {
                //使用getVisibility判斷prograssBar是否可見,如果可見隱藏,否則顯示
                if (progress_bar01.visibility == View.VISIBLE) {
                    //調用了setVisibility
                    progress_bar01.visibility = View.GONE
                } else {
                    progress_bar01.visibility = View.VISIBLE
                }
            }
            //進度條增加
            R.id.btn_click04 -> {
                progress_bar01.progress += 10
            }

4.2.6.AlertDialog
       AlertDialog在當前界面顯示一個置頂於所有界面之上的對話框,屏蔽掉其他控件的交互,在這裏譬如實現防止用戶誤刪除的AlertDialog。

     R.id.btn_click05 -> {
                //構建一個對話框並使用apply函數
                AlertDialog.Builder(this).apply {
                    //消息標題
                    setTitle("This is Dialog")
                    //消息內容
                    setMessage("Sth important")
                    //不可取消
                    setCancelable(false)
                    //確定按鈕點擊事件
                    setPositiveButton("OK") { dialog, which -> "刪除吧" }
                    //取消按鈕點擊事件
                    setNegativeButton("Cancel") { dialog, which -> "保留吧" }
                    //對話框顯示出來
                    show()
                }
            }

4.3.詳解三種基本佈局
        佈局是放置很多控件的容器,佈局內部可以放置佈局或者控件,也可以通過多層佈局嵌套完成複雜界面。
4.3.1.LinearLayout
         線性佈局,控件在線性方向上水平或者豎直排列,在這裏使用android:orientation屬性來指定。這裏着重注意android:gravity、android:layout_gravity和android:layout_weight三個屬性。

<?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="match_parent"
    android:orientation="vertical">
    <!--vertical豎直方向上佈局,horiziontal水平佈局,默認是水平佈局。wrap_content剛好包含文字。-->
    <!--介紹一下android:gravity和android:layout_gravity,前者是文字在控件中的對齊方式,後者是指定控件在佈局中的對齊方式-->
    <!--兩者可選值差不多,但horizontal時,只有垂直方向上的對齊方式可以改變,換之亦然。每添加一個控件,水平上的長度會改變。-->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="9"
        android:orientation="horizontal">

        <Button
            android:id="@+id/btn_01"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="top"
            android:text="Button 01" />

        <Button
            android:id="@+id/btn_02"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:text="Button 03" />

        <Button
            android:id="@+id/btn_03"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom"
            android:text="Button 03" />
    </LinearLayout>

    <!--可能有人說爲什麼layout_width是0dp呢?這是因爲使用android:layout_weight,0dp是比較規範的寫法-->
    <!--android:layout_weight先將layout_weight所有值相加,再按照比例進行劃分。-->
    <!--如果將後者的layout_width設置爲wrap_content。前者layout_weight等於1,那麼後面剛好包着,前面是屏幕剩餘的所有空間-->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:layout_gravity="bottom"
        android:orientation="horizontal">

        <EditText
            android:id="@+id/edit_text01"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="4"
            android:hint="Type sth please" />

        <Button
            android:id="@+id/btn_send"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Send" />
    </LinearLayout>
</LinearLayout>

4.3.2.RelativeLayout
       RelativeLayout相對定位的方式可以讓控件出現在佈局中的任何位置。控件不僅可以相對於父佈局進行定位,也可以相對於其他控件進行定位。代碼如下所示:

<?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="match_parent">
    <!--Button 1與父佈局左上角對齊,layout_alignParentLeft和layout_alignParentTop指定-->
    <Button
        android:id="@+id/btn_click_01"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:text="Button 1" />
    <!--Button 2與父佈局右上角對齊-->
    <Button
        android:id="@+id/btn_click_02"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_alignParentRight="true"
        android:text="Button 2" />

    <!--Button 3居中顯示-->
    <Button
        android:id="@+id/btn_click_03"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="Button 3" />
    <!--相對於控件進行定位,控件左上,需要注意的是:若一個控件引用另一個控件的id,那麼該控件一定要定義在引用控件的後面-->
    <Button
        android:id="@+id/btn_click_06"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@id/btn_click_03"
        android:layout_toLeftOf="@id/btn_click_03"
        android:text="Button 6" />
    <!--相對於控件進行定位,控件右上-->
    <Button
        android:id="@+id/btn_click_07"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@id/btn_click_03"
        android:layout_toRightOf="@id/btn_click_03"
        android:text="Button 7" />

    <!--相對於控件進行定位,控件左下-->
    <Button
        android:id="@+id/btn_click_08"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/btn_click_03"
        android:layout_toLeftOf="@id/btn_click_03"
        android:text="Button 8" />

    <!--相對於控件進行定位,控件右下-->
    <Button
        android:id="@+id/btn_click_09"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/btn_click_03"
        android:layout_toRightOf="@id/btn_click_03"
        android:text="Button 9" />
    <!--Button 4與父佈局左下角對齊-->
    <Button
        android:id="@+id/btn_click_04"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentBottom="true"
        android:text="Button 4" />
    <!--Button 5與父佈局右下角對齊-->
    <Button
        android:id="@+id/btn_click_05"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_alignParentBottom="true"
        android:text="Button 5" />
</RelativeLayout>

4.3.3.FrameLayout
       幀佈局,所有空間都會默認擺放在佈局左上角,可能存在控件重疊的情況。除了默認效果之外,可以使用layout_gravity屬性來指定控件在佈局中的對齊方式。總體來講,應用場景很少。

<?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">

    <TextView
        android:id="@+id/text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="left"
        android:text="This is TextView" />

    <Button
        android:id="@+id/btn_001"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="right"
        android:text="Button" />
</FrameLayout>

4.4.創建自定義控件
        所有控件直接或者間接繼承自View的,所有佈局直接或者間接繼承自ViewGroup的。ViewGroup是特殊的View,包含很多子View和ViewGroup是用於放置空間和佈局的容器。
4.4.1.引入佈局
        自定義一個標題欄並讓所有Activity引用,防止代碼大量重複。Layout目錄下建立標題欄title.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="60dp"
    android:background="@drawable/title_bg">
    <!--android:background表示用圖片或者顏色填充背景-->
    <!--android:layout_margin用於指定控件在上下左右方向上的舉例,當然也可以通過android:layout_marginLeft等指定-->
    <Button
        android:id="@+id/titleBack"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="left"
        android:layout_margin="5dp"
        android:background="@drawable/back_bg"
        android:text="Back"
        android:textColor="#fff" />

    <TextView
        android:id="@+id/title_text"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:gravity="center"
        android:text="Title text"
        android:textColor="#fff"
        android:textSize="24sp" />

    <Button
        android:id="@+id/titleEdit"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="right"
        android:layout_margin="5dp"
        android:background="@drawable/edit_bg"
        android:text="Edit"
        android:textColor="#fff" />

</LinearLayout>

       那麼如何在程序中引入,修改Activity_main.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="match_parent">
    
    <include layout="@layout/title" />

</LinearLayout>

       另外,需要在MainActivity中隱藏系統自帶標題欄:

package com.example.myapplication

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //通過語法糖調用getSupportActionBar來獲得ActionBar的實例,然後調用hide方法將標題隱藏
        //由於ActionBar可能爲空,因此使用了?.操作符,目的是隱藏原始標題欄
        supportActionBar?.hide()
    }
}

4.4.2.創建自定義控件
      有些佈局中需要有一些控件有響應事件的能力,但在每個Activity中單獨編寫無疑有很多冗餘的代碼,新建TitleLayout繼承自LinearLayout,讓它成爲我們自定義的標題欄控件,代碼如下:

package com.example.myapplication

import android.app.Activity
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.LinearLayout
import android.widget.Toast
import kotlinx.android.synthetic.main.title.view.*

//繼承自LinearLayout使其成爲我們的自定義控件。聲明兩個參數,在init結構體中對標題欄佈局進行動態加載
class TitleLayout(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
    init {
        //LayoutInflater的from方法可以構建出一個LayoutInflater對象,然後inflate方法動態加載一個佈局文件
        //inflate加載兩個參數,一個是佈局文件ID,一個是給加載好的佈局再添加一個父佈局,在這裏指定爲TitleLayout,直接傳入this。
        LayoutInflater.from(context).inflate(R.layout.title, this)
        titleBack.setOnClickListener {
            //context參數實際上是Activity實例,首先將其抓換爲Activity類型,然後再行銷燬。
//            Kotlin的強制類型轉換用as
            val activity = context as Activity
            activity.finish()
        }
        titleEdit.setOnClickListener {
            Toast.makeText(context, "You clicked the edit", Toast.LENGTH_SHORT).show()
        }
    }
}

        在Activity_main.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="match_parent">

    <!--    <include layout="@layout/title" />-->
    <com.example.myapplication.TitleLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

4.5.ListView
4.5.1.ListView的簡單用法 

       新建activity_main.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="match_parent">

    <ListView
        android:id = "@+id/listView_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

       修改MainActivity、構建適配器,並將其傳入ListView中去。這樣可以通過滾動來看屏幕外數據。

package com.example.myapplication

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.ArrayAdapter
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {
    //提供待展示數據,使用listOf初始化集合
    private val data = listOf(
        "Apple", "Banana", "Orange", "WaterMelon", "Pear", "Grape", "Cherry","Mango", "Apple", "Banana", "Orange", "WaterMelon", "Pear", "Grape", "Cherry", "Mango"
    )

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //集合數據無法直接傳遞,需要藉助適配器。比較好用的是ArrayAdapter,泛型指定爲String
        //在構造方法中分別傳入Activity實例、ListView子項佈局的id以及數據源
        //simple_list_item_1爲子項佈局id,Android內置佈局文件,裏面只有TextView用於顯示一段文本
        val adapter = ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, data)
        //最後利用setAdapter方法將構建好的適配器傳遞進去,這樣ListView和數據之間的關聯算是搞定了
        listView_main.adapter = adapter
    }
}

4.5.2.定製ListView的界面
       ListView的每個子項中包含圖片和文字,即自定義。新建實體類Fruit:

package com.example.myapplication
//Fruit類有兩個字段,一個是水果名,一個是水果對應圖片資源的ID
class Fruit(val name: String, val imageid: Int)

       在layout目錄下新建fruit_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="60dp">
<!--讓兩個玩意在垂直方向上居中顯示-->
    <ImageView
        android:id="@+id/fruit_image"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="10dp" />

    <TextView
        android:id="@+id/fruit_Name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="10dp" />
</LinearLayout>

      新建自定義適配器FruitAdapter,繼承自ArrayAdapter,泛型指定爲Fruit類。

package com.example.myapplication

import android.app.Activity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.ImageView
import android.widget.TextView
//定義一個主構造函數來將Activity實例、ListView子項佈局id和數據源傳遞進來
class FruitAdapter(activity: Activity, val resourceId: Int, data: List<Fruit>) :
    ArrayAdapter<Fruit>(activity, resourceId, data) {
    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
        //首先使用LayoutInflater子項加載我們的佈局,三個參數。
        // 最後一個false的意思是父佈局聲明的layout生效,不會爲該View增加父佈局。保準寫法
        val view = LayoutInflater.from(context).inflate(resourceId, parent, false)
        //獲取到ImageView和TextView的實例
        val fruitImage: ImageView = view.findViewById(R.id.fruit_image)
        val fruitName: TextView = view.findViewById(R.id.fruit_Name)
        //得到當前項的Fruit實例
        val fruit = getItem(position)
        //設置圖片和文字
        if (fruit != null) {
            fruitImage.setImageResource(fruit.imageid)
            fruitName.text = fruit.name
        }
        //將佈局返回
        return view
    }
}

       最後修改MainActivity裏的代碼。

package com.example.myapplication

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.ArrayAdapter
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {
    private val fruit_list = ArrayList<Fruit>()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initFruits()//初始化水果數據
        //創建Adapter對象,並將其作爲適配器傳遞給ListView
        val adapter = FruitAdapter(this, R.layout.fruit_item, fruit_list)
        listView_main.adapter = adapter
    }

    fun initFruits() {
        //repeat函數將水果數據重複兩遍
        repeat(2) {
            //構造方法中將水果名字和水果id傳入,將創建好的對象添加到水果列表中。
            fruit_list.add(Fruit("Apple", R.drawable.apple_pic))
            fruit_list.add(Fruit("Banana", R.drawable.banana_pic))
            fruit_list.add(Fruit("Orange", R.drawable.orange_pic))
            fruit_list.add(Fruit("WaterMelon", R.drawable.watermelon_pic))
            fruit_list.add(Fruit("Pear", R.drawable.pear_pic))
            fruit_list.add(Fruit("PineApple", R.drawable.pineapple_pic))
            fruit_list.add(Fruit("StrawBerry", R.drawable.strawberry_pic))
            fruit_list.add(Fruit("Cherry", R.drawable.cherry_pic))
        }
    }
}

4.5.3.提升ListView的效率
        ListView快速滑動時,性能成爲一個瓶頸,在這裏我們藉助了兩種方案:getView的convertView參數和ViewHolder進行性能優化。
       convertView用於將之前已經加載好的佈局進行緩存,以便以後重用;如果convertView爲空,使用LayoutInflater直接加載,如果不爲空,直接對convertView進行重用。
       優化convertView後,雖然已經不會加載重複佈局,但仍然在調用getView方法時調用view的findviewByID獲取控件實例,因此藉助ViewHolder來進行優化。優化後的代碼如下所示:

package com.example.myapplication

import android.app.Activity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.ImageView
import android.widget.TextView

//定義一個主構造函數來將Activity實例、ListView子項佈局id和數據源傳遞進來
class FruitAdapter(activity: Activity, val resourceId: Int, data: List<Fruit>) :
    ArrayAdapter<Fruit>(activity, resourceId, data) {
    //innerclass來定義內部類
    inner class ViewHolder(val fruitImage: ImageView, val fruitName: TextView)

    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
        //首先使用LayoutInflater子項加載我們的佈局,三個參數。
        // 最後一個false的意思是父佈局聲明的layout生效,不會爲該View增加父佈局。保準寫法
        val view: View
        val viewHolder: ViewHolder
        if (convertView == null) {
            view = LayoutInflater.from(context).inflate(resourceId, parent, false)
            //用於對ImageView和TextView的控件實例進行緩存
            val fruitImage: ImageView = view.findViewById(R.id.fruit_image)
            val fruitName: TextView = view.findViewById(R.id.fruit_Name)
            //創建ViewHolder對象並將控件實例放在ViewHolder裏
            viewHolder = ViewHolder(fruitImage, fruitName)
            //使用View的setTag方法將ViewHolder存儲在View中。
            view.tag = viewHolder
        } else {
            view = convertView
            //緩存對象不爲空時,使用View的getTag方法將ViewHolder重新取出,這樣所有控件的實例都存儲於ViewHolder中了
            viewHolder = view.tag as ViewHolder
        }

        //得到當前項的Fruit實例
        val fruit = getItem(position)
        //設置圖片和文字
        if (fruit != null) {
            viewHolder.fruitImage.setImageResource(fruit.imageid)
            viewHolder.fruitName.text = fruit.name
        }
        //將佈局返回
        return view
    }
}

4.5.4.ListView的點擊事件
      使用setOnItemClickListener爲ListView註冊一個監聽器,當點擊子項時,調用Lambda表達式,通過position確定哪一個子項。代碼示例如下:

//        listView_main.setOnItemClickListener { parent, view, position, id ->
//            val fruit = fruit_list[position]
//            Toast.makeText(this, fruit.name, Toast.LENGTH_SHORT).show()
//        }
        //沒用到的參數可以用_來代替。因爲是Java單抽象方法接口,所以可用函數式API的寫法。onItemClick中接收四個參數。
        listView_main.setOnItemClickListener { _, _, position, _ ->
            val fruit = fruit_list[position]
            Toast.makeText(this, fruit.name, Toast.LENGTH_SHORT).show()
        }

4.6.RecyclerView
       ListView的運行效率和擴展性(只能縱向滾動)仍然有待提高,因此我們介紹一下RecyclerView。RecycleView屬於新增空間,需要在app/build.gradle中添加該庫依賴。

dependencies {
    ...
    compile 'androidx.recyclerview:recyclerview:1.0.0'
   ....
}

        在這裏如果使用implementation引入新庫時會報錯”org.gradle.internal.metaobject.AbstractDynamicObject”。需要將其改變爲compile。隨後sync now。修改activity_main.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="match_parent">

    <ListView
        android:id = "@+id/listView_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

       複製上一小節的fruir_item.xml、Fruit類和所有圖片,爲RecyclerView新建FruitAdapter適配器,並制定相應的泛型,ViewHolder是一個內部類。代碼如下:

package com.example.myapplication

import android.app.Activity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.ImageView
import android.widget.TextView

//定義一個主構造函數來將Activity實例、ListView子項佈局id和數據源傳遞進來
class FruitAdapter(activity: Activity, val resourceId: Int, data: List<Fruit>) :
    ArrayAdapter<Fruit>(activity, resourceId, data) {
    //innerclass來定義內部類
    inner class ViewHolder(val fruitImage: ImageView, val fruitName: TextView)

    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
        //首先使用LayoutInflater子項加載我們的佈局,三個參數。
        // 最後一個false的意思是父佈局聲明的layout生效,不會爲該View增加父佈局。保準寫法
        val view: View
        val viewHolder: ViewHolder
        if (convertView == null) {
            view = LayoutInflater.from(context).inflate(resourceId, parent, false)
            //用於對ImageView和TextView的控件實例進行緩存
            val fruitImage: ImageView = view.findViewById(R.id.fruit_image)
            val fruitName: TextView = view.findViewById(R.id.fruit_Name)
            //創建ViewHolder對象並將控件實例放在ViewHolder裏
            viewHolder = ViewHolder(fruitImage, fruitName)
            //使用View的setTag方法將ViewHolder存儲在View中。
            view.tag = viewHolder
        } else {
            view = convertView
            //緩存對象不爲空時,使用View的getTag方法將ViewHolder重新取出,這樣所有控件的實例都存儲於ViewHolder中了
            viewHolder = view.tag as ViewHolder
        }

        //得到當前項的Fruit實例
        val fruit = getItem(position)
        //設置圖片和文字
        if (fruit != null) {
            viewHolder.fruitImage.setImageResource(fruit.imageid)
            viewHolder.fruitName.text = fruit.name
        }
        //將佈局返回
        return view
    }
}

      最後修改MainActivity中的代碼。

package com.example.myapplication

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    private val fruit_list = ArrayList<Fruit>()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initFruits()
        //創建一個LinearLayoutManager線性佈局對象,並將其設置到RecyclerView當中
        val layoutmanager = LinearLayoutManager(this)
        recycler_view.layoutManager = layoutmanager
        //創建FruitAdapter實例並將水果數據傳入FruitAdapter構造函數中
        val adapter = FruitAdapter(fruit_list)
        //最後調用setAdapter來完成適配器設置,從而完成RecyclerView與數據的關聯
        recycler_view.adapter = adapter
    }

    private fun initFruits() {
        //repeat函數將水果數據重複兩遍
        repeat(2) {
            //構造方法中將水果名字和水果id傳入,將創建好的對象添加到水果列表中。
            fruit_list.add(Fruit("Apple", R.drawable.apple_pic))
            fruit_list.add(Fruit("Banana", R.drawable.banana_pic))
            fruit_list.add(Fruit("Orange", R.drawable.orange_pic))
            fruit_list.add(Fruit("WaterMelon", R.drawable.watermelon_pic))
            fruit_list.add(Fruit("Pear", R.drawable.pear_pic))
            fruit_list.add(Fruit("PineApple", R.drawable.pineapple_pic))
            fruit_list.add(Fruit("StrawBerry", R.drawable.strawberry_pic))
            fruit_list.add(Fruit("Cherry", R.drawable.cherry_pic))
        }
    }
}

4.6.2.實現橫向滾動和瀑布流佈局
       ListView擴展性不好的原因是隻能進行縱向滾動,RecyclerView能做到挺多滾動方式,譬如橫向滾動。簡單修改fruit_item佈局文件。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="80dp"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/fruit_image"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="10dp" />

    <TextView
        android:id="@+id/fruit_Name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="10dp" />
</LinearLayout>

       核心的是在MainActivity.java中加上一句橫向排列即可。

layoutmanager.orientation = LinearLayoutManager.HORIZONTAL

      相比ListView爲什麼如此簡單?ListView基於自身管理,RecyclerView則將這個工作交給了LayoutManager,除了LinearLayoutManager之外,還有網格佈局GridLayoutManager和瀑布流佈局StaggeredGridLayoutManager。下面談談瀑布流佈局,還是得先修改fruit_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="5dp"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/fruit_image"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="10dp" />
<!--    layout_gravity使得文字左對齊-->
    <TextView
        android:id="@+id/fruit_Name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="left"
        android:layout_marginTop="10dp" />
</LinearLayout>

   其次,修改MainActivity的代碼:

package com.example.myapplication

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import kotlinx.android.synthetic.main.activity_main.*
import java.lang.StringBuilder

class MainActivity : AppCompatActivity() {

    private val fruit_list = ArrayList<Fruit>()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initFruits()
        //兩個參數,第一個是幾列;第二個是佈局的排列方向,我們選擇垂直
        val layoutmanager = StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL)
        recycler_view.layoutManager = layoutmanager
        //創建FruitAdapter實例並將水果數據傳入FruitAdapter構造函數中
        val adapter = FruitAdapter(fruit_list)
        //最後調用setAdapter來完成適配器設置,從而完成RecyclerView與數據的關聯
        recycler_view.adapter = adapter
    }

    private fun initFruits() {
        //repeat函數將水果數據重複兩遍
        repeat(2) {
            //構造方法中將水果名字和水果id傳入,將創建好的對象添加到水果列表中。
            fruit_list.add(Fruit(getRandomLengthString("Apple"), R.drawable.apple_pic))
            fruit_list.add(Fruit(getRandomLengthString("Banana"), R.drawable.banana_pic))
            fruit_list.add(Fruit(getRandomLengthString("Orange"), R.drawable.orange_pic))
            fruit_list.add(Fruit(getRandomLengthString("WaterMelon"), R.drawable.watermelon_pic))
            fruit_list.add(Fruit(getRandomLengthString("Pear"), R.drawable.pear_pic))
            fruit_list.add(Fruit(getRandomLengthString("PineApple"), R.drawable.pineapple_pic))
            fruit_list.add(Fruit(getRandomLengthString("StrawBerry"), R.drawable.strawberry_pic))
            fruit_list.add(Fruit(getRandomLengthString("Cherry"), R.drawable.cherry_pic))
        }
    }
    private fun getRandomLengthString(str:String):String{
        val n = (1..20).random()
        val builder = StringBuilder()
        repeat(n){
            builder.append(str)
        }
        return builder.toString()
    }
}

4.6.3.RecyclerView的點擊事件
       RecyclerView沒有setOnItemClickListener這樣的註冊監聽器方法,而是需要我們給子項具體的View去註冊點擊事件。修改適配器FruitAdapter的onCreateViewHolder方法。

   //用於創建ViewHolder實例,將fruit_item加載進來,然後創建ViewHolder實例,最後將佈局傳入構造函數當中
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitAdapter.ViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.fruit_item, parent, false)
        val viewholder = ViewHolder(view)
        //爲最外層的佈局和ImageView都註冊了點擊事件,先獲取用戶點擊的position,然後通過position獲取相應的Fruit實例,最後Toast
        viewholder.itemView.setOnClickListener {
            val position = viewholder.adapterPosition
            val fruit = fruitList[position]
            Toast.makeText(parent.context, "you clicked view ${fruit.name}", Toast.LENGTH_SHORT)
                .show()
        }
        viewholder.fruitImage.setOnClickListener{
            val position = viewholder.adapterPosition
            val fruit = fruitList[position]
            Toast.makeText(parent.context, "you clicked image ${fruit.name}", Toast.LENGTH_SHORT)
                .show()
        }
        return viewholder
    }

4.7.編寫界面的最佳實踐
      實戰一下。
4.7.1.製作9-Patch圖片
      9-Patch圖片是經過特殊處理的png圖片,能夠指定哪些區域可以被拉伸,哪些區域不能被拉伸。普通照片拉伸效果非常差。對普通圖片右擊->Create 9-Patch file->對圖片拖拽表示拉伸部分->刪除原來的普通照片。
4.7.2.編寫聊天界面
     仿聊天軟件的聊天界面。首先在app/build.gradle裏面添加RecyclerView的依賴庫:

compile 'androidx.recyclerview:recyclerview:1.0.0'

     其次,修改activity_main.xml裏面的代碼,放置顯示聊天內容RecyclerView、EditText和發送button。

<?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="match_parent"
    android:background="#d8e0e8"
    android:orientation="vertical">
    <!--主界面放置一個RecyclerView來顯示聊天內容-->
    <androidx.recyclerview.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:id="@+id/recycler_view"
        android:layout_weight="1" />
    <!--EditText輸入消息,Button用於發送消息-->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <EditText
            android:id="@+id/input_text"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:hint="type sth here"
            android:maxLines="2" />

        <Button
            android:id="@+id/btn_send"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Send" />
    </LinearLayout>

</LinearLayout>

      再者,編寫消息實體類Msg。

package com.example.myapplication

//消息實體類,兩個參數,一個是消息內容,一個是消息類型,消息類型爲發出消息和接收消息兩種。
class Msg(val content: String, val type: Int) {

    companion object {
        //將其定義成常量,定義常量的關鍵字爲const,只有在單例類、companion object和頂層方法才能使用const關鍵字
        const val TYPE_RECEIVED = 0
        const val TYPE_SENT = 1
    }
}

      隨後,編寫RecyclerView的子項左佈局msg_left_item.xml和右佈局msg_right_item.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="70dp"
    android:orientation="vertical"
    android:padding="10dp">
    <!--    接受消息的子佈局,並將收到的消息左對齊,使用相應的nine-patch背景圖片。-->
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="left"
        android:background="@drawable/message_left_original">

        <TextView
            android:id="@+id/left_Msg"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_margin="10dp"
            android:textColor="#fff" />

    </LinearLayout>
</FrameLayout>
<?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="70dp"
    android:orientation="vertical"
    android:padding="10dp">
    <!--發出的消息右對齊,並使用相應的背景圖-->
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="right"
        android:background="@drawable/message_right">

        <TextView
            android:id="@+id/right_Msg"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_margin="10dp"
            android:textColor="#000" />

    </LinearLayout>
</FrameLayout>

      創建RecyclerView適配器MsgAdapter,根據不同的ViewType來創建不同的界面。代碼如下:

package com.example.myapplication

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import java.lang.IllegalArgumentException
//根據不同的type創建不同的界面
class MsgAdapter(val msgList: List<Msg>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    //定義兩個ViewHolder用於緩存msg_left_item和msg_right_item佈局中的控件
    inner class LeftViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val leftMsg: TextView = view.findViewById(R.id.left_Msg)
    }

    inner class RightViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val rightMsg: TextView = view.findViewById(R.id.right_Msg)
    }
    //返回當前position相應的消息類型
    override fun getItemViewType(position: Int): Int {
        val msg = msgList[position]
        return msg.type
    }
    //根據不同的ViewType加載不同的佈局並創建不同的ViewHolder
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        if (viewType == Msg.TYPE_RECEIVED) {
            val view =
                LayoutInflater.from(parent.context).inflate(R.layout.msg_left_item, parent, false)
            return LeftViewHolder(view)
        } else {
            val view =
                LayoutInflater.from(parent.context).inflate(R.layout.msg_right_item, parent, false)
            return RightViewHolder(view)
        }
    }

    override fun getItemCount(): Int = msgList.size
    //判斷ViewHolder類型,若爲LeftViewHolder,顯示左邊消息佈局,否則顯示右邊。
    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val msg = msgList[position]
        when (holder) {
            is LeftViewHolder -> holder.leftMsg.text = msg.content
            is RightViewHolder -> holder.rightMsg.text = msg.content
            else -> throw IllegalArgumentException()
        }
    }
}

      最後修改MainActivity裏的代碼,爲RecyclerView初始化數據,並添加相應的點擊事件。

package com.example.myapplication

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity(), View.OnClickListener {
    private val msgList = ArrayList<Msg>()
    private var adapter: MsgAdapter? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //初始化幾條數據顯示
        initMsg()
        //構建RecyclerView,指定一個LayoutManager和一個適配器
        val layoutManager = LinearLayoutManager(this)
        recycler_view.layoutManager = layoutManager
        adapter = MsgAdapter(msgList)
        recycler_view.adapter = adapter
        btn_send.setOnClickListener(this)
    }

    fun initMsg() {
        val msg1 = Msg("Hello guy.", Msg.TYPE_RECEIVED)
        msgList.add(msg1)
        val msg2 = Msg("Hello,who is that?", Msg.TYPE_SENT)
        msgList.add(msg2)
        val msg3 = Msg("Tom", Msg.TYPE_RECEIVED)
        msgList.add(msg3)
    }

    override fun onClick(p0: View?) {
        when (p0) {
            btn_send -> {
                //獲取EditText裏面的內容
                val content = input_text.text.toString()
                if (!content.isEmpty()) {
                    //如果不爲空,則新建Msg將其添加至msgList列表中
                    val msg = Msg(content, Msg.TYPE_SENT)
                    msgList.add(msg)
                    //notifyItemInserted通知列表有新數據插入,顯示出來,刷新RecyclerView中的顯示
                    adapter?.notifyItemInserted(msgList.size - 1)
                    //將RecyclerView顯示的數據定位至最後一行
                    recycler_view.scrollToPosition(msgList.size - 1)
                    //清空輸入框內容
                    input_text.setText("")
                }
            }
        }
    }
}

4.8.Kotlin之延遲初始化和密封類
4.8.1.對變量延遲初始化

       很多全局變量不可能爲空,但由於Kotlin語法特性,你不得不做許多非空判斷保護,即使你很確定這玩意不可能爲空。

class MainActivity : AppCompatActivity(), View.OnClickListener { 
   //因爲初始化是在onCreate方法中進行,因此不得不將adapter賦值爲null,同時將他的類型聲明改成 MsgAdapter?
    private var adapter: MsgAdapter? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        adapter = MsgAdapter(msgList)
       
    }
    override fun onClick(p0: View?) {
          .....
         //肯定要進行判空處理,否則編譯無法進行
         adapter?.notifyItemInserted(msgList.size - 1)
          ....
    }
}

       上述的代碼全局變量實例越多,編寫額外的判空處理代碼就越多。因此我們使用lateinit延遲初始化對上面代碼進行優化,告訴編譯器我待會初始化,一開始不用置null。另外,我們需要判斷全局變量是否已經完成了初始化,以避免某個變量重複初始化。代碼示例如下:

class MainActivity : AppCompatActivity(), View.OnClickListener { 
    private lateinit var adapter: MsgAdapter
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
         //判斷變量是否進行初始化,如果初始化,則不用重複對變量初始化,否則初始化
        if(!::adapter.isInitialized) {
            adapter = MsgAdapter(msgList)
        }
       
    }

    override fun onClick(p0: View?) {
          .....
         adapter.notifyItemInserted(msgList.size - 1)
          ....
    }
}

4.8.2.使用密封類優化代碼
      目的是解決爲滿足編譯器要求編寫無用條件分支的情況。優化前代碼示例如下:

package com.example.myapplication

import java.lang.IllegalArgumentException
//定義一個接口,表示某個操作的執行結果
interface Result
//Success類用於表示成功時的結果
class Success(val msg: String) : Result
//Failure類用於表示失敗時的結果
class Failure(val error: String) : Result
//接受一個Result參數,通過判斷result類型返回不同的結果
fun getResultMsg(result: Result) = when (result) {
    is Success -> result.msg
    is Failure -> result.error
    //else這塊是完全執行不到的,但缺少的話代碼將無法編譯過。
    //如果新增一個UnKnown類並實現了Result接口,用於表示未知的執行結果,但忘記寫分支,將會拋出異常使程序崩潰
    else -> throw IllegalArgumentException()
}

       解決方法是使用Kotlin密封類,密封類的關鍵字是sealed class。優化後的代碼如下所示:

package com.example.myapplication

import java.lang.IllegalArgumentException
//密封類
sealed class Result
//繼承類需要後面加上一對括號
class Success(val msg: String) : Result()

class Failure(val error: String) : Result()

//class unkonwn(val time: String) : Result()
//else條件已經沒有了?爲什麼呢?
//when傳入密封類時,Kotlin會自動檢查該密封類有哪些子類,並強制要求你對每一個都需要處理(若不處理,編譯不會通過)。即使沒有else,也不會出現遺漏分支
fun getResultMsg(result: Result) = when (result) {
    is Success -> result.msg
    is Failure -> result.error
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章