 Chromium Code Reviews
 Chromium Code Reviews Issue 1343913002:
  Introduce Animated Logo to Chrome on Android  (Closed) 
  Base URL: https://chromium.googlesource.com/chromium/src.git@master
    
  
    Issue 1343913002:
  Introduce Animated Logo to Chrome on Android  (Closed) 
  Base URL: https://chromium.googlesource.com/chromium/src.git@master| Index: chrome/android/java/src/org/chromium/chrome/browser/ntp/LogoView.java | 
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/LogoView.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/LogoView.java | 
| index 94a7f3df7f0e7521d7ddb30dcf304a1a0a6a6cda..9262ddfb478df76d9b0163d79aa911cd099249a0 100644 | 
| --- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/LogoView.java | 
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/LogoView.java | 
| @@ -8,10 +8,12 @@ import android.animation.Animator; | 
| import android.animation.ObjectAnimator; | 
| import android.content.Context; | 
| import android.graphics.Bitmap; | 
| +import android.graphics.Bitmap.Config; | 
| import android.graphics.BitmapFactory; | 
| import android.graphics.Canvas; | 
| import android.graphics.Matrix; | 
| import android.graphics.Paint; | 
| +import android.graphics.drawable.Drawable; | 
| import android.text.TextUtils; | 
| import android.util.AttributeSet; | 
| import android.util.Property; | 
| @@ -24,9 +26,13 @@ import org.chromium.chrome.browser.ntp.NewTabPageView.NewTabPageManager; | 
| import java.lang.ref.WeakReference; | 
| +import jp.tomorrowkey.android.gifplayer.BaseGifDrawable; | 
| +import jp.tomorrowkey.android.gifplayer.BaseGifImage; | 
| + | 
| /** | 
| * This view shows the default search provider's logo and fades in a new logo if one becomes | 
| - * available. | 
| + * available. It also maintains a {@link BaseGifDrawable} that will be played when the user clicks | 
| + * this view and we have an animated GIF logo ready. | 
| */ | 
| public class LogoView extends View implements OnClickListener { | 
| @@ -36,14 +42,18 @@ public class LogoView extends View implements OnClickListener { | 
| // The default logo is shared across all NTPs. | 
| private static WeakReference<Bitmap> sDefaultLogo; | 
| - private Paint mPaint; | 
| + // mLogo and mNewLogo are remembered for cross fading animation. | 
| private Bitmap mLogo; | 
| private Bitmap mNewLogo; | 
| + private BaseGifDrawable mAnimatedLogoDrawable; | 
| + | 
| + private ObjectAnimator mFadeAnimation; | 
| + private Paint mPaint; | 
| private Matrix mLogoMatrix; | 
| private Matrix mNewLogoMatrix; | 
| + private Matrix mAnimatedLogoMatrix; | 
| private boolean mLogoIsDefault; | 
| private boolean mNewLogoIsDefault; | 
| - private ObjectAnimator mAnimation; | 
| /** | 
| * A measure from 0 to 1 of how much the new logo has faded in. 0 shows the old logo, 1 shows | 
| @@ -99,16 +109,37 @@ public class LogoView extends View implements OnClickListener { | 
| } | 
| /** | 
| - * Jumps to the end of the current logo animation, if any. | 
| + * Jumps to the end of the logo cross-fading animation, if any. | 
| */ | 
| - public void endAnimation() { | 
| - if (mAnimation != null) { | 
| - mAnimation.end(); | 
| - mAnimation = null; | 
| + public void endFadeAnimation() { | 
| + if (mFadeAnimation != null) { | 
| + mFadeAnimation.end(); | 
| + mFadeAnimation = null; | 
| } | 
| } | 
| /** | 
| + * @return True after the animated GIF logo starts playing. False otherwise. | 
| + */ | 
| + public boolean isAnimatedLogoShowing() { | 
| + return mAnimatedLogoDrawable != null && mAnimatedLogoDrawable.isRunning() | 
| + && mAnimatedLogoDrawable.isValid(); | 
| + } | 
| + | 
| + /** | 
| + * Starts playing the given animated GIF logo. | 
| + */ | 
| + public void playAnimatedLogo(BaseGifImage gifImage) { | 
| + mAnimatedLogoDrawable = new BaseGifDrawable(gifImage, Config.ARGB_8888); | 
| + mAnimatedLogoMatrix = new Matrix(); | 
| + setMatrix(mAnimatedLogoDrawable.getIntrinsicWidth(), | 
| + mAnimatedLogoDrawable.getIntrinsicHeight(), mAnimatedLogoMatrix, false); | 
| + // Set callback here to ensure #invalidateDrawable() is called. | 
| + mAnimatedLogoDrawable.setCallback(this); | 
| + mAnimatedLogoDrawable.start(); | 
| + } | 
| + | 
| + /** | 
| * Fades in a new logo over the current logo. | 
| * | 
| * @param logo The new logo to fade in. May be null to reset to the default logo. | 
| @@ -124,16 +155,16 @@ public class LogoView extends View implements OnClickListener { | 
| } | 
| private void updateLogo(Bitmap logo, final String contentDescription, boolean isDefaultLogo) { | 
| - if (mAnimation != null) mAnimation.end(); | 
| + if (mFadeAnimation != null) mFadeAnimation.end(); | 
| mNewLogo = logo; | 
| mNewLogoMatrix = new Matrix(); | 
| mNewLogoIsDefault = isDefaultLogo; | 
| - setMatrix(mNewLogo, mNewLogoMatrix, mNewLogoIsDefault); | 
| + setMatrix(mNewLogo.getWidth(), mNewLogo.getHeight(), mNewLogoMatrix, mNewLogoIsDefault); | 
| - mAnimation = ObjectAnimator.ofFloat(this, mTransitionProperty, 0f, 1f); | 
| - mAnimation.setDuration(LOGO_TRANSITION_TIME_MS); | 
| - mAnimation.addListener(new Animator.AnimatorListener() { | 
| + mFadeAnimation = ObjectAnimator.ofFloat(this, mTransitionProperty, 0f, 1f); | 
| + mFadeAnimation.setDuration(LOGO_TRANSITION_TIME_MS); | 
| + mFadeAnimation.addListener(new Animator.AnimatorListener() { | 
| @Override | 
| public void onAnimationStart(Animator animation) { | 
| } | 
| @@ -150,7 +181,7 @@ public class LogoView extends View implements OnClickListener { | 
| mNewLogo = null; | 
| mNewLogoMatrix = null; | 
| mTransitionAmount = 0f; | 
| - mAnimation = null; | 
| + mFadeAnimation = null; | 
| setContentDescription(contentDescription); | 
| setClickable(!mNewLogoIsDefault); | 
| } | 
| @@ -161,7 +192,7 @@ public class LogoView extends View implements OnClickListener { | 
| invalidate(); | 
| } | 
| }); | 
| - mAnimation.start(); | 
| + mFadeAnimation.start(); | 
| } | 
| /** | 
| @@ -178,11 +209,10 @@ public class LogoView extends View implements OnClickListener { | 
| * @param preventUpscaling Whether the image should not be scaled up. If true, the image might | 
| * not fill the entire view but will still be centered. | 
| */ | 
| - private void setMatrix(Bitmap image, Matrix matrix, boolean preventUpscaling) { | 
| + private void setMatrix(int imageWidth, int imageHeight, Matrix matrix, | 
| + boolean preventUpscaling) { | 
| int width = getWidth(); | 
| int height = getHeight(); | 
| - int imageWidth = image.getWidth(); | 
| - int imageHeight = image.getHeight(); | 
| float scale = Math.min((float) width / imageWidth, (float) height / imageHeight); | 
| if (preventUpscaling) scale = Math.min(1.0f, scale); | 
| @@ -207,36 +237,71 @@ public class LogoView extends View implements OnClickListener { | 
| } | 
| @Override | 
| + protected boolean verifyDrawable(Drawable who) { | 
| + return (who == mAnimatedLogoDrawable) || super.verifyDrawable(who); | 
| + } | 
| + | 
| + @Override | 
| + public void invalidateDrawable(Drawable drawable) { | 
| + // mGifDrawable doesn't actually know its bounds, so super.invalidateDrawable() doesn't | 
| 
newt (away)
2015/09/23 22:18:02
"mAnimatedLogoDrawable"
 | 
| + // invalidate the right area. Instead invalidate the entire view; the drawable takes up most | 
| + // of the view anyway so this is just as efficient. @see ImageView#invalidateDrawable(). | 
| + if (drawable == mAnimatedLogoDrawable) invalidate(); | 
| + else super.invalidateDrawable(drawable); | 
| + } | 
| + | 
| + @Override | 
| protected void onDraw(Canvas canvas) { | 
| - if (mLogo != null && mTransitionAmount < 0.5f) { | 
| - mPaint.setAlpha((int) (255 * 2 * (0.5f - mTransitionAmount))); | 
| - canvas.save(); | 
| - canvas.concat(mLogoMatrix); | 
| - canvas.drawBitmap(mLogo, 0, 0, mPaint); | 
| - canvas.restore(); | 
| - } | 
| + if (isAnimatedLogoShowing()) { | 
| + if (mFadeAnimation != null) mFadeAnimation.cancel(); | 
| + // Free the old bitmaps to allow them to be GC'd. | 
| + mLogo = null; | 
| + mNewLogo = null; | 
| - if (mNewLogo != null && mTransitionAmount > 0.5f) { | 
| - mPaint.setAlpha((int) (255 * 2 * (mTransitionAmount - 0.5f))); | 
| canvas.save(); | 
| - canvas.concat(mNewLogoMatrix); | 
| - canvas.drawBitmap(mNewLogo, 0, 0, mPaint); | 
| + canvas.concat(mAnimatedLogoMatrix); | 
| + mAnimatedLogoDrawable.draw(canvas); | 
| canvas.restore(); | 
| + } else { | 
| + if (mLogo != null && mTransitionAmount < 0.5f) { | 
| + mPaint.setAlpha((int) (255 * 2 * (0.5f - mTransitionAmount))); | 
| + canvas.save(); | 
| + canvas.concat(mLogoMatrix); | 
| + canvas.drawBitmap(mLogo, 0, 0, mPaint); | 
| + canvas.restore(); | 
| + } | 
| + | 
| + if (mNewLogo != null && mTransitionAmount > 0.5f) { | 
| + mPaint.setAlpha((int) (255 * 2 * (mTransitionAmount - 0.5f))); | 
| + canvas.save(); | 
| + canvas.concat(mNewLogoMatrix); | 
| + canvas.drawBitmap(mNewLogo, 0, 0, mPaint); | 
| + canvas.restore(); | 
| + } | 
| } | 
| } | 
| @Override | 
| protected void onSizeChanged(int w, int h, int oldw, int oldh) { | 
| if (w != oldw || h != oldh) { | 
| - if (mLogo != null) setMatrix(mLogo, mLogoMatrix, mLogoIsDefault); | 
| - if (mNewLogo != null) setMatrix(mNewLogo, mNewLogoMatrix, mNewLogoIsDefault); | 
| + if (mAnimatedLogoDrawable != null) { | 
| + setMatrix(mAnimatedLogoDrawable.getIntrinsicWidth(), | 
| + mAnimatedLogoDrawable.getIntrinsicHeight(), mAnimatedLogoMatrix, false); | 
| + } | 
| + if (mLogo != null) { | 
| + setMatrix(mLogo.getWidth(), mLogo.getHeight(), mLogoMatrix, mLogoIsDefault); | 
| + } | 
| + if (mNewLogo != null) { | 
| + setMatrix(mNewLogo.getWidth(), mNewLogo.getHeight(), mNewLogoMatrix, | 
| + mNewLogoIsDefault); | 
| + } | 
| } | 
| } | 
| @Override | 
| public void onClick(View view) { | 
| if (view == this && mManager != null && !isTransitioning()) { | 
| - mManager.openLogoLink(); | 
| + mManager.onLogoClicked(isAnimatedLogoShowing()); | 
| } | 
| } | 
| } |