1. 程式人生 > >關於 android listview 載入資料錯位(錯亂)問題

關於 android listview 載入資料錯位(錯亂)問題

一般的關於Adapter中getView的寫法不外乎以下形式:

@Overridepublic 

ViewgetView(int position, View convertView, ViewGroup parent) 

{  

ViewHolder holder;  

if (convertView == null

{  

convertView = mLayout.inflate(R.layout....);  

holder =new ViewHolder();  

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

... ...  

convertView.setTag(holder);  

else {  

holder = (ViewHolder) convertView.getTag();  

}  

holder.textView.setText(mText + position);  

return convertView;

}

在Android原始碼中關於getView方法的實現就是採用的以上形式,如ArrayAdapter等。因為這種寫法的好處也是顯而易見的,如果該position的convertview曾經被載入過,在資料集合未被改動的前提下,系統會自動將該position的convertview快取起來,避免重複載入耗費資源。


我自己的程式碼:

@Override
public View getView(int position, View view, ViewGroup parent) {

    final ViewHolder mViewHolder;
    if(null == view){
        mViewHolder = new ViewHolder();
        view = LayoutInflater.from(mContext).inflate(R.layout.fragment_new_order_list_item, null);

        mViewHolder.txtPaystatus 
= (TextView) view.findViewById(R.id.order_pay_status); mViewHolder.txtOrdertime = (TextView) view.findViewById(R.id.order_time); mViewHolder.txtCustomerName = (TextView) view.findViewById(R.id.customer_name); mViewHolder.txtCustomerAddress = (TextView) view.findViewById(R.id.customer_address); mViewHolder.txtOrderSendTime = (TextView) view.findViewById(R.id.customer_post_time); mViewHolder.txtOrderGoodsDes = (TextView) view.findViewById(R.id.customer_list_goods_des); mViewHolder.txtCustomerPhone = (TextView) view.findViewById(R.id.customer_phone); mViewHolder.btnOrderOk = (Button) view.findViewById(R.id.order_ok); mViewHolder.btnOrderCancel = (Button) view.findViewById(R.id.order_cancel); mViewHolder.listgoods = (ListView) view.findViewById(R.id.customer_list_goods); if(listOrder.get(position).getPaystatus() == 0) { mViewHolder.txtPaystatus.setText("未付款"); } else { mViewHolder.txtPaystatus.setText("已付款"); } mViewHolder.txtOrdertime.setText(listOrder.get(position).getOrdertime()); mViewHolder.txtCustomerName.setText(listOrder.get(position).getAcceptname()); mViewHolder.txtCustomerAddress.setText(listOrder.get(position).getAcceptlocation()); mViewHolder.txtOrderSendTime.setText(listOrder.get(position).getGoodsarrivetime()); mViewHolder.txtOrderGoodsDes.setText(listOrder.get(position).getOrderSeller().getRemark()); mViewHolder.txtCustomerPhone.setText(listOrder.get(position).getAcceptphonenum()); OrderGoodsListItemAdapter mOrderGoodsListItemAdapter = new OrderGoodsListItemAdapter(mContext, listOrder.get(position).getOrderSeller().getLstOrderGoods()); mViewHolder.listgoods.setAdapter(mOrderGoodsListItemAdapter); setListViewHeightOnChildren(mViewHolder.listgoods); final int index = position; mViewHolder.btnOrderOk.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View v) { String sellerorderid = String.valueOf(listOrder.get(index).getOrderSeller().getSellerorderid()); String token = App.getInstance().sellerInfo.getToken(); orderService.modifyOrder(sellerorderid, "1", token) .subscribe(new Action1<String>() { @Override public void call(String s) { Toast.makeText(mContext, s, Toast.LENGTH_SHORT).show(); newOrderFragment.listOrder.clear(); newOrderFragment.pageindex = 1; newOrderFragment.requestData(); App.isNeedFreshData = true; App.isNeedFresShophData = true; } }, new Action1<Throwable>() { @Override public void call(Throwable throwable) { Toast.makeText(mContext, throwable.getMessage(), Toast.LENGTH_SHORT).show(); } }); } }); mViewHolder.btnOrderCancel.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View v) { String sellerorderid = String.valueOf(listOrder.get(index).getOrderSeller().getSellerorderid()); String token = App.getInstance().sellerInfo.getToken(); orderService.modifyOrder(sellerorderid, "2", token) .subscribe(new Action1<String>() { @Override public void call(String s) { Toast.makeText(mContext, s, Toast.LENGTH_SHORT).show(); newOrderFragment.listOrder.clear(); newOrderFragment.pageindex = 1; newOrderFragment.requestData(); App.isNeedFreshData = true; App.isNeedFresShophData = true; } }, new Action1<Throwable>() { @Override public void call(Throwable throwable) { Toast.makeText(mContext, throwable.getMessage(), Toast.LENGTH_SHORT).show(); } }); } }); view.setTag(mViewHolder); } else { mViewHolder = (ViewHolder) view.getTag(); } return view; }

然後問題就來了,當時我就”自作小聰明“或者說“沒有注意”,覺得當convertview==null時只是做了item佈局的載入以及相關控制元件ID的繫結操作,為什麼連內容的載入操作也放入其中呢,這樣下次載入快取是就省去內容set的操作了,然後就出現了滑動ListView後資料顯示錯位的問題。

剖析原因:

後來看原始碼發現,原來AbListView中獲取getView()和滑動操作是非同步進行的,其中滑動操作在一個FlingRunnable的支執行緒中執行,所以這就導致了在ListView在滑動時可能已經滑動到了第十行,但可能第二行的資料這時就被直接使用了,這就是導致資料載入錯亂的根本原因。
附上原始碼中對FlingRunnable的註釋:

/**
 * Responsible for fling behavior. Use {@link #start(int)} to
 * initiate a fling. Each frame of the fling is handled in {@link #run()}.
 * A FlingRunnable will keep re-posting itself until the fling is done.
 *
 */
private class FlingRunnable implements Runnable {
    /**
     * Tracks the decay of a fling scroll
     */
    private final OverScroller mScroller;
    ... ...
}

解決方法

所以唯一的解決方法就是隻在convertview中快取該ChildView的layout,但ChildView 中的資料必須每次都重新獲取並載入。

修改後的程式碼:

@Override
public View getView(int position, View view, ViewGroup parent) {

    final ViewHolder mViewHolder;
    if(null == view){
        mViewHolder = new ViewHolder();
        view = LayoutInflater.from(mContext).inflate(R.layout.fragment_new_order_list_item, null);

        mViewHolder.txtPaystatus = (TextView) view.findViewById(R.id.order_pay_status);
        mViewHolder.txtOrdertime = (TextView) view.findViewById(R.id.order_time);
        mViewHolder.txtCustomerName = (TextView) view.findViewById(R.id.customer_name);
        mViewHolder.txtCustomerAddress = (TextView) view.findViewById(R.id.customer_address);
        mViewHolder.txtOrderSendTime = (TextView) view.findViewById(R.id.customer_post_time);
        mViewHolder.txtOrderGoodsDes = (TextView) view.findViewById(R.id.customer_list_goods_des);
        mViewHolder.txtCustomerPhone = (TextView) view.findViewById(R.id.customer_phone);
        mViewHolder.btnOrderOk = (Button) view.findViewById(R.id.order_ok);
        mViewHolder.btnOrderCancel = (Button) view.findViewById(R.id.order_cancel);
        mViewHolder.listgoods = (ListView) view.findViewById(R.id.customer_list_goods);
        view.setTag(mViewHolder);
    }
    else
{
        mViewHolder = (ViewHolder) view.getTag();
    }
    if(listOrder.get(position).getPaystatus() == 0)
    {
        mViewHolder.txtPaystatus.setText("未付款");
    }
    else
{
        mViewHolder.txtPaystatus.setText("已付款");
    }

    mViewHolder.txtOrdertime.setText(listOrder.get(position).getOrdertime());
    mViewHolder.txtCustomerName.setText(listOrder.get(position).getAcceptname());
    mViewHolder.txtCustomerAddress.setText(listOrder.get(position).getAcceptlocation());
    mViewHolder.txtOrderSendTime.setText(listOrder.get(position).getGoodsarrivetime());
    mViewHolder.txtOrderGoodsDes.setText(listOrder.get(position).getOrderSeller().getRemark());
    mViewHolder.txtCustomerPhone.setText(listOrder.get(position).getAcceptphonenum());

    OrderGoodsListItemAdapter mOrderGoodsListItemAdapter = new OrderGoodsListItemAdapter(mContext,
            listOrder.get(position).getOrderSeller().getLstOrderGoods());
    mViewHolder.listgoods.setAdapter(mOrderGoodsListItemAdapter);
    setListViewHeightOnChildren(mViewHolder.listgoods);
    final int index = position;
    mViewHolder.btnOrderOk.setOnClickListener(new View.OnClickListener(){
        @Override
public void onClick(View v) {
            String sellerorderid = String.valueOf(listOrder.get(index).getOrderSeller().getSellerorderid());
            String token = App.getInstance().sellerInfo.getToken();
            orderService.modifyOrder(sellerorderid, "1", token)
                    .subscribe(new Action1<String>() {
                        @Override
public void call(String s) {
                            Toast.makeText(mContext, s, Toast.LENGTH_SHORT).show();
                            newOrderFragment.listOrder.clear();
                            newOrderFragment.pageindex = 1;
                            newOrderFragment.requestData();
                            App.isNeedFreshData = true;
                            App.isNeedFresShophData = true;
                        }
                    }, new Action1<Throwable>() {
                        @Override
public void call(Throwable throwable) {
                            Toast.makeText(mContext, throwable.getMessage(), Toast.LENGTH_SHORT).show();
                        }
                    });
        }
    });

    mViewHolder.btnOrderCancel.setOnClickListener(new View.OnClickListener(){
        @Override
public void onClick(View v) {
            String sellerorderid = String.valueOf(listOrder.get(index).getOrderSeller().getSellerorderid());
            String token = App.getInstance().sellerInfo.getToken();
            orderService.modifyOrder(sellerorderid, "2", token)
                    .subscribe(new Action1<String>() {
                        @Override
public void call(String s) {
                            Toast.makeText(mContext, s, Toast.LENGTH_SHORT).show();
                            newOrderFragment.listOrder.clear();
                            newOrderFragment.pageindex = 1;
                            newOrderFragment.requestData();
                            App.isNeedFreshData = true;
                            App.isNeedFresShophData = true;
                        }
                    }, new Action1<Throwable>() {
                        @Override
public void call(Throwable throwable) {
                            Toast.makeText(mContext, throwable.getMessage(), Toast.LENGTH_SHORT).show();
                        }
                    });
        }
    });
    return view;
}

其實ListView資料載入及資料快取是比較複雜的,所以以後有機會還是要好好研讀原始碼,這樣才能有助於提升自己開發Android的效能和對Android工作的原理的理解。

相關推薦

關於 android listview 載入資料錯位錯亂問題

一般的關於Adapter中getView的寫法不外乎以下形式: @Overridepublic  ViewgetView(int position, View convertView, ViewGroup parent)  {   ViewHolder holder;  

Android基礎之資料儲存SharedPreference

Android資料持久化是說在斷電後資料不會丟失,而根據儲存位置和實現方式一般有3種方式,這裡說sharedpreferences: 一,sharedpreferences儲存 該種方式是在應用獨有目錄data/data/[packgename]/shared_prefs/下

Android編譯生成資料out詳解

Android編譯生成的所有檔案都是和原始碼分離的,所有中間檔案和結果都放在out資料夾中。out資料夾結構如下: |-- host/ # 構建原始碼需要的工具和庫檔案 |-- target/product/generi

Android listview載入資料後沒有立即重新整理的解決辦法

最近用到listview顯示從伺服器拉取回來的json資料,主要是圖片和文字資源。      Listview在載入完資料後,當我們需要ListView進行重新整理的時候,我們需要呼叫Adapter.

Android&OpenCv之Android程式載入OpenCv庫

好的開始是成功的一半,在第一節中我總結了如何在Eclipse中建立開發帶有OpenCv庫的Android APP的環境的工作。 本節講如何在Android程式中載入OpenCv庫,以便後續開發工作中呼叫OpenCv提供的API的介面。 我們知道OpenCv庫作為一個.so的

Android中用SmartRefreshLayout實現ListView列表的資料重新整理與載入更多總結

這裡用到的是第三方外掛:SmartRefreshLayout 效果圖如下: 使用步驟如下: 1、新增遠端依賴 /*重新整理和載入*/ implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0-

android ListView佈局之三使用自定義的Adapter繫結資料,通過contextView.setTag繫結資料有按鈕的ListView

http://blog.csdn.net/chenzheng_java/article/details/6202586 最終結果圖: 程式碼結構示意圖 vlist2.xml程式碼: &

Android自定義控制元件 下拉重新整理,上拉分頁載入更多(支援ListView, GridView, ScrollView)

        首先說明,這幾篇文章是對一些前輩的成果進行學習後的心得總結。也借這種方式對他們表示謝意。         最近專案中好幾個模組都用到listview和gridview的下拉重新整理,上拉載入更多等功能,而且涉及到圖片的批量下載。水平有限,首先是想到找一些比較

Android中apk動態載入技術研究2android插件化及實現

name creat package path iss fontsize 調用 dex con 了解了android中類載入的前期知識點後,來看看android中DexClassLoader詳細的實現 詳細載入流程例如以下: 宿主程序會到文件系統比

Android手機通過wifi進行資料傳輸

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

android資料儲存3(LitePal)

在上一章的SQLiteDatebase來操作資料庫好用嗎?不同的人有不同的答案,接下來你將接觸一個開源庫LitePal,它採用了物件關係對映的(ORM)的模式,並將我們平常用到的資料庫功能進行封裝,使用一行sql語句就可以完成各種建表和增刪改查的操作。   一、配置LitePal

android資料儲存2SQLiteDatebase、SQLiteOpenHelper)

一、android系統的整合輕量級的資料庫,SQLite只是一個嵌入式的資料引擎,專門適用於(手機,PDA)上的適量資料儲存。SQLite資料庫實際上是檔案。 二、android提供了SQLiteDatebase代表一個數據庫(底層就是一個數據庫檔案),一旦程式獲得了代表指定的SQLiteDat

android資料儲存1SharedPreference、File)

一、有些時候程式有少量的資料需要儲存時,而且格式簡單,是普通的字串,標量等,對於這種資料android提供了SharedPreference進行儲存。   二、SharedPreference儲存的資料是簡單的key--value對,SharedPreference介面主要負責,讀

Android 控制元件之 RecyclerView—— 載入檢視和佈局選擇

本文目錄 一、概述 二、列表檢視的處理 1. item 的佈局檔案 2. 構造 Adapter 類 3. 佈局管理器 1)LinearLayoutManager 2)GridLayoutManager

Android WebView載入https網頁親測

只需加兩處設定: 1 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { settings.

android listview中插入按鈕或圖片的並新增監聽器

        最近,剛搞android,要實現一個類似listview的東西,要其中每個item中均有不同的控制元件,且要每個控制元件均能接受點選事件並通知到底是哪個item中的哪個控制元件被點選了。         之前,在網上看到類似的帖子,能夠實現每個item中新增

Ubuntu18.04 載入windows 共享資料自用

主要參考部落格 https://blog.csdn.net/jzzy_hony/article/details/81353944 在已經建立好共享資料夾的條件下,在控制終端執行以下命令,便可載入window下的共享資料夾 sudo vmhgfs-fuse .host:/ /mnt/hgfs

android利用ksoap2返回複雜資料資料dataset

在讀這篇文章之前建議你先讀一下上一篇文章android如何使用ksoap2對sql server的操作實現登陸,原理是一樣的只是返回的資料不同而已。 web端程式碼: //database.cs檔案利用ADO.NET技術 public DataSet G

Android Gallery3D原始碼學習總結——Cache快取及資料處理流程

第一,在應用程式中有三個執行緒存在:主執行緒(隨activity的宣告週期啟動銷燬)、feed初始化執行緒(進入程式時只執行一次,用於載入相簿初始資訊)、feed監聽執行緒(一直在跑,監聽相簿和相片的變更)。 第二,不考慮CacheService 啟動的主要流程歸納如下: 1

android studio建立第一個安卓程式載入html5頁面

前言 軟體版本:android studio v1.0正式版,由於v0.x以來軟體變化一直比較大,很多問題搜尋的解決方案也都是v0.x版本時代的,故首先宣告一下版本。 動機:由於工作中需要對移動端軟體開發的幾種方式進行一下對比研究,故有了此文章的產生,估計後續還會有其他技