Performance optimizations for animations and toolkit
Change-Id: I316a48273a9cbb428a965e4b849b3e5e9e8202f1
diff --git a/core/java/android/animation/KeyframeSet.java b/core/java/android/animation/KeyframeSet.java
index 1741e60..2242462 100644
--- a/core/java/android/animation/KeyframeSet.java
+++ b/core/java/android/animation/KeyframeSet.java
@@ -28,12 +28,18 @@
private int mNumKeyframes;
- ArrayList<Keyframe> mKeyframes;
+ Keyframe mFirstKeyframe;
+ Keyframe mLastKeyframe;
+ TimeInterpolator mInterpolator; // only used in the 2-keyframe case
+ ArrayList<Keyframe> mKeyframes; // only used when there are not 2 keyframes
public KeyframeSet(Keyframe... keyframes) {
+ mNumKeyframes = keyframes.length;
mKeyframes = new ArrayList<Keyframe>();
mKeyframes.addAll(Arrays.asList(keyframes));
- mNumKeyframes = mKeyframes.size();
+ mFirstKeyframe = mKeyframes.get(0);
+ mLastKeyframe = mKeyframes.get(mNumKeyframes - 1);
+ mInterpolator = mLastKeyframe.getInterpolator();
}
public static KeyframeSet ofInt(int... values) {
@@ -125,32 +131,40 @@
* @return The animated value.
*/
public Object getValue(float fraction, TypeEvaluator evaluator) {
- // TODO: special-case 2-keyframe common case
+
+ // Special-case optimization for the common case of only two keyframes
+ if (mNumKeyframes == 2) {
+ if (mInterpolator != null) {
+ fraction = mInterpolator.getInterpolation(fraction);
+ }
+ return evaluator.evaluate(fraction, mFirstKeyframe.getValue(),
+ mLastKeyframe.getValue());
+ }
if (fraction <= 0f) {
- final Keyframe prevKeyframe = mKeyframes.get(0);
final Keyframe nextKeyframe = mKeyframes.get(1);
final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
if (interpolator != null) {
fraction = interpolator.getInterpolation(fraction);
}
- float intervalFraction = (fraction - prevKeyframe.getFraction()) /
- (nextKeyframe.getFraction() - prevKeyframe.getFraction());
- return evaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
+ final float prevFraction = mFirstKeyframe.getFraction();
+ float intervalFraction = (fraction - prevFraction) /
+ (nextKeyframe.getFraction() - prevFraction);
+ return evaluator.evaluate(intervalFraction, mFirstKeyframe.getValue(),
nextKeyframe.getValue());
} else if (fraction >= 1f) {
final Keyframe prevKeyframe = mKeyframes.get(mNumKeyframes - 2);
- final Keyframe nextKeyframe = mKeyframes.get(mNumKeyframes - 1);
- final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
+ final TimeInterpolator interpolator = mLastKeyframe.getInterpolator();
if (interpolator != null) {
fraction = interpolator.getInterpolation(fraction);
}
- float intervalFraction = (fraction - prevKeyframe.getFraction()) /
- (nextKeyframe.getFraction() - prevKeyframe.getFraction());
+ final float prevFraction = prevKeyframe.getFraction();
+ float intervalFraction = (fraction - prevFraction) /
+ (mLastKeyframe.getFraction() - prevFraction);
return evaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
- nextKeyframe.getValue());
+ mLastKeyframe.getValue());
}
- Keyframe prevKeyframe = mKeyframes.get(0);
+ Keyframe prevKeyframe = mFirstKeyframe;
for (int i = 1; i < mNumKeyframes; ++i) {
Keyframe nextKeyframe = mKeyframes.get(i);
if (fraction < nextKeyframe.getFraction()) {
@@ -158,14 +172,15 @@
if (interpolator != null) {
fraction = interpolator.getInterpolation(fraction);
}
- float intervalFraction = (fraction - prevKeyframe.getFraction()) /
- (nextKeyframe.getFraction() - prevKeyframe.getFraction());
+ final float prevFraction = prevKeyframe.getFraction();
+ float intervalFraction = (fraction - prevFraction) /
+ (nextKeyframe.getFraction() - prevFraction);
return evaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
nextKeyframe.getValue());
}
prevKeyframe = nextKeyframe;
}
- // shouldn't get here
- return mKeyframes.get(mNumKeyframes - 1).getValue();
+ // shouldn't reach here
+ return mLastKeyframe.getValue();
}
}
diff --git a/core/java/android/animation/ObjectAnimator.java b/core/java/android/animation/ObjectAnimator.java
index 7427651..5b8c91d 100644
--- a/core/java/android/animation/ObjectAnimator.java
+++ b/core/java/android/animation/ObjectAnimator.java
@@ -376,9 +376,14 @@
*/
@Override
public void setTarget(Object target) {
- mTarget = target;
- // New property/values/target should cause re-initialization prior to starting
- mInitialized = false;
+ if (mTarget != target) {
+ mTarget = target;
+ if (mTarget != null && target != null && mTarget.getClass() == target.getClass()) {
+ return;
+ }
+ // New target type should cause re-initialization prior to starting
+ mInitialized = false;
+ }
}
@Override
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index ad8c971..a0b70b5 100755
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -1142,7 +1142,7 @@
switch (mPlayingState) {
case RUNNING:
case SEEKED:
- float fraction = (float)(currentTime - mStartTime) / mDuration;
+ float fraction = mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f;
if (fraction >= 1f) {
if (mCurrentIteration < mRepeatCount || mRepeatCount == INFINITE) {
// Time to repeat
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 87e03cf..ab97569 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -6296,8 +6296,14 @@
if (invalidateCache) {
mPrivateFlags &= ~DRAWING_CACHE_VALID;
}
- final ViewParent p = mParent;
final AttachInfo ai = mAttachInfo;
+ final ViewParent p = mParent;
+ if (ai != null && ai.mHardwareAccelerated) {
+ // fast-track for GL-enabled applications; just invalidate the whole hierarchy
+ // with a null dirty rect, which tells the ViewRoot to redraw everything
+ p.invalidateChild(this, null);
+ return;
+ }
if (p != null && ai != null) {
final Rect r = ai.mTmpInvalRect;
r.set(0, 0, mRight - mLeft, mBottom - mTop);
@@ -6321,7 +6327,8 @@
*/
@ViewDebug.ExportedProperty(category = "drawing")
public boolean isOpaque() {
- return (mPrivateFlags & OPAQUE_MASK) == OPAQUE_MASK;
+ return (mPrivateFlags & OPAQUE_MASK) == OPAQUE_MASK &&
+ (mAlpha >= 1.0f - ViewConfiguration.ALPHA_THRESHOLD);
}
private void computeOpaqueFlags() {
@@ -8618,7 +8625,11 @@
*/
@RemotableViewMethod
public void setBackgroundColor(int color) {
- setBackgroundDrawable(new ColorDrawable(color));
+ if (mBGDrawable instanceof ColorDrawable) {
+ ((ColorDrawable) mBGDrawable).setColor(color);
+ } else {
+ setBackgroundDrawable(new ColorDrawable(color));
+ }
}
/**
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index b881bdd..aef13ad 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -2101,6 +2101,8 @@
final int cr = child.mRight;
final int cb = child.mBottom;
+ final boolean childHasIdentityMatrix = child.hasIdentityMatrix();
+
final int flags = mGroupFlags;
if ((flags & FLAG_CLEAR_TRANSFORMATION) == FLAG_CLEAR_TRANSFORMATION) {
@@ -2182,7 +2184,7 @@
}
}
- concatMatrix |= !child.hasIdentityMatrix();
+ concatMatrix |= !childHasIdentityMatrix;
// Sets the flag as early as possible to allow draw() implementations
// to call invalidate() successfully when doing animations
@@ -2231,40 +2233,41 @@
}
if (transformToApply != null || alpha < 1.0f || !child.hasIdentityMatrix()) {
- int transX = 0;
- int transY = 0;
+ if (transformToApply != null || !childHasIdentityMatrix) {
+ int transX = 0;
+ int transY = 0;
- if (hasNoCache) {
- transX = -sx;
- transY = -sy;
- }
+ if (hasNoCache) {
+ transX = -sx;
+ transY = -sy;
+ }
- if (transformToApply != null) {
- if (concatMatrix) {
- // Undo the scroll translation, apply the transformation matrix,
- // then redo the scroll translate to get the correct result.
+ if (transformToApply != null) {
+ if (concatMatrix) {
+ // Undo the scroll translation, apply the transformation matrix,
+ // then redo the scroll translate to get the correct result.
+ canvas.translate(-transX, -transY);
+ canvas.concat(transformToApply.getMatrix());
+ canvas.translate(transX, transY);
+ mGroupFlags |= FLAG_CLEAR_TRANSFORMATION;
+ }
+
+ float transformAlpha = transformToApply.getAlpha();
+ if (transformAlpha < 1.0f) {
+ alpha *= transformToApply.getAlpha();
+ mGroupFlags |= FLAG_CLEAR_TRANSFORMATION;
+ }
+ }
+
+ if (!childHasIdentityMatrix) {
canvas.translate(-transX, -transY);
- canvas.concat(transformToApply.getMatrix());
+ canvas.concat(child.getMatrix());
canvas.translate(transX, transY);
- mGroupFlags |= FLAG_CLEAR_TRANSFORMATION;
}
-
- float transformAlpha = transformToApply.getAlpha();
- if (transformAlpha < 1.0f) {
- alpha *= transformToApply.getAlpha();
- mGroupFlags |= FLAG_CLEAR_TRANSFORMATION;
- }
- }
-
- if (!child.hasIdentityMatrix()) {
- canvas.translate(-transX, -transY);
- canvas.concat(child.getMatrix());
- canvas.translate(transX, transY);
}
if (alpha < 1.0f) {
mGroupFlags |= FLAG_CLEAR_TRANSFORMATION;
-
if (hasNoCache) {
final int multipliedAlpha = (int) (255 * alpha);
if (!child.onSetAlpha(multipliedAlpha)) {
@@ -3209,19 +3212,6 @@
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
- final int[] location = attachInfo.mInvalidateChildLocation;
- location[CHILD_LEFT_INDEX] = child.mLeft;
- location[CHILD_TOP_INDEX] = child.mTop;
- Matrix childMatrix = child.getMatrix();
- if (!childMatrix.isIdentity()) {
- RectF boundingRect = attachInfo.mTmpTransformRect;
- boundingRect.set(dirty);
- childMatrix.mapRect(boundingRect);
- dirty.set((int) boundingRect.left, (int) boundingRect.top,
- (int) (boundingRect.right + 0.5f),
- (int) (boundingRect.bottom + 0.5f));
- }
-
// If the child is drawing an animation, we want to copy this flag onto
// ourselves and the parent to make sure the invalidate request goes
// through
@@ -3229,45 +3219,95 @@
// Check whether the child that requests the invalidate is fully opaque
final boolean isOpaque = child.isOpaque() && !drawAnimation &&
- child.getAnimation() != null;
+ child.getAnimation() == null;
// 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 (dirty == null) {
+ do {
+ View view = null;
+ if (parent instanceof View) {
+ view = (View) parent;
+ if ((view.mPrivateFlags & DIRTY_MASK) != 0) {
+ // already marked dirty - we're done
+ break;
+ }
+ }
+
+ if (drawAnimation) {
+ if (view != null) {
+ view.mPrivateFlags |= DRAW_ANIMATION;
+ } else if (parent instanceof ViewRoot) {
+ ((ViewRoot) parent).mIsAnimating = true;
+ }
+ }
+
+ if (parent instanceof ViewRoot) {
+ ((ViewRoot) parent).invalidate();
+ parent = null;
+ } else if (view != null) {
+ if ((mPrivateFlags & DRAWN) == DRAWN) {
+ view.mPrivateFlags &= ~DRAWING_CACHE_VALID;
+ if (view != null && (view.mPrivateFlags & DIRTY_MASK) != DIRTY) {
+ view.mPrivateFlags =
+ (view.mPrivateFlags & ~DIRTY_MASK) | opaqueFlag;
+ }
+ parent = view.mParent;
+ } else {
+ parent = null;
+ }
+ }
+ } while (parent != null);
+ } else {
+ final int[] location = attachInfo.mInvalidateChildLocation;
+ location[CHILD_LEFT_INDEX] = child.mLeft;
+ location[CHILD_TOP_INDEX] = child.mTop;
+ Matrix childMatrix = child.getMatrix();
+ if (!childMatrix.isIdentity()) {
+ RectF boundingRect = attachInfo.mTmpTransformRect;
+ boundingRect.set(dirty);
+ childMatrix.mapRect(boundingRect);
+ dirty.set((int) boundingRect.left, (int) boundingRect.top,
+ (int) (boundingRect.right + 0.5f),
+ (int) (boundingRect.bottom + 0.5f));
}
- if (drawAnimation) {
+ do {
+ View view = null;
+ if (parent instanceof View) {
+ view = (View) parent;
+ }
+
+ if (drawAnimation) {
+ 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);
if (view != null) {
- view.mPrivateFlags |= DRAW_ANIMATION;
- } else if (parent instanceof ViewRoot) {
- ((ViewRoot) parent).mIsAnimating = true;
+ // Account for transform on current parent
+ Matrix m = view.getMatrix();
+ if (!m.isIdentity()) {
+ RectF boundingRect = attachInfo.mTmpTransformRect;
+ boundingRect.set(dirty);
+ m.mapRect(boundingRect);
+ dirty.set((int) boundingRect.left, (int) boundingRect.top,
+ (int) (boundingRect.right + 0.5f),
+ (int) (boundingRect.bottom + 0.5f));
+ }
}
- }
-
- // 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);
- if (view != null) {
- // Account for transform on current parent
- Matrix m = view.getMatrix();
- if (!m.isIdentity()) {
- RectF boundingRect = attachInfo.mTmpTransformRect;
- boundingRect.set(dirty);
- m.mapRect(boundingRect);
- dirty.set((int) boundingRect.left, (int) boundingRect.top,
- (int) (boundingRect.right + 0.5f),
- (int) (boundingRect.bottom + 0.5f));
- }
- }
- } while (parent != null);
+ } while (parent != null);
+ }
}
}
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index 06261bb..22a7773 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -543,6 +543,11 @@
public void invalidateChild(View child, Rect dirty) {
checkThread();
if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty);
+ if (dirty == null) {
+ // Fast invalidation for GL-enabled applications; GL must redraw everything
+ invalidate();
+ return;
+ }
if (mCurScrollY != 0 || mTranslator != null) {
mTempRect.set(dirty);
dirty = mTempRect;