Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright 2018 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 androidx.cardview.widget;
     17 
     18 import android.content.res.ColorStateList;
     19 import android.graphics.Canvas;
     20 import android.graphics.Color;
     21 import android.graphics.ColorFilter;
     22 import android.graphics.Outline;
     23 import android.graphics.Paint;
     24 import android.graphics.PixelFormat;
     25 import android.graphics.PorterDuff;
     26 import android.graphics.PorterDuffColorFilter;
     27 import android.graphics.Rect;
     28 import android.graphics.RectF;
     29 import android.graphics.drawable.Drawable;
     30 
     31 import androidx.annotation.Nullable;
     32 import androidx.annotation.RequiresApi;
     33 
     34 /**
     35  * Very simple drawable that draws a rounded rectangle background with arbitrary corners and also
     36  * reports proper outline for Lollipop.
     37  * <p>
     38  * Simpler and uses less resources compared to GradientDrawable or ShapeDrawable.
     39  */
     40 @RequiresApi(21)
     41 class RoundRectDrawable extends Drawable {
     42     private float mRadius;
     43     private final Paint mPaint;
     44     private final RectF mBoundsF;
     45     private final Rect mBoundsI;
     46     private float mPadding;
     47     private boolean mInsetForPadding = false;
     48     private boolean mInsetForRadius = true;
     49 
     50     private ColorStateList mBackground;
     51     private PorterDuffColorFilter mTintFilter;
     52     private ColorStateList mTint;
     53     private PorterDuff.Mode mTintMode = PorterDuff.Mode.SRC_IN;
     54 
     55     RoundRectDrawable(ColorStateList backgroundColor, float radius) {
     56         mRadius = radius;
     57         mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
     58         setBackground(backgroundColor);
     59 
     60         mBoundsF = new RectF();
     61         mBoundsI = new Rect();
     62     }
     63 
     64     private void setBackground(ColorStateList color) {
     65         mBackground = (color == null) ?  ColorStateList.valueOf(Color.TRANSPARENT) : color;
     66         mPaint.setColor(mBackground.getColorForState(getState(), mBackground.getDefaultColor()));
     67     }
     68 
     69     void setPadding(float padding, boolean insetForPadding, boolean insetForRadius) {
     70         if (padding == mPadding && mInsetForPadding == insetForPadding
     71                 && mInsetForRadius == insetForRadius) {
     72             return;
     73         }
     74         mPadding = padding;
     75         mInsetForPadding = insetForPadding;
     76         mInsetForRadius = insetForRadius;
     77         updateBounds(null);
     78         invalidateSelf();
     79     }
     80 
     81     float getPadding() {
     82         return mPadding;
     83     }
     84 
     85     @Override
     86     public void draw(Canvas canvas) {
     87         final Paint paint = mPaint;
     88 
     89         final boolean clearColorFilter;
     90         if (mTintFilter != null && paint.getColorFilter() == null) {
     91             paint.setColorFilter(mTintFilter);
     92             clearColorFilter = true;
     93         } else {
     94             clearColorFilter = false;
     95         }
     96 
     97         canvas.drawRoundRect(mBoundsF, mRadius, mRadius, paint);
     98 
     99         if (clearColorFilter) {
    100             paint.setColorFilter(null);
    101         }
    102     }
    103 
    104     private void updateBounds(Rect bounds) {
    105         if (bounds == null) {
    106             bounds = getBounds();
    107         }
    108         mBoundsF.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
    109         mBoundsI.set(bounds);
    110         if (mInsetForPadding) {
    111             float vInset = RoundRectDrawableWithShadow.calculateVerticalPadding(mPadding, mRadius, mInsetForRadius);
    112             float hInset = RoundRectDrawableWithShadow.calculateHorizontalPadding(mPadding, mRadius, mInsetForRadius);
    113             mBoundsI.inset((int) Math.ceil(hInset), (int) Math.ceil(vInset));
    114             // to make sure they have same bounds.
    115             mBoundsF.set(mBoundsI);
    116         }
    117     }
    118 
    119     @Override
    120     protected void onBoundsChange(Rect bounds) {
    121         super.onBoundsChange(bounds);
    122         updateBounds(bounds);
    123     }
    124 
    125     @Override
    126     public void getOutline(Outline outline) {
    127         outline.setRoundRect(mBoundsI, mRadius);
    128     }
    129 
    130     void setRadius(float radius) {
    131         if (radius == mRadius) {
    132             return;
    133         }
    134         mRadius = radius;
    135         updateBounds(null);
    136         invalidateSelf();
    137     }
    138 
    139     @Override
    140     public void setAlpha(int alpha) {
    141         mPaint.setAlpha(alpha);
    142     }
    143 
    144     @Override
    145     public void setColorFilter(ColorFilter cf) {
    146         mPaint.setColorFilter(cf);
    147     }
    148 
    149     @Override
    150     public int getOpacity() {
    151         return PixelFormat.TRANSLUCENT;
    152     }
    153 
    154     public float getRadius() {
    155         return mRadius;
    156     }
    157 
    158     public void setColor(@Nullable ColorStateList color) {
    159         setBackground(color);
    160         invalidateSelf();
    161     }
    162 
    163     public ColorStateList getColor() {
    164         return mBackground;
    165     }
    166 
    167     @Override
    168     public void setTintList(ColorStateList tint) {
    169         mTint = tint;
    170         mTintFilter = createTintFilter(mTint, mTintMode);
    171         invalidateSelf();
    172     }
    173 
    174     @Override
    175     public void setTintMode(PorterDuff.Mode tintMode) {
    176         mTintMode = tintMode;
    177         mTintFilter = createTintFilter(mTint, mTintMode);
    178         invalidateSelf();
    179     }
    180 
    181     @Override
    182     protected boolean onStateChange(int[] stateSet) {
    183         final int newColor = mBackground.getColorForState(stateSet, mBackground.getDefaultColor());
    184         final boolean colorChanged = newColor != mPaint.getColor();
    185         if (colorChanged) {
    186             mPaint.setColor(newColor);
    187         }
    188         if (mTint != null && mTintMode != null) {
    189             mTintFilter = createTintFilter(mTint, mTintMode);
    190             return true;
    191         }
    192         return colorChanged;
    193     }
    194 
    195     @Override
    196     public boolean isStateful() {
    197         return (mTint != null && mTint.isStateful())
    198                 || (mBackground != null && mBackground.isStateful()) || super.isStateful();
    199     }
    200 
    201     /**
    202      * Ensures the tint filter is consistent with the current tint color and
    203      * mode.
    204      */
    205     private PorterDuffColorFilter createTintFilter(ColorStateList tint, PorterDuff.Mode tintMode) {
    206         if (tint == null || tintMode == null) {
    207             return null;
    208         }
    209         final int color = tint.getColorForState(getState(), Color.TRANSPARENT);
    210         return new PorterDuffColorFilter(color, tintMode);
    211     }
    212 }
    213