diff --git a/library/src/main/java/com/flaviofaria/kenburnsview/KenBurnsView.java b/library/src/main/java/com/flaviofaria/kenburnsview/KenBurnsView.java index 7290dbb..de6c5b6 100644 --- a/library/src/main/java/com/flaviofaria/kenburnsview/KenBurnsView.java +++ b/library/src/main/java/com/flaviofaria/kenburnsview/KenBurnsView.java @@ -25,6 +25,8 @@ import android.util.AttributeSet; import android.widget.ImageView; +import com.burningthumb.premiervideokiosk.DrawOnImageView; + /** * {@link ImageView} extension that animates its image with the * Ken Burns Effect. @@ -32,7 +34,8 @@ * @see Transition * @see TransitionGenerator */ -public class KenBurnsView extends ImageView { +public class KenBurnsView extends DrawOnImageView +{ /** Delay between a pair of frames at a 60 FPS frame rate. */ private static final long FRAME_DELAY = 1000 / 60; @@ -73,32 +76,37 @@ public class KenBurnsView extends ImageView { private boolean mInitialized; - public KenBurnsView(Context context) { + public KenBurnsView(Context context) + { this(context, null); } - public KenBurnsView(Context context, AttributeSet attrs) { + public KenBurnsView(Context context, AttributeSet attrs) + { this(context, attrs, 0); } - public KenBurnsView(Context context, AttributeSet attrs, int defStyle) { + public KenBurnsView(Context context, AttributeSet attrs, int defStyle) + { super(context, attrs, defStyle); mInitialized = true; // Attention to the super call here! super.setScaleType(ImageView.ScaleType.MATRIX); } - +/* @Override public void setScaleType(ScaleType scaleType) { - // It'll always be center-cropped by default. + // It needs to be matrix or it does not work } +*/ @Override - public void setVisibility(int visibility) { + public void setVisibility(int visibility) + { super.setVisibility(visibility); /* When not visible, onDraw() doesn't get called, but the time elapses anyway. */ @@ -114,44 +122,51 @@ public void setVisibility(int visibility) { @Override - public void setImageBitmap(Bitmap bm) { + public void setImageBitmap(Bitmap bm) + { super.setImageBitmap(bm); handleImageChange(); } @Override - public void setImageResource(int resId) { + public void setImageResource(int resId) + { super.setImageResource(resId); handleImageChange(); } @Override - public void setImageURI(Uri uri) { + public void setImageURI(Uri uri) + { super.setImageURI(uri); handleImageChange(); } @Override - public void setImageDrawable(Drawable drawable) { + public void setImageDrawable(Drawable drawable) + { super.setImageDrawable(drawable); handleImageChange(); } @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { + protected void onSizeChanged(int w, int h, int oldw, int oldh) + { super.onSizeChanged(w, h, oldw, oldh); - restart(); + + //restart(); + handleResize(); } @Override - protected void onDraw(Canvas canvas) { + public void onDraw(Canvas canvas) { Drawable d = getDrawable(); - if (!mPaused && d != null) { + if (ScaleType.MATRIX == getScaleType() && !mPaused && d != null) { if (mDrawableRect.isEmpty()) { updateDrawableBounds(); } else if (hasBounds()) { @@ -159,7 +174,9 @@ protected void onDraw(Canvas canvas) { startNewTransition(); } - if (mCurrentTrans.getDestinyRect() != null) { // If null, it's supposed to stop. + if (mCurrentTrans.getDestinationRect() != null) + { + // If null, it's supposed to stop. mElapsedTime += System.currentTimeMillis() - mLastFrameTime; RectF currentRect = mCurrentTrans.getInterpolatedRect(mElapsedTime); @@ -167,10 +184,12 @@ protected void onDraw(Canvas canvas) { float heightScale = mDrawableRect.height() / currentRect.height(); // Scale to make the current rect match the smallest drawable dimension. float currRectToDrwScale = Math.min(widthScale, heightScale); + // Scale to make the current rect match the viewport bounds. float vpWidthScale = mViewportRect.width() / currentRect.width(); float vpHeightScale = mViewportRect.height() / currentRect.height(); float currRectToVpScale = Math.min(vpWidthScale, vpHeightScale); + // Combines the two scales to fill the viewport with the current rect. float totalScale = currRectToDrwScale * currRectToVpScale; @@ -219,7 +238,8 @@ private void startNewTransition() { /** * Creates a new transition and starts over. */ - public void restart() { + public void restart() + { int width = getWidth(); int height = getHeight(); @@ -233,12 +253,26 @@ public void restart() { startNewTransition(); } + private void handleResize() + { + int width = getWidth(); + int height = getHeight(); + + if (width == 0 || height == 0) { + return; // Can't call handleResize() when view area is zero. + } + + updateViewport(width, height); + + } + /** * Checks whether this view has bounds. * @return */ - private boolean hasBounds() { + private boolean hasBounds() + { return !mViewportRect.isEmpty(); } @@ -247,7 +281,8 @@ private boolean hasBounds() { * Fires a start event on {@link #mTransitionListener}; * @param transition the transition that just started. */ - private void fireTransitionStart(Transition transition) { + private void fireTransitionStart(Transition transition) + { if (mTransitionListener != null && transition != null) { mTransitionListener.onTransitionStart(transition); } @@ -258,7 +293,8 @@ private void fireTransitionStart(Transition transition) { * Fires an end event on {@link #mTransitionListener}; * @param transition the transition that just ended. */ - private void fireTransitionEnd(Transition transition) { + private void fireTransitionEnd(Transition transition) + { if (mTransitionListener != null && transition != null) { mTransitionListener.onTransitionEnd(transition); } @@ -269,7 +305,8 @@ private void fireTransitionEnd(Transition transition) { * Sets the {@link TransitionGenerator} to be used in animations. * @param transgen the {@link TransitionGenerator} to be used in animations. */ - public void setTransitionGenerator(TransitionGenerator transgen) { + public void setTransitionGenerator(TransitionGenerator transgen) + { mTransGen = transgen; startNewTransition(); } @@ -280,7 +317,8 @@ public void setTransitionGenerator(TransitionGenerator transgen) { * @param width the new viewport with. * @param height the new viewport height. */ - private void updateViewport(float width, float height) { + private void updateViewport(float width, float height) + { mViewportRect.set(0, 0, width, height); } @@ -289,12 +327,17 @@ private void updateViewport(float width, float height) { * Updates the drawable bounds rect. This must be called every time the drawable * associated to this view changes. */ - private void updateDrawableBounds() { - if (mDrawableRect == null) { + private void updateDrawableBounds() + { + if (mDrawableRect == null) + { mDrawableRect = new RectF(); } + Drawable d = getDrawable(); - if (d != null && d.getIntrinsicHeight() > 0 && d.getIntrinsicWidth() > 0) { + + if (d != null && d.getIntrinsicHeight() > 0 && d.getIntrinsicWidth() > 0) + { mDrawableRect.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); } } @@ -304,7 +347,8 @@ private void updateDrawableBounds() { * This method is called every time the underlying image * is changed. */ - private void handleImageChange() { + private void handleImageChange() + { updateDrawableBounds(); /* Don't start a new transition if this event was fired during the super constructor execution. @@ -316,7 +360,8 @@ private void handleImageChange() { } - public void setTransitionListener(TransitionListener transitionListener) { + public void setTransitionListener(TransitionListener transitionListener) + { mTransitionListener = transitionListener; } @@ -324,7 +369,8 @@ public void setTransitionListener(TransitionListener transitionListener) { /** * Pauses the Ken Burns Effect animation. */ - public void pause() { + public void pause() + { mPaused = true; } @@ -332,7 +378,8 @@ public void pause() { /** * Resumes the Ken Burns Effect animation. */ - public void resume() { + public void resume() + { mPaused = false; // This will make the animation to continue from where it stopped. mLastFrameTime = System.currentTimeMillis(); @@ -343,7 +390,8 @@ public void resume() { /** * A transition listener receives notifications when a transition starts or ends. */ - public interface TransitionListener { + public interface TransitionListener + { /** * Notifies the start of a transition. * @param transition the transition that just started. diff --git a/library/src/main/java/com/flaviofaria/kenburnsview/MathUtils.java b/library/src/main/java/com/flaviofaria/kenburnsview/MathUtils.java index 4dd9672..b624f0e 100644 --- a/library/src/main/java/com/flaviofaria/kenburnsview/MathUtils.java +++ b/library/src/main/java/com/flaviofaria/kenburnsview/MathUtils.java @@ -15,6 +15,7 @@ */ package com.flaviofaria.kenburnsview; +import android.graphics.Rect; import android.graphics.RectF; /** @@ -60,4 +61,65 @@ protected static boolean haveSameAspectRatio(RectF r1, RectF r2) { protected static float getRectRatio(RectF rect) { return rect.width() / rect.height(); } + + /** + * Computes the rect inside the given rect for the aspect ratio. + * @param a_currentRect the rect to find the inside rect of. + * @param a_desiredAspect the aspect ratio to use + * @return the rect aspect ratio. + */ + protected static RectF getInsideRect(RectF a_currentRect, float a_desiredAspect) + { + /* + Example: + + a_currentRect.left = 0; + a_currentRect.right = 4; + a_currentRect.top = 0; + a_currentRect.bottom = 3; + + a_desiredAspect = 1.777777777777778f; + + Aspect ration of a 16:9 Rect = 1.777777777777778 + Inverse aspect of a 16:9 Rect = 0.5625 + + Given a 4:3 Rect at the origin the result would be + + l_currentAspect = 1.333333333333333 + + l_newWidth = 4.0f + + l_newHeight = 3.0f * 0.5625 + */ + + + float l_currentAspect = a_currentRect.width() / a_currentRect.height(); + + float l_inverseAspect = 1.0f / a_desiredAspect; + + float l_newWidth = 0.0f; + float l_newHeight = 0.0f; + + RectF l_newRect = new RectF(); + + if (a_desiredAspect > 1.0f) + { + l_newWidth = a_currentRect.width(); + l_newHeight = a_currentRect.width() * l_inverseAspect; + } + else + { + l_newWidth = a_currentRect.height() * l_inverseAspect; + l_newHeight = a_currentRect.height(); + } + + l_newRect.left = a_currentRect.centerX() - (l_newWidth / 2.0f); + l_newRect.right = a_currentRect.centerX() + (l_newWidth / 2.0f); + l_newRect.top = a_currentRect.centerY() - (l_newHeight / 2.0f); + l_newRect.bottom = a_currentRect.centerY() + (l_newHeight / 2.0f); + + float l_newRectAspect = MathUtils.getRectRatio(l_newRect); + + return l_newRect; + } } diff --git a/library/src/main/java/com/flaviofaria/kenburnsview/RandomTransitionGenerator.java b/library/src/main/java/com/flaviofaria/kenburnsview/RandomTransitionGenerator.java index b4e8150..d7f7793 100644 --- a/library/src/main/java/com/flaviofaria/kenburnsview/RandomTransitionGenerator.java +++ b/library/src/main/java/com/flaviofaria/kenburnsview/RandomTransitionGenerator.java @@ -66,14 +66,25 @@ public Transition generateNextTransition(RectF drawableBounds, RectF viewport) { RectF dstRect = null; if (!firstTransition) { - dstRect = mLastGenTrans.getDestinyRect(); + dstRect = mLastGenTrans.getDestinationRect(); drawableBoundsChanged = !drawableBounds.equals(mLastDrawableBounds); viewportRatioChanged = !MathUtils.haveSameAspectRatio(dstRect, viewport); } - if (dstRect == null || drawableBoundsChanged || viewportRatioChanged) { + if (dstRect == null || drawableBoundsChanged) + { srcRect = generateRandomRect(drawableBounds, viewport); - } else { + } + // A scaling of the viewport can result in the aspect ratio being out of symc and a new + // random transiton results in a jump in the effect. Instead just accept that the aspect ratio + // is out of sync and let the transition take the aspect ratio back into sync. + else if (viewportRatioChanged) + { + // The next transition will fix the aspect ratio so we can relax + srcRect = dstRect; + // srcRect = MathUtils.getInsideRect(dstRect, MathUtils.getRectRatio(viewport)); + } + else { /* Sets the destiny rect of the last transition as the source one if the current drawable has the same dimensions as the one of the last transition. */ diff --git a/library/src/main/java/com/flaviofaria/kenburnsview/Transition.java b/library/src/main/java/com/flaviofaria/kenburnsview/Transition.java index 8da0d85..491f251 100644 --- a/library/src/main/java/com/flaviofaria/kenburnsview/Transition.java +++ b/library/src/main/java/com/flaviofaria/kenburnsview/Transition.java @@ -48,15 +48,23 @@ public class Transition { private Interpolator mInterpolator; - public Transition(RectF srcRect, RectF dstRect, long duration, Interpolator interpolator) { - if (!MathUtils.haveSameAspectRatio(srcRect, dstRect)) { - throw new IncompatibleRatioException(); - } - mSrcRect = srcRect; - mDstRect = dstRect; + public Transition(RectF srcRect, RectF dstRect, long duration, Interpolator interpolator) + { + // Scaling the viewport can result in 1 transition where the aspect ratio is not the same + // but that's OK since this transition will fix it. Throwing an exception that never gets + // caught is simply not a good idea. + + // if (!MathUtils.haveSameAspectRatio(srcRect, dstRect)) + // { + // throw new IncompatibleRatioException(); + //} + mDuration = duration; mInterpolator = interpolator; + mSrcRect = srcRect; + mDstRect = dstRect; + // Precomputes a few variables to avoid doing it in onDraw(). mWidthDiff = dstRect.width() - srcRect.width(); mHeightDiff = dstRect.height() - srcRect.height(); @@ -69,7 +77,8 @@ public Transition(RectF srcRect, RectF dstRect, long duration, Interpolator inte * Gets the rect that will take the scene when a Ken Burns transition starts. * @return the rect that starts the transition. */ - public RectF getSourceRect() { + public RectF getSourceRect() + { return mSrcRect; } @@ -78,17 +87,37 @@ public RectF getSourceRect() { * Gets the rect that will take the scene when a Ken Burns transition ends. * @return the rect that ends the transition. */ - public RectF getDestinyRect() { + public RectF getDestinationRect() + { return mDstRect; } + public void setDestinationRect(RectF a_rect) + { + this.mDstRect = a_rect; + } + + public void setSourceRect(RectF a_rect) + { + this.mSrcRect = a_rect; + } + + public void recompute() + { + mWidthDiff = mDstRect.width() - mSrcRect.width(); + mHeightDiff = mDstRect.height() - mSrcRect.height(); + mCenterXDiff = mDstRect.centerX() - mSrcRect.centerX(); + mCenterYDiff = mDstRect.centerY() - mSrcRect.centerY(); + } + /** * Gets the current rect that represents the part of the image to take the scene * in the current frame. * @param elapsedTime the elapsed time since this transition started. */ - public RectF getInterpolatedRect(long elapsedTime) { + public RectF getInterpolatedRect(long elapsedTime) + { float elapsedTimeFraction = elapsedTime / (float) mDuration; float interpolationProgress = Math.min(elapsedTimeFraction, 1); float interpolation = mInterpolator.getInterpolation(interpolationProgress); @@ -104,6 +133,7 @@ public RectF getInterpolatedRect(long elapsedTime) { float bottom = top + currentHeight; mCurrentRect.set(left, top, right, bottom); + return mCurrentRect; }