package info.guardianproject.securereaderinterface.widgets; import android.content.Context; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.Region.Op; import android.util.AttributeSet; import android.util.SparseArray; import android.view.View; import android.view.animation.Animation; import android.view.animation.Animation.AnimationListener; import android.view.animation.Transformation; import android.widget.RelativeLayout; public class AnimatedRelativeLayout extends RelativeLayout { private SparseArray<Rect> mOriginalStartPositions; private SparseArray<Rect> mStartPositions; private SparseArray<Rect> mEndPositions; private float mAnimationValue; private boolean mAnimating; public AnimatedRelativeLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public AnimatedRelativeLayout(Context context, AttributeSet attrs) { super(context, attrs); } public AnimatedRelativeLayout(Context context) { super(context); } public void setStartPositions(SparseArray<Rect> startPositions) { mStartPositions = startPositions; } public void animateToStartPositions(int duration) { mStartPositions = mOriginalStartPositions; applyAnimation(true, duration); } public boolean isAnimating() { return mAnimating; } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); applyAnimation(false, 1000); } private void applyAnimation(final boolean reversed, int duration) { if (mStartPositions != null && mStartPositions.size() > 0) { mEndPositions = new SparseArray<Rect>(); // Since the RelativeLayout clips children, set the "real" position // of the view to the // leftmost and topmost coordinate and size to the biggest size. // Then use // translation for the animations. When the animation is done we can // reset the actual // view size and position again. for (int index = 0; index < mStartPositions.size(); index++) { int id = mStartPositions.keyAt(index); View view = findViewById(id); if (view != null) { LayoutParams lpView = (LayoutParams) view.getLayoutParams(); Rect currentRect = new Rect(lpView.leftMargin, lpView.topMargin, lpView.leftMargin + view.getWidth(), lpView.topMargin + view.getHeight()); mEndPositions.put(id, currentRect); Rect startRect = mStartPositions.get(id); LayoutParams lp = (LayoutParams) view.getLayoutParams(); lp.height = Math.max(currentRect.height(), startRect.height()); lp.leftMargin = Math.min(currentRect.left, startRect.left); lp.topMargin = Math.min(currentRect.top, startRect.top); view.setLayoutParams(lp); } } final LayoutAnim anim = new LayoutAnim(reversed); anim.setDuration(duration); anim.setFillBefore(true); anim.setFillAfter(true); anim.setAnimationListener(new AnimationListener() { @Override public void onAnimationEnd(Animation animation) { post(new Runnable() { @Override public void run() { if (!reversed) resetAnimatedProperties(); } }); } @Override public void onAnimationRepeat(Animation animation) { } @Override public void onAnimationStart(Animation animation) { } }); mAnimationValue = reversed ? 1.0f : 0; mAnimating = true; this.startAnimation(anim); } } private void resetAnimatedProperties() { mAnimating = false; if (mStartPositions != null) { for (int index = 0; index < mStartPositions.size(); index++) { int id = mStartPositions.keyAt(index); View view = findViewById(id); if (view != null) { Rect endRect = mEndPositions.get(id); LayoutParams lp = (LayoutParams) view.getLayoutParams(); lp.height = endRect.height(); lp.leftMargin = endRect.left; lp.topMargin = endRect.top; view.setLayoutParams(lp); } } } if (mOriginalStartPositions == null) mOriginalStartPositions = mStartPositions; mStartPositions = null; this.clearAnimation(); } public class LayoutAnim extends Animation { private boolean mIsReversed; public LayoutAnim(boolean reversed) { mIsReversed = reversed; } @Override protected void applyTransformation(float interpolatedTime, Transformation t) { mAnimationValue = mIsReversed ? (1.0f - interpolatedTime) : interpolatedTime; invalidate(); } @Override public void initialize(int width, int height, int parentWidth, int parentHeight) { super.initialize(width, height, parentWidth, parentHeight); } @Override public boolean willChangeBounds() { return false; } } @Override protected boolean drawChild(Canvas canvas, View child, long drawingTime) { boolean clipSet = false; int sc = canvas.save(); if (isAnimating()) { if (child.getId() != View.NO_ID) { if (mStartPositions != null && mStartPositions.indexOfKey(child.getId()) >= 0) { Rect startRect = mStartPositions.get(child.getId()); Rect endRect = mEndPositions.get(child.getId()); boolean invertedLeft = endRect.left < startRect.left; boolean invertedTop = endRect.top < startRect.top; int height = (int) (startRect.height() + (mAnimationValue * (endRect.height() - startRect.height()))); int leftDelta = (int) ((invertedLeft ? (1 - mAnimationValue) : (mAnimationValue)) * (endRect.left - startRect.left)); int topDelta = (int) ((invertedTop ? (1 - mAnimationValue) : (mAnimationValue)) * (endRect.top - startRect.top)); if (invertedLeft) leftDelta = -leftDelta; if (invertedTop) topDelta = -topDelta; clipSet = true; canvas.clipRect(child.getLeft() + leftDelta, child.getTop() + topDelta, child.getRight() + leftDelta, child.getTop() + topDelta + height, Op.INTERSECT); canvas.translate(leftDelta, topDelta); } } } if (!clipSet) { canvas.clipRect(child.getLeft(), child.getTop(), child.getRight(), child.getBottom(), Op.INTERSECT); } boolean ret = super.drawChild(canvas, child, drawingTime); canvas.restoreToCount(sc); return ret; } }