Home | History | Annotate | Download | only in util
      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