ListView是在Android开发中用得非常多的控件之一,并且这些列表还经常需要我们去对listView的数据进行刷新操作,在这种情况下,我们往往都会去调用adapter的notifyDataSetChanged()方法对listView的界面重新进行绘制。众所周知,notifyDataSetChanged()这个方法是Adapter的观察者模式的体现,它的实现原理就是对我们的数据源进行监听,一旦我们的数据源发生了变化,就会去调用getView()方法对整个界面上可见的Item进行刷新。但是,这同时也对很多本不需要刷新的Item也进行了刷新,这样的效率无疑是很低的,当数据量很大的时候还有可能会出现卡顿或者图片闪烁等问题。这对于用户体验上来说,也是很不友好的。
在下文中,我是以一个小的Demo来介绍怎么用非notifyDataSetChanged()的方法来对listView的界面进行刷新,并利用Item的点击来模拟数据源的变化。
不多说,直接看代码:(布局简单,就不放了)
1. 用notifyDataSetChanged()方式刷新界面
package com.example.zohar.androidtest.listView;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import com.example.zohar.androidtest.R;
import java.util.ArrayList;
import java.util.List;
import butterknife.Bind;
import butterknife.ButterKnife;
public class UpdateSingleListViewActivity extends AppCompatActivity {
@Bind(R.id.list_view)
ListView listView;
private List<String> dataList = new ArrayList<>();
private ListViewAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_update_single_list_view);
ButterKnife.bind(this);
initData();
initView();
}
private void initData() {
for (int i = 0; i < 20; i++) {
dataList.add("第" + i + "个数据");
}
}
private void initView() {
adapter = new ListViewAdapter(this, dataList);
listView.setAdapter(adapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Log.d("OnItemClick", "点击了第" + position + "项");
dataList.set(position, "修改后的数据:position = " + position);
adapter.notifyDataSetChanged();
}
});
}
}
package com.example.zohar.androidtest.listView;
import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
import com.example.zohar.androidtest.R;
import java.util.List;
import butterknife.Bind;
import butterknife.ButterKnife;
public class ListViewAdapter extends BaseAdapter {
private List<String> dataList;
private LayoutInflater inflater;
public ListViewAdapter(Context context, List<String> dataList) {
this.dataList = dataList;
inflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
return dataList.size();
}
@Override
public Object getItem(int position) {
return dataList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = inflater.inflate(R.layout.item_layout_update_single_list_view, null);
holder = new ViewHolder(convertView);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
Log.d("TAG", "getView ======> position = " + position);
holder.tvItem.setText(dataList.get(position));
return convertView;
}
static class ViewHolder {
@Bind(R.id.tv_item)
TextView tvItem;
ViewHolder(View view) {
ButterKnife.bind(this, view);
}
}
}
我们来看看打印的Log
11-06 00:36:09.419 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 0
11-06 00:36:09.419 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 1
11-06 00:36:09.419 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 2
11-06 00:36:09.419 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 3
11-06 00:36:09.429 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 4
11-06 00:36:09.429 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 5
11-06 00:36:09.429 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 6
11-06 00:36:09.429 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 7
11-06 00:36:09.429 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 8
11-06 00:36:09.439 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 9
11-06 00:36:09.439 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 10
11-06 00:36:09.439 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 11
11-06 00:36:09.439 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 12
11-06 00:36:09.459 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 13
11-06 00:36:09.459 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 14
11-06 00:36:11.789 24736-24736/com.example.zohar.androidtest D/TAG: 点击了第5项
11-06 00:36:11.839 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 0
11-06 00:36:11.839 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 1
11-06 00:36:11.839 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 2
11-06 00:36:11.839 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 3
11-06 00:36:11.839 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 4
11-06 00:36:11.839 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 5
11-06 00:36:11.839 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 6
11-06 00:36:11.839 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 7
11-06 00:36:11.839 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 8
11-06 00:36:11.839 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 9
11-06 00:36:11.839 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 10
11-06 00:36:11.839 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 11
11-06 00:36:11.839 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 12
11-06 00:36:11.839 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 13
11-06 00:36:11.839 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 14
可以看到,在我点击了第5项item时,我的界面上可见的0~14项这15个item全都被刷新了,但实际上,我只需要它对第5项的item进行刷新就可以的,这相当于我有15分之14的操作都是多余的。
2. 通过直接调用getView()方法来刷新对应item的界面。
既然我们看到Log中打印的是多次调用getView()方法来对界面进行刷新,那么我们可以想想,能否直接通过position这一参数来直接调用对应的getView()方法来达到相同的效果呢?
我们看到adapte的 getView(int position, View convertVie, ViewGroup parent) 中有三个参数,其中position是序号,convertView就是我们item中的子View,parent是我们需要刷新界面的控件。
于是,我们将item的点击事件方法修改为:
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Log.d("TAG", "点击了第" + position + "项");
dataList.set(position, "修改后的数据:position = " + position);
View item = listView.getChildAt(position);
adapter.getView(position, item, listView);
}
});
此时,我们在来看看Log,会发现,我点击某一个item的时候,adapter只会去调用对应position的getView()方法来对界面进行刷新了
11-06 01:03:42.269 24736-24736/com.example.zohar.androidtest D/TAG: 点击了第13项
11-06 01:03:42.269 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 13
但是当我们看界面的时候,却发现一个问题,就是当我可见的item不是从第一条数据开始时,我点击的item的position是13,修改的确实position是18的item。
这是因为当item变成不可见时会回收掉对应的convertView的原因,因此此时调用position为13的getView()方法时,更新的是可见的序号为13的item,也就是整个listView中的序号为18的item。此时,我们就需要去计算出对应item真正的position。
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Log.d("TAG", "点击了第" + position + "项");
dataList.set(position, "修改后的数据:position = " + position);
notifyDataSetChanged(position, listView);
}
});
private void notifyDataSetChanged(int position, ListView listView) {
int firstVisiblePosition = listView.getFirstVisiblePosition();//获得可见的第一个item的position
int lastVisiblePosition = listView.getLastVisiblePosition();//获得可见的最后一个item的position
if (position >= firstVisiblePosition && position <= lastVisiblePosition) {
View view = listView.getChildAt(position - firstVisiblePosition);
adapter.getView(position, view, listView);
}
}
至此,我们便得到了正确的结果,利用adapter的getView()方法,让我们在刷新界面的时候只需要去刷新需要刷新的item。
3. 接下来,假如我们一个item中的控件较多,而我们又只需要刷新其中的某一个控件,要怎么办呢?
通过Debug,我们可以看到在我们通过listView.getChildAt(position)得到的view的tag属性是有值的,而且这个值其实就是adapter的getView()方法返回的子View的数据对象ViewHolder。
那么,我们的 notifyDataSetChanged(int position, ListView listView) 方法就可以改为如下形势,直接通过view.getTag() 方法得到ViewHolder对象,然后就可以修改我们所希望的控件了。
private void notifyDataSetChanged(int position, ListView listView) {
int firstVisiblePosition = listView.getFirstVisiblePosition();
int lastVisiblePosition = listView.getLastVisiblePosition();
if (position >= firstVisiblePosition && position <= lastVisiblePosition) {
ListViewAdapter.ViewHolder holder = (ListViewAdapter.ViewHolder) listView.getChildAt(position - firstVisiblePosition).getTag();
holder.tvItem.setText(dataList.get(position));
}
}
PS:如有发现本文内容错误或不足之处的,欢迎指正。
来源:oschina
链接:https://my.oschina.net/u/2296916/blog/782338