Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Timob 15951: Android: Animating a child view in vertical layout no longer works correctly #5128

Merged
merged 8 commits into from
Jan 3, 2014
Expand Up @@ -320,6 +320,13 @@ private AnimatorSet buildPropertyAnimators(int x, int y, int w, int h, int paren
{
List<Animator> animators = new ArrayList<Animator>();
boolean includesRotation = false;
AnimatorUpdateListener updateListener = null;
// For pre-Honeycomb, there will be flicker during animation if we update the layout based on the
// scaling/translation factors between animation frames.
// Only re-layout for Honeycomb+. For now, we only re-layout for scaling and translation.
if (!PRE_HONEYCOMB) {
updateListener = new AnimatorUpdateListener();
}

if (toOpacity != null) {
addAnimator(animators, ObjectAnimator.ofFloat(view, "alpha", toOpacity.floatValue()));
Expand Down Expand Up @@ -372,18 +379,29 @@ private AnimatorSet buildPropertyAnimators(int x, int y, int w, int h, int paren
break;
case Operation.TYPE_SCALE:
if (operation.scaleFromValuesSpecified) {
addAnimator(animators, ObjectAnimator.ofFloat(view, "scaleX", operation.scaleFromX,
operation.scaleToX));
ObjectAnimator animX = ObjectAnimator.ofFloat(view, "scaleX", operation.scaleFromX, operation.scaleToX);
if (updateListener != null) {
animX.addUpdateListener(updateListener);
}
addAnimator(animators, animX);
addAnimator(animators, ObjectAnimator.ofFloat(view, "scaleY", operation.scaleFromY,
operation.scaleToY));

} else {
addAnimator(animators, ObjectAnimator.ofFloat(view, "scaleX", operation.scaleToX));
ObjectAnimator animX = ObjectAnimator.ofFloat(view, "scaleX", operation.scaleToX);
if (updateListener != null) {
animX.addUpdateListener(updateListener);
}
addAnimator(animators, animX);
addAnimator(animators, ObjectAnimator.ofFloat(view, "scaleY", operation.scaleToY));
}
break;
case Operation.TYPE_TRANSLATE:
addAnimator(animators, ObjectAnimator.ofFloat(view, "translationX", operation.translateX));
ObjectAnimator animX = ObjectAnimator.ofFloat(view, "translationX", operation.translateX);
if (updateListener != null) {
animX.addUpdateListener(updateListener);
}
addAnimator(animators, animX);
addAnimator(animators, ObjectAnimator.ofFloat(view, "translationY", operation.translateY));
}
}
Expand Down Expand Up @@ -447,7 +465,11 @@ private AnimatorSet buildPropertyAnimators(int x, int y, int w, int h, int paren
int translationX = horizontal[0] - x;
int translationY = vertical[0] - y;

addAnimator(animators, ObjectAnimator.ofFloat(view, "translationX", translationX));
ObjectAnimator animX = ObjectAnimator.ofFloat(view, "translationX", translationX);
if (updateListener != null) {
animX.addUpdateListener(updateListener);
}
addAnimator(animators, animX);
addAnimator(animators, ObjectAnimator.ofFloat(view, "translationY", translationY));

// Pre-Honeycomb, we will need to update layout params at end of
Expand Down Expand Up @@ -515,7 +537,11 @@ private AnimatorSet buildPropertyAnimators(int x, int y, int w, int h, int paren
view.setLayoutParams(params);
}

addAnimator(animators, ObjectAnimator.ofFloat(view, "scaleX", scaleX));
ObjectAnimator animX = ObjectAnimator.ofFloat(view, "scaleX", scaleX);
if (updateListener != null) {
animX.addUpdateListener(updateListener);
}
addAnimator(animators, animX);
addAnimator(animators, ObjectAnimator.ofFloat(view, "scaleY", scaleY));

setAnchor(w, h);
Expand Down Expand Up @@ -1084,6 +1110,17 @@ public void onAnimationEnd(Animation animation)
}
}

/**
* The listener to receive callbacks on every animation frame.
*/
protected class AnimatorUpdateListener implements ValueAnimator.AnimatorUpdateListener
{
public void onAnimationUpdate(ValueAnimator animation)
{
view.requestLayout();
}
}

/**
* The listener for Honeycomb+ property Animators.
*/
Expand Down
Expand Up @@ -23,11 +23,14 @@
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.OnHierarchyChangeListener;

import com.nineoldandroids.view.animation.AnimatorProxy;

/**
* Base layout class for all Titanium views.
*/
Expand All @@ -54,6 +57,7 @@ public enum LayoutArrangement {
}

protected static final String TAG = "TiCompositeLayout";
protected static final boolean PRE_HONEYCOMB = Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB;

public static final int NOT_SET = Integer.MIN_VALUE;

Expand Down Expand Up @@ -470,6 +474,32 @@ protected int getMeasuredHeight(int maxHeight, int heightSpec)
return resolveSize(maxHeight, heightSpec);
}

/**
* In Property Animation, the scaling/translation factors may change, so we need to take them
* into account when laying out the view.
* @param child the view
* @return an array with the view's scaling and translation factors, in the form of
* [scaleX, scaleY, translationX, translationY].
*/
protected float[] getScaleAndTranslation(View child)
{
float childScaleX = 1f;
float childScaleY = 1f;
float childTranslationX = 0f;
float childTranslationY = 0f;

// We only re-layout between animation frames for Honeycomb+ for now due to the flicker issue on Gingerbread.
// So we only need to get scaling/translation factors for Honeycomb+.
if (!PRE_HONEYCOMB) {
childScaleX = child.getScaleX();
childScaleY = child.getScaleY();
childTranslationX = child.getTranslationX();
childTranslationY = child.getTranslationY();
}

return new float[]{childScaleX, childScaleY, childTranslationX, childTranslationY};
}

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
Expand Down Expand Up @@ -518,6 +548,16 @@ protected void onLayout(boolean changed, int l, int t, int r, int b)
int childMeasuredHeight = child.getMeasuredHeight();
int childMeasuredWidth = child.getMeasuredWidth();

// In Property Animation, the scaling/translation factors may change, so we need to take them
// into account when calculating the child's position if it's in a vertical/horizontal layout.
float[] childScaleAndTranslation = getScaleAndTranslation(child);
float childScaleX = childScaleAndTranslation[0];
float childScaleY = childScaleAndTranslation[1];
float childTranslationX = childScaleAndTranslation[2];
float childTranslationY = childScaleAndTranslation[3];
int childRenderedHeight = (int)(childMeasuredHeight * childScaleY);
int childRenderedWidth = (int)(childMeasuredWidth * childScaleX);

if (isHorizontalArrangement()) {
if (i == 0) {
horizontalLayoutCurrentLeft = left;
Expand All @@ -527,25 +567,29 @@ protected void onLayout(boolean changed, int l, int t, int r, int b)
horiztonalLayoutPreviousRight = 0;
updateRowForHorizontalWrap(right, i);
}
computeHorizontalLayoutPosition(params, childMeasuredWidth, childMeasuredHeight, right, top, bottom, horizontal, vertical, i);
computeHorizontalLayoutPosition(params, childRenderedWidth, childRenderedHeight, right, top, bottom, horizontal, vertical, i);
horizontalLayoutCurrentLeft += childTranslationX;

} else {
// Try to calculate width/height from pins, and default to measured width/height. We have to do this in
// onLayout since we can't get the correct top, bottom, left, and right values inside constrainChild().
childMeasuredHeight = calculateHeightFromPins(params, top, bottom, getHeight(), childMeasuredHeight);
childMeasuredWidth = calculateWidthFromPins(params, left, right, getWidth(), childMeasuredWidth);

computePosition(this, params.optionLeft, params.optionCenterX, params.optionRight, childMeasuredWidth, left, right, horizontal);
childRenderedHeight = (int)(childMeasuredHeight * childScaleY);
childRenderedWidth = (int)(childMeasuredWidth * childScaleX);

computePosition(this, params.optionLeft, params.optionCenterX, params.optionRight, childRenderedWidth, left, right, horizontal);
if (isVerticalArrangement()) {
computeVerticalLayoutPosition(currentHeight, params.optionTop, childMeasuredHeight, top, vertical,
computeVerticalLayoutPosition(currentHeight, params.optionTop, childRenderedHeight, top, vertical,
bottom);
// Include bottom in height calculation for vertical layout (used as padding)
TiDimension optionBottom = params.optionBottom;
if (optionBottom != null) {
currentHeight += optionBottom.getAsPixels(this);
}
} else {
computePosition(this, params.optionTop, params.optionCenterY, params.optionBottom, childMeasuredHeight, top, bottom, vertical);
computePosition(this, params.optionTop, params.optionCenterY, params.optionBottom, childRenderedHeight, top, bottom, vertical);
}
}

Expand All @@ -558,8 +602,7 @@ protected void onLayout(boolean changed, int l, int t, int r, int b)
int newHeight = vertical[1] - vertical[0];
// If the old child measurements do not match the new measurements that we calculated, then update the
// child measurements accordingly
if (newWidth != child.getMeasuredWidth()
|| newHeight != child.getMeasuredHeight()) {
if (newWidth != childRenderedWidth || newHeight != childRenderedHeight) {
int newWidthSpec = MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY);
int newHeightSpec = MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY);
child.measure(newWidthSpec, newHeightSpec);
Expand All @@ -575,9 +618,15 @@ protected void onLayout(boolean changed, int l, int t, int r, int b)
}
}

if (childScaleX != 0) {
horizontal[1] = (int) (horizontal[0] + newWidth / childScaleX);
}
if (childScaleY != 0) {
vertical[1] = (int) (vertical[0] + newHeight / childScaleY);
}
child.layout(horizontal[0], vertical[0], horizontal[1], vertical[1]);

currentHeight += newHeight;
currentHeight += newHeight + childTranslationY;
if (params.optionTop != null) {
currentHeight += params.optionTop.getAsPixels(this);
}
Expand Down Expand Up @@ -732,9 +781,17 @@ private void updateRowForHorizontalWrap(int maxRight, int currentIndex)

for (i = currentIndex; i < getChildCount(); i++) {
View child = getChildAt(i);
// Calculate row width/height with padding
rowWidth += child.getMeasuredWidth() + getViewWidthPadding(child, getWidth());
rowHeight = child.getMeasuredHeight() + getViewHeightPadding(child, parentHeight);

float[] childScaleAndTranslation = getScaleAndTranslation(child);
float childScaleX = childScaleAndTranslation[0];
float childScaleY = childScaleAndTranslation[1];
float childTranslationX = childScaleAndTranslation[2];
float childTranslationY = childScaleAndTranslation[3];

// Calculate row width/height with padding, scaling and translation
rowWidth += child.getMeasuredWidth() * childScaleX + getViewWidthPadding(child, getWidth()) + childTranslationX;
rowHeight = (int) (child.getMeasuredHeight() * childScaleY) + getViewHeightPadding(child, parentHeight)
+ (int) childTranslationY;

if (rowWidth > maxRight) {
horizontalLayoutLastIndexBeforeWrap = i - 1;
Expand Down