热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

listview滑动源码分析(二)

4,listview滑动刷新刷新流程如下,主要是trackMotionScroll方法,当于手指只要在屏幕上稍微有一点点移动,这个方法就会被调用,而如果是正常在屏幕上滑动的话,那么这个方法就会被调

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 final View child = getChildAt(i);
if (child.getBottom() >= top) {
break;
} else {
count++;
int position = firstPosition + i;
if (position >= headerViewsCount && position // 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 // 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 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中的数据更新成当前位置上应该显示的数据,那么看起来就好像是全新加载出来的一个布局一样,大大提高了效率。


推荐阅读
author-avatar
一生一世0521
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有