listview、gridview單項更新及滑動時資料錯亂重複問題
前言
listview和gridview原理是一樣的,只是顯示方式不一樣。這裡我就以gridview來說明
首先,為什麼要單項更新?因為notifyDataSetChanged()方法是重新整理整個資料,當我們的資料量很大時,原本只需要重新整理一項,但是整個資料都重新整理了,這會導致操作不流暢,因此單項重新整理很有必要。
單項重新整理
我這裡的需求是監聽gridview的點選事件,當點選某一項時把這一項標記為選擇狀態,再次點選時取消標記。這裡使用List儲存需要標記項的索引,當集合中包含當前項時,說明已經標記需要取消標記,反之標記當前項。
public List<String> mSelectedDelete = new LinkedList<String>();
class OnGridItemClickClick implements OnItemClickListener {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (mSelectedDelete.contains(String.valueOf(position))) {
mSelectedDelete.remove(String.valueOf(position));
setItemStatus(position, false );
} else {
mSelectedDelete.add(String.valueOf(position));
setItemStatus(position, true);
}
}
}
}
public void setItemStatus(int position, boolean b) {
View localView = getViewByPosition(position, gridView);
ImageView img = (ImageView) localView.findViewById(R.id.img_state);
if (b) {
img.setVisibility(View.VISIBLE);
} else {
img.setVisibility(View.GONE);
}
}
//根據pos獲得此項檢視
public View getViewByPosition(int pos, GridView gridView) {
int firstListItemPosition = gridView.getFirstVisiblePosition();
int lastListItemPosition = firstListItemPosition + gridView.getChildCount() - 1;
if (pos < firstListItemPosition || pos > lastListItemPosition) {
return gridView.getAdapter().getView(pos, null, gridView);
} else {
final int childIndex = pos - firstListItemPosition;
return gridView.getChildAt(childIndex);
}
}
資料錯亂
可以看到,當我選中了第一個,但是在滑動過程中,沒有被選中的項被標記為了選中狀態,選中項資料錯亂了。(根據情況的不同,資料可以是文字,checkbox的選中狀態,圖片,背景色,控制元件的顯示狀態等等…)
說道資料錯亂,首先說一下listview、gridview的複用機制
上圖可知listview的快取優化機制,滾出螢幕的檢視會被快取下來並被複用。這種方法就出現了上述的問題,他會導致滑動時控制元件的資料錯亂。解決也很簡單,上面的mSelectedDelete 集合就派上用場了。
看程式碼:
public class GridViewAdapter extends BaseAdapter {
private Context mContext;
public GridViewAdapter(Context mContext) {
super();
this.mContext = mContext;
}
@Override
public int getCount() {
return imageInfoList.size();
}
@Override
public Object getItem(int position) {
return imageInfoList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null) {
holder = new ViewHolder();
convertView = LayoutInflater.from(this.mContext).inflate(R.layout.gridview_item_file, null, false);
holder.imgState = (ImageView) convertView.findViewById(R.id.img_state);
holder.imgFile = (ImageView) convertView.findViewById(R.id.img_file);
holder.relativeParent = (RelativeLayout) convertView.findViewById(R.id.relative_item);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
if (imageInfoList != null) {
int width = gridView.getWidth() / 3;
int height = width;
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(width, height);
holder.relativeParent.setLayoutParams(params);
String url = imageInfoList.get(position);
holder.imgFile.setVisibility(View.VISIBLE);
Glide.with(GridViewActivity.this).load(url).fitCenter().into(holder.imgFile);
//判斷是否為標記項
if (mSelectedDelete.contains(String.valueOf(position)))
// 顯示
holder.imgState.setVisibility(View.VISIBLE);
else
// 隱藏
holder.imgState.setVisibility(View.GONE);
}
return convertView;
}
private class ViewHolder {
RelativeLayout relativeParent;
ImageView imgState, imgFile;
}
}
在這裡我的例子是view的顯示狀態,當然解決思路是一樣的。程式碼中我用一個集合來儲存需要標記的item的position值(這裡不能直接儲存int,要轉成string,不然會格式混亂),在getView()中判斷此項是否是被標記項來判斷是否顯示view。
ps:
最好的方式是建一個HashMap來儲存兩個資料,第一個就是item的position值,這個值表示的是item真正在listview的序號而不是頁面上這個item所在的序號。第二個則是這個position的事件情況,可用false,true表示。 當更新資料時,同時更新HashMap裡面對應項的狀態,然後在getview中根據HashMap裡面的對應狀態去改變,這樣資料事件就是根據HashMap中存入的資料來判斷,而不是複用時的直接使用了。