代码优化Android ListView适配器三级优化详解
轉載本專欄每一篇博客請注明轉載出處地址,尊重原創。此博客轉載鏈接地址:點擊打開鏈接? http://blog.csdn.net/qq_32059827/article/details/52718489
對ListView的優化,也就是對其封裝:抽取方法共性,封裝 BaseAdapter 和 ViewHolder
大多App都會使用到的基本控件 ——- Listiew,特別像新聞瀏覽類的比如說“今日關注”,或者“應用寶”這種匯集手機軟件集合的。而且大家都知道 需要給每個單獨的 ListView 搭配相應的適配器 Adapter 。如果你的項目中使用ListView 的頻率很少甚至沒有,那我不建議你對 ListView 進行抽取封裝,但是!如果它的使用滲透到App中大多頁面時,你必須考慮 對Adapter的公共方法進行抽取封裝到單獨的類中,來避免整個項目代碼的冗雜,使代碼結構規范化!
首先,我們在寫ListView的時候一般會這么寫:
一. 未封裝版 ListView@Override public View onCreateSuccessView() {ListView view = new ListView(UIUtils.getContext());initData();view.setAdapter(new MyListViewAdapter());return view; }private void initData() {for (int i = 0; i < 50; i++) {mList.add("測試數據" + i);} }class MyListViewAdapterextends BaseAdapter {@Overridepublic int getCount() {return mList.size();}@Overridepublic String getItem(int position) {return mList.get(position);}@Overridepublic long getItemId(int position) {return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {ViewHolder holder = null;if (convertView == null) {convertView = View.inflate(UIUtils.getContext(),R.layout.list_item_home, null);holder = new ViewHolder();holder.mListTextView= (TextView) convertView.findViewById(R.id.tv_content);convertView.setTag(holder);} else {holder = (ViewHolder) convertView.getTag();}holder.mListTextView.setText(getItem(position));return convertView;} }static class ViewHolder {public TextView mListTextView; }這里我們主要 把重點放在 Adapter 和 ViewHolder 的抽取封裝,首先簡單分析其邏輯組成:
1.首先看到我自定義的 HomeAdapter 繼承的是 BaseAdapter 。繼承的四個方法中,前三個:getCount 、getItem 、getItemId 看的出來方法及其簡單,
但是getView方法中步驟略復雜,首先梳理清楚方法里的邏輯,才好進一步的封裝:
(1)加載布局文件,布局轉換(xml —> view)
(2)初始化控件(finViewById)
(3)給ViewHolder設置標記(setTag),便于下次復用
(4)給控件設置數據
封裝一》》》普通封裝:方式,保留getView(),getCount 、getItem 、getItemId 先封裝。
方法:抽取類名:MyBaseAdapter。基類是需要一個數據源的,因此通過構造方法得到這個數據源;注意:數據源是什么類型,通過泛型指定。
因為抽取了一些方法,我們原來的Adapter類繼承自MyBaseAdapter的代碼就簡單了一些,如下:
private class MyListViewAdapter extends MyBaseAdapter<String>{//在這里告知父親,我繼承你我要給你的集合傳遞什么類型//通過構造函數,把孩子的集合傳給父親public MyListViewAdapter(ArrayList<String> list) {super(list);}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {// 自定義listview界面ViewHolder holder = null;if(convertView == null){holder = new ViewHolder();//1、加載item布局convertView = UIUtils.inflate(R.layout.homefragment_listview_item);//2、獲取item布局實例holder.mListTextView = (TextView) convertView.findViewById(R.id.tv_listview_item_text);//3、viewholder設置到tagconvertView.setTag(holder);}else{//若不為空,在緩存對象里取出holder = (ViewHolder) convertView.getTag();}//4、設置listview布局上的控件數據holder.mListTextView.setText(getItem(position));return convertView;}}同時,原來實例化適配器代碼也要稍作修改,傳遞數據源到基類里面: //獲取適配器對象 MyListViewAdapter listAdapter = new MyListViewAdapter(mListDatas);
現在運行程序,跟以前的效果是一樣的。如下:
到目前為止,叫做是普通抽取,也就是60%應用可能是這么做的,對于每個子類的getView()都是自己實現自己的。
但是要想再高大上一些,要想根那些牛逼點的app一樣,就要抽取getView(),對于這一點,比較復雜。接下來就一點點的演變整個過程。
在演變之前,要對ListView的加載流程幾個問題要清楚,只有理解了ListView的加載流程,才能更好的而理解抽取getView()的思想。
HomeHolder抽取前思考問
HomeHolder抽取前思考答
為什么想到去抽取?
convertView的作用 ?
相信您已經理解了LitView的加載流程,就看一下第一次演變:
對ViewHolder抽取,定義為HomeHolder。
getView()有兩層:視圖層V、數據源模型層M。其實屬于典型的MVC。抽取的過程,也按照MVC模式
整體的步驟,都詳細的注釋在代碼中了,一目了然:
public class HomeHolder {//根據getView的設置過程來寫這里的代碼//getView()過程://0、初始化ViewHolder//1、初始化布局//2、viewholder設置到根布局的tag。這里根布局給了mHolderView【viewHolder可以綁定tag的條件:該viewHolder要有孩子的布局,或者所有根布局的控件實例】//3、初始化孩子對象,即item布局上的控件實例//4、給孩子(item的布局控件實例)設置數據/*******************初始化視圖************************/public View mHolderView;//條件:做Holder需要有孩子對象。該viewHolder要有孩子所有根布局的控件實例,該布局只有一個控件,就是展示文本TextView mListTextView;private String mData;//0、初始化ViewHolderpublic HomeHolder(){/**可見,構造方法一調用,初始化了viewholder、初始化了布局、布局控件實例化、此viewHolder綁定到了當前布局的tag中**/mHolderView = initView();//2、viewholder設置到根布局的tag。這里根布局給了mHolderViewmHolderView.setTag(this);//this指當前的viewHolder}//1、初始化布局private View initView() {//return UIUtils.inflate(R.layout.homefragment_listview_item);View view = UIUtils.inflate(R.layout.homefragment_listview_item);//3、初始化孩子對象,即item布局上的控件實例mListTextView = (TextView) view.findViewById(R.id.tv_listview_item_text);return view;}/***********************初始化數據***********************///4、給孩子(item的布局控件實例)設置數據public void setDataAndRefreshHolderView(String data) {//保存數據this.mData = data;//刷新顯示,設置數據refreshHolderView(data);}private void refreshHolderView(String data) {// 刷新數據顯示mListTextView.setText(data);} }經過抽取ViewHolder后,再看一看getView()代碼怎么寫: @Overridepublic View getView(int position, View convertView, ViewGroup parent) {/**********************初始化視圖 決定根布局***********************/HomeHolder holder = null;if(convertView == null){holder = new HomeHolder();}else{holder = (HomeHolder) convertView.getTag();}/**********************數據刷新顯示***********************/holder.setDataAndRefreshHolderView(getItem(position));return holder.mHolderView;}}
可見,getView()明顯的少了好多代碼,一下子變得輕松了許多。運行程序,結果一模一樣。
這個時候,明顯輕松了很多,但是上邊的抽取僅僅是針對HomeHolder的,如果頁面很多的話,顯然要寫很多的Holder類。為了節省Holder的代碼,在網上抽取。
》》》》HomeHolder抽取成BaseHolder。該類的抽取,是基于上邊HomeHolder做修改的。把覺得基類取不到的具體東西定義為抽象方法。例如:加載具體的布局、設置刷新具體的數據。在基類里面都無法得知,交給子類實現。并且,由于成了基類,所以具體的設置的數據類型也不清楚,所以設置傳遞數據的時候,使用泛型
那么最后,再回到getView()所在的MyListViewAdapter適配器類。當然記得,它也是有父類的MyBaseAdapter。
父類里面還有一個getView()沒去寫代碼,最后就去搞一搞父親的這個方法,讓兩個基類MyBaseAdapter和BaseHolder打招呼整合一下。把子類的getView()放到基類里面去
在基類里面做如下修改(與BaseHolder建立了連接)
public abstract class MyBaseAdapter<T> extends BaseAdapter {private ArrayList<T> mList;public MyBaseAdapter(ArrayList<T> list){this.mList = list;}@Overridepublic int getCount() {// TODO Auto-generated method stubreturn mList.size();}@Overridepublic T getItem(int position) {// TODO Auto-generated method stubreturn mList.get(position);}@Overridepublic long getItemId(int position) {// TODO Auto-generated method stubreturn position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {/**********************初始化視圖 決定根布局***********************///基類根基類交談,不能使用具體的實現類BaseHolder<T> holder = null;if(convertView == null){//該holder應該是具體哪個Holder不清楚。如:HomeHolder、AppHolder等。因此定義抽象方法獲取holder = getSpecialHolder();}else{holder = (BaseHolder) convertView.getTag();//注意:從緩存里面取tag}/**********************數據刷新顯示***********************/holder.setDataAndRefreshHolderView(getItem(position));return holder.mHolderView;}/*** 返回具體的BaseHolder的子類* @call getView()方法中,如果沒有converView的時候被創建* @return*/public abstract BaseHolder<T> getSpecialHolder();}Adapter的基類抽象方法,且在獲取Holder對象時,調用子類具體哪個Holder,只需要在子類中實現這個方法,并且返回具體的哪個Holder就好了。代碼如下: private class MyListViewAdapter extends MyBaseAdapter<String>{//在這里告知父親,我繼承你我要給你的集合傳遞什么類型//通過構造函數,把孩子的集合傳給父親public MyListViewAdapter(ArrayList<String> list) {super(list);}@Overridepublic BaseHolder<String> getSpecialHolder() {// TODO Auto-generated method stubreturn new HomeHolder();}}這個時候,您發現getView方法的代碼以及適配器里面的代碼少了太多太多了。而且運行結果還是一樣的。
可能僅僅一個Adapter看不出有多強大,如果說有100個類需要適配器的話,那么只需要一個基類,其他的只要繼承自基類,每個適配器類里面的方法就上邊那幾行,很顯然,簡化了大量的冗余的代碼。
最后再總結一下此時的調用加載流程。
當ListView想要關聯適配器的時候,創建自己的adapter適配器類對象,同時把集合數據源數據傳遞過去。
例如此例中的MyListViewAdapter listAdapter = new MyListViewAdapter(mListDatas);這樣把mListDatas傳給了adapter的基類,是通過帶參構造函數的形式傳給adapter父類的,他把以前孩子自己要寫的getCount 、getItem 、getItemId方法幫孩子完成,孩子簡寫三大方法。同時,在ListVIew的item加載布局和數據的時候,getView方法被調用,此時的getView()方法在adapter基類里面呢,對于item的布局的顯示和布局實例的數據刷新,都封裝在了對應的Holder類里面;先執行holder = getSpecialHolder();方法來看該adapter配套的Holder是誰。馬上調用該adapter的實現方法
??????? @Override
?? ??? ?public BaseHolder<String> getSpecialHolder() {
?? ??? ??? ?// TODO Auto-generated method stub
?? ??? ??? ?return new HomeHolder();
?? ??? ?}
看到返回的是HomeHolder,在new HomeHolder();的同時BaseHolder的構造也會被加載(子類初始化先初識化父類構造),此時父類的構造函數
?????????? public BaseHolder(){/**可見,構造方法一調用,初始化了viewholder、初始化了布局、布局控件實例化、此viewHolder綁定到了當前布局的tag中**/
?? ??? ??? ?mHolderView = initHolderView();
?? ??? ??? ?//2、viewholder設置到根布局的tag。因為能獲取item的根布局也可以設置tag
?? ??? ??? ?mHolderView.setTag(this);//this指當前的viewHolder
?? ??? ?}
加載了布局、設置了tag。注意此時public abstract View initHolderView();是調用對應子類實現類HomeHolder的具體方法(調用父類抽象實際調用子類實現方法)。
此時HomeHolder中的
@Override
?? ?public View initHolderView() {
?? ??? ?View view = UIUtils.inflate(R.layout.homefragment_listview_item);
?? ??? ?mListTextView = (TextView) view
?? ??? ??? ??? ?.findViewById(R.id.tv_listview_item_text);
?? ??? ?return view;
?? ?}
被調用,終于看到在哪里初始化布局了!
這一系列方法走完之后,再回到adapter基類的getView()方法,繼續往下執行到holder.setDataAndRefreshHolderView(getItem(position));。同上,先調用holder的
?? ??? //4、給孩子(item的布局控件實例)設置數據
?? ??? ?public void setDataAndRefreshHolderView(T data) {//只有HomeHolder子類是String類型,其他的孩子不一定也是String,有可能還是bean等。因此使用泛型
?? ??? ??? ?//保存數據
?? ??? ??? ?this.mData = data;
?? ??? ??? ?//刷新顯示,設置數據
?? ??? ??? ?refreshHolderView(data);
?? ??? ?}
?refreshHolderView(data);抽象,調用實現類HomeHolder的實現類方法,完成了數據的設置和刷新。
到此,此次ListView的優化,以及詳細的解析終于結束了。
9點半開始寫博客,現在0:36,搞了3個多小時。由衷佩服自己,看完您記得關注本博客,或者點贊留下包括意見哦。
轉載于:https://www.cnblogs.com/wanghang/p/6299577.html
總結
以上是生活随笔為你收集整理的代码优化Android ListView适配器三级优化详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android内存优化2—使用软引用和弱
- 下一篇: 解决 GTK+/GNOME 3 环境下