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 */ 16package android.support.v7.widget; 17 18import android.content.res.ColorStateList; 19import android.graphics.Canvas; 20import android.graphics.Color; 21import android.graphics.ColorFilter; 22import android.graphics.Outline; 23import android.graphics.Paint; 24import android.graphics.PixelFormat; 25import android.graphics.PorterDuff; 26import android.graphics.PorterDuffColorFilter; 27import android.graphics.Rect; 28import android.graphics.RectF; 29import android.graphics.drawable.Drawable; 30import android.support.annotation.Nullable; 31 32import static android.support.v7.widget.RoundRectDrawableWithShadow.calculateVerticalPadding; 33import static android.support.v7.widget.RoundRectDrawableWithShadow.calculateHorizontalPadding; 34 35/** 36 * Very simple drawable that draws a rounded rectangle background with arbitrary corners and also 37 * reports proper outline for Lollipop. 38 * <p> 39 * Simpler and uses less resources compared to GradientDrawable or ShapeDrawable. 40 */ 41class 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 public 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 = calculateVerticalPadding(mPadding, mRadius, mInsetForRadius); 112 float hInset = 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