diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index d042f28d86547..753cdf2e360a7 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -849,6 +849,28 @@ public class View implements Drawable.Callback, KeyEvent.Callback { */ public static final int HAPTIC_FEEDBACK_ENABLED = 0x10000000; + /** + * View flag indicating whether this view was invalidated (fully or partially.) + * + * @hide + */ + static final int DIRTY = 0x20000000; + + /** + * View flag indicating whether this view was invalidated by an opaque + * invalidate request. + * + * @hide + */ + static final int DIRTY_OPAQUE = 0x40000000; + + /** + * Mask for {@link #DIRTY} and {@link #DIRTY_OPAQUE}. + * + * @hide + */ + static final int DIRTY_MASK = 0x60000000; + /** * Use with {@link #focusSearch}. Move focus to the previous selectable * item. @@ -4521,6 +4543,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback { } } + /** + * Indicates whether this View is opaque. An opaque View guarantees that it will + * draw all the pixels overlapping its bounds using a fully opaque color. + * + * Subclasses of View should override this method whenever possible to indicate + * whether an instance is opaque. Opaque Views are treated in a special way by + * the View hierarchy, possibly allowing it to perform optimizations during + * invalidate/draw passes. + * + * @return True if this View is guaranteed to be fully opaque, false otherwise. + * + * @hide Pending API council approval + */ + public boolean isOpaque() { + return mBGDrawable != null && mBGDrawable.getOpacity() == PixelFormat.OPAQUE; + } + /** * @return A handler associated with the thread running the View. This * handler can be used to pump events in the UI events queue. @@ -5687,7 +5726,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback { final int restoreCount = canvas.save(); canvas.translate(-mScrollX, -mScrollY); - mPrivateFlags |= DRAWN; + mPrivateFlags = (mPrivateFlags & ~DIRTY_MASK) | DRAWN; // Fast path for layouts with no backgrounds if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) { @@ -5875,7 +5914,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback { ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW); } - mPrivateFlags |= DRAWN; + final boolean dirtyOpaque = (mPrivateFlags & DIRTY_MASK) == DIRTY_OPAQUE; + mPrivateFlags = (mPrivateFlags & ~DIRTY_MASK) | DRAWN; + + if (dirtyOpaque) android.util.Log.d("View", "Skipping draw in " + this); /* * Draw traversal performs several drawing steps which must be executed @@ -5892,22 +5934,24 @@ public class View implements Drawable.Callback, KeyEvent.Callback { // Step 1, draw the background, if needed int saveCount; - final Drawable background = mBGDrawable; - if (background != null) { - final int scrollX = mScrollX; - final int scrollY = mScrollY; + if (!dirtyOpaque) { + final Drawable background = mBGDrawable; + if (background != null) { + final int scrollX = mScrollX; + final int scrollY = mScrollY; - if (mBackgroundSizeChanged) { - background.setBounds(0, 0, mRight - mLeft, mBottom - mTop); - mBackgroundSizeChanged = false; - } + if (mBackgroundSizeChanged) { + background.setBounds(0, 0, mRight - mLeft, mBottom - mTop); + mBackgroundSizeChanged = false; + } - if ((scrollX | scrollY) == 0) { - background.draw(canvas); - } else { - canvas.translate(scrollX, scrollY); - background.draw(canvas); - canvas.translate(-scrollX, -scrollY); + if ((scrollX | scrollY) == 0) { + background.draw(canvas); + } else { + canvas.translate(scrollX, scrollY); + background.draw(canvas); + canvas.translate(-scrollX, -scrollY); + } } } @@ -5917,7 +5961,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback { boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0; if (!verticalEdges && !horizontalEdges) { // Step 3, draw the content - onDraw(canvas); + if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); @@ -6020,7 +6064,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback { } // Step 3, draw the content - onDraw(canvas); + if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index e686d1c20bd21..31159d722d75d 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -1796,7 +1796,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager boolean preventRequestLayout) { child.mParent = null; addViewInner(child, index, params, preventRequestLayout); - child.mPrivateFlags |= DRAWN; + child.mPrivateFlags = (child.mPrivateFlags & ~DIRTY_MASK) | DRAWN; return true; } @@ -2210,7 +2210,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager addInArray(child, index); child.mParent = this; - child.mPrivateFlags |= DRAWN; + child.mPrivateFlags = (child.mPrivateFlags & ~DIRTY_MASK) | DRAWN; if (child.hasFocus()) { requestChildFocus(child, child.findFocus()); @@ -2320,15 +2320,33 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager // ourselves and the parent to make sure the invalidate request goes // through final boolean drawAnimation = (child.mPrivateFlags & DRAW_ANIMATION) == DRAW_ANIMATION; - + + // Check whether the child that requests the invalidate is fully opaque + final boolean isOpaque = child.isOpaque(); + // Mark the child as dirty, using the appropriate flag + // Make sure we do not set both flags at the same time + final int opaqueFlag = isOpaque ? DIRTY_OPAQUE : DIRTY; + do { + View view = null; + if (parent instanceof View) { + view = (View) parent; + } + if (drawAnimation) { - if (parent instanceof View) { - ((View) parent).mPrivateFlags |= DRAW_ANIMATION; + if (view != null) { + view.mPrivateFlags |= DRAW_ANIMATION; } else if (parent instanceof ViewRoot) { ((ViewRoot) parent).mIsAnimating = true; } } + + // If the parent is dirty opaque or not dirty, mark it dirty with the opaque + // flag coming from the child that initiated the invalidate + if (view != null && (view.mPrivateFlags & DIRTY_MASK) != DIRTY) { + view.mPrivateFlags = (view.mPrivateFlags & ~DIRTY_MASK) | opaqueFlag; + } + parent = parent.invalidateChildInParent(location, dirty); } while (parent != null); } diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java index 0b03626e6b678..819bc31192009 100644 --- a/core/java/android/view/ViewRoot.java +++ b/core/java/android/view/ViewRoot.java @@ -539,6 +539,7 @@ public final class ViewRoot extends Handler implements ViewParent, } dirty = mTempRect; } + // TODO: When doing a union with mDirty != empty, we must cancel all the DIRTY_OPAQUE flags mDirty.union(dirty); if (!mWillDrawSoon) { scheduleTraversals(); diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 767c7e7920d08..ffb6785dca275 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -1947,7 +1947,7 @@ public abstract class AbsListView extends AdapterView implements Te if (y != mLastY) { deltaY -= mMotionCorrection; int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY; - trackMotionScroll(deltaY, incrementalDeltaY); + trackMotionScroll(deltaY, incrementalDeltaY, true); // Check to see if we have bumped into the scroll limit View motionView = this.getChildAt(mMotionPosition - mFirstPosition); @@ -2286,7 +2286,7 @@ public abstract class AbsListView extends AdapterView implements Te delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta); } - trackMotionScroll(delta, delta); + trackMotionScroll(delta, delta, false); // Check to see if we have bumped into the scroll limit View motionView = getChildAt(mMotionPosition - mFirstPosition); @@ -2299,6 +2299,7 @@ public abstract class AbsListView extends AdapterView implements Te } if (more) { + invalidate(); mLastFlingY = y; post(this); } else { @@ -2323,6 +2324,7 @@ public abstract class AbsListView extends AdapterView implements Te private void clearScrollingCache() { if (mCachingStarted) { + mCachingStarted = false; setChildrenDrawnWithCacheEnabled(false); if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) { setChildrenDrawingCacheEnabled(false); @@ -2330,7 +2332,6 @@ public abstract class AbsListView extends AdapterView implements Te if (!isAlwaysDrawnWithCacheEnabled()) { invalidate(); } - mCachingStarted = false; } } @@ -2340,8 +2341,9 @@ public abstract class AbsListView extends AdapterView implements Te * @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. + * @param invalidate True to make this method call invalidate(), false otherwise. */ - void trackMotionScroll(int deltaY, int incrementalDeltaY) { + void trackMotionScroll(int deltaY, int incrementalDeltaY, boolean invalidate) { final int childCount = getChildCount(); if (childCount == 0) { return; @@ -2375,7 +2377,7 @@ public abstract class AbsListView extends AdapterView implements Te if (spaceAbove >= absIncrementalDeltaY && spaceBelow >= absIncrementalDeltaY) { hideSelector(); offsetChildrenTopAndBottom(incrementalDeltaY); - invalidate(); + if (invalidate) invalidate(); mMotionViewNewTop = mMotionViewOriginalTop + deltaY; } else { final int firstPosition = mFirstPosition; @@ -2453,7 +2455,7 @@ public abstract class AbsListView extends AdapterView implements Te mFirstPosition += count; } - invalidate(); + if (invalidate) invalidate(); fillGap(down); mBlockLayoutRequests = false; @@ -2788,7 +2790,7 @@ public abstract class AbsListView extends AdapterView implements Te int screenHeight = getResources().getDisplayMetrics().heightPixels; final int[] xy = new int[2]; getLocationOnScreen(xy); - // TODO: The 20 below should come from the theme and be expressed in dip + // TODO: The 20 below should come from the theme // TODO: And the gravity should be defined in the theme as well final int bottomGap = screenHeight - xy[1] - getHeight() + (int) (mDensityScale * 20); if (!mPopup.isShowing()) { diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java index 66c162eefc6dd..4a9957ad63f55 100644 --- a/core/java/android/widget/ListView.java +++ b/core/java/android/widget/ListView.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Rect; +import android.graphics.PixelFormat; import android.graphics.drawable.Drawable; import android.graphics.drawable.ColorDrawable; import android.os.Parcel; @@ -113,7 +114,11 @@ public class ListView extends AbsListView { Drawable mDivider; int mDividerHeight; + + private boolean mIsCacheColorOpaque; + private boolean mDividerIsOpaque; private boolean mClipDivider; + private boolean mHeaderDividersEnabled; private boolean mFooterDividersEnabled; @@ -2776,6 +2781,17 @@ public class ListView extends AbsListView { return mItemsCanFocus; } + @Override + public boolean isOpaque() { + return (mCachingStarted && mIsCacheColorOpaque && mDividerIsOpaque) || super.isOpaque(); + } + + @Override + public void setCacheColorHint(int color) { + mIsCacheColorOpaque = (color >>> 24) == 0xFF; + super.setCacheColorHint(color); + } + @Override protected void dispatchDraw(Canvas canvas) { // Draw the dividers @@ -2897,6 +2913,7 @@ public class ListView extends AbsListView { mClipDivider = false; } mDivider = divider; + mDividerIsOpaque = divider == null || divider.getOpacity() == PixelFormat.OPAQUE; requestLayoutIfNecessary(); } diff --git a/core/res/res/drawable/divider_horizontal_bright.9.png b/core/res/res/drawable/divider_horizontal_bright.9.png index 144fc224e9269..30c9b2b63e4ab 100644 Binary files a/core/res/res/drawable/divider_horizontal_bright.9.png and b/core/res/res/drawable/divider_horizontal_bright.9.png differ diff --git a/core/res/res/drawable/divider_horizontal_dark.9.png b/core/res/res/drawable/divider_horizontal_dark.9.png index 08838cafa8153..ce21acd8a9d42 100644 Binary files a/core/res/res/drawable/divider_horizontal_dark.9.png and b/core/res/res/drawable/divider_horizontal_dark.9.png differ