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