Android之ViewPager.getChildCount()无限自增问题解析

在最近的一次工作中,要做一个广告展示栏。果断使用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;
    }

好了,到这问题完美解决,赶紧去试一下吧。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值