ListView,RecyclerView 用过很多,写起来也没什么难度,但最近面试时被问及这个问题,发觉答的都不全面,现总结下最全面的优化方案如下:
- ContentVeiw的复用。在RecyclerView中强制必须复用。listView中就要自己实现,listview的复用机制就是条目很多的时候,只会创建满一屏的条目,当滑动的时候不会再去创建新的view,而是复用已经滑出屏幕的view,对于view有一个缓存。
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
view = inflater.inflate(R.layout.item, null);
}
return convertView;
}
ViewHolder 的使用。RecyclerView自带了ViewHolder 。ListView 需要我们自己实现。ViewHolder作用主要是减少findViewById的操纵。
分页加载。如果数据较多不用全部展示在listView中,可以分页加载。每次往数据源中添加一页的数据,这样如果用户一直刷一直加载,数据源中数据也会越来越多,这个时候页可以保持一页的数据源,每次下拉或上拉就清空数据源在放入上一页或下一页的数据,数据源始终保持一页或几页的数据。
减少item布局层次,可以用ConstraintLayout 约束布局使布局只有一个层次,使用merge和include 绘制布局。
滑动时停止加载图片,停止滑动时再加载图片。例如使用Glide加载图片时可以给listview或recyclerView设置滑动监听
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch (scrollState) {
case SCROLL_STATE_IDLE: //停止滑动
Glide.with(mContext).resumeRequests();
break;
case SCROLL_STATE_FLING://惯性滑动
case SCROLL_STATE_TOUCH_SCROLL://手指触摸滑动
Glide.with(mContext).pauseRequests();
break;
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
}
});
但是这种方式在滑动视觉效果上有点突兀,因为滑动的时候界面不加载,一停止就加载出来了,有点闪动的感觉,这个看取舍了。
错位问题
ListView 和RecyclerView中加载图片时会出现图片错位的问题。出现该问题的条件必须是 复用+异步,缺少一个都不会造成错乱。假设一屏上有7个条目,向上滑动,条目1会复用到条目8的位置。虽然位置不一样了,但是再内存中是同一个指向,还是同一个view。再getView方法中会先刷上位置1的数据,然后再刷上位置8的数据,因为是异布,如果网速再不好,那么会出现这种可能,条目复用到8的位置时刷的数据才加载出图片,如果1处的数据先加载,8的后加载,就出现条目8先出现位置1的图片后又出现条目8的图片,如果8处的图片先加载,就出现条目8先出现位置8的图片,后又出现位置1处的图片。
解决办法,在getView刷数据的时候给View设置图片url的tag,异步加载图片完成后去取tag,判断是否是加载的图片的url。这样在每次刷数据的时候都会给view设置当前加载图片的url,即使复用后异步加载了不属于该位置的图片也不会设置了。
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = inflater.inflate(R.layout.item, null);
}
final ImageView imageView = (ImageView) convertView.findViewById(R.id.img);
final String pic_url = (String) list.get(position);
imageView.setTag(pic_url);
Glide.with(mContext).load(pic_url).into(new SimpleTarget<GlideDrawable>() {
@Override
public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) {
String urlTag = (String) imageView.getTag();
if (!TextUtils.isEmpty(urlTag) && urlTag.equals(pic_url)) {
imageView.setImageDrawable(resource);
}
}
});
return convertView;
}