Jetpack架構組件學習(3)——Activity Results API使用

原文地址:Jetpack架構組件學習(3)——Activity Results API使用 – Stars-One的雜貨小窩

技術與時俱進,頁面跳轉傳值一直使用的是startActivityForResult方法,如今有了新的API實現方式,學習並稍微總結下 😄

startActivityForResult複習

MainActivity程式碼:

Main2Activity程式碼:

效果:

上面的程式碼應該是比較基礎的程式碼,這裡我就不再贅述了

主要說些缺點

所有邏輯都在onActivityResult()方法里進行判斷,根據requestCoderesultCode進行判斷

如果單個還好說,但是如果有多個的,就會看見onActivityResult()里一堆的if邏輯,閱讀起來就十分繁瑣,且維護困難

Google官方也是考慮到了這個,於是便是在新版本推出了個Activity Results API去替代了上面所述的方式,下面就介紹下如何使用

簡單使用

1.引入依賴

首先,需要我們引入依賴:

implementation 'androidx.appcompat:appcompat:1.3.1'

PS: 請使用1.3.1以上版本,低版本沒有這個枚舉類ActivityResultContracts

我們先以上面的例子,使用Activity Results API

Main2Acitivity程式碼不用動,我們只需要調整MainActivity文件里程式碼,如下所示:

2.創建契約

val contract = ActivityResultContracts.StartActivityForResult()

contract變數的對象類名為ActivityResultContract

ActivityResultContracts相當於一個枚舉類,是Google官方貼心封裝的,裡面提供了一些常用的ActivityResultContract類對象供我們使用

像拍照,申請許可權的等操作,從程式碼提示就可以看到了,如下圖所示:

這裡我們選用StartActivityForResult(),字面意思應該很好理解,就是應用在就是頁面跳轉並返回數據的情景

PS:根據我們選用的ActivityResultContracts,會影響第4步中的傳參類型

下面補充下對應的選擇說明:

  • StartActivityForResult: 通用的Contract,不做任何轉換,Intent作為輸入,ActivityResult作為輸出,這也是最常用的一個協定。
  • CreateDocument: 提示用戶選擇一個文檔,返回一個(file:/http:/content:)開頭的Uri。
  • GetContent: 提示用選擇一條內容,返回一個通過ContentResolver#openInputStream(Uri)訪問原生數據的Uri地址(content://形式) 。默認情況下,它增加了 Intent#CATEGORY_OPENABLE, 返回可以表示流的內容。
  • GetMultipleContents:獲取多條內容
  • OpenDocument: 提示用戶選擇指定類型文件(輸入參數為mimeType),返回用戶所選文件Uri
  • OpenDocumentTree: 提示用戶選擇一個目錄,並返回用戶選擇的作為一個Uri返回,應用程式可以完全管理返回目錄中的文檔。
  • OpenMultipleDocuments: 提示用戶選擇文檔(可以選擇多個),分別返回它們的Uri,以List的形式。
  • PickContact: 從通訊錄APP獲取聯繫人
  • RequestMultiplePermissions:用於請求一組許可權
  • RequestPermission: 用於請求單個許可權
  • TakePicturePreview: 調用MediaStore.ACTION_IMAGE_CAPTURE拍照,返回值為Bitmap圖片
  • TakePicture: 調用MediaStore.ACTION_IMAGE_CAPTURE拍照,並將圖片保存到給定的Uri地址,返回true表示保存成功。
  • TakeVideo: 調用MediaStore.ACTION_VIDEO_CAPTURE 拍攝影片,保存到給定的Uri地址,返回一張縮略圖。

具體參數和說明可以使用的時候查看文檔哦~

實際上,如果上面所列還不能滿足我們的需求,那麼我們也可以自定義契約操作,在文章下面再進行補充說明,這裡就先不進行擴展了

3.建立契約(註冊Contact)

//註冊ActivityResultContract
val myLauncher = registerForActivityResult(contract){
    if (it.resultCode==2) {
        val data = it.data
        if (data != null) {
            val resultData = data.getStringExtra("mydata")
            Toast.makeText(this, resultData, Toast.LENGTH_SHORT).show()
        }
    }
}

使用Activity類中registerForActivityResult()方法,進行契約的註冊,實際上就是相當於註冊了一個監聽,之後從Main2Activity頁面返回MainActivity頁面,會回調這個裡面的方法

注意: 這裡有個變數myLauncher,之後第三步需要使用

4.發起頁面跳轉

val intent = Intent(this, Main2Activity::class.java)
myLauncher.launch(intent)

調用myLauncher對象的launch()方法,將intent對象傳遞即可實現頁面跳轉的操作

PS: 這裡還是在按鈕的點擊事件里,方便閱讀就省略了

之後從Main2Activity頁面返回之後,會回調第二步中的操作,效果與上面的動圖演示一致,這裡就不再重新貼個圖了

自定義ActivityResultContract

ActivityResultContract實際上還包含兩個泛型,完整應該是這樣ActivityResultContract<I,O>

  • I為input的意思,意為輸入參數類型
  • O為output的意思,意為輸出參數類型

ActivityResultContract<I,O>是個抽象類,我們想要實現自定義,那麼就直接繼承它

class MyContract: ActivityResultContract<String, String>() {

    override fun createIntent(context: Context, input: String?): Intent {
        //這裡input的類型,就是上文說到的I
    }

    override fun parseResult(resultCode: Int, intent: Intent?): String {
        //這裡方法返回的結果類型,就是上文說到的O
        
    }

}

繼承發現,需要我們實現兩個方法,createIntent()parseResult()

一眼過去其實很好理解,createIntent()就是創建一個intent對象,調用launch()方法的時候(上面使用的第4步操作),裡面就會根據此intent進行頁面的跳轉操作

parseResult()方法,則是建立契約那步,裡面回調的數據類型

我們以上面的例子,發現我們還得聲明一個Intent對象傳遞,以及回傳的時候還得通過intent對象去獲取數據,有些繁瑣,有些程式碼可以封裝成通用的

照著這個想法,我們可以實現一個自定義ActivityResultContract,傳遞頁面參數即可拿到Main2Activity返回的數據,程式碼如下所示:

class MyContract: ActivityResultContract<KClass<out Activity>, String>() {
    
    override fun parseResult(resultCode: Int, intent: Intent?): String? {
        if (resultCode == 2 && intent!=null) {
            return intent.getStringExtra("mydata")
        }
        return null
    }

    override fun createIntent(context: Context, input: KClass<out Activity>?): Intent {
        val intent = Intent(context,input?.java)
        return intent
    }

}

使用:

//1.創建契約
val contract = MyContract()

//2.註冊ActivityResultContract
val myLauncher = registerForActivityResult(contract){
    Toast.makeText(this, it, Toast.LENGTH_SHORT).show()
}

btnGo.setOnClickListener {
    //2.發起頁面跳轉
    myLauncher.launch(Main2Activity::class)
}

當然,這裡還可以優化,如我們讓MyContract多個構造函數,這樣取值的key也可以通過此進行定義

class MyContract(val key: String) : ActivityResultContract<KClass<out Activity>, String>() {

    override fun parseResult(resultCode: Int, intent: Intent?): String? {
        if (resultCode == 2 && intent != null) {
            return intent.getStringExtra(key)
        }
        return null
    }

    override fun createIntent(context: Context, input: KClass<out Activity>?): Intent {
        val intent = Intent(context, input?.java)
        return intent
    }

}

使用:

//創建契約里傳參即可
val contract = MyContract("mydata")

參考