Skip to content

Commit 9d849a2

Browse files
author
Romain Guy
committed
Optimize invalidate calls in lists.
AbsListView was doing too many invalidates during scrolls/flings. Some of them were also covering too large an area of the screen. Change-Id: I68fe5dda3657bddd673996e7cf4f3c3672c66cfc
1 parent 390f882 commit 9d849a2

File tree

2 files changed

+101
-40
lines changed

2 files changed

+101
-40
lines changed

core/java/android/widget/AbsListView.java

Lines changed: 57 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
8888
ViewTreeObserver.OnTouchModeChangeListener,
8989
RemoteViewsAdapter.RemoteAdapterConnectionCallback {
9090

91+
@SuppressWarnings("UnusedDeclaration")
9192
private static final String TAG = "AbsListView";
9293

9394
/**
@@ -2429,7 +2430,7 @@ protected void onDetachedFromWindow() {
24292430
final ViewTreeObserver treeObserver = getViewTreeObserver();
24302431
treeObserver.removeOnTouchModeChangeListener(this);
24312432
if (mTextFilterEnabled && mPopup != null) {
2432-
treeObserver.removeGlobalOnLayoutListener(this);
2433+
treeObserver.removeOnGlobalLayoutListener(this);
24332434
mGlobalLayoutListenerAddedFilter = false;
24342435
}
24352436

@@ -2943,11 +2944,23 @@ private void scrollIfNeeded(int y) {
29432944
mDirection = 0; // Reset when entering overscroll.
29442945
mTouchMode = TOUCH_MODE_OVERSCROLL;
29452946
if (rawDeltaY > 0) {
2947+
if (!mEdgeGlowTop.isIdle()) {
2948+
invalidate(mEdgeGlowTop.getBounds());
2949+
} else {
2950+
invalidate();
2951+
}
2952+
29462953
mEdgeGlowTop.onPull((float) overscroll / getHeight());
29472954
if (!mEdgeGlowBottom.isFinished()) {
29482955
mEdgeGlowBottom.onRelease();
29492956
}
29502957
} else if (rawDeltaY < 0) {
2958+
if (!mEdgeGlowBottom.isIdle()) {
2959+
invalidate(mEdgeGlowBottom.getBounds());
2960+
} else {
2961+
invalidate();
2962+
}
2963+
29512964
mEdgeGlowBottom.onPull((float) overscroll / getHeight());
29522965
if (!mEdgeGlowTop.isFinished()) {
29532966
mEdgeGlowTop.onRelease();
@@ -2956,7 +2969,6 @@ private void scrollIfNeeded(int y) {
29562969
}
29572970
}
29582971
mMotionY = y;
2959-
invalidate();
29602972
}
29612973
mLastY = y;
29622974
}
@@ -2990,26 +3002,26 @@ private void scrollIfNeeded(int y) {
29903002
if (!mEdgeGlowBottom.isFinished()) {
29913003
mEdgeGlowBottom.onRelease();
29923004
}
3005+
invalidate(mEdgeGlowTop.getBounds());
29933006
} else if (rawDeltaY < 0) {
29943007
mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight());
29953008
if (!mEdgeGlowTop.isFinished()) {
29963009
mEdgeGlowTop.onRelease();
29973010
}
3011+
invalidate(mEdgeGlowBottom.getBounds());
29983012
}
2999-
invalidate();
30003013
}
30013014
}
30023015

30033016
if (incrementalDeltaY != 0) {
30043017
// Coming back to 'real' list scrolling
3005-
mScrollY = 0;
3006-
invalidateParentIfNeeded();
3007-
3008-
// No need to do all this work if we're not going to move anyway
3009-
if (incrementalDeltaY != 0) {
3010-
trackMotionScroll(incrementalDeltaY, incrementalDeltaY);
3018+
if (mScrollY != 0) {
3019+
mScrollY = 0;
3020+
invalidateParentIfNeeded();
30113021
}
30123022

3023+
trackMotionScroll(incrementalDeltaY, incrementalDeltaY);
3024+
30133025
mTouchMode = TOUCH_MODE_SCROLL;
30143026

30153027
// We did not scroll the full amount. Treat this essentially like the
@@ -3468,11 +3480,12 @@ public void draw(Canvas canvas) {
34683480
final int rightPadding = mListPadding.right + mGlowPaddingRight;
34693481
final int width = getWidth() - leftPadding - rightPadding;
34703482

3471-
canvas.translate(leftPadding,
3472-
Math.min(0, scrollY + mFirstPositionDistanceGuess));
3483+
int edgeY = Math.min(0, scrollY + mFirstPositionDistanceGuess);
3484+
canvas.translate(leftPadding, edgeY);
34733485
mEdgeGlowTop.setSize(width, getHeight());
34743486
if (mEdgeGlowTop.draw(canvas)) {
3475-
invalidate();
3487+
mEdgeGlowTop.setPosition(leftPadding, edgeY);
3488+
invalidate(mEdgeGlowTop.getBounds());
34763489
}
34773490
canvas.restoreToCount(restoreCount);
34783491
}
@@ -3483,12 +3496,15 @@ public void draw(Canvas canvas) {
34833496
final int width = getWidth() - leftPadding - rightPadding;
34843497
final int height = getHeight();
34853498

3486-
canvas.translate(-width + leftPadding,
3487-
Math.max(height, scrollY + mLastPositionDistanceGuess));
3499+
int edgeX = -width + leftPadding;
3500+
int edgeY = Math.max(height, scrollY + mLastPositionDistanceGuess);
3501+
canvas.translate(edgeX, edgeY);
34883502
canvas.rotate(180, width, 0);
34893503
mEdgeGlowBottom.setSize(width, height);
34903504
if (mEdgeGlowBottom.draw(canvas)) {
3491-
invalidate();
3505+
// Account for the rotation
3506+
mEdgeGlowBottom.setPosition(edgeX + width, edgeY - mEdgeGlowBottom.getHeight());
3507+
invalidate(mEdgeGlowBottom.getBounds());
34923508
}
34933509
canvas.restoreToCount(restoreCount);
34943510
}
@@ -3874,7 +3890,8 @@ public void run() {
38743890
}
38753891

38763892
// Don't stop just because delta is zero (it could have been rounded)
3877-
final boolean atEnd = trackMotionScroll(delta, delta) && (delta != 0);
3893+
final boolean atEdge = trackMotionScroll(delta, delta);
3894+
final boolean atEnd = atEdge && (delta != 0);
38783895
if (atEnd) {
38793896
if (motionView != null) {
38803897
// Tweak the scroll for how far we overshot
@@ -3889,7 +3906,7 @@ public void run() {
38893906
}
38903907

38913908
if (more && !atEnd) {
3892-
invalidate();
3909+
if (atEdge) invalidate();
38933910
mLastFlingY = y;
38943911
post(this);
38953912
} else {
@@ -4431,31 +4448,33 @@ void smoothScrollByOffset(int position) {
44314448
}
44324449

44334450
private void createScrollingCache() {
4434-
if (mScrollingCacheEnabled && !mCachingStarted) {
4451+
if (mScrollingCacheEnabled && !mCachingStarted && !isHardwareAccelerated()) {
44354452
setChildrenDrawnWithCacheEnabled(true);
44364453
setChildrenDrawingCacheEnabled(true);
44374454
mCachingStarted = mCachingActive = true;
44384455
}
44394456
}
44404457

44414458
private void clearScrollingCache() {
4442-
if (mClearScrollingCache == null) {
4443-
mClearScrollingCache = new Runnable() {
4444-
public void run() {
4445-
if (mCachingStarted) {
4446-
mCachingStarted = mCachingActive = false;
4447-
setChildrenDrawnWithCacheEnabled(false);
4448-
if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) {
4449-
setChildrenDrawingCacheEnabled(false);
4450-
}
4451-
if (!isAlwaysDrawnWithCacheEnabled()) {
4452-
invalidate();
4459+
if (!isHardwareAccelerated()) {
4460+
if (mClearScrollingCache == null) {
4461+
mClearScrollingCache = new Runnable() {
4462+
public void run() {
4463+
if (mCachingStarted) {
4464+
mCachingStarted = mCachingActive = false;
4465+
setChildrenDrawnWithCacheEnabled(false);
4466+
if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) {
4467+
setChildrenDrawingCacheEnabled(false);
4468+
}
4469+
if (!isAlwaysDrawnWithCacheEnabled()) {
4470+
invalidate();
4471+
}
44534472
}
44544473
}
4455-
}
4456-
};
4474+
};
4475+
}
4476+
post(mClearScrollingCache);
44574477
}
4458-
post(mClearScrollingCache);
44594478
}
44604479

44614480
/**
@@ -4599,14 +4618,18 @@ boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
45994618
mRecycler.removeSkippedScrap();
46004619
}
46014620

4621+
// invalidate before moving the children to avoid unnecessary invalidate
4622+
// calls to bubble up from the children all the way to the top
4623+
if (!awakenScrollBars()) {
4624+
invalidate();
4625+
}
4626+
46024627
offsetChildrenTopAndBottom(incrementalDeltaY);
46034628

46044629
if (down) {
46054630
mFirstPosition += count;
46064631
}
46074632

4608-
invalidate();
4609-
46104633
final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
46114634
if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
46124635
fillGap(down);
@@ -4629,7 +4652,6 @@ boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
46294652
mBlockLayoutRequests = false;
46304653

46314654
invokeOnItemScrollListener();
4632-
awakenScrollBars();
46334655

46344656
return false;
46354657
}

core/java/android/widget/EdgeEffect.java

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package android.widget;
1818

19+
import android.graphics.Rect;
1920
import com.android.internal.R;
2021

2122
import android.content.Context;
@@ -45,6 +46,7 @@
4546
* {@link #draw(Canvas)} method.</p>
4647
*/
4748
public class EdgeEffect {
49+
@SuppressWarnings("UnusedDeclaration")
4850
private static final String TAG = "EdgeEffect";
4951

5052
// Time it will take the effect to fully recede in ms
@@ -57,10 +59,7 @@ public class EdgeEffect {
5759
private static final int PULL_DECAY_TIME = 1000;
5860

5961
private static final float MAX_ALPHA = 1.f;
60-
private static final float HELD_EDGE_ALPHA = 0.7f;
6162
private static final float HELD_EDGE_SCALE_Y = 0.5f;
62-
private static final float HELD_GLOW_ALPHA = 0.5f;
63-
private static final float HELD_GLOW_SCALE_Y = 0.5f;
6463

6564
private static final float MAX_GLOW_HEIGHT = 4.f;
6665

@@ -76,7 +75,9 @@ public class EdgeEffect {
7675
private final Drawable mGlow;
7776
private int mWidth;
7877
private int mHeight;
79-
private final int MIN_WIDTH = 300;
78+
private int mX;
79+
private int mY;
80+
private static final int MIN_WIDTH = 300;
8081
private final int mMinWidth;
8182

8283
private float mEdgeAlpha;
@@ -119,6 +120,8 @@ public class EdgeEffect {
119120
private int mState = STATE_IDLE;
120121

121122
private float mPullDistance;
123+
124+
private final Rect mBounds = new Rect();
122125

123126
/**
124127
* Construct a new EdgeEffect with a theme appropriate for the provided context.
@@ -144,6 +147,29 @@ public void setSize(int width, int height) {
144147
mHeight = height;
145148
}
146149

150+
/**
151+
* Set the position of this edge effect in pixels. This position is
152+
* only used by {@link #getBounds()}.
153+
*
154+
* @param x The position of the edge effect on the X axis
155+
* @param y The position of the edge effect on the Y axis
156+
*/
157+
void setPosition(int x, int y) {
158+
mX = x;
159+
mY = y;
160+
}
161+
162+
boolean isIdle() {
163+
return mState == STATE_IDLE;
164+
}
165+
166+
/**
167+
* Returns the height of the effect itself.
168+
*/
169+
int getHeight() {
170+
return Math.max(mGlow.getBounds().height(), mEdge.getBounds().height());
171+
}
172+
147173
/**
148174
* Reports if this EdgeEffect's animation is finished. If this method returns false
149175
* after a call to {@link #draw(Canvas)} the host widget should schedule another
@@ -301,7 +327,6 @@ public boolean draw(Canvas canvas) {
301327
update();
302328

303329
final int edgeHeight = mEdge.getIntrinsicHeight();
304-
final int edgeWidth = mEdge.getIntrinsicWidth();
305330
final int glowHeight = mGlow.getIntrinsicHeight();
306331
final int glowWidth = mGlow.getIntrinsicWidth();
307332

@@ -334,9 +359,23 @@ public boolean draw(Canvas canvas) {
334359
}
335360
mEdge.draw(canvas);
336361

362+
if (mState == STATE_RECEDE && glowBottom == 0 && edgeBottom == 0) {
363+
mState = STATE_IDLE;
364+
}
365+
337366
return mState != STATE_IDLE;
338367
}
339368

369+
/**
370+
* Returns the bounds of the edge effect.
371+
*/
372+
public Rect getBounds() {
373+
mBounds.set(mGlow.getBounds());
374+
mBounds.union(mEdge.getBounds());
375+
mBounds.offset(mX, mY);
376+
return mBounds;
377+
}
378+
340379
private void update() {
341380
final long time = AnimationUtils.currentAnimationTimeMillis();
342381
final float t = Math.min((time - mStartTime) / mDuration, 1.f);

0 commit comments

Comments
 (0)