安卓高手之路之图形系统(6)ListView继续
2015-03-26 18:09
274 查看
安卓高手之路之图形系统(6)ListView继续 - 修补C++ - ITeye技术网站
综述:
本篇首先介绍了ListView的实现细节。然后介绍了Gallery,ListView,ViewPager的效率对比分析。以及效率低下的原因。最后给出了一些解决方案。
1.在上一篇讨论了requestLayout的效率问题。对如何避免这个问题也进行了深入探讨。本篇就内存问题进行讨论。一般情况下,安卓的ListView实现方式上,就存在要移动childView位置的需求。
如果对childView进行了回收并且回收的childView必须仍然在原来的位置上,那么childView的位置可能要出现在两个位置上。这非常有挑战性,因为安卓的很多东西都是基于一个View一个位置的这样的思想。现在突然出现两个位置。那么也就是说,回收的View不能再呆在原来的位置了。必须被remove掉。remove掉之后呢,其他的View必须挤过去。总之所有的children都得改变位置。
也就是说必须改变布局。但是改变布局是否就一定要reqestLayout,也是个问题。刚才说了不需要。只要调用一下onLayout就行了。onLayout调用的时候,必须知道childView的位置吧。、那还得改变child的位置,那又调用什么呢?
那么,仅仅改变childView位置的函数是什么呢?这个函数就是offset系列函数。在Gallery那边是offsetChildrenLeftAndRight另外还有个setX和setY。这些都是改变位置的函数。
ListView的实现时非常高效的。既保证了回收childiew,有能不进行layout。非常高效。
第一。View的回收机制。
第二。View不进行requestLayout
2.Gallery的实现方式
Gallery在实现的时候没有采用回收机制。经过测试的Adapter.getView方法参数,View都是null。也就是说不对View进行复用。其实上Gallery中的View是越来越多。而且每一个View都不会进行回收。这跟一个
ScrollView+ViewPager的实现方式是一样的。唯一的区别是Gallery采用了Adapter机制,并且使用了ListView的滚动原理。但是Gallery没有对View进行回收,全部保存了起来。在内存不够用的时候,尽量不要使用这个。下面拿Gallery和ListView的回收机制进行了对比,
先看trackMotionScroll方法:
Gallery:
Java代码
/**
* Tracks a motion scroll. In reality, this is used to do just about any
* movement to items (touch scroll, arrow-key scroll, set an item as selected).
*
* @param deltaX Change in X from the previous event.
*/
void trackMotionScroll(int deltaX) {
if (getChildCount() == 0) {
return;
}
boolean toLeft = deltaX < 0;
int limitedDeltaX = getLimitedMotionScrollAmount(toLeft, deltaX);
if (limitedDeltaX != deltaX) {
// The above call returned a limited amount, so stop any scrolls/flings
mFlingRunnable.endFling(false);
onFinishedMovement();
}
offsetChildrenLeftAndRight(limitedDeltaX);
detachOffScreenChildren(toLeft);
if (toLeft) {
// If moved left, there will be empty space on the right
fillToGalleryRight();
} else {
// Similarly, empty space on the left
fillToGalleryLeft();
}
// Clear unused views
mRecycler.clear();
setSelectionToCenterChild();
onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
invalidate();
}
ListView
Java代码
/**
* Track a motion scroll
*
* @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion
* began. Positive numbers mean the user's finger is moving down the screen.
* @param incrementalDeltaY Change in deltaY from the previous event.
* @return true if we're already at the beginning/end of the list and have nothing to do.
*/
boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
final int childCount = getChildCount();
if (childCount == 0) {
return true;
}
final int firstTop = getChildAt(0).getTop();
final int lastBottom = getChildAt(childCount - 1).getBottom();
final Rect listPadding = mListPadding;
// "effective padding" In this case is the amount of padding that affects
// how much space should not be filled by items. If we don't clip to padding
// there is no effective padding.
int effectivePaddingTop = 0;
int effectivePaddingBottom = 0;
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
effectivePaddingTop = listPadding.top;
effectivePaddingBottom = listPadding.bottom;
}
// FIXME account for grid vertical spacing too?
final int spaceAbove = effectivePaddingTop - firstTop;
final int end = getHeight() - effectivePaddingBottom;
final int spaceBelow = lastBottom - end;
final int height = getHeight() - mPaddingBottom - mPaddingTop;
if (deltaY < 0) {
deltaY = Math.max(-(height - 1), deltaY);
} else {
deltaY = Math.min(height - 1, deltaY);
}
if (incrementalDeltaY < 0) {
incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
} else {
= Math.min(height - 1, incrementalDeltaY);
}
final int firstPosition = mFirstPosition;
// Update our guesses for where the first and last views are
if (firstPosition == 0) {
mFirstPositionDistanceGuess = firstTop - listPadding.top;
} else {
mFirstPositionDistanceGuess += incrementalDeltaY;
}
if (firstPosition + childCount == mItemCount) {
mLastPositionDistanceGuess = lastBottom + listPadding.bottom;
} else {
mLastPositionDistanceGuess += incrementalDeltaY;
}
final boolean cannotScrollDown = (firstPosition == 0 &&
firstTop >= listPadding.top && incrementalDeltaY >= 0);
final boolean cannotScrollUp = (firstPosition + childCount == mItemCount &&
lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0);
if (cannotScrollDown || cannotScrollUp) {
return incrementalDeltaY != 0;
}
final boolean down = incrementalDeltaY < 0;
final boolean inTouchMode = isInTouchMode();
if (inTouchMode) {
hideSelector();
}
final int headerViewsCount = getHeaderViewsCount();
final int footerViewsStart = mItemCount - getFooterViewsCount();
int start = 0;
int count = 0;
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) {
mRecycler.addScrapView(child, position);
if (ViewDebug.TRACE_RECYCLER) {
ViewDebug.trace(child,
ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
firstPosition + i, -1);
}
}
}
}
} 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) {
mRecycler.addScrapView(child, position);
if (ViewDebug.TRACE_RECYCLER) {
ViewDebug.trace(child,
ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
firstPosition + i, -1);
}
}
}
}
}
mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
mBlockLayoutRequests = true;
if (count > 0) {
detachViewsFromParent(start, count);
}
offsetChildrenTopAndBottom(incrementalDeltaY);
if (down) {
mFirstPosition += count;
}
invalidate();
final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
fillGap(down);
}
if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
final int childIndex = mSelectedPosition - mFirstPosition;
if (childIndex >= 0 && childIndex < getChildCount()) {
positionSelector(mSelectedPosition, getChildAt(childIndex));
}
} else if (mSelectorPosition != INVALID_POSITION) {
final int childIndex = mSelectorPosition - mFirstPosition;
if (childIndex >= 0 && childIndex < getChildCount()) {
positionSelector(INVALID_POSITION, getChildAt(childIndex));
}
} else {
mSelectorRect.setEmpty();
}
mBlockLayoutRequests = false;
invokeOnItemScrollListener();
awakenScrollBars();
return false;
}
可以看到Gallery处理View的关键代码:
Java代码
offsetChildrenLeftAndRight(limitedDeltaX);
detachOffScreenChildren(toLeft);
。。。。
mRecycler.clear();
而ListView中为:
Java代码
if (count > 0) {
detachViewsFromParent(start, count);
}
offsetChildrenTopAndBottom(incrementalDeltaY);
detachOffScreenChildren和detachViewsFromParent跟ListView功能一样。但是Gallery多出一个 mRecycler.clear()。等待重复利用的mRecycler被回收了,这就造成了再Gallery中无法复用之前的View的情况,由此可见Gallery在内存的使用上存在很大的设计缺陷。
再看Gallery与ListView的makeAndAddView方法
Gallery
Java代码
private View makeAndAddView(int position, int offset, int x, boolean fromLeft) {
View child;
if (!mDataChanged) {
child = mRecycler.get(position);
if (child != null) {
// Can reuse an existing view
int childLeft = child.getLeft();
// Remember left and right edges of where views have been placed
mRightMost = Math.max(mRightMost, childLeft
+ child.getMeasuredWidth());
mLeftMost = Math.min(mLeftMost, childLeft);
// Position the view
setUpChild(child, offset, x, fromLeft);
return child;
}
}
// Nothing found in the recycler -- ask the adapter for a view
child = mAdapter.getView(position, null, this);
// Position the view
setUpChild(child, offset, x, fromLeft);
return child;
}
ListView:
Java代码
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) {
if (ViewDebug.TRACE_RECYCLER) {
ViewDebug.trace(child, ViewDebug.RecyclerTraceType.RECYCLE_FROM_ACTIVE_HEAP,
position, getChildCount());
}
// 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;
}
由此可见Gallery每次都将不可见的View进行了清理。对比ListView来说,在滚动过程中多了new一个View的开销。那么就存在一个避免此类问题的方法,自己对View建一个recyler从而弥补这个缺点。
另外安卓的ViewPager+ScrollView 采用了传统的整个容器进行滚动算法,而不是调整childView的位置,但是也存在卡顿问题。
ViewPager的用法见下文:
http://blog.csdn.net/wangjinyu501/article/details/816
ViewPager的卡顿见下文:
http://marspring.mobi/viewpager-majorization/
里面google提供了一个接口解决卡顿问题。至少目前看来,只是五十步笑百步。不能根本解决问题。默认当前正中位置前后缓存一个View。改后可以缓存多个View。当缓存的多个View被用完的时候,仍然是卡顿。
另外,
有人说用异步加载,异步加载解决的是数据加载问题,跟这个new View造成的卡顿问题是两码事儿。
如何解决?
自己做类似ListView的回收机制,对View进行复用,从而根本上解决这个问题。
另外,安卓没有采用享元模式。。。。很遗憾。。。也许google大牛们会想到,希望如此。希望google能体谅我们,再提供一个类似View的轻量级显示控件来直接支持享元模式。这在Brew平台是有的。。。。。。。。。。。。。。。。。。。。。。郁闷。
综述:
本篇首先介绍了ListView的实现细节。然后介绍了Gallery,ListView,ViewPager的效率对比分析。以及效率低下的原因。最后给出了一些解决方案。
1.在上一篇讨论了requestLayout的效率问题。对如何避免这个问题也进行了深入探讨。本篇就内存问题进行讨论。一般情况下,安卓的ListView实现方式上,就存在要移动childView位置的需求。
如果对childView进行了回收并且回收的childView必须仍然在原来的位置上,那么childView的位置可能要出现在两个位置上。这非常有挑战性,因为安卓的很多东西都是基于一个View一个位置的这样的思想。现在突然出现两个位置。那么也就是说,回收的View不能再呆在原来的位置了。必须被remove掉。remove掉之后呢,其他的View必须挤过去。总之所有的children都得改变位置。
也就是说必须改变布局。但是改变布局是否就一定要reqestLayout,也是个问题。刚才说了不需要。只要调用一下onLayout就行了。onLayout调用的时候,必须知道childView的位置吧。、那还得改变child的位置,那又调用什么呢?
那么,仅仅改变childView位置的函数是什么呢?这个函数就是offset系列函数。在Gallery那边是offsetChildrenLeftAndRight另外还有个setX和setY。这些都是改变位置的函数。
ListView的实现时非常高效的。既保证了回收childiew,有能不进行layout。非常高效。
第一。View的回收机制。
第二。View不进行requestLayout
2.Gallery的实现方式
Gallery在实现的时候没有采用回收机制。经过测试的Adapter.getView方法参数,View都是null。也就是说不对View进行复用。其实上Gallery中的View是越来越多。而且每一个View都不会进行回收。这跟一个
ScrollView+ViewPager的实现方式是一样的。唯一的区别是Gallery采用了Adapter机制,并且使用了ListView的滚动原理。但是Gallery没有对View进行回收,全部保存了起来。在内存不够用的时候,尽量不要使用这个。下面拿Gallery和ListView的回收机制进行了对比,
先看trackMotionScroll方法:
Gallery:
Java代码
/**
* Tracks a motion scroll. In reality, this is used to do just about any
* movement to items (touch scroll, arrow-key scroll, set an item as selected).
*
* @param deltaX Change in X from the previous event.
*/
void trackMotionScroll(int deltaX) {
if (getChildCount() == 0) {
return;
}
boolean toLeft = deltaX < 0;
int limitedDeltaX = getLimitedMotionScrollAmount(toLeft, deltaX);
if (limitedDeltaX != deltaX) {
// The above call returned a limited amount, so stop any scrolls/flings
mFlingRunnable.endFling(false);
onFinishedMovement();
}
offsetChildrenLeftAndRight(limitedDeltaX);
detachOffScreenChildren(toLeft);
if (toLeft) {
// If moved left, there will be empty space on the right
fillToGalleryRight();
} else {
// Similarly, empty space on the left
fillToGalleryLeft();
}
// Clear unused views
mRecycler.clear();
setSelectionToCenterChild();
onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
invalidate();
}
/** * Tracks a motion scroll. In reality, this is used to do just about any * movement to items (touch scroll, arrow-key scroll, set an item as selected). * * @param deltaX Change in X from the previous event. */ void trackMotionScroll(int deltaX) { if (getChildCount() == 0) { return; } boolean toLeft = deltaX < 0; int limitedDeltaX = getLimitedMotionScrollAmount(toLeft, deltaX); if (limitedDeltaX != deltaX) { // The above call returned a limited amount, so stop any scrolls/flings mFlingRunnable.endFling(false); onFinishedMovement(); } offsetChildrenLeftAndRight(limitedDeltaX); detachOffScreenChildren(toLeft); if (toLeft) { // If moved left, there will be empty space on the right fillToGalleryRight(); } else { // Similarly, empty space on the left fillToGalleryLeft(); } // Clear unused views mRecycler.clear(); setSelectionToCenterChild(); onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these. invalidate(); }
ListView
Java代码
/**
* Track a motion scroll
*
* @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion
* began. Positive numbers mean the user's finger is moving down the screen.
* @param incrementalDeltaY Change in deltaY from the previous event.
* @return true if we're already at the beginning/end of the list and have nothing to do.
*/
boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
final int childCount = getChildCount();
if (childCount == 0) {
return true;
}
final int firstTop = getChildAt(0).getTop();
final int lastBottom = getChildAt(childCount - 1).getBottom();
final Rect listPadding = mListPadding;
// "effective padding" In this case is the amount of padding that affects
// how much space should not be filled by items. If we don't clip to padding
// there is no effective padding.
int effectivePaddingTop = 0;
int effectivePaddingBottom = 0;
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
effectivePaddingTop = listPadding.top;
effectivePaddingBottom = listPadding.bottom;
}
// FIXME account for grid vertical spacing too?
final int spaceAbove = effectivePaddingTop - firstTop;
final int end = getHeight() - effectivePaddingBottom;
final int spaceBelow = lastBottom - end;
final int height = getHeight() - mPaddingBottom - mPaddingTop;
if (deltaY < 0) {
deltaY = Math.max(-(height - 1), deltaY);
} else {
deltaY = Math.min(height - 1, deltaY);
}
if (incrementalDeltaY < 0) {
incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
} else {
= Math.min(height - 1, incrementalDeltaY);
}
final int firstPosition = mFirstPosition;
// Update our guesses for where the first and last views are
if (firstPosition == 0) {
mFirstPositionDistanceGuess = firstTop - listPadding.top;
} else {
mFirstPositionDistanceGuess += incrementalDeltaY;
}
if (firstPosition + childCount == mItemCount) {
mLastPositionDistanceGuess = lastBottom + listPadding.bottom;
} else {
mLastPositionDistanceGuess += incrementalDeltaY;
}
final boolean cannotScrollDown = (firstPosition == 0 &&
firstTop >= listPadding.top && incrementalDeltaY >= 0);
final boolean cannotScrollUp = (firstPosition + childCount == mItemCount &&
lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0);
if (cannotScrollDown || cannotScrollUp) {
return incrementalDeltaY != 0;
}
final boolean down = incrementalDeltaY < 0;
final boolean inTouchMode = isInTouchMode();
if (inTouchMode) {
hideSelector();
}
final int headerViewsCount = getHeaderViewsCount();
final int footerViewsStart = mItemCount - getFooterViewsCount();
int start = 0;
int count = 0;
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) {
mRecycler.addScrapView(child, position);
if (ViewDebug.TRACE_RECYCLER) {
ViewDebug.trace(child,
ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
firstPosition + i, -1);
}
}
}
}
} 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) {
mRecycler.addScrapView(child, position);
if (ViewDebug.TRACE_RECYCLER) {
ViewDebug.trace(child,
ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
firstPosition + i, -1);
}
}
}
}
}
mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
mBlockLayoutRequests = true;
if (count > 0) {
detachViewsFromParent(start, count);
}
offsetChildrenTopAndBottom(incrementalDeltaY);
if (down) {
mFirstPosition += count;
}
invalidate();
final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
fillGap(down);
}
if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
final int childIndex = mSelectedPosition - mFirstPosition;
if (childIndex >= 0 && childIndex < getChildCount()) {
positionSelector(mSelectedPosition, getChildAt(childIndex));
}
} else if (mSelectorPosition != INVALID_POSITION) {
final int childIndex = mSelectorPosition - mFirstPosition;
if (childIndex >= 0 && childIndex < getChildCount()) {
positionSelector(INVALID_POSITION, getChildAt(childIndex));
}
} else {
mSelectorRect.setEmpty();
}
mBlockLayoutRequests = false;
invokeOnItemScrollListener();
awakenScrollBars();
return false;
}
/** * Track a motion scroll * * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion * began. Positive numbers mean the user's finger is moving down the screen. * @param incrementalDeltaY Change in deltaY from the previous event. * @return true if we're already at the beginning/end of the list and have nothing to do. */ boolean trackMotionScroll(int deltaY, int incrementalDeltaY) { final int childCount = getChildCount(); if (childCount == 0) { return true; } final int firstTop = getChildAt(0).getTop(); final int lastBottom = getChildAt(childCount - 1).getBottom(); final Rect listPadding = mListPadding; // "effective padding" In this case is the amount of padding that affects // how much space should not be filled by items. If we don't clip to padding // there is no effective padding. int effectivePaddingTop = 0; int effectivePaddingBottom = 0; if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { effectivePaddingTop = listPadding.top; effectivePaddingBottom = listPadding.bottom; } // FIXME account for grid vertical spacing too? final int spaceAbove = effectivePaddingTop - firstTop; final int end = getHeight() - effectivePaddingBottom; final int spaceBelow = lastBottom - end; final int height = getHeight() - mPaddingBottom - mPaddingTop; if (deltaY < 0) { deltaY = Math.max(-(height - 1), deltaY); } else { deltaY = Math.min(height - 1, deltaY); } if (incrementalDeltaY < 0) { incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY); } else { = Math.min(height - 1, incrementalDeltaY); } final int firstPosition = mFirstPosition; // Update our guesses for where the first and last views are if (firstPosition == 0) { mFirstPositionDistanceGuess = firstTop - listPadding.top; } else { mFirstPositionDistanceGuess += incrementalDeltaY; } if (firstPosition + childCount == mItemCount) { mLastPositionDistanceGuess = lastBottom + listPadding.bottom; } else { mLastPositionDistanceGuess += incrementalDeltaY; } final boolean cannotScrollDown = (firstPosition == 0 && firstTop >= listPadding.top && incrementalDeltaY >= 0); final boolean cannotScrollUp = (firstPosition + childCount == mItemCount && lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0); if (cannotScrollDown || cannotScrollUp) { return incrementalDeltaY != 0; } final boolean down = incrementalDeltaY < 0; final boolean inTouchMode = isInTouchMode(); if (inTouchMode) { hideSelector(); } final int headerViewsCount = getHeaderViewsCount(); final int footerViewsStart = mItemCount - getFooterViewsCount(); int start = 0; int count = 0; 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) { mRecycler.addScrapView(child, position); if (ViewDebug.TRACE_RECYCLER) { ViewDebug.trace(child, ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, firstPosition + i, -1); } } } } } 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) { mRecycler.addScrapView(child, position); if (ViewDebug.TRACE_RECYCLER) { ViewDebug.trace(child, ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, firstPosition + i, -1); } } } } } mMotionViewNewTop = mMotionViewOriginalTop + deltaY; mBlockLayoutRequests = true; if (count > 0) { detachViewsFromParent(start, count); } offsetChildrenTopAndBottom(incrementalDeltaY); if (down) { mFirstPosition += count; } invalidate(); final int absIncrementalDeltaY = Math.abs(incrementalDeltaY); if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) { fillGap(down); } if (!inTouchMode && mSelectedPosition != INVALID_POSITION) { final int childIndex = mSelectedPosition - mFirstPosition; if (childIndex >= 0 && childIndex < getChildCount()) { positionSelector(mSelectedPosition, getChildAt(childIndex)); } } else if (mSelectorPosition != INVALID_POSITION) { final int childIndex = mSelectorPosition - mFirstPosition; if (childIndex >= 0 && childIndex < getChildCount()) { positionSelector(INVALID_POSITION, getChildAt(childIndex)); } } else { mSelectorRect.setEmpty(); } mBlockLayoutRequests = false; invokeOnItemScrollListener(); awakenScrollBars(); return false; }
可以看到Gallery处理View的关键代码:
Java代码
offsetChildrenLeftAndRight(limitedDeltaX);
detachOffScreenChildren(toLeft);
。。。。
mRecycler.clear();
offsetChildrenLeftAndRight(limitedDeltaX); detachOffScreenChildren(toLeft); 。。。。 mRecycler.clear();
而ListView中为:
Java代码
if (count > 0) {
detachViewsFromParent(start, count);
}
offsetChildrenTopAndBottom(incrementalDeltaY);
if (count > 0) { detachViewsFromParent(start, count); } offsetChildrenTopAndBottom(incrementalDeltaY);
detachOffScreenChildren和detachViewsFromParent跟ListView功能一样。但是Gallery多出一个 mRecycler.clear()。等待重复利用的mRecycler被回收了,这就造成了再Gallery中无法复用之前的View的情况,由此可见Gallery在内存的使用上存在很大的设计缺陷。
再看Gallery与ListView的makeAndAddView方法
Gallery
Java代码
private View makeAndAddView(int position, int offset, int x, boolean fromLeft) {
View child;
if (!mDataChanged) {
child = mRecycler.get(position);
if (child != null) {
// Can reuse an existing view
int childLeft = child.getLeft();
// Remember left and right edges of where views have been placed
mRightMost = Math.max(mRightMost, childLeft
+ child.getMeasuredWidth());
mLeftMost = Math.min(mLeftMost, childLeft);
// Position the view
setUpChild(child, offset, x, fromLeft);
return child;
}
}
// Nothing found in the recycler -- ask the adapter for a view
child = mAdapter.getView(position, null, this);
// Position the view
setUpChild(child, offset, x, fromLeft);
return child;
}
private View makeAndAddView(int position, int offset, int x, boolean fromLeft) { View child; if (!mDataChanged) { child = mRecycler.get(position); if (child != null) { // Can reuse an existing view int childLeft = child.getLeft(); // Remember left and right edges of where views have been placed mRightMost = Math.max(mRightMost, childLeft + child.getMeasuredWidth()); mLeftMost = Math.min(mLeftMost, childLeft); // Position the view setUpChild(child, offset, x, fromLeft); return child; } } // Nothing found in the recycler -- ask the adapter for a view child = mAdapter.getView(position, null, this); // Position the view setUpChild(child, offset, x, fromLeft); return child; }
ListView:
Java代码
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) {
if (ViewDebug.TRACE_RECYCLER) {
ViewDebug.trace(child, ViewDebug.RecyclerTraceType.RECYCLE_FROM_ACTIVE_HEAP,
position, getChildCount());
}
// 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;
}
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) { if (ViewDebug.TRACE_RECYCLER) { ViewDebug.trace(child, ViewDebug.RecyclerTraceType.RECYCLE_FROM_ACTIVE_HEAP, position, getChildCount()); } // 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; }
由此可见Gallery每次都将不可见的View进行了清理。对比ListView来说,在滚动过程中多了new一个View的开销。那么就存在一个避免此类问题的方法,自己对View建一个recyler从而弥补这个缺点。
另外安卓的ViewPager+ScrollView 采用了传统的整个容器进行滚动算法,而不是调整childView的位置,但是也存在卡顿问题。
ViewPager的用法见下文:
http://blog.csdn.net/wangjinyu501/article/details/816
ViewPager的卡顿见下文:
http://marspring.mobi/viewpager-majorization/
里面google提供了一个接口解决卡顿问题。至少目前看来,只是五十步笑百步。不能根本解决问题。默认当前正中位置前后缓存一个View。改后可以缓存多个View。当缓存的多个View被用完的时候,仍然是卡顿。
另外,
有人说用异步加载,异步加载解决的是数据加载问题,跟这个new View造成的卡顿问题是两码事儿。
如何解决?
自己做类似ListView的回收机制,对View进行复用,从而根本上解决这个问题。
另外,安卓没有采用享元模式。。。。很遗憾。。。也许google大牛们会想到,希望如此。希望google能体谅我们,再提供一个类似View的轻量级显示控件来直接支持享元模式。这在Brew平台是有的。。。。。。。。。。。。。。。。。。。。。。郁闷。
相关文章推荐
- 安卓高手之路之图形系统(6)ListView继续
- 安卓高手之路之图形系统【5】安卓ListView和EditText配合使用时的注意事项。
- 安卓高手之路之图形系统(6)requestLayout的流程
- 安卓高手之路 图形系统(4 Measure的算法)
- 安卓高手之路之图形系统(6)requestLayout的流程
- 安卓高手之路 图形系统(2)----------------基于Binder的架构思路)
- 安卓高手之路之 图形系统之 图形框架(1)
- 安卓高手之路之图形系统(6)requestLayout的流程
- 安卓高手之路 图形系统(3 底层SurfceFlinger系统)
- 安卓高手之路 图形系统(4 Measure的算法)
- 安卓高手之路之图形系统【5】安卓ListView和EditText配合使用时的注意事项。
- 安卓高手之路之 图形系统之 图形框架(1)
- 安卓高手之路之图形系统(6)requestLayout的流程
- 安卓高手之路 图形系统(3 底层SurfceFlinger系统)
- 安卓小项目之Everyday(4)---分享功能、listview优化、获取系统时间
- 安卓高手之路之 GDI图形引擎篇
- 安卓高手之路之 GDI图形引擎篇
- 如何快速开发图形仿真软件系统
- 走向Linux系统高手之路 编译内核解析
- D-Day +18 of .NET 后勤服务系统告一段落,继续回到自己的学习进程