1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.tv.settings.util; 18 19 import com.android.tv.settings.widget.RefcountBitmapDrawable; 20 21 import android.content.Context; 22 import android.graphics.Canvas; 23 import android.graphics.Color; 24 import android.graphics.ColorMatrix; 25 import android.graphics.ColorMatrixColorFilter; 26 import android.graphics.Rect; 27 import android.graphics.RectF; 28 import android.graphics.Region.Op; 29 import android.graphics.drawable.BitmapDrawable; 30 import android.util.AttributeSet; 31 import android.view.View; 32 import android.view.ViewGroup.LayoutParams; 33 34 /** 35 * Util widget that can animate the following factors 36 * - scale of the view 37 * - position of the view 38 * - background color of the view 39 * - unclipped bounds of bitmap 40 * - clipping bounds of bitmap 41 * - alpha of bitmap 42 * - saturation of bitmap 43 */ 44 class TransitionImageView extends View { 45 46 private TransitionImage mSrc; 47 private TransitionImage mDst; 48 49 private BitmapDrawable mBitmapDrawable; 50 51 /** 52 * values for difference between src and dst 53 */ 54 private float mScaleX; 55 private float mScaleY; 56 private float mScaleXDiff; 57 private float mScaleYDiff; 58 private float mTranslationXDiff; 59 private float mTranslationYDiff; 60 private float mClipLeftDiff; 61 private float mClipRightDiff; 62 private float mClipTopDiff; 63 private float mClipBottomDiff; 64 private float mUnclipCenterXDiff; 65 private float mUnclipCenterYDiff; 66 private float mUnclipWidthDiffBeforeScale; 67 private float mUnclipHeightDiffBeforeScale; 68 private float mSaturationDiff; 69 private float mAlphaDiff; 70 private int mBgAlphaDiff; 71 private int mBgRedDiff; 72 private int mBgGreenDiff; 73 private int mBgBlueDiff; 74 private boolean mBgHasDiff; 75 76 private float mProgress; 77 private Rect mSrcRect = new Rect(); 78 private RectF mSrcUnclipRect = new RectF(); 79 private RectF mSrcClipRect = new RectF(); 80 private Rect mDstRect = new Rect(); 81 82 private RectF mClipRect = new RectF(); 83 private Rect mUnclipRect = new Rect(); 84 private int mSrcBgColor; 85 private ColorMatrix mColorMatrix = new ColorMatrix(); 86 87 private RectF mExcludeRect; 88 89 public TransitionImageView(Context context) { 90 this(context, null); 91 } 92 93 public TransitionImageView(Context context, AttributeSet attrs) { 94 this(context, attrs, 0); 95 } 96 97 public TransitionImageView(Context context, AttributeSet attrs, int defStyle) { 98 super(context, attrs, defStyle); 99 // the scale and translation is based on left/up corner of the view 100 setPivotX(0f); 101 setPivotY(0f); 102 setWillNotDraw(false); 103 } 104 105 public void setSourceTransition(TransitionImage src) { 106 mSrc = src; 107 initializeView(); 108 } 109 110 public void setDestTransition(TransitionImage dst) { 111 mDst = dst; 112 calculateDiffs(); 113 } 114 115 @Override 116 protected void onDetachedFromWindow() { 117 if (mBitmapDrawable instanceof RefcountBitmapDrawable) { 118 ((RefcountBitmapDrawable) mBitmapDrawable).getRefcountObject().releaseRef(); 119 } 120 super.onDetachedFromWindow(); 121 } 122 123 private void initializeView() { 124 mBitmapDrawable = mSrc.getBitmap(); 125 mBitmapDrawable.mutate(); 126 127 mSrc.getOptimizedRect(mSrcRect); 128 // initialize size 129 LayoutParams params = getLayoutParams(); 130 params.width = mSrcRect.width(); 131 params.height = mSrcRect.height(); 132 133 // get src clip rect relative to the view 134 mSrcClipRect.set(mSrc.getClippedRect()); 135 mSrcClipRect.offset(-mSrcRect.left, -mSrcRect.top); 136 137 // get src rect relative to the view 138 mSrcUnclipRect.set(mSrc.getUnclippedRect()); 139 mSrcUnclipRect.offset(-mSrcRect.left, -mSrcRect.top); 140 141 // initialize alpha, saturation, background color 142 if (mSrc.getAlpha() != 1f) { 143 mBitmapDrawable.setAlpha((int) (mSrc.getAlpha() * 255)); 144 } 145 if (mSrc.getSaturation() != 1f) { 146 mColorMatrix.setSaturation(mSrc.getSaturation()); 147 mBitmapDrawable.setColorFilter(new ColorMatrixColorFilter(mColorMatrix)); 148 } 149 mSrcBgColor = mSrc.getBackground(); 150 if (mSrcBgColor != Color.TRANSPARENT) { 151 setBackgroundColor(mSrcBgColor); 152 getBackground().setAlpha((int) (mSrc.getAlpha() * 255)); 153 } 154 155 invalidate(); 156 } 157 158 private void calculateDiffs() { 159 mDst.getOptimizedRect(mDstRect); 160 mScaleX = (float) mDstRect.width() / mSrcRect.width(); 161 mScaleY = (float) mDstRect.height() / mSrcRect.height(); 162 mScaleXDiff = mScaleX - 1f; 163 mScaleYDiff = mScaleY - 1f; 164 mTranslationXDiff = mDstRect.left - mSrcRect.left; 165 mTranslationYDiff = mDstRect.top - mSrcRect.top; 166 167 RectF dstClipRect = new RectF(); 168 // get dst clip rect relative to the view 169 dstClipRect.set(mDst.getClippedRect()); 170 dstClipRect.offset(-mDstRect.left, -mDstRect.top); 171 // get dst clip rect before scaling 172 dstClipRect.left /= mScaleX; 173 dstClipRect.right /= mScaleX; 174 dstClipRect.top /= mScaleY; 175 dstClipRect.bottom /= mScaleY; 176 mClipLeftDiff = dstClipRect.left - mSrcClipRect.left; 177 mClipRightDiff = dstClipRect.right - mSrcClipRect.right; 178 mClipTopDiff = dstClipRect.top - mSrcClipRect.top; 179 mClipBottomDiff = dstClipRect.bottom - mSrcClipRect.bottom; 180 181 RectF dstUnclipRect = new RectF(); 182 // get dst rect relative to the view 183 dstUnclipRect.set(mDst.getUnclippedRect()); 184 dstUnclipRect.offset(-mDstRect.left, -mDstRect.top); 185 mUnclipWidthDiffBeforeScale = dstUnclipRect.width() - mSrcUnclipRect.width(); 186 mUnclipHeightDiffBeforeScale = dstUnclipRect.height() - mSrcUnclipRect.height(); 187 // get dst clip rect before scaling 188 dstUnclipRect.left /= mScaleX; 189 dstUnclipRect.right /= mScaleX; 190 dstUnclipRect.top /= mScaleY; 191 dstUnclipRect.bottom /= mScaleY; 192 mUnclipCenterXDiff = dstUnclipRect.centerX() - mSrcUnclipRect.centerX(); 193 mUnclipCenterYDiff = dstUnclipRect.centerY() - mSrcUnclipRect.centerY(); 194 195 mAlphaDiff = mDst.getAlpha() - mSrc.getAlpha(); 196 int srcColor = mSrc.getBackground(); 197 int dstColor = mDst.getBackground(); 198 mBgAlphaDiff = Color.alpha(dstColor) - Color.alpha(srcColor); 199 mBgRedDiff = Color.red(dstColor) - Color.red(srcColor); 200 mBgGreenDiff = Color.green(dstColor) - Color.green(srcColor); 201 mBgBlueDiff = Color.blue(dstColor) - Color.blue(srcColor); 202 mSaturationDiff = mDst.getSaturation() - mSrc.getSaturation(); 203 mBgHasDiff = mBgAlphaDiff != 0 || mBgRedDiff != 0 || mBgGreenDiff != 0 204 || mBgBlueDiff != 0; 205 } 206 207 public TransitionImage getSourceTransition() { 208 return mSrc; 209 } 210 211 public TransitionImage getDestTransition() { 212 return mDst; 213 } 214 215 public void setProgress(float progress) { 216 mProgress = progress; 217 218 // animating scale factor 219 setScaleX(1f + mScaleXDiff * mProgress); 220 setScaleY(1f + mScaleYDiff * mProgress); 221 222 // animating view position 223 setTranslationX(mSrcRect.left + mProgress * mTranslationXDiff); 224 setTranslationY(mSrcRect.top + mProgress * mTranslationYDiff); 225 226 // animating unclipped bitmap bounds 227 float unclipCenterX = mSrcUnclipRect.centerX() + mUnclipCenterXDiff * mProgress; 228 float unclipCenterY = mSrcUnclipRect.centerY() + mUnclipCenterYDiff * mProgress; 229 float unclipWidthBeforeScale = 230 mSrcUnclipRect.width() + mUnclipWidthDiffBeforeScale * mProgress; 231 float unclipHeightBeforeScale = 232 mSrcUnclipRect.height() + mUnclipHeightDiffBeforeScale * mProgress; 233 float unclipWidth = unclipWidthBeforeScale / getScaleX(); 234 float unclipHeight = unclipHeightBeforeScale / getScaleY(); 235 mUnclipRect.left = (int) (unclipCenterX - unclipWidth * 0.5f); 236 mUnclipRect.top = (int) (unclipCenterY - unclipHeight * 0.5f); 237 mUnclipRect.right = (int) (unclipCenterX + unclipWidth * 0.5f); 238 mUnclipRect.bottom = (int) (unclipCenterY + unclipHeight * 0.5f); 239 // rounding to integer will cause a shaking effect if the target unclip rect is much 240 // smaller than view bounds; e.g. a portrait bitmap inside a landscape imageView. 241 mBitmapDrawable.setBounds(mUnclipRect); 242 243 // animate clip bounds 244 mClipRect.left = mSrcClipRect.left + mClipLeftDiff * mProgress; 245 mClipRect.top = mSrcClipRect.top + mClipTopDiff * mProgress; 246 mClipRect.right = mSrcClipRect.right + mClipRightDiff * mProgress; 247 mClipRect.bottom = mSrcClipRect.bottom + mClipBottomDiff * mProgress; 248 249 // animate bitmap alpha, bitmap saturation 250 if (mAlphaDiff != 0f) { 251 int alpha = (int) ((mSrc.getAlpha() + mAlphaDiff * mProgress) * 255); 252 mBitmapDrawable.setAlpha(alpha); 253 if (getBackground() != null) { 254 getBackground().setAlpha(alpha); 255 } 256 } 257 if (mSaturationDiff != 0f) { 258 mColorMatrix.setSaturation(mSrc.getSaturation() + mSaturationDiff * mProgress); 259 mBitmapDrawable.setColorFilter(new ColorMatrixColorFilter(mColorMatrix)); 260 } 261 262 // animate background color 263 if (mBgHasDiff) { 264 setBackgroundColor(Color.argb( 265 Color.alpha(mSrcBgColor) + (int) (mBgAlphaDiff * mProgress), 266 Color.red(mSrcBgColor) + (int) (mBgRedDiff * mProgress), 267 Color.green(mSrcBgColor) + (int) (mBgGreenDiff * mProgress), 268 Color.blue(mSrcBgColor) + (int) (mBgBlueDiff * mProgress))); 269 } 270 271 invalidate(); 272 } 273 274 public float getProgress() { 275 return mProgress; 276 } 277 278 @Override 279 protected void onDraw(Canvas canvas) { 280 super.onDraw(canvas); 281 if (mBitmapDrawable == null) { 282 return; 283 } 284 int count = canvas.save(); 285 canvas.clipRect(mClipRect); 286 if (mExcludeRect != null) { 287 canvas.clipRect(mExcludeRect, Op.DIFFERENCE); 288 } 289 mBitmapDrawable.draw(canvas); 290 canvas.restoreToCount(count); 291 } 292 293 public void setExcludeClipRect(RectF rect) { 294 if (mExcludeRect == null) { 295 mExcludeRect = new RectF(); 296 } 297 mExcludeRect.set(rect); 298 // get rect relative to left/top corner of the view 299 mExcludeRect.offset(-getX(), -getY()); 300 // get locations before scale applied 301 mExcludeRect.left /= (1f + mScaleXDiff * mProgress); 302 mExcludeRect.right /= (1f + mScaleXDiff * mProgress); 303 mExcludeRect.top /= (1f + mScaleYDiff * mProgress); 304 mExcludeRect.bottom /= (1f + mScaleYDiff * mProgress); 305 } 306 307 public void clearExcludeClipRect() { 308 mExcludeRect = null; 309 } 310 } 311