在最近的一次工作中,要做一个广告展示栏。果断使用ViewPager来实现,但是,在实现玩效果之后,打印日志发现一个巨大的问题。ViewPager在多次划动后 ViewPager.getChildCount(),竟然会无限自增。
好在老夫花了2个小时研究了一下ViewGroup的源码,总算把问题解决了。接下来进入正题。
首先:
ViewPager是ViewGroup的子类,getChildCount()的作用并不是说获取该GroupView下有多少个子视图,而是有多少个可见视图。
好,第二步,我们查看源码:
/*由此可以确定,mChildrenCount就是我们的线索*/
public int getChildCount() {
return mChildrenCount;
}
/*自增*/
private void addInArray(View child, int index) {
View[] children = mChildren;
final int count = mChildrenCount;
final int size = children.length;
if (index == count) {
if (size == count) {
mChildren = new View[size + ARRAY_CAPACITY_INCREMENT];
System.arraycopy(children, 0, mChildren, 0, size);
children = mChildren;
}
children[mChildrenCount++] = child;
} else if (index < count) {
if (size == count) {
mChildren = new View[size + ARRAY_CAPACITY_INCREMENT];
System.arraycopy(children, 0, mChildren, 0, index);
System.arraycopy(children, index, mChildren, index + 1, count - index);
children = mChildren;
} else {
System.arraycopy(children, index, children, index + 1, count - index);
}
children[index] = child;
mChildrenCount++;
if (mLastTouchDownIndex >= index) {
mLastTouchDownIndex++;
}
} else {
throw new IndexOutOfBoundsException("index=" + index + " count=" + count);
}
}
/*自减*/
private void removeFromArray(int index) {
final View[] children = mChildren;
if (!(mTransitioningViews != null && mTransitioningViews.contains(children[index]))) {
children[index].mParent = null;
}
final int count = mChildrenCount;
if (index == count - 1) {
children[--mChildrenCount] = null;
} else if (index >= 0 && index < count) {
System.arraycopy(children, index + 1, children, index, count - index - 1);
children[--mChildrenCount] = null;
} else {
throw new IndexOutOfBoundsException();
}
if (mLastTouchDownIndex == index) {
mLastTouchDownTime = 0;
mLastTouchDownIndex = -1;
} else if (mLastTouchDownIndex > index) {
mLastTouchDownIndex--;
}
}
/*调用自减代码源码*/
private void removeViewInternal(int index, View view) {
//...略
}
然后,给大伙看下我的Adapter中是如何解决这个问题的:
/**
* Created by yanjunhui
* on 2016/9/10.
* email:303767416@qq.com
*/
public class RecomAdapter extends PagerAdapter {
static final String TAG = "RecomAdapter";
Context context;
LayoutInflater layoutInflater;
List<CourseInfo> dataList;
//网上经常看到的,自定义一个viewList来管理itemView,并直接在viewList内销毁视图做法是不正确的,
List<View> viewList = new ArrayList<>();
public RecomAdapter(Context context, List<CourseInfo> dataList) {
this.context = context;
this.dataList = dataList;
this.layoutInflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
if (dataList == null) {
return 0;
}
return dataList.size();
}
/**
* 关联key 与 obj是否相等,即是否为同一个对象
* 如果一直返回false,界面不显示
*/
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object; // key
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
//错误一:这里虽然销毁了viewList中的view,但是没有将ViewGroup内的view销毁,这会导致ViewGroup中的mChildren不能有效回收,并无限增长
// viewList.remove(position);
//错误二:这里的removeView,会在第一次遍历成功后发生错误,原因是因为执行ViewGroup中以下代码时返回false
// container.removeView(viewList.get(position));
/*注解:
因为我反射测试的时候,出了一点问题,没有确切证据论证。所以这是我大胆的猜测,viewList中的view对象指向内存发生变化导致。
如果它返回true的话,就可以顺利走到方法 removeViewInternal >> removeFromArray
而 removeFromArray 正是销毁 itemView 和管理mChildrenCount长度的源码处
private boolean removeViewInternal(View view) {
final int index = indexOfChild(view);
if (index >= 0) {
removeViewInternal(index, view);
return true;
}
return false;
}
*/
//正确,不需判断,直接执行方法 removeViewInternal >> removeFromArray
container.removeViewAt(position);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
CourseInfo item = dataList.get(position);
View view = layoutInflater.inflate(R.layout.item_recomm, container, false);
TextView tvTheme = (TextView) view.findViewById(R.id.tv_theme);
SimpleDraweeView drawBg = (SimpleDraweeView) view.findViewById(R.id.draw_recomm);
tvTheme.setText(item.title);
FrescoUtils.getInstance().displayImage(drawBg, item.portraitpicid);
container.addView(view, position);
viewList.add(view);
return view;
}
}
最后给大家贴一下,removeViewAt和removeView的区别
public void removeViewAt(int index) {
removeViewInternal(index, getChildAt(index));
requestLayout();
invalidate(true);
}
public void removeView(View view) {
if (removeViewInternal(view)) {
requestLayout();
invalidate(true);
}
}
private boolean removeViewInternal(View view) {
final int index = indexOfChild(view);
/*视图能在列表中找到,index >= 0才能往下执行*/
if (index >= 0) {
removeViewInternal(index, view);
return true;
}
return false;
}
好了,到这问题完美解决,赶紧去试一下吧。