Kotlin入門(23)適配器的進階表達

来源:https://www.cnblogs.com/aqi00/archive/2018/10/09/9763725.html
-Advertisement-
Play Games

前面在介紹列表視圖和網格視圖時,它們的適配器代碼都存在視圖持有者ViewHolder,因為Android對列表類視圖提供了回收機制,如果某些列表項在屏幕上看不到了,則系統會自動回收相應的視圖對象。隨著用戶的下拉或者上拉手勢,已經被回收的列表項要重新載入到界面上,倘若每次載入都得從頭創建視圖對象,勢必 ...


前面在介紹列表視圖和網格視圖時,它們的適配器代碼都存在視圖持有者ViewHolder,因為Android對列表類視圖提供了回收機制,如果某些列表項在屏幕上看不到了,則系統會自動回收相應的視圖對象。隨著用戶的下拉或者上拉手勢,已經被回收的列表項要重新載入到界面上,倘若每次載入都得從頭創建視圖對象,勢必增加了系統的資源開銷。所以ViewHolder便應運而生,它在列表項首次初始化時,就將其視圖對象保存起來,後面再次載入該視圖時,即可直接從持有者處獲得先前的視圖對象,從而減少了系統開銷,提高了系統的運行效率。
視圖持有者的設計理念固然美好,卻苦了Android開發者,每次由BaseAdapter派生新的適配器類,都必須手工處理視圖持有者的相關邏輯,實在是個沉重的負擔。有鑒於此,迴圈視圖的適配器把視圖持有者的重用邏輯剝離出來,由系統自行判斷並處理持有者的重用操作。開發者繼承RecyclerView.Adapter之後,只要完成業務上的代碼邏輯即可,無需進行BaseAdapter視圖持有者的手工重用。
現在由Kotlin實現迴圈視圖的適配器類,綜合前面兩小節提到的優化技術,加上視圖持有者的自動重用,適配器代碼又得到了進一步的精簡。由於迴圈視圖適配器並不提供列表項的點擊事件,因此開發者要自己編寫包括點擊、長按在內的事件處理代碼。為方便理解迴圈適配器的Kotlin編碼,下麵以微信的公眾號消息列表為例,給出對應的消息列表Kotlin代碼:

//ViewHolder在構造時初始化佈局中的控制項對象
class RecyclerLinearAdapter(private val context: Context, private val infos: MutableList<RecyclerInfo>) : RecyclerView.Adapter<ViewHolder>(), OnItemClickListener, OnItemLongClickListener {
    val inflater: LayoutInflater = LayoutInflater.from(context)

    //獲得列表項的數目
    override fun getItemCount(): Int = infos.size

    //創建整個佈局的視圖持有者
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view: View = inflater.inflate(R.layout.item_recycler_linear, parent, false)
        return ItemHolder(view)
    }

    //綁定每項的視圖持有者
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val vh: ItemHolder = holder as ItemHolder
        vh.iv_pic.setImageResource(infos[position].pic_id)
        vh.tv_title.text = infos[position].title
        vh.tv_desc.text = infos[position].desc
        // 列表項的點擊事件需要自己實現
        vh.ll_item.setOnClickListener { v ->
            itemClickListener?.onItemClick(v, position)
        }
        vh.ll_item.setOnLongClickListener { v ->
            itemLongClickListener?.onItemLongClick(v, position)
            true
        }
    }

    //ItemHolder中的屬性在構造時初始化
    inner class ItemHolder(view: View) : RecyclerView.ViewHolder(view) {
        var ll_item = view.findViewById(R.id.ll_item) as LinearLayout
        var iv_pic = view.findViewById(R.id.iv_pic) as ImageView
        var tv_title = view.findViewById(R.id.tv_title) as TextView
        var tv_desc = view.findViewById(R.id.tv_desc) as TextView
    }

    private var itemClickListener: OnItemClickListener? = null
    fun setOnItemClickListener(listener: OnItemClickListener) {
        this.itemClickListener = listener
    }

    private var itemLongClickListener: OnItemLongClickListener? = null
    fun setOnItemLongClickListener(listener: OnItemLongClickListener) {
        this.itemLongClickListener = listener
    }

    override fun onItemClick(view: View, position: Int) {
        val desc = "您點擊了第${position+1}項,標題是${infos[position].title}"
        context.toast(desc)
    }

    override fun onItemLongClick(view: View, position: Int) {
        val desc = "您長按了第${position+1}項,標題是${infos[position].title}"
        context.toast(desc)
    }
}

以上的適配器代碼初步實現了公眾號消息列表的展示頁面,具體的列表效果如下圖所示。

可是這個迴圈適配器RecyclerLinearAdapter仍然體量龐大,細細觀察發現其實它有著數個與具體業務無關的屬性與方法,譬如上下文對象context、佈局載入對象inflater、點擊監聽器itemClickListener、長按監聽器itemLongClickListener等等,故而完全可以把這些通用部分提取到一個基類,然後具體業務再從該基類派生出特定的業務適配器類。根據這種設計思路,提取出了迴圈視圖基礎適配器,它的Kotlin代碼如下所示:

//迴圈視圖基礎適配器
abstract class RecyclerBaseAdapter<VH : RecyclerView.ViewHolder>(val context: Context) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), OnItemClickListener, OnItemLongClickListener {
    val inflater: LayoutInflater = LayoutInflater.from(context)

    //獲得列表項的個數,需要子類重寫
    override abstract fun getItemCount(): Int

    //根據佈局文件創建視圖持有者,需要子類重寫
    override abstract fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder

    //綁定視圖持有者中的各個控制項對象,需要子類重寫
    override abstract fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int)

    override fun getItemViewType(position: Int): Int = 0

    override fun getItemId(position: Int): Long = position.toLong()

    var itemClickListener: OnItemClickListener? = null
    fun setOnItemClickListener(listener: OnItemClickListener) {
        this.itemClickListener = listener
    }

    var itemLongClickListener: OnItemLongClickListener? = null
    fun setOnItemLongClickListener(listener: OnItemLongClickListener) {
        this.itemLongClickListener = listener
    }

    override fun onItemClick(view: View, position: Int) {}

    override fun onItemLongClick(view: View, position: Int) {}
}

一旦有了這個基礎適配器,實際業務的適配器即可由此派生而來,真正需要開發者編寫的代碼一下精簡了不少。下麵便是個迴圈視圖的網格適配器,它實現了類似淘寶主頁的網格頻道欄目,具體的Kotlin代碼如下所示:

//把公共屬性和公共方法剝離到基類RecyclerBaseAdapter,
//此處僅需實現getItemCount、onCreateViewHolder、onBindViewHolder三個方法,以及視圖持有者的類定義
class RecyclerGridAdapter(context: Context, private val infos: MutableList<RecyclerInfo>) : RecyclerBaseAdapter<RecyclerView.ViewHolder>(context) {

    override fun getItemCount(): Int = infos.size

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val view: View = inflater.inflate(R.layout.item_recycler_grid, parent, false)
        return ItemHolder(view)
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val vh = holder as ItemHolder
        vh.iv_pic.setImageResource(infos[position].pic_id)
        vh.tv_title.text = infos[position].title
    }

    inner class ItemHolder(view: View) : RecyclerView.ViewHolder(view) {
        var ll_item = view.findViewById(R.id.ll_item) as LinearLayout
        var iv_pic = view.findViewById(R.id.iv_pic) as ImageView
        var tv_title = view.findViewById(R.id.tv_title) as TextView
    }
}

改進後的迴圈網格適配器,運行之後的界面效果如下圖所示,無縫實現了原來需要數十行Java代碼才能實現的功能。

然而基類不過是雕蟲小技,Java也照樣能夠運用,所以這根本不入Kotlin的法眼,要想超越Java,還得擁有獨門秘笈才行。註意到適配器代碼仍然通過findViewById方法獲得控制項對象,可是號稱在Anko庫的支持之下,Kotlin早就無需該方法就能直接訪問控制項對象了呀,為啥這裡依舊靠老牛拉破車呢?其中的緣由是Anko庫僅僅實現了Activity活動頁面的控制項自動獲取,並未實現適配器內部的自動獲取。不過Kotlin早就料到了這一手,為此專門提供了一個插件名叫LayoutContainer,只要開發者讓自定義的ViewHolder繼承該介面,即可在視圖持有者內部無需獲取就能使用控制項對象了。這下不管是在Activity代碼,還是在適配器代碼中,均可將控制項名稱拿來直接調用了。這麼神奇的魔法,快來看看Kotlin的適配器代碼是如何書寫的:

//利用Kotlin的插件LayoutContainer,在適配器中直接使用控制項對象,而無需對其進行顯式聲明
class RecyclerStaggeredAdapter(context: Context, private val infos: MutableList<RecyclerInfo>) : RecyclerBaseAdapter<RecyclerView.ViewHolder>(context) {

    override fun getItemCount(): Int = infos.size

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val view: View = inflater.inflate(R.layout.item_recycler_staggered, parent, false)
        return ItemHolder(view)
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        (holder as ItemHolder).bind(infos[position])
    }

    //註意這裡要去掉inner,否則運行報錯“java.lang.NoSuchMethodError: No virtual method _$_findCachedViewById”
    class ItemHolder(override val containerView: View?) : RecyclerView.ViewHolder(containerView), LayoutContainer {
        fun bind(item: RecyclerInfo) {
            iv_pic.setImageResource(item.pic_id)
            tv_title.text = item.title
        }
    }
}

當然,為了能夠正常使用該功能,需要在適配器代碼頭部加上以下兩行代碼,其中第一行代碼表示引用了Kotlin的擴展插件LayoutContainer,第二行代碼與Activity的一樣表示導入了指定佈局文件裡面所有控制項對象:

import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_recycler_staggered.*

 

另外,因為LayoutContainer是Kotlin針對性提供給Android的擴展插件,所以需要修改模塊的build.gradle,在文件末尾添加下麵幾行配置,表示允許引用安卓插件庫:

androidExtensions {
    experimental = true
}

 

即使修改後的適配器代碼用了新插件,外部仍舊同原來一樣給迴圈視圖設置適配器,調用代碼並無任何變化:

    //第一種方式:使用採取了LayoutContainer的插件適配器
    val adapter = RecyclerStaggeredAdapter(this, RecyclerInfo.defaultStag)
    rv_staggered.adapter = adapter

採用了新的適配器插件,似乎已經大功告成,可是依然要書寫單獨的適配器代碼,仔細研究發現這個RecyclerStaggeredAdapter還有三個要素是隨著具體業務而變化的,包括:

1、列表項的佈局文件資源編碼,如R.layout.item_recycler_staggered;
2、列表項信息的數據結構名稱,如RecyclerInfo;
3、對各種控制項對象的設置操作,如ItemHolder類的bind方法;
除了以上三個要素,RecyclerStaggeredAdapter內部的其餘代碼都是允許復用的,因此,接下來的工作就是想辦法把這三個要素抽象為公共類的某種變數。對於第一個的佈局編碼,可以考慮將其作為一個整型的輸入參數;對於第二個的數據結構,可以考慮定義一個模板類,在外部調用時再指定具體的數據類;對於第三個的bind方法,若是Java編碼早已束手無策,現用Kotlin編碼正好將該方法作為一個函數參數傳入。依照三個要素的三種處理對策,進而提煉出來了迴圈適配器的通用類RecyclerCommonAdapter,詳細的Kotlin代碼示例如下:

//迴圈視圖通用適配器
//將具體業務中會變化的三類要素抽取出來,作為外部傳進來的變數。這三類要素包括:
//佈局文件對應的資源編號、列表項的數據結構、各個控制項對象的初始化操作
class RecyclerCommonAdapter<T>(context: Context, private val layoutId: Int, private val items: List<T>, val init: (View, T) -> Unit): RecyclerBaseAdapter<RecyclerCommonAdapter.ItemHolder<T>>(context) {

    override fun getItemCount(): Int = items.size

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val view: View = inflater.inflate(layoutId, parent, false)
        return ItemHolder<T>(view, init)
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val vh: ItemHolder<T> = holder as ItemHolder<T>
        vh.bind(items.get(position))
    }

    //註意init是個函數形式的輸入參數
    class ItemHolder<in T>(val view: View, val init: (View, T) -> Unit) : RecyclerView.ViewHolder(view) {
        fun bind(item: T) {
            init(view, item)
        }
    }
}

有了這個通用適配器,外部使用適配器只需像函數調用那樣傳入這三種變數就好了,具體調用的Kotlin代碼如下所示:

    //第二種方式:使用把三類可變要素抽象出來的通用適配器
    val adapter = RecyclerCommonAdapter(this, R.layout.item_recycler_staggered, RecyclerInfo.defaultStag,
            {view, item ->
                val iv_pic = view.findViewById(R.id.iv_pic) as ImageView
                val tv_title = view.findViewById(R.id.tv_title) as TextView
                iv_pic.setImageResource(item.pic_id)
                tv_title.text = item.title
            })
    rv_staggered.adapter = adapter

 

最終出爐的適配器僅有十行代碼不到,其中的關鍵技術——函數參數真是不鳴則已、一鳴驚人。至此本節的適配器實現過程終於落下帷幕,一路上可謂是過五關斬六將,硬生生把數十行的Java代碼壓縮到不到十行的Kotlin代碼,經過不斷迭代優化方取得如此彪炳戰績。尤其是最後的兩種實現方式,分別運用了Kotlin的多項綜合技術,才能集Kotlin精妙語法之大成。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 基本知識點: 1.似乎mongo3.6之前不允許插入帶點(.)或美元符號($)的鍵,但是當我使用mongoimport工具導入包含點的JSON文件時,它工作正常。 2.在使用spring-data-mongodb處理mongodb的增刪改查時會通過一個MappingMongoConverter(Do ...
  • 數據表的建立和刪除: (瞭解) CREATE TABLE 數據表名稱(欄位1 類型1(長度),欄位2 類型2(長度) …… ) DROP TABLE 數據表名稱 (永久性刪除一個數據表) SQL插入數據語法: sql=“INSERT INTO 數據表 (欄位1,欄位2,欄位3 …) VALUES ( ...
  • 在本文中,我們將分享在為事務性數據構建高度可伸縮的多租戶分析服務時所吸取的教訓。我們將從大局和業務需求開始。然後描述具有用於數據準備、發佈和查詢引擎的批處理和互動式模塊的體繫結構,並註意相關的Spark技術。然後我們將深入Prism查詢引擎的內部,重點介紹所使用的Spark SQL、DataFram ...
  • 本章屬於總結章節,從矩陣的基礎知識講起,介紹了協方差、橢圓對稱矩陣、Dyads、Directional variance(方向差)等知識並從而擴展到了概率論知識例如貝葉斯公式,Bayes Rule for density matrices等,並用簡潔有力的語言總結了主成分分析的執行思想。 資源 下載 ...
  • 前言: Mapreduce程式的效率的瓶頸在於兩點: MapReduce優化方法 數據輸入: (1)合併小文件:在執行任務前將小文件進行合併 (2)採用CombineTextInputformat來作為輸入,解決輸入端大量小文件的場景。將多個小文件從邏輯上規划到一個切片中,這樣,多個小文件就可以交給 ...
  • 現如今,數據增長速度快於處理速度,唯一的解決方案是在大型集群上並行化,而且這種技術以及廣泛應用於企業和網路行業。本章主要內容有:講解數據流與傳統的網路編程的區別、MapReduce的局限性、Spark computing engine、Matrix operations on Spark等。 資源下 ...
  • 很多初學者在剛剛接觸大數據的時候會有很多疑惑,比如對MapReduce、Storm、Spark三個計算框架的理解經常會產生混亂。 哪一個適合對大量數據進行處理?哪一個又適合對實時的流數據進行處理?又該如何來區分他們呢? 我對比整理了這3個計算框架的基本知識,大家可以瞭解一下以便對這個3個計算框架有一 ...
  • 作者:天山老妖S 鏈接:http://blog.51cto.com/9291927 一、字元集與編碼 1、字元集簡介 字元(Character)是各種文字和符號的總稱,包括各國家文字、標點符號、圖形符號、數字等。字元集(Character set)是多個字元的集合,字元集種類較多,每個字元集包含的字 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...