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

Handle a scalable kenburnsview #53

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 78 additions & 30 deletions library/src/main/java/com/flaviofaria/kenburnsview/KenBurnsView.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,17 @@
import android.util.AttributeSet;
import android.widget.ImageView;

import com.burningthumb.premiervideokiosk.DrawOnImageView;

/**
* {@link ImageView} extension that animates its image with the
* <a href="http://en.wikipedia.org/wiki/Ken_Burns_effect">Ken Burns Effect</a>.
* @author Flavio Faria
* @see Transition
* @see TransitionGenerator
*/
public class KenBurnsView extends ImageView {
public class KenBurnsView extends DrawOnImageView

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are using a bad indentation, it seems like C# is not the standard used in Kotlin!

{

/** Delay between a pair of frames at a 60 FPS frame rate. */
private static final long FRAME_DELAY = 1000 / 60;
Expand Down Expand Up @@ -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. */
Expand All @@ -114,63 +122,74 @@ 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()) {
if (mCurrentTrans == null) { // Starting the first transition.
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);

float widthScale = mDrawableRect.width() / currentRect.width();
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;

Expand Down Expand Up @@ -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();

Expand All @@ -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();
}

Expand All @@ -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);
}
Expand All @@ -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);
}
Expand All @@ -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();
}
Expand All @@ -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);
}

Expand All @@ -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());
}
}
Expand All @@ -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.
Expand All @@ -316,23 +360,26 @@ private void handleImageChange() {
}


public void setTransitionListener(TransitionListener transitionListener) {
public void setTransitionListener(TransitionListener transitionListener)
{
mTransitionListener = transitionListener;
}


/**
* Pauses the Ken Burns Effect animation.
*/
public void pause() {
public void pause()
{
mPaused = true;
}


/**
* 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();
Expand All @@ -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.
Expand Down
62 changes: 62 additions & 0 deletions library/src/main/java/com/flaviofaria/kenburnsview/MathUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.flaviofaria.kenburnsview;

import android.graphics.Rect;
import android.graphics.RectF;

/**
Expand Down Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down
Loading