Kotlin入門(22)適配器的簡單優化

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

列表視圖 為實現各種排列組合類的視圖(包括但不限於Spinner、ListView、GridView等等),Android提供了五花八門的適配器用於組裝某個規格的數據,常見的適配器有:數組適配器ArrayAdapter、簡單適配器SimpleAdapter、基本適配器BaseAdapter、翻頁適配 ...


列表視圖

為實現各種排列組合類的視圖(包括但不限於Spinner、ListView、GridView等等),Android提供了五花八門的適配器用於組裝某個規格的數據,常見的適配器有:數組適配器ArrayAdapter、簡單適配器SimpleAdapter、基本適配器BaseAdapter、翻頁適配器PagerAdapter。適配器的種類雖多,卻個個都不好用,以數組適配器為例,它與Spinner配合實現下拉框效果,其實現代碼紛復繁雜,一直為人所詬病。故而在下拉框一小節之中,乾脆把ArrayAdapter連同Spinner一股腦都摒棄了,取而代之的是Kotlin擴展函數selector。
到了列表視圖ListView這裡,與之搭檔的一般是基本適配器BaseAdapter,這個BaseAdapter更不簡單,基於它的列表適配器得重寫好幾個方法,還有那個想讓初學者撞牆的ViewHolder。總之,每當要實現類似新聞列表、商品列表之類的頁面,一想到這個難纏的BaseAdapter,心裡便發怵。譬如下圖所示的六大行星的說明列表,左側是圖標,右邊為文字說明,很普通的一個頁面。

可是這個行星列表頁面,倘若使用Java編碼,就得書寫下麵一大段長長的代碼:

public class PlanetJavaAdapter extends BaseAdapter  {
    private Context mContext;
    private ArrayList<Planet> mPlanetList;
    private int mBackground;

    public PlanetJavaAdapter(Context context, ArrayList<Planet> planet_list, int background) {
        mContext = context;
        mPlanetList = planet_list;
        mBackground = background;
    }

    @Override
    public int getCount() {
        return mPlanetList.size();
    }

    @Override
    public Object getItem(int arg0) {
        return mPlanetList.get(arg0);
    }

    @Override
    public long getItemId(int arg0) {
        return arg0;
    }

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
        if (convertView == null) {
            holder = new ViewHolder();
            convertView = LayoutInflater.from(mContext).inflate(R.layout.item_list_view, null);
            holder.ll_item = (LinearLayout) convertView.findViewById(R.id.ll_item);
            holder.iv_icon = (ImageView) convertView.findViewById(R.id.iv_icon);
            holder.tv_name = (TextView) convertView.findViewById(R.id.tv_name);
            holder.tv_desc = (TextView) convertView.findViewById(R.id.tv_desc);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }
        Planet planet = mPlanetList.get(position);
        holder.ll_item.setBackgroundColor(mBackground);
        holder.iv_icon.setImageResource(planet.image);
        holder.tv_name.setText(planet.name);
        holder.tv_desc.setText(planet.desc);
        return convertView;
    }

    public final class ViewHolder {
        public LinearLayout ll_item;
        public ImageView iv_icon;
        public TextView tv_name;
        public TextView tv_desc;
    }
}

上面Java實現的適配器類PlanetJavaAdapter,果真又冗長又晦澀,然而這段代碼模版基本上是列表視圖的標配,只要用Java編碼,就必須依樣畫瓢。如果用Kotlin實現這個適配器類會是怎樣的呢?馬上利用Android Studio把上述Java代碼轉換為Kotlin編碼,轉換後的Kotlin代碼類似以下片段:

class PlanetKotlinAdapter(private val mContext: Context, private val mPlanetList: ArrayList<Planet>, private val mBackground: Int) : BaseAdapter() {

    override fun getCount(): Int {
        return mPlanetList.size
    }

    override fun getItem(arg0: Int): Any {
        return mPlanetList[arg0]
    }

    override fun getItemId(arg0: Int): Long {
        return arg0.toLong()
    }

    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
        var view = convertView
        var holder: ViewHolder?
        if (view == null) {
            holder = ViewHolder()
            view = LayoutInflater.from(mContext).inflate(R.layout.item_list_view, null)
            holder.ll_item = view.findViewById(R.id.ll_item) as LinearLayout
            holder.iv_icon = view.findViewById(R.id.iv_icon) as ImageView
            holder.tv_name = view.findViewById(R.id.tv_name) as TextView
            holder.tv_desc = view.findViewById(R.id.tv_desc) as TextView
            view.tag = holder
        } else {
            holder = view.tag as ViewHolder
        }
        val planet = mPlanetList[position]
        holder.ll_item!!.setBackgroundColor(mBackground)
        holder.iv_icon!!.setImageResource(planet.image)
        holder.tv_name!!.text = planet.name
        holder.tv_desc!!.text = planet.desc
        return view!!
    }

    inner class ViewHolder {
        var ll_item: LinearLayout? = null
        var iv_icon: ImageView? = null
        var tv_name: TextView? = null
        var tv_desc: TextView? = null
    }
}

相比之下,直接轉換得來的Kotlin代碼,最大的改進是把構造函數及初始化參數放到了第一行,其它地方未有明顯優化。眼瞅著沒多大改善,反而因為Kotlin的空安全機制,平白無故多了好些問號和雙感嘆號,可謂得不償失。問題出在Kotlin要求每個變數都要初始化上面,視圖持有者ViewHolder作為一個內部類,目前雖然無法直接對控制項對象賦值,但是從代碼邏輯可以看出先從佈局文件獲取控制項,然後才會調用各種設置方法。這意味著,上面的控制項對象必定是先獲得實例,在它們被使用的時候肯定是非空的,因此完全可以告訴編譯器,這些控制項對象一定會在使用前賦值,編譯器您老就高抬貴手,睜一隻眼閉一隻眼放行好了。

毋庸置疑,該想法合情合理,Kotlin正好提供了這種後門,它便是關鍵字lateinit。lateinit的意思是延遲初始化,它放在var或者val前面,表示被修飾的變數屬於延遲初始化屬性,即使沒有初始化也仍然是非空的。如此一來,這些控制項在聲明之時無需賦空值,在使用的時候也不必畫蛇添足加上兩個感嘆號了。根據新來的lateinit修改前面的Kotlin適配器,改寫後的Kotlin代碼如下所示:

class PlanetListAdapter(private val context: Context, private val planetList: MutableList<Planet>, private val background: Int) : BaseAdapter() {

    override fun getCount(): Int = planetList.size

    override fun getItem(position: Int): Any = planetList[position]

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

    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
        var view = convertView
        val holder: ViewHolder
        if (convertView == null) {
            view = LayoutInflater.from(context).inflate(R.layout.item_list_view, null)
            holder = ViewHolder()
            //先聲明視圖持有者的實例,再依次獲取內部的各個控制項對象
            holder.ll_item = view.findViewById(R.id.ll_item) as LinearLayout
            holder.iv_icon = view.findViewById(R.id.iv_icon) as ImageView
            holder.tv_name = view.findViewById(R.id.tv_name) as TextView
            holder.tv_desc = view.findViewById(R.id.tv_desc) as TextView
            view.tag = holder
        } else {
            holder = view.tag as ViewHolder
        }
        val planet = planetList[position]
        holder.ll_item.setBackgroundColor(background)
        holder.iv_icon.setImageResource(planet.image)
        holder.tv_name.text = planet.name
        holder.tv_desc.text = planet.desc
        return view!!
    }

    //ViewHolder中的屬性使用關鍵字lateinit延遲初始化
    inner class ViewHolder {
        lateinit var ll_item: LinearLayout
        lateinit var iv_icon: ImageView
        lateinit var tv_name: TextView
        lateinit var tv_desc: TextView
    }
}

以上的Kotlin代碼總算有點模樣了,雖然總體代碼還不夠精簡,但是至少清晰明瞭,其中主要運用了Kotlin的以下三項技術:

1、構造函數和初始化參數放在類定義的首行,無需單獨構造,也無需手工初始化;
2、像getCount、getItem、getItemId這三個函數,僅僅返回簡單運算的數值,可以直接用等號取代大括弧;
3、對於視圖持有者的內部控制項,在變數名稱前面添加lateinit,表示該屬性為延遲初始化屬性;


網格視圖

在前面的列表視圖一小節中,給出了Kotlin改寫後的適配器類,通過關鍵字lateinit固然避免了麻煩的空校驗,可是控制項對象遲早要初始化的呀,晚賦值不如早賦值。翻到前面PlanetListAdapter的實現代碼,認真觀察發現控制項對象的獲取其實依賴於佈局文件的視圖對象view,既然如此,不妨把該視圖對象作為ViewHolder的構造參數傳過去,使得視圖持有者在構造之時便能一塊初始化內部控制項。據此改寫後的Kotlin適配器代碼如下所示:

class PlanetGridAdapter(private val context: Context, private val planetList: MutableList<Planet>, private val background: Int) : BaseAdapter() {

    override fun getCount(): Int = planetList.size

    override fun getItem(position: Int): Any = planetList[position]

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

    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
        var view = convertView
        val holder: ViewHolder
        if (view == null) {
            view = LayoutInflater.from(context).inflate(R.layout.item_grid_view, null)
            holder = ViewHolder(view)
            //視圖持有者的內部控制項對象已經在構造時一併初始化了,故這裡無需再做賦值
            view.tag = holder
        } else {
            holder = view.tag as ViewHolder
        }
        val planet = planetList[position]
        holder.ll_item.setBackgroundColor(background)
        holder.iv_icon.setImageResource(planet.image)
        holder.tv_name.text = planet.name
        holder.tv_desc.text = planet.desc
        return view!!
    }

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

利用該適配器運行測試應用,得到的網格效果如下圖所示,可見與Java代碼的運行結果完全一致。

至此基於BaseAdapter的Kotlin列表適配器告一段落,上述的適配器代碼模版,同時適用於列表視圖ListView與網格視圖GridView。


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

-Advertisement-
Play Games
更多相關文章
  • 前幾天剛接手一個MySQL數據,操作系統為Ubuntu 16.04.5 LTS, 資料庫版本為5.7.23-0ubuntu0.16.04.1(APT方式安裝的MySQL)。這個操作系統下的MySQL的配置文件my.cnf很多地方都讓人有點不適應(跟之前的MySQL環境有些出入,之前都是維護RHEL、... ...
  • 測試環境: create table bqh6 (xm varchar2(10),bmbh number(2),bmmc varchar2(15),gz int);insert into bqh6 values ('張三',01,'技術支持',3500);insert into bqh6 value ...
  • 步驟一:安裝mysql依賴 步驟二:下載mysql社區版 步驟三:創建mysql用戶和用戶組 步驟四:解壓mysql文件 步驟五:創建文件夾 步驟六:初始化mysql 將root初始化密碼複製出來,等會登錄mysql需要使用這個密碼 步驟七:分配mysql文件夾許可權 步驟八:啟動mysql 步驟九: ...
  • 本文將在MySQL源碼探索系列技術博客的第1篇的基礎上接著分析dispatch_command()函數之後的工作流程,主要是分析mysql_parse()和mysql_execute_command()兩個函數的代碼框架,並對其中涉及到的隱式事務如何判斷等等問題結合MySQL源碼進行了介紹。 本人技 ...
  • Oracle用戶 Oracle用戶創建和授權詳解,參考網址如下: http://www.oraclejsq.com/getOracle_jcjc.do?nodeid=010100133 oracle用戶的概念對於Oracle資料庫至關重要,在現實環境當中一個伺服器一般只會安裝一個Oracle實例,一 ...
  • 點進來的同學,大部分是為了學編程而來的,這裡面有一部分學編程是出於興趣愛好,但大部分都是為了找工作或跳槽吧!其中有些人也許是覺得難,也許是遇到瓶頸,也許是因為惰性,總之半途而廢了。在這新一年的開始,我想對你說一句:不要輕易放棄,如果你覺得艱難,說明你正在走上坡路!在為你講為什麼要學習大數據前給分享一 ...
  • 據外媒phonearena報道,估計有3200萬台Android設備很快就無法使用谷歌Chrome移動瀏覽器。根據XDA最近提交的一份文件顯示,Chrome移動瀏覽器應用程式的最低API級別將從4.1提高到4.4。 這意味著仍然運行由Jelly Bean(Jelly Bean是Android 4.1 ...
  • 一、概述 在 RxJava 中,一個實現了 介面的對象可以訂閱一個 類的實例。訂閱者對 發射的任何數據或數據序列作出響應。這種模式簡化了併發操作,因為它不需要阻塞等待 發射數據,而是創建了一個處於待命狀態的觀察者哨兵,哨兵在未來某個時刻響應 的通知。RxJava 提供了一套非同步編程的 API,並且支 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...