關於 android listview 載入資料錯位(錯亂)問題
一般的關於Adapter中getView的寫法不外乎以下形式:
public
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動態載入技術研究(2)android插件化及實現
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的資料儲存(2)(SQLiteDatebase、SQLiteOpenHelper)
一、android系統的整合輕量級的資料庫,SQLite只是一個嵌入式的資料引擎,專門適用於(手機,PDA)上的適量資料儲存。SQLite資料庫實際上是檔案。 二、android提供了SQLiteDatebase代表一個數據庫(底層就是一個數據庫檔案),一旦程式獲得了代表指定的SQLiteDat
android的資料儲存(1)(SharedPreference、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版本時代的,故首先宣告一下版本。 動機:由於工作中需要對移動端軟體開發的幾種方式進行一下對比研究,故有了此文章的產生,估計後續還會有其他技