溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

listview詳解

發(fā)布時間:2020-07-16 17:21:05 來源:網(wǎng)絡(luò) 閱讀:577 作者:zyl111ok 欄目:移動開發(fā)

首先,讓我們來了解下系統(tǒng)時怎么繪制listview的:

ListView繪制的過程如下:

首先,系統(tǒng)在繪制ListView之前,將會先調(diào)用getCount方法來獲取Item的個數(shù)。

之后每繪制一個Item就會調(diào)用一次getView方法,在此方法(getView)內(nèi)就可以引用事先定義好的xml來確定顯示的效果并返回一個View對象作為一個Item顯示出來。也正是在這個過程中完成了適配器的主要轉(zhuǎn)換功能,把數(shù)據(jù)和資源以開發(fā)者想要的效果顯示出來。也正是getView的重復(fù)調(diào)用,使得ListView的使用更為簡單和靈活。這兩個方法是自定ListView顯示效果中最為重要的,同時只要重寫好了就兩個方法,ListView就能完全按開發(fā)者的要求顯示。而getItem和getItemId方法將會在調(diào)用ListView的響應(yīng)方法的時候被調(diào)用到。所以要保證ListView的各個方法有效的話,這兩個方法也得重寫。比如:沒有完成getItemId方法的功能實現(xiàn)的話,當(dāng)調(diào)用ListView的getItemIdAtPosition方法時將會得不到想要的結(jié)果,因為該方法就是調(diào)用了對應(yīng)的適配器的getItemId方法。

 

Adapter :

Adapter是連接后端數(shù)據(jù)和前端顯示的適配器接口,是數(shù)據(jù)和UIView)之間一個重要的紐帶。在常見的View(ListView,GridView)等地方都需要用到Adapter。如下圖直觀的表達(dá)了Data、Adapter、View三者的關(guān)系:


 listview詳解

 

Adapter的類有好幾個,直接看圖:

listview詳解

 

Android ListView理解,BaseAdapter

ListView是Android開發(fā)過程中較為常見的組件之一,它將數(shù)據(jù)以列表的形式展現(xiàn)出來。一般而言,一個ListView由以下三個元素組成:

1.View,用于展示列表,通常是一個xml所指定的。大家都知道Android的界面基本上是由xml文件負(fù)責(zé)完成的,所以ListView的界面也理所應(yīng)當(dāng)?shù)氖褂昧藊ml定義。例如在ListView中經(jīng)常用到的“android.R.layout.simple_list_item_1”等,就是Android系統(tǒng)內(nèi)部定義好的一個xml文件。

2.適配器,用來將不同的數(shù)據(jù)映射到View上。不同的數(shù)據(jù)對應(yīng)不同的適配器,如ArrayAdapter,CursorAdapter,SimpleAdapter等,他們能夠?qū)?shù)組,指針指向的數(shù)據(jù),Map等數(shù)據(jù)映射到View上。也正是由于適配器的存在,使得ListView的使用相當(dāng)靈活,經(jīng)過適配器的處理后,在view看來所有的數(shù)據(jù)映射過來都是一樣的。

3.數(shù)據(jù),具體的別映射的數(shù)據(jù)和資源,可以是字符串,圖片等,通過適配器,這些數(shù)據(jù)將會被現(xiàn)實到ListView上。所有的數(shù)據(jù)和資源要顯示到ListView上都通過適配器來完成。

系統(tǒng)已有的適配器可以將基本的數(shù)據(jù)顯示到ListView上,如:數(shù)組,Cursor指向的數(shù)據(jù),Map里的數(shù)據(jù)。但是在實際開發(fā)中這些系統(tǒng)已實現(xiàn)的適配器,有時不能滿足我們的需求。而且系統(tǒng)自帶的含有多選功能ListView在實際使用過程中會有一些問題。要實現(xiàn)復(fù)雜的ListView可以通過繼承ListView并重寫相應(yīng)的方法完成,同時也可以通過繼承BaseAdapter來實現(xiàn)。通過文檔可以看出,ArrayAdapter,CursorAdapter,SimpleAdapter都繼承于BaseAdapter。所以通過繼承BaseAdapter就可以完成自己的Adapter,可以將任何復(fù)雜組合的數(shù)據(jù)和資源,以任何你想要的顯示效果展示給大家。

繼承BaseAdapter之后,需要重寫以下四個方法getCount,getItem,getItemId,getView。

getView中可以完成自己想要的界面布局

具體的實現(xiàn)方法如下

public ManagerAdapter(List<User> mRegulatorList, Context context, DataBase mDataBase) {

        this.mRegulatorList = mRegulatorList;

        this.mContext = context;

        this.mDataBase = mDataBase;

    }


    public int getCount() {


        return (this.mRegulatorList.size());

    }


    public Object getItem(int position) {

        return (this.mRegulatorList.get(position));

    }


    public long getItemId(int position) {

        return (position);

    }


    /**

     * 加載xml的條目,實現(xiàn)數(shù)據(jù)的初始化,為自己的控件設(shè)置監(jiān)聽事件

     * @param position

     * @param contentview

     * @param arg2

     * @return

     */

    public View getView(int position, View contentview, ViewGroup arg2) {//加載XML視圖文件


        ViewHolder holder;

        this.regulator = this.mRegulatorList.get(position);

        //如果視圖之間沒有加載過,就加載xml

        if (contentview == null) {

            contentview = LayoutInflater.from(this.mContext).inflate(R.layout.manager_items, null);

            holder = new ViewHolder();

            this.bindID(contentview, holder);

        //保存視圖狀態(tài)

            contentview.setTag(holder);

        } else {

            holder = (ViewHolder) contentview.getTag();

            this.setItemInfo( holder, position, mDataBase);

            this.setClickListener(holder, position, mDataBase);

        }

        return contentview;

    }


    /**

     * 設(shè)置一個容器用于存放控件

     */

    public class ViewHolder {


        TextView txtManagerItemMarkName;

        TextView txtManagertmeLastTouchTime;

        TextView txtManagerItemLastLocation;

        TextView txtManagertgetDeviceName;

        ImageView imgManagerItemUserHead;

        Button btnManagerItemPhone;

        Button btnManagerItemPosition;


    }

/**

     * 綁定視圖ID,holder是組件容器

     *

     * @param contentview

     * @param holder

     */

    private void bindID(View contentview, ViewHolder holder) {

        holder.imgManagerItemUserHead = (ImageView) contentview.findViewById(R.id.manageractivity_imv1);

        holder.txtManagerItemLastLocation = (TextView) contentview.findViewById(R.id.manageractivity_txt2_location);

        holder.txtManagerItemMarkName = (TextView) contentview.findViewById(R.id.manageractivity_txt2_name);

        holder.txtManagertgetDeviceName = (TextView) contentview.findViewById(R.id.manageractivity_txt2_name);

        holder.txtManagertmeLastTouchTime = (TextView) contentview.findViewById(R.id.manageractivity_txt3_time);

        holder.btnManagerItemPhone = (Button) contentview.findViewById(R.id.manageractivity_btn_phone);

        holder.btnManagerItemPosition = (Button) contentview.findViewById(R.id.manageractivity_btn_position);

    }


ListView的工作原理如下:

                 ListView 針對每個item,要求 adapter “返回一個視圖” (getView),也就是說ListView在開始繪制的時候,系統(tǒng)首先調(diào)用getCount()函數(shù),根據(jù)他的返回值得到ListView的長度,然后根據(jù)這個長度,調(diào)用getView()一行一行的繪制ListView的每一項。如果你的getCount()返回值是0的話,列表一行都不會顯示,如果返回1,就只顯示一行。返回幾則顯示幾行。如果我們有幾千幾萬甚至更多的item要顯示怎么辦?為每個Item創(chuàng)建一個新的View?不可能!??!實際上Android早已經(jīng)緩存了這些視圖,大家可以看下下面這個截圖來理解下,這個圖是解釋ListView工作原理的最經(jīng)典的圖了大家可以收藏下,不懂的時候拿來看看,加深理解,其實Android中有個叫做Recycler的構(gòu)件,順帶列舉下與Recycler相關(guān)的已經(jīng)由Google做過N多優(yōu)化過的東東比如:AbsListView.RecyclerListener、ViewDebug.RecyclerTraceType等等,要了解的朋友自己查下,不難理解,下圖是ListView加載數(shù)據(jù)的工作原理(原理圖看不清楚的點(diǎn)擊后看大圖):

listview詳解


下面簡單說下上圖的原理:

  1. 如果你有幾千幾萬甚至更多的選項(item)時,其中只有可見的項目存在內(nèi)存(內(nèi)存內(nèi)存哦,說的優(yōu)化就是說在內(nèi)存中的優(yōu)化?。。。┲校渌脑赗ecycler中

  2. ListView先請求一個type1視圖(getView)然后請求其他可見的項目。convertView在getView中是空(null)的

  3. 當(dāng)item1滾出屏幕,并且一個新的項目從屏幕低端上來時,ListView再請求一個type1視圖。convertView此時不是空值了,它的值是item1。你只需設(shè)定新的數(shù)據(jù)然后返回convertView,不必重新創(chuàng)建一個視圖

  4. 示例代碼如下


private MyCustomAdapter mAdapter;

 

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        mAdapter = new MyCustomAdapter();

        for (int i = 0; i < 50; i++) {

            mAdapter.addItem("item " + i);

        }

        setListAdapter(mAdapter);

    }

 

    private class MyCustomAdapter extends BaseAdapter {

 

        private ArrayList mData = new ArrayList();

        private LayoutInflater mInflater;

 

        public MyCustomAdapter() {

            mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        }

 

        public void addItem(final String item) {

            mData.add(item);

            notifyDataSetChanged();

        }

 

        @Override

        public int getCount() {

            return mData.size();

        }

 

        @Override

        public String getItem(int position) {

            return mData.get(position);

        }

 

        @Override

        public long getItemId(int position) {

            return position;

        }

 

        @Override

        public View getView(int position, View convertView, ViewGroup parent) {

            System.out.println("getView " + position + " " + convertView);

            ViewHolder holder = null;

            if (convertView == null) {

                convertView = mInflater.inflate(R.layout.item1, null);

                holder = new ViewHolder();

                holder.textView = (TextView)convertView.findViewById(R.id.text);

                convertView.setTag(holder);

            } else {

                holder = (ViewHolder)convertView.getTag();

            }

            holder.textView.setText(mData.get(position));

            return convertView;

        }

 

    }

 

    public static class ViewHolder {

        public TextView textView;

    }

}

執(zhí)行程序,查看日志:


listview詳解

getView 被調(diào)用 9 次 ,convertView 對于所有的可見項目是空值(如下):

listview詳解

然后稍微向下滾動List,直到item10出現(xiàn):

listview詳解

 

convertView仍然是空值,因為recycler中沒有視圖(item1的邊緣仍然可見,在頂端)再滾動列表,繼續(xù)滾動:

listview詳解

 

convertView不是空值了!item1離開屏幕到Recycler中去了,然后item11被創(chuàng)建,再滾動下:

listview詳解

           

此時的convertView非空了,在item11離開屏幕之后,它的視圖(…0f8)作為convertView容納item12了,好啦,結(jié)合以上原理,下面來看看今天最主要的話題,主角ListView的優(yōu)化:

             首先,這個地方先記兩個ListView優(yōu)化的一個小點(diǎn):

                       1. ExpandableListView 與 ListActivity 由官方提供的,里面要使用到的ListView是已經(jīng)經(jīng)過優(yōu)化的ListView,如果大家的需求可以用Google自帶的ListView滿足的的話盡量用官方的,絕對沒錯!

                       2.其次,像小馬前面講的,說ListView優(yōu)化,其實并不是指其它的優(yōu)化,就是內(nèi)存是的優(yōu)化,提到內(nèi)存…(想到OOM,折騰了我不少時間),很多很多,先來寫下,如果我們的ListView中的選項僅僅是一些簡單的TextView的話,就好辦啦,消耗不了多少的,但如果你的Item是自定義的Item的話,例如你的自定義Item布局ViewGroup中包含:按鈕、圖片、flash、CheckBox、RadioButton等一系列你能想到的控件的話, 你要在getView中單單使用文章開頭提到的ViewHolder是遠(yuǎn)遠(yuǎn)不夠的,如果數(shù)據(jù)過多,加載的圖片過多過大,你BitmapFactory.decode的猛多的話,OOM搞死你,這個地方再警告下大家,是警告……….也提醒下自己:

                         小馬碰到的問題大家應(yīng)該也都碰到過的,自定義的ListView項亂序問題,我很天真的在getView()中強(qiáng)制清除了下ListView的緩存數(shù)據(jù)convertView,也就是convertView = null了,雖然當(dāng)時是解決了這個問題讓其它每次重繪,但是犯了大錯了,如果數(shù)據(jù)太多的話,出現(xiàn)最最惡心的錯,手機(jī)卡死或強(qiáng)制關(guān)機(jī),關(guān)機(jī)啊哥哥們……O_O,客戶殺了我都有可能,但大家以后別犯這樣的錯了,單單使用清除緩存convertView是解決不了實際問題的,繼續(xù)……
下面是小記:圖片用完了正確的釋放… 

下面來列舉下真正意義上的優(yōu)化吧:

  1.  ViewHolder   Tag 必不可少,這個不多說!

  2. 如果自定義Item中有涉及到圖片等等的,一定要狠狠的處理圖片,圖片占的內(nèi)存是ListView項中最惡心的,處理圖片的方法大致有以下幾種:
    2.1:不要直接拿個路徑就去循環(huán)decodeFile();這是找死….用Option保存圖片大小、不要加載圖片到內(nèi)存去;
    2.2:  拿到的圖片一定要經(jīng)過邊界壓縮
    2.3:在ListView中取圖片時也不要直接拿個路徑去取圖片,而是以WeakReference(使用WeakReference代替強(qiáng)引用。比如可以使        用WeakReference<Context> mContextRef)、SoftReference、WeakHashMap等的來存儲圖片信息,是圖片信息不是圖片哦!
    2.4:在getView中做圖片轉(zhuǎn)換時,產(chǎn)生的中間變量一定及時釋放,用以下形式:

  3. 盡量避免在BaseAdapter中使用static 來定義全局靜態(tài)變量,我以為這個沒影響 ,這個影響很大,static是Java中的一個關(guān)鍵字,當(dāng)用它來修飾成員變量時,那么該變量就屬于該類,而不是該類的實例。所以用static修飾的變量,它的生命周期是很長的,如果用它來引用一些資源耗費(fèi)過多的實例(比如Context的情況最多),這時就要盡量避免使用了..

  4. 如果為了滿足需求下必須使用Context的話:Context盡量使用Application Context,因為Application的Context的生命周期比較長,引用它不會出現(xiàn)內(nèi)存泄露的問題

  5. 盡量避免在ListView適配器中使用線程,因為線程產(chǎn)生內(nèi)存泄露的主要原因在于線程生命周期的不可控制


向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI