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 = resources.getDimensionPixelSize(
     94                 R.dimen.recents_task_view_rounded_corners_radius);
     95         mCardBounds = new RectF();
     96         mEdgeShadowPaint = new Paint(mCornerShadowPaint);
     97     }
     98 
     99     @Override
    100     public void setAlpha(int alpha) {
    101         mCornerShadowPaint.setAlpha(alpha);
    102         mEdgeShadowPaint.setAlpha(alpha);
    103     }
    104 
    105     @Override
    106     protected void onBoundsChange(Rect bounds) {
    107         super.onBoundsChange(bounds);
    108         mDirty = true;
    109     }
    110 
    111     void setShadowSize(float shadowSize, float maxShadowSize) {
    112         if (shadowSize < 0 || maxShadowSize < 0) {
    113             throw new IllegalArgumentException("invalid shadow size");
    114         }
    115         if (shadowSize > maxShadowSize) {
    116             shadowSize = maxShadowSize;
    117             if (!mPrintedShadowClipWarning) {
    118                 Log.w("CardView", "Shadow size is being clipped by the max shadow size. See "
    119                         + "{CardView#setMaxCardElevation}.");
    120                 mPrintedShadowClipWarning = true;
    121             }
    122         }
    123         if (mRawShadowSize == shadowSize && mRawMaxShadowSize == maxShadowSize) {
    124             return;
    125         }
    126         mRawShadowSize = shadowSize;
    127         mRawMaxShadowSize = maxShadowSize;
    128         mShadowSize = shadowSize * SHADOW_MULTIPLIER + mInsetShadow;
    129         mMaxShadowSize = maxShadowSize + mInsetShadow;
    130         mDirty = true;
    131         invalidateSelf();
    132     }
    133 
    134     @Override
    135     public boolean getPadding(Rect padding) {
    136         int vOffset = (int) Math.ceil(calculateVerticalPadding(mRawMaxShadowSize, mCornerRadius,
    137                 mAddPaddingForCorners));
    138         int hOffset = (int) Math.ceil(calculateHorizontalPadding(mRawMaxShadowSize, mCornerRadius,
    139                 mAddPaddingForCorners));
    140         padding.set(hOffset, vOffset, hOffset, vOffset);
    141         return true;
    142     }
    143 
    144     static float calculateVerticalPadding(float maxShadowSize, float cornerRadius,
    145             boolean addPaddingForCorners) {
    146         if (addPaddingForCorners) {
    147             return (float) (maxShadowSize * SHADOW_MULTIPLIER + (1 - COS_45) * cornerRadius);
    148         } else {
    149             return maxShadowSize * SHADOW_MULTIPLIER;
    150         }
    151     }
    152 
    153     static float calculateHorizontalPadding(float maxShadowSize, float cornerRadius,
    154             boolean addPaddingForCorners) {
    155         if (addPaddingForCorners) {
    156             return (float) (maxShadowSize + (1 - COS_45) * cornerRadius);
    157         } else {
    158             return maxShadowSize;
    159         }
    160     }
    161 
    162     @Override
    163     public void setColorFilter(ColorFilter colorFilter) {
    164         mCornerShadowPaint.setColorFilter(colorFilter);
    165         mEdgeShadowPaint.setColorFilter(colorFilter);
    166     }
    167 
    168     @Override
    169     public int getOpacity() {
    170         return PixelFormat.OPAQUE;
    171     }
    172 
    173     @Override
    174     public void draw(Canvas canvas) {
    175         if (mDirty) {
    176             buildComponents(getBounds());
    177             mDirty = false;
    178         }
    179         canvas.translate(0, mRawShadowSize / 4);
    180         drawShadow(canvas);
    181         canvas.translate(0, -mRawShadowSize / 4);
    182     }
    183 
    184     private void drawShadow(Canvas canvas) {
    185         final float edgeShadowTop = -mCornerRadius - mShadowSize;
    186         final float inset = mCornerRadius + mInsetShadow + mRawShadowSize / 2;
    187         final boolean drawHorizontalEdges = mCardBounds.width() - 2 * inset > 0;
    188         final boolean drawVerticalEdges = mCardBounds.height() - 2 * inset > 0;
    189         // LT
    190         int saved = canvas.save();
    191         canvas.translate(mCardBounds.left + inset, mCardBounds.top + inset);
    192         canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
    193         if (drawHorizontalEdges) {
    194             canvas.drawRect(0, edgeShadowTop,
    195                     mCardBounds.width() - 2 * inset, -mCornerRadius,
    196                     mEdgeShadowPaint);
    197         }
    198         canvas.restoreToCount(saved);
    199         // RB
    200         saved = canvas.save();
    201         canvas.translate(mCardBounds.right - inset, mCardBounds.bottom - inset);
    202         canvas.rotate(180f);
    203         canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
    204         if (drawHorizontalEdges) {
    205             canvas.drawRect(0, edgeShadowTop,
    206                     mCardBounds.width() - 2 * inset, -mCornerRadius + mShadowSize,
    207                     mEdgeShadowPaint);
    208         }
    209         canvas.restoreToCount(saved);
    210         // LB
    211         saved = canvas.save();
    212         canvas.translate(mCardBounds.left + inset, mCardBounds.bottom - inset);
    213         canvas.rotate(270f);
    214         canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
    215         if (drawVerticalEdges) {
    216             canvas.drawRect(0, edgeShadowTop,
    217                     mCardBounds.height() - 2 * inset, -mCornerRadius, mEdgeShadowPaint);
    218         }
    219         canvas.restoreToCount(saved);
    220         // RT
    221         saved = canvas.save();
    222         canvas.translate(mCardBounds.right - inset, mCardBounds.top + inset);
    223         canvas.rotate(90f);
    224         canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
    225         if (drawVerticalEdges) {
    226             canvas.drawRect(0, edgeShadowTop,
    227                     mCardBounds.height() - 2 * inset, -mCornerRadius, mEdgeShadowPaint);
    228         }
    229         canvas.restoreToCount(saved);
    230     }
    231 
    232     private void buildShadowCorners() {
    233         RectF innerBounds = new RectF(-mCornerRadius, -mCornerRadius, mCornerRadius, mCornerRadius);
    234         RectF outerBounds = new RectF(innerBounds);
    235         outerBounds.inset(-mShadowSize, -mShadowSize);
    236 
    237         if (mCornerShadowPath == null) {
    238             mCornerShadowPath = new Path();
    239         } else {
    240             mCornerShadowPath.reset();
    241         }
    242         mCornerShadowPath.setFillType(Path.FillType.EVEN_ODD);
    243         mCornerShadowPath.moveTo(-mCornerRadius, 0);
    244         mCornerShadowPath.rLineTo(-mShadowSize, 0);
    245         // outer arc
    246         mCornerShadowPath.arcTo(outerBounds, 180f, 90f, false);
    247         // inner arc
    248         mCornerShadowPath.arcTo(innerBounds, 270f, -90f, false);
    249         mCornerShadowPath.close();
    250 
    251         float startRatio = mCornerRadius / (mCornerRadius + mShadowSize);
    252         mCornerShadowPaint.setShader(new RadialGradient(0, 0, mCornerRadius + mShadowSize,
    253                 new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor},
    254                 new float[]{0f, startRatio, 1f}
    255                 , Shader.TileMode.CLAMP));
    256 
    257         // we offset the content shadowSize/2 pixels up to make it more realistic.
    258         // this is why edge shadow shader has some extra space
    259         // When drawing bottom edge shadow, we use that extra space.
    260         mEdgeShadowPaint.setShader(new LinearGradient(0, -mCornerRadius + mShadowSize, 0,
    261                 -mCornerRadius - mShadowSize,
    262                 new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor},
    263                 new float[]{0f, .5f, 1f}, Shader.TileMode.CLAMP));
    264     }
    265 
    266     private void buildComponents(Rect bounds) {
    267         // Card is offset SHADOW_MULTIPLIER * maxShadowSize to account for the shadow shift.
    268         // We could have different top-bottom offsets to avoid extra gap above but in that case
    269         // center aligning Views inside the CardView would be problematic.
    270         final float verticalOffset = mMaxShadowSize * SHADOW_MULTIPLIER;
    271         mCardBounds.set(bounds.left + mMaxShadowSize, bounds.top + verticalOffset,
    272                 bounds.right - mMaxShadowSize, bounds.bottom - verticalOffset);
    273         buildShadowCorners();
    274     }
    275 
    276     float getMinWidth() {
    277         final float content = 2 *
    278                 Math.max(mRawMaxShadowSize, mCornerRadius + mInsetShadow + mRawMaxShadowSize / 2);
    279         return content + (mRawMaxShadowSize + mInsetShadow) * 2;
    280     }
    281 
    282     float getMinHeight() {
    283         final float content = 2 * Math.max(mRawMaxShadowSize, mCornerRadius + mInsetShadow
    284                         + mRawMaxShadowSize * SHADOW_MULTIPLIER / 2);
    285         return content + (mRawMaxShadowSize * SHADOW_MULTIPLIER + mInsetShadow) * 2;
    286     }
    287 }