日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

Android:打造“万能”Adapter与ViewHolder

發布時間:2025/3/21 Android 51 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android:打造“万能”Adapter与ViewHolder 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

##寫在前面

最近一直忙著各種結課大作業,重新看起Android還有種親切感。前段時間寫項目的時候,學習了一個萬能Adapter與ViewHolder的寫法。說是“萬能”其實就是在各種情況下都能通用。

我們知道,在寫項目的時候,項目中肯定有很多的ListView或者RecyclerView,這個時候我們就要寫大量的Adapter與ViewHolder。盡管重復寫的難度并不大,但是這會讓項目看起來十分冗余,因為存在大量的重復代碼。

所以能不能有一個通用的ViewHolder與Adapter,讓項目中只存在一個ViewHolder與Adapter呢?

當然可以,現在就通過一個小Demo將我學習的知識分享給大家。下面是本文的目錄:

  • 項目介紹
  • 傳統寫法分析
  • 簡單認識SparseArray
  • 萬能ViewHolder
  • 萬能Adapter
  • 結語
  • 項目源碼

##項目介紹

先來看這個Demo,很簡單,我就不多說了。

這是項目結構,為了方便后期對比,我將三種Adapter分離開了:

  • MainActivity:模擬新聞頁面
  • NewsBean:封裝了新聞的Bean
  • CommonViewHolder:通用ViewHolder
  • CommonAdapter:通用Adapter
  • TraditionAdapterWithTraditionHolder:基于傳統Holder的傳統Adapter
  • TraditionAdapterWithCommonHolder:基于通用ViewHolder的傳統Adapter
  • CommonAdapterWithCommoeHolder:基于通用ViewHolder的通用Adapter

##傳統寫法分析

至于頁面布局、模擬加載數據在這里我就不提了,十分簡單。現在主要看一下傳統的Adapter的寫法。

/*** 基于傳統Holder的傳統Adapter*/ public class TraditionAdapterWithTraditionHolder extends BaseAdapter {private Context context;private List<NewsBean> list;public TraditionAdapterWithTraditionHolder(Context context, List<NewsBean> list) {this.context = context;this.list = list;}@Overridepublic int getCount() {return list.size();}@Overridepublic Object getItem(int position) {return list.get(position);}@Overridepublic long getItemId(int position) {return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {ViewHolder viewHolder ;if (convertView == null) {convertView = View.inflate(context, R.layout.item_list, null);viewHolder = new ViewHolder();viewHolder.titleText = (TextView) convertView.findViewById(R.id.tv_title);viewHolder.descText = (TextView) convertView.findViewById(R.id.tv_desc);viewHolder.timeText = (TextView) convertView.findViewById(R.id.tv_time);viewHolder.phoneText = (TextView) convertView.findViewById(R.id.tv_phone);convertView.setTag(viewHolder);}else{viewHolder = (ViewHolder) convertView.getTag();}NewsBean bean = list.get(position);viewHolder.titleText.setText(bean.getTitle());viewHolder.descText.setText(bean.getDesc());viewHolder.timeText.setText(bean.getTime());viewHolder.phoneText.setText(bean.getPhone());return convertView;}private class ViewHolder {TextView titleText;TextView descText;TextView timeText;TextView phoneText;} } 復制代碼

由于代碼也比較簡單,基本都是 套路 代碼,大家都會寫,都能看懂,所以我就不加以詳細注釋了。

我們知道,如果需要一個通用的Adapter,肯定要對之前的代碼進行封裝。所以現在主要來分析一下這個傳統寫法,看到底哪個地方可以進行封裝。

依次來看,首先是構造函數。只要稍微有點經驗的開發者都知道,一般來說,這里面傳遞的參數幾乎都是一個 ContextList ,而List中通常都裝了一個具體內容的 Bean

public TraditionAdapterWithTraditionHolder(Context context, List<NewsBean> list) {this.context = context;this.list = list; } 復制代碼

所以設想,既然這個Bean每次都需要,那我們是否能否將這個Bean直接給自定義的Adapter呢?比如這樣:

public MyAdapter<T>(Context context, List<T> list) {this.context = context;this.list = list; } 復制代碼

先把這個問題拋向天空,來看固定的三個方法,這也沒啥好說的,依舊是套路,所以可以封裝成固定方法,內部實現它,不需暴露出來再復寫:

@Override public int getCount() {return list.size(); }@Override public Object getItem(int position) {return list.get(position); }@Override public long getItemId(int position) {return position; } 復制代碼

然后在重頭戲 getView 方法中需要一個ViewHolder,來復用已有的View。然后 new 出List中的 Bean ,賦值后顯示在View上。這就是基本的套路。

private class ViewHolder {TextView titleText;TextView descText;TextView timeText;TextView phoneText; }Override public View getView(int position, View convertView, ViewGroup parent) {ViewHolder viewHolder ;if (convertView == null) {convertView = View.inflate(context, R.layout.item_list, null);viewHolder = new ViewHolder();viewHolder.titleText = (TextView) convertView.findViewById(R.id.tv_title);viewHolder.descText = (TextView) convertView.findViewById(R.id.tv_desc);viewHolder.timeText = (TextView) convertView.findViewById(R.id.tv_time);viewHolder.phoneText = (TextView) convertView.findViewById(R.id.tv_phone);convertView.setTag(viewHolder);}else{viewHolder = (ViewHolder) convertView.getTag();}NewsBean bean = list.get(position);viewHolder.titleText.setText(bean.getTitle());viewHolder.descText.setText(bean.getDesc());viewHolder.timeText.setText(bean.getTime());viewHolder.phoneText.setText(bean.getPhone());return convertView; } 復制代碼

而這個ViewHolder套路就更深了,先定義一個ViewHolder類,類中是布局中所需的控件,然后在getView方法中new一個ViewHolder出來,通過這個ViewHolder找到對應的控件,找到后需要設置個Tag,方便之后復用。最后就是通過ViewHolder設置控件的內容了。

既然熟悉了過程,那封裝起來就簡單了許多。首先肯定需要封裝ViewHolder類,不然怎么算的上通用,但是每一個ListView中item布局可能不一樣,肯定不能將控件寫死,那么如何定義控件呢?當控件定義好后,又如何找到這些控件呢?控件找到后又如何設置控件內容呢?

仍然將這些問題拋向天空,接下來再考慮convertView的復用問題,固定寫法,當然也可以封裝。

所以目前來看,如果想要一個Adapter與ViewHolder可以通用,那么 至少 必須做如下工作:

  • 將List的泛型參數轉移到Adapter中
  • 封裝 getCount、getItem、getItemId方法
  • 封裝ViewHolder,并解決不同布局控件不統一問題
  • 通用ViewHolder需要找到相應的控件
  • 通用ViewHolder需要提供方法來設置相應控件的內容

##簡單認識SparseArray

在寫萬能ViewHolder之前,先來了解一個新的API。我們知道,在Java中一般會用HashMap以鍵值對的形式來存儲一些數據。但是Android給我們提供了一種工具類 SparseArray ,它是Android框架獨有的類,在標準的JDK中不存在這個類。

為什么需要用SparseArray代替HashMap呢?

SparseArray要比 HashMap 節省內存,某些情況下比HashMap性能更好

那為什么SparseArray性能更好呢?按照官方的解釋,原因有以下幾點:

  • SparseArray不需要對key和value進行自動裝箱
  • 結構比HashMap簡單
  • SparseArray內部主要使用兩個一維數組來保存數據,一個用來存key,一個用來存value
  • 不需要額外的數據結構(主要是針對HashMap中的HashMapEntry 而言的)

從源碼的構造函數來看,與List一樣,可以通過new的形式來創建一個SparseArray,與Map一樣,可以通過 put(int key, E value) 的形式來添加鍵值對。也可以通過 get(int key) 的方式來獲取值。

好了,就介紹這么多,關于具體的用法,文末附有參考資料鏈接,如有需要可以自行查看。

##萬能ViewHolder

現在就來打造萬能ViewHolder,打造之前再次明確我們需要做的事情:

  • 提供方法返回ViewHolder
  • 提供方法獲取控件
  • 提供方法對控件進行設置
  • 提供方法返回復用的View,也就是convertView

先來看如何解決不同布局有不同控件的問題。由于每個控件都有自己固定的ID和控件類型,那么我們可以通過鍵值對的形式來存儲這些控件。在之前可以看到SparseArray能夠提高性能,所以就用SparseArray來存儲控件。

這樣可以先寫出構造函數,在構造函數中,初始化SparseArray,并設置一些內容。

/*** 通用ViewHolder*/ public class CommonViewHolder {//所有控件的集合private SparseArray<View> mViews;//記錄位置 可能會用到private int mPosition;//復用的Viewprivate View mConvertView; 復制代碼

?

/*** 構造函數** @param context 上下文對象* @param parent 父類容器* @param layoutId 布局的ID* @param position item的位置*/public CommonViewHolder(Context context, ViewGroup parent, int layoutId, int position) {this.mPosition = position;this.mViews = new SparseArray<>();//構造方法中就指定布局mConvertView = LayoutInflater.from(context).inflate(layoutId, parent, false);//設置TagmConvertView.setTag(this);} } 復制代碼

接下來我們就需要得到一個ViewHolder,這個比較簡單,大家都能看懂,就是對Adapter中的getView方法進行一定的封裝:

/*** 得到一個ViewHolder** @param context 上下文對象* @param convertView 復用的View* @param parent 父類容器* @param layoutId 布局的ID* @param position item的位置* @return*/ public static CommonViewHolder get(Context context, View convertView, ViewGroup parent, int layoutId, int position) {//如果為空 直接新建一個ViewHolderif (convertView == null) {return new CommonViewHolder(context, parent, layoutId, position);} else {//否則返回一個已經存在的ViewHolderCommonViewHolder viewHolder = (CommonViewHolder) convertView.getTag();//記得更新條目位置viewHolder.mPosition = position;return viewHolder;} } 復制代碼

再接下來就是一個重難點,如何得到布局中的控件?因為我們肯定知道控件的ID,那么可以通過控件的ID來從SparseArray得到具體的控件類型。而Android中所有的控件都是繼承自 View ,所以可以如下這樣寫:

/*** 通過ViewId獲取控件** @param viewId View的Id* @param <T> View的子類* @return 返回View*/ public <T extends View> T getView(int viewId) {View view = mViews.get(viewId);if (view == null) {view = mConvertView.findViewById(viewId);mViews.put(viewId, view);}return (T) view; } 復制代碼

通過上述方法,就能得到對應的控件類型。既然得到了,那么設置控件內容就比較簡單了,在本例中都是TextView,所以我封裝了下面的方法:

/*** 為文本設置text** @param viewId view的Id* @param text 文本* @return 返回ViewHolder*/ public CommonViewHolder setText(int viewId, String text) {TextView tv = getView(viewId);tv.setText(text);return this; } 復制代碼

最后提供一個方法返回復用的convertView,這也比較簡單。

/*** @return 返回復用的View*/ public View getConvertView() {return mConvertView; } 復制代碼

好了,再來看全部的代碼,是不是清晰了很多:

/*** 通用ViewHolder*/ public class CommonViewHolder {//所有控件的集合private SparseArray<View> mViews;//記錄位置 可能會用到private int mPosition;//復用的Viewprivate View mConvertView; 復制代碼

?

/*** 構造函數** @param context 上下文對象* @param parent 父類容器* @param layoutId 布局的ID* @param position item的位置*/public CommonViewHolder(Context context, ViewGroup parent, int layoutId, int position) {this.mPosition = position;this.mViews = new SparseArray<>();mConvertView = LayoutInflater.from(context).inflate(layoutId, parent, false);mConvertView.setTag(this);}/*** 得到一個ViewHolder** @param context 上下文對象* @param convertView 復用的View* @param parent 父類容器* @param layoutId 布局的ID* @param position item的位置* @return*/public static CommonViewHolder get(Context context, View convertView, ViewGroup parent, int layoutId, int position) {//如果為空 直接新建一個ViewHolderif (convertView == null) {return new CommonViewHolder(context, parent, layoutId, position);} else {//否則返回一個已經存在的ViewHolderCommonViewHolder viewHolder = (CommonViewHolder) convertView.getTag();//記得更新條目位置viewHolder.mPosition = position;return viewHolder;}}/*** @return 返回復用的View*/public View getConvertView() {return mConvertView;}/*** 通過ViewId獲取控件** @param viewId View的Id* @param <T> View的子類* @return 返回View*/public <T extends View> T getView(int viewId) {View view = mViews.get(viewId);if (view == null) {view = mConvertView.findViewById(viewId);mViews.put(viewId, view);}return (T) view;}/*** 為文本設置text** @param viewId view的Id* @param text 文本* @return 返回ViewHolder*/public CommonViewHolder setText(int viewId, String text) {TextView tv = getView(viewId);tv.setText(text);return this;} } 復制代碼

接下來我們就重寫一個基于萬能ViewHolder的Adapter,其他方法都不變,主要是getView方法。

@Override public View getView(int position, View convertView, ViewGroup parent) {//得到一個ViewHolderCommonViewHolder viewHolder = CommonViewHolder.get(context, convertView, parent, R.layout.item_list, position);NewsBean bean = list.get(position);//直接設置控件內容,鏈式調用viewHolder.setText(R.id.tv_title, bean.getTitle()).setText(R.id.tv_desc, bean.getDesc()).setText(R.id.tv_time, bean.getTime()).setText(R.id.tv_phone, bean.getPhone());//返回復用的Viewreturn viewHolder.getConvertView(); } 復制代碼

現在來與之前的方法對比,是不是簡單了很多,只需三步:

  • 得到一個ViewHolder
  • 通過這個ViewHolder直接設置控件內容
  • 返回復用的View

看到這里大家肯定有個疑問,在上面ViewHolder中只提供了TextView設置文本的方法,那如果控件不是TextView呢?沒關系,繼續在萬能ViewHolder中封裝就好了:

/*** 設置ImageView** @param viewId view的Id* @param resId 資源Id* @return*/ public CommonViewHolder setImageResource(int viewId, int resId) {ImageView iv = getView(viewId);iv.setImageResource(resId);return this; }/*** 還可以添加更多的方法*/ 復制代碼

至此,我們就搞定了一個通用的“萬能”ViewHolder。

##萬能Adapter

有了萬能ViewHolder,我們就可以來打造萬能Adapter了,在文章開頭已經分析過,需要做的事情有一下幾點:

  • 將Bean對象直接設置成Adapter的泛型
  • 封裝三個固定方法
  • 封裝getView方法
  • 提供方法設置控件內容

先直接上代碼,其實比較簡單,大家應該能看懂:

/*** 通用Adapter抽象類*/ public abstract class CommonAdapter<T> extends BaseAdapter {protected Context context;protected List<T> list;private int layoutId;public CommonAdapter(Context context, List<T> list, int layoutId) {this.context = context;this.list = list;this.layoutId = layoutId;}@Overridepublic int getCount() {return list.size();}@Overridepublic T getItem(int position) {return list.get(position);}@Overridepublic long getItemId(int position) {return position;}/*** 封裝getView方法*/@Overridepublic View getView(int position, View convertView, ViewGroup parent) {//得到一個ViewHolderCommonViewHolder viewHolder = CommonViewHolder.get(context, convertView, parent, layoutId, position);//設置控件內容setViewContent(viewHolder, (T) getItem(position));//返回復用的Viewreturn viewHolder.getConvertView();}/*** 提供抽象方法,來設置控件內容** @param viewHolder 一個ViewHolder* @param t 一個數據集*/public abstract void setViewContent(CommonViewHolder viewHolder, T t); } 復制代碼

這里可以看到我們先自定義一個Adapter繼承BaseAdapter,并將Bean換成Adapter的泛型T了,然后封裝了四個方法。又由于各個控件不一樣,所以提供抽象方法來設置控件內容,我們只要復寫就行了。

此時我們再來看基于萬能ViewHolder的萬能Adapter應該怎樣寫:

/*** 繼承通用Adapter且使用通用Holder的適配器*/ public class CommonAdapterWithCommonHolder extends CommonAdapter<NewsBean> {public CommonAdapterWithCommonHolder(Context context, List<NewsBean> list) {super(context, list,R.layout.item_list);}/*** 復寫抽象方法* @param viewHolder 一個ViewHolder* @param bean Bean對象*/@Overridepublic void setViewContent(CommonViewHolder viewHolder, NewsBean bean) {//直接設置內容 鏈式調用viewHolder.setText(R.id.tv_title, bean.getTitle()).setText(R.id.tv_desc, bean.getDesc()).setText(R.id.tv_time, bean.getTime()).setText(R.id.tv_phone, bean.getPhone());} } 復制代碼

看到這里,是不是有點神奇,對比之前的Adapter,這里只要幾行代碼就OK了。

##結語

由于本文說明的不是一種固定的知識,而是一種設計的思想,所以理解起來比較晦澀難懂。我自己在學這個的時候,也是消化了很久,現在回頭看看真的是很巧妙。

不過值得注意的是,這里說的“萬能”其實就是一個俗稱,代表一種通用的Adapter,能避免項目中的大量的重復代碼,提高代碼質量。而這種通用,不一定就是文中的這樣的格式,這里只是提供一個設計思想與大致流程,大家可以自己寫一個通用的、更加強大的Adapter。

最后由于我水平有限與篇幅限制等原因,在寫文章的過程中,有很多地方寫的不夠詳細或者有明顯的疏漏與錯誤,歡迎大家交流與指正。

##參考資料

Android應用性能優化之使用SparseArray替代HashMap

SparseArray替代HashMap來提高性能

如何打造萬能適配器

##項目源碼 CommonAdapter-GitHub-IamXiaRui


個人博客:www.iamxiarui.com 原文鏈接:http://www.iamxiarui.com/?p=727

總結

以上是生活随笔為你收集整理的Android:打造“万能”Adapter与ViewHolder的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。