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


列表視圖

為實現各種排列組合類的視圖(包括但不限於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。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM