listview滑动源码分析(二)

来源:互联网 发布:微云直链解析php源码 编辑:程序博客网 时间:2024/06/06 03:30

4, listview滑动刷新

刷新流程如下,

主要是trackMotionScroll方法, 当于手指只要在屏幕上稍微有一点点移动,这个方法就会被调用,而如果是正常在屏幕上滑动的话,那么这个方法就会被调用很多次。

boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {        final int childCount = getChildCount();        •••int start = 0;        int count = 0; // item移动的数量if (down) { // 手指往上滑动            int top = -incrementalDeltaY;            if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {                top += listPadding.top;            }            for (int i = 0; i < childCount; i++) {                final View child = getChildAt(i);                if (child.getBottom() >= top) {                    break;                } else {                    count++;                    int position = firstPosition + i;                    if (position >= headerViewsCount && position < footerViewsStart) {                        // The view will be rebound to new data, clear any                        // system-managed transient state.                        child.clearAccessibilityFocus();                        mRecycler.addScrapView(child, position);                    }                }            }        } else { //手指往下滑动            int bottom = getHeight() - incrementalDeltaY;            if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {                bottom -= listPadding.bottom;            }            for (int i = childCount - 1; i >= 0; i--) {                final View child = getChildAt(i);                if (child.getTop() <= bottom) {                    break;                } else {                    start = i;                    count++;                    int position = firstPosition + i;                    if (position >= headerViewsCount && position < footerViewsStart) {                        // The view will be rebound to new data, clear any                        // system-managed transient state.                        child.clearAccessibilityFocus();                        mRecycler.addScrapView(child, position);                    }                }            }        }••••if (count > 0) {            detachViewsFromParent(start, count);            mRecycler.removeSkippedScrap();        }••••        offsetChildrenTopAndBottom(incrementalDeltaY);        if (down) {            mFirstPosition += count;        }        final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);        if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {            fillGap(down);        }•••}

1,无论向上还是向下滑动,调用addScrapView方法缓存滑出的View

2,调用offsetChildrenTopAndBottom 方法,将ListView中所有的子View都按照传入的参数值进行相应的偏移,这样就实现了随着手指的拖动,ListView的内容也会随着滚动的效果。

3, 如果ListView中最后一个View的底部已经移入了屏幕,或者ListView中第一个View的顶部移入了屏幕,就会调用fillGap()方法,加载屏幕外数据。

Listview中的fillGap方法源码如下,

void fillGap(boolean down) {        final int count = getChildCount();        if (down) { // 向上滑            int paddingTop = 0;            if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {                paddingTop = getListPaddingTop();            }           final int startOffset = count > 0 ? getChildAt(count - 1).getBottom() + mDividerHeight :                    paddingTop;            fillDown(mFirstPosition + count, startOffset);            correctTooHigh(getChildCount());        } else { // 向下滑            int paddingBottom = 0;            if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {                paddingBottom = getListPaddingBottom();            }            final int startOffset = count > 0 ? getChildAt(0).getTop() - mDividerHeight :                    getHeight() - paddingBottom;            fillUp(mFirstPosition - 1, startOffset);            correctTooLow(getChildCount());        }    }

fillDown和fillUp方法内部都是通过一个循环调用makeAndAddView方法对ListView进行填充。再次查看makeAndAddView方法,

private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,            boolean selected) {        View child;        if (!mDataChanged) {            // Try to use an existing view for this position            child = mRecycler.getActiveView(position);            if (child != null) {                // Found it -- we're using an existing child                // This just needs to be positioned                setupChild(child, position, y, flow, childrenLeft, selected, true);                return child;            }        }        // Make a new view for this position, or convert an unused view if possible        child = obtainView(position, mIsScrap);        // This needs to be positioned and measured        setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);        return child;    }

首先仍然是会尝试调用RecycleBin的getActiveView()方法来获取子布局,只不过肯定是获取不到的了,因为在第二次Layout过程中我们已经从mActiveViews中获取过了数据,而根据RecycleBin的机制,mActiveViews是不能够重复利用的,因此这里返回的值肯定是null。所以会调用obtainView方法。

obtainView方法如下,

View obtainView(int position, boolean[] isScrap) {        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");        isScrap[0] = false;        // Check whether we have a transient state view. Attempt to re-bind the        // data and discard the view if we fail.        final View transientView = mRecycler.getTransientStateView(position);        if (transientView != null) {// 短暂view的缓存,原理和一般缓存一样。            final LayoutParams params = (LayoutParams) transientView.getLayoutParams();            // If the view type hasn't changed, attempt to re-bind the data.            if (params.viewType == mAdapter.getItemViewType(position)) {                final View updatedView = mAdapter.getView(position, transientView, this);                // If we failed to re-bind the data, scrap the obtained view.                if (updatedView != transientView) {                    setItemViewLayoutParams(updatedView, position);                    mRecycler.addScrapView(updatedView, position);                }            }            isScrap[0] = true;            // Finish the temporary detach started in addScrapView().            transientView.dispatchFinishTemporaryDetach();            return transientView;        }        final View scrapView = mRecycler.getScrapView(position);        final View child = mAdapter.getView(position, scrapView, this);        if (scrapView != null) {              if (child != scrapView) {                // Failed to re-bind the data, return scrap to the heap.                mRecycler.addScrapView(scrapView, position);            } else {                isScrap[0] = true;                // Finish the temporary detach started in addScrapView().                child.dispatchFinishTemporaryDetach();            }        }        if (mCacheColorHint != 0) {            child.setDrawingCacheBackgroundColor(mCacheColorHint);        }        if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {            child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);        }        setItemViewLayoutParams(child, position);        if (AccessibilityManager.getInstance(mContext).isEnabled()) {            if (mAccessibilityDelegate == null) {                mAccessibilityDelegate = new ListItemAccessibilityDelegate();            }            if (child.getAccessibilityDelegate() == null) {                child.setAccessibilityDelegate(mAccessibilityDelegate);            }        }        Trace.traceEnd(Trace.TRACE_TAG_VIEW);        return child;    }

1,首先判断是否存在短暂缓存,如果存在,从短暂缓存中获取view

2, 调用getScrapView()方法来尝试从废弃缓存中获取一个View. 在trackMotionScroll()方法中我们就已经看到了,一旦有任何子View被移出了屏幕,就会将它加入到废弃缓存中,而从obtainView()方法中的逻辑来看,一旦有新的数据需要显示到屏幕上,就会尝试从废弃缓存中获取View。所以它们之间就形成了一个生产者和消费者的模式,那么ListView神奇的地方也就在这里体现出来了,不管你有任意多条数据需要显示,ListView中的子View其实来来回回就那么几个,移出屏幕的子View会很快被移入屏幕的数据重新利用起来,因而不管加载多少数据都不会出现OOM的情况,甚至内存都不会有所增加。

public View getView(final int position, View view, ViewGroup arg2) {

                 ViewHolder viewHolder = null;

                 final SortModel mContent = list.get(position);

                 if (view == null) {

                          viewHolder = newViewHolder();

                          view =LayoutInflater.from(mContext).inflate(R.layout.item, null);

                           viewHolder.tvTitle =(TextView) view.findViewById(R.id.title);

                          viewHolder.tvLetter =(TextView) view.findViewById(R.id.catalog);

                           viewHolder.tvnumber =(TextView) view.findViewById(R.id.number);

                           view.setTag(viewHolder);

                 } else {

                           viewHolder =(ViewHolder) view.getTag();

                 }

                

                 int section =getSectionForPosition(position);

                 if(position ==getPositionForSection(section)){

                           viewHolder.tvLetter.setVisibility(View.VISIBLE);

                           viewHolder.tvLetter.setText(mContent.getSortLetters());

                 }else{

                           viewHolder.tvLetter.setVisibility(View.GONE);

                 }

       

                 viewHolder.tvTitle.setText(this.list.get(position).getName());

                 viewHolder.tvnumber.setText(this.list.get(position).getNumber());

                 return view;

        }

在写getView()方法是要判断一下view是不是等于null,如果等于null才调用inflate()方法来加载布局,不等于null就可以直接利用view,因为该view就是我们之间利用过的子View,只不过被移出屏幕后进入到了废弃缓存中,现在又重新拿出来使用而已。只需要把view中的数据更新成当前位置上应该显示的数据,那么看起来就好像是全新加载出来的一个布局一样,大大提高了效率。

0 0
原创粉丝点击