Home | History | Annotate | Download | only in views
      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 package com.android.systemui.recents.views;
     17 
     18 import android.content.res.Resources;
     19 import android.graphics.Canvas;
     20 import android.graphics.ColorFilter;
     21 import android.graphics.LinearGradient;
     22 import android.graphics.Paint;
     23 import android.graphics.Path;
     24 import android.graphics.PixelFormat;
     25 import android.graphics.RadialGradient;
     26 import android.graphics.Rect;
     27 import android.graphics.RectF;
     28 import android.graphics.Shader;
     29 import android.graphics.drawable.Drawable;
     30 import android.util.Log;
     31 
     32 import com.android.systemui.R;
     33 import com.android.systemui.recents.RecentsConfiguration;
     34 
     35 /**
     36  * A rounded rectangle drawable which also includes a shadow around. This is mostly copied from
     37  * frameworks/support/v7/cardview/eclair-mr1/android/support/v7/widget/
     38  * RoundRectDrawableWithShadow.java revision c42ba8c000d1e6ce85e152dfc17089a0a69e739f with a few
     39  * modifications to suit our needs in SystemUI.
     40  */
     41 class FakeShadowDrawable extends Drawable {
     42     // used to calculate content padding
     43     final static double COS_45 = Math.cos(Math.toRadians(45));
     44 
     45     final static float SHADOW_MULTIPLIER = 1.5f;
     46 
     47     final float mInsetShadow; // extra shadow to avoid gaps between card and shadow
     48 
     49     Paint mCornerShadowPaint;
     50 
     51     Paint mEdgeShadowPaint;
     52 
     53     final RectF mCardBounds;
     54 
     55     float mCornerRadius;
     56 
     57     Path mCornerShadowPath;
     58 
     59     // updated value with inset
     60     float mMaxShadowSize;
     61 
     62     // actual value set by developer
     63     float mRawMaxShadowSize;
     64 
     65     // multiplied value to account for shadow offset
     66     float mShadowSize;
     67 
     68     // actual value set by developer
     69     float mRawShadowSize;
     70 
     71     private boolean mDirty = true;
     72 
     73     private final int mShadowStartColor;
     74 
     75     private final int mShadowEndColor;
     76 
     77     private boolean mAddPaddingForCorners = true;
     78 
     79     /**
     80      * If shadow size is set to a value above max shadow, we print a warning
     81      */
     82     private boolean mPrintedShadowClipWarning = false;
     83 
     84     public FakeShadowDrawable(Resources resources, RecentsConfiguration config) {
     85         mShadowStartColor = resources.getColor(R.color.fake_shadow_start_color);
     86         mShadowEndColor = resources.getColor(R.color.fake_shadow_end_color);
     87         mInsetShadow = resources.getDimension(R.dimen.fake_shadow_inset);
     88         setShadowSize(resources.getDimensionPixelSize(R.dimen.fake_shadow_size),
     89                 resources.getDimensionPixelSize(R.dimen.fake_shadow_size));
     90         mCornerShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
     91         mCornerShadowPaint.setStyle(Paint.Style.FILL);
     92         mCornerShadowPaint.setDither(true);
     93         mCornerRadius = config.taskViewRoundedCornerRadiusPx;
     94         mCardBounds = new RectF();
     95         mEdgeShadowPaint = new Paint(mCornerShadowPaint);
     96     }
     97 
     98     @Override
     99     public void setAlpha(int alpha) {
    100         mCornerShadowPaint.setAlpha(alpha);
    101         mEdgeShadowPaint.setAlpha(alpha);
    102     }
    103 
    104     @Override
    105     protected void onBoundsChange(Rect bounds) {
    106         super.onBoundsChange(bounds);
    107         mDirty = true;
    108     }
    109 
    110     void setShadowSize(float shadowSize, float maxShadowSize) {
    111         if (shadowSize < 0 || maxShadowSize < 0) {
    112             throw new IllegalArgumentException("invalid shadow size");
    113         }
    114         if (shadowSize > maxShadowSize) {
    115             shadowSize = maxShadowSize;
    116             if (!mPrintedShadowClipWarning) {
    117                 Log.w("CardView", "Shadow size is being clipped by the max shadow size. See "
    118                         + "{CardView#setMaxCardElevation}.");
    119                 mPrintedShadowClipWarning = true;
    120             }
    121         }
    122         if (mRawShadowSize == shadowSize && mRawMaxShadowSize == maxShadowSize) {
    123             return;
    124         }
    125         mRawShadowSize = shadowSize;
    126         mRawMaxShadowSize = maxShadowSize;
    127         mShadowSize = shadowSize * SHADOW_MULTIPLIER + mInsetShadow;
    128         mMaxShadowSize = maxShadowSize + mInsetShadow;
    129         mDirty = true;
    130         invalidateSelf();
    131     }
    132 
    133     @Override
    134     public boolean getPadding(Rect padding) {
    135         int vOffset = (int) Math.ceil(calculateVerticalPadding(mRawMaxShadowSize, mCornerRadius,
    136                 mAddPaddingForCorners));
    137         int hOffset = (int) Math.ceil(calculateHorizontalPadding(mRawMaxShadowSize, mCornerRadius,
    138                 mAddPaddingForCorners));
    139         padding.set(hOffset, vOffset, hOffset, vOffset);
    140         return true;
    141     }
    142 
    143     static float calculateVerticalPadding(float maxShadowSize, float cornerRadius,
    144             boolean addPaddingForCorners) {
    145         if (addPaddingForCorners) {
    146             return (float) (maxShadowSize * SHADOW_MULTIPLIER + (1 - COS_45) * cornerRadius);
    147         } else {
    148             return maxShadowSize * SHADOW_MULTIPLIER;
    149         }
    150     }
    151 
    152     static float calculateHorizontalPadding(float maxShadowSize, float cornerRadius,
    153             boolean addPaddingForCorners) {
    154         if (addPaddingForCorners) {
    155             return (float) (maxShadowSize + (1 - COS_45) * cornerRadius);
    156         } else {
    157             return maxShadowSize;
    158         }
    159     }
    160 
    161     @Override
    162     public void setColorFilter(ColorFilter cf) {
    163         mCornerShadowPaint.setColorFilter(cf);
    164         mEdgeShadowPaint.setColorFilter(cf);
    165     }
    166 
    167     @Override
    168     public int getOpacity() {
    169         return PixelFormat.OPAQUE;
    170     }
    171 
    172     @Override
    173     public void draw(Canvas canvas) {
    174         if (mDirty) {
    175             buildComponents(getBounds());
    176             mDirty = false;
    177         }
    178         canvas.translate(0, mRawShadowSize / 4);
    179         drawShadow(canvas);
    180         canvas.translate(0, -mRawShadowSize / 4);
    181     }
    182 
    183     private void drawShadow(Canvas canvas) {
    184         final float edgeShadowTop = -mCornerRadius - mShadowSize;
    185         final float inset = mCornerRadius + mInsetShadow + mRawShadowSize / 2;
    186         final boolean drawHorizontalEdges = mCardBounds.width() - 2 * inset > 0;
    187         final boolean drawVerticalEdges = mCardBounds.height() - 2 * inset > 0;
    188         // LT
    189         int saved = canvas.save();
    190         canvas.translate(mCardBounds.left + inset, mCardBounds.top + inset);
    191         canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
    192         if (drawHorizontalEdges) {
    193             canvas.drawRect(0, edgeShadowTop,
    194                     mCardBounds.width() - 2 * inset, -mCornerRadius,
    195                     mEdgeShadowPaint);
    196         }
    197         canvas.restoreToCount(saved);
    198         // RB
    199         saved = canvas.save();
    200         canvas.translate(mCardBounds.right - inset, mCardBounds.bottom - inset);
    201         canvas.rotate(180f);
    202         canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
    203         if (drawHorizontalEdges) {
    204             canvas.drawRect(0, edgeShadowTop,
    205                     mCardBounds.width() - 2 * inset, -mCornerRadius + mShadowSize,
    206                     mEdgeShadowPaint);
    207         }
    208         canvas.restoreToCount(saved);
    209         // LB
    210         saved = canvas.save();
    211         canvas.translate(mCardBounds.left + inset, mCardBounds.bottom - inset);
    212         canvas.rotate(270f);
    213         canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
    214         if (drawVerticalEdges) {
    215             canvas.drawRect(0, edgeShadowTop,
    216                     mCardBounds.height() - 2 * inset, -mCornerRadius, mEdgeShadowPaint);
    217         }
    218         canvas.restoreToCount(saved);
    219         // RT
    220         saved = canvas.save();
    221         canvas.translate(mCardBounds.right - inset, mCardBounds.top + inset);
    222         canvas.rotate(90f);
    223         canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
    224         if (drawVerticalEdges) {
    225             canvas.drawRect(0, edgeShadowTop,
    226                     mCardBounds.height() - 2 * inset, -mCornerRadius, mEdgeShadowPaint);
    227         }
    228         canvas.restoreToCount(saved);
    229     }
    230 
    231     private void buildShadowCorners() {
    232         RectF innerBounds = new RectF(-mCornerRadius, -mCornerRadius, mCornerRadius, mCornerRadius);
    233         RectF outerBounds = new RectF(innerBounds);
    234         outerBounds.inset(-mShadowSize, -mShadowSize);
    235 
    236         if (mCornerShadowPath == null) {
    237             mCornerShadowPath = new Path();
    238         } else {
    239             mCornerShadowPath.reset();
    240         }
    241         mCornerShadowPath.setFillType(Path.FillType.EVEN_ODD);
    242         mCornerShadowPath.moveTo(-mCornerRadius, 0);
    243         mCornerShadowPath.rLineTo(-mShadowSize, 0);
    244         // outer arc
    245         mCornerShadowPath.arcTo(outerBounds, 180f, 90f, false);
    246         // inner arc
    247         mCornerShadowPath.arcTo(innerBounds, 270f, -90f, false);
    248         mCornerShadowPath.close();
    249 
    250         float startRatio = mCornerRadius / (mCornerRadius + mShadowSize);
    251         mCornerShadowPaint.setShader(new RadialGradient(0, 0, mCornerRadius + mShadowSize,
    252                 new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor},
    253                 new float[]{0f, startRatio, 1f}
    254                 , Shader.TileMode.CLAMP));
    255 
    256         // we offset the content shadowSize/2 pixels up to make it more realistic.
    257         // this is why edge shadow shader has some extra space
    258         // When drawing bottom edge shadow, we use that extra space.
    259         mEdgeShadowPaint.setShader(new LinearGradient(0, -mCornerRadius + mShadowSize, 0,
    260                 -mCornerRadius - mShadowSize,
    261                 new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor},
    262                 new float[]{0f, .5f, 1f}, Shader.TileMode.CLAMP));
    263     }
    264 
    265     private void buildComponents(Rect bounds) {
    266         // Card is offset SHADOW_MULTIPLIER * maxShadowSize to account for the shadow shift.
    267         // We could have different top-bottom offsets to avoid extra gap above but in that case
    268         // center aligning Views inside the CardView would be problematic.
    269         final float verticalOffset = mMaxShadowSize * SHADOW_MULTIPLIER;
    270         mCardBounds.set(bounds.left + mMaxShadowSize, bounds.top + verticalOffset,
    271                 bounds.right - mMaxShadowSize, bounds.bottom - verticalOffset);
    272         buildShadowCorners();
    273     }
    274 
    275     float getMinWidth() {
    276         final float content = 2 *
    277                 Math.max(mRawMaxShadowSize, mCornerRadius + mInsetShadow + mRawMaxShadowSize / 2);
    278         return content + (mRawMaxShadowSize + mInsetShadow) * 2;
    279     }
    280 
    281     float getMinHeight() {
    282         final float content = 2 * Math.max(mRawMaxShadowSize, mCornerRadius + mInsetShadow
    283                         + mRawMaxShadowSize * SHADOW_MULTIPLIER / 2);
    284         return content + (mRawMaxShadowSize * SHADOW_MULTIPLIER + mInsetShadow) * 2;
    285     }
    286 }