StyledCornersBitmapDrawable.java revision ad6ca3f895022ded1a11f3eedc50d70ea90cd4da
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 17package com.android.bitmap.drawable; 18 19import android.content.res.Resources; 20import android.graphics.Canvas; 21import android.graphics.Color; 22import android.graphics.Paint; 23import android.graphics.Paint.Style; 24import android.graphics.Path; 25import android.graphics.Rect; 26import android.graphics.RectF; 27 28import com.android.bitmap.BitmapCache; 29 30/** 31 * A custom ExtendedBitmapDrawable that styles the corners in configurable ways. 32 * 33 * All four corners can be configured as {@link #CORNER_STYLE_SHARP}, 34 * {@link #CORNER_STYLE_ROUND}, or {@link #CORNER_STYLE_FLAP}. 35 * This is accomplished applying a non-rectangular clip applied to the canvas. 36 * 37 * A border is draw that conforms to the styled corners. 38 * 39 * {@link #CORNER_STYLE_FLAP} corners have a colored flap drawn within the bounds. 40 */ 41public class StyledCornersBitmapDrawable extends ExtendedBitmapDrawable { 42 43 public static final int CORNER_STYLE_SHARP = 0; 44 public static final int CORNER_STYLE_ROUND = 1; 45 public static final int CORNER_STYLE_FLAP = 2; 46 47 private static final int START_RIGHT = 0; 48 private static final int START_BOTTOM = 90; 49 private static final int START_LEFT = 180; 50 private static final int START_TOP = 270; 51 private static final int QUARTER_CIRCLE = 90; 52 private static final RectF sRectF = new RectF(); 53 54 private final Paint mFlapPaint = new Paint(); 55 private final Paint mBorderPaint = new Paint(); 56 private final Path mClipPath = new Path(); 57 private final float mCornerRoundRadius; 58 private final float mCornerFlapSide; 59 60 private int mTopLeftCornerStyle = CORNER_STYLE_SHARP; 61 private int mTopRightCornerStyle = CORNER_STYLE_SHARP; 62 private int mBottomRightCornerStyle = CORNER_STYLE_SHARP; 63 private int mBottomLeftCornerStyle = CORNER_STYLE_SHARP; 64 private int mScrimColor; 65 private float mBorderWidth; 66 67 /** 68 * Create a new StyledCornersBitmapDrawable. 69 */ 70 public StyledCornersBitmapDrawable(Resources res, BitmapCache cache, 71 boolean limitDensity, ExtendedOptions opts, float cornerRoundRadius, 72 float cornerFlapSide) { 73 super(res, cache, limitDensity, opts); 74 75 mCornerRoundRadius = cornerRoundRadius; 76 mCornerFlapSide = cornerFlapSide; 77 78 mFlapPaint.setColor(Color.TRANSPARENT); 79 mFlapPaint.setStyle(Style.FILL); 80 81 mBorderPaint.setColor(Color.TRANSPARENT); 82 mBorderPaint.setStyle(Style.STROKE); 83 mBorderPaint.setStrokeWidth(mBorderWidth); 84 mBorderPaint.setAntiAlias(true); 85 86 mScrimColor = Color.TRANSPARENT; 87 } 88 89 /** 90 * Set the border stroke width of this drawable. 91 */ 92 public void setBorderWidth(final float borderWidth) { 93 final boolean changed = mBorderPaint.getStrokeWidth() != borderWidth; 94 mBorderPaint.setStrokeWidth(borderWidth); 95 mBorderWidth = borderWidth; 96 97 if (changed) { 98 invalidateSelf(); 99 } 100 } 101 102 /** 103 * Set the border stroke color of this drawable. Set to {@link Color#TRANSPARENT} to disable. 104 */ 105 public void setBorderColor(final int color) { 106 final boolean changed = mBorderPaint.getColor() != color; 107 mBorderPaint.setColor(color); 108 109 if (changed) { 110 invalidateSelf(); 111 } 112 } 113 114 /** Set the corner styles for all four corners */ 115 public void setCornerStyles(int topLeft, int topRight, int bottomRight, int bottomLeft) { 116 boolean changed = mTopLeftCornerStyle != topLeft 117 || mTopRightCornerStyle != topRight 118 || mBottomRightCornerStyle != bottomRight 119 || mBottomLeftCornerStyle != bottomLeft; 120 121 mTopLeftCornerStyle = topLeft; 122 mTopRightCornerStyle = topRight; 123 mBottomRightCornerStyle = bottomRight; 124 mBottomLeftCornerStyle = bottomLeft; 125 126 if (changed) { 127 recalculatePath(); 128 } 129 } 130 131 /** 132 * Set the flap color for all corners that have style {@link #CORNER_STYLE_SHARP}. 133 * 134 * Use {@link android.graphics.Color#TRANSPARENT} to disable flap colors. 135 */ 136 public void setFlapColor(int flapColor) { 137 boolean changed = mFlapPaint.getColor() != flapColor; 138 mFlapPaint.setColor(flapColor); 139 140 if (changed) { 141 invalidateSelf(); 142 } 143 } 144 145 /** 146 * Get the color of the scrim that is drawn over the contents, but under the flaps and borders. 147 */ 148 public int getScrimColor() { 149 return mScrimColor; 150 } 151 152 /** 153 * Set the color of the scrim that is drawn over the contents, but under the flaps and borders. 154 * 155 * Use {@link android.graphics.Color#TRANSPARENT} to disable the scrim. 156 */ 157 public void setScrimColor(int color) { 158 boolean changed = mScrimColor != color; 159 mScrimColor = color; 160 161 if (changed) { 162 invalidateSelf(); 163 } 164 } 165 166 @Override 167 protected void onBoundsChange(Rect bounds) { 168 super.onBoundsChange(bounds); 169 170 recalculatePath(); 171 } 172 173 /** 174 * Override draw(android.graphics.Canvas) instead of 175 * {@link #onDraw(android.graphics.Canvas)} to clip all the drawable layers. 176 */ 177 @Override 178 public void draw(Canvas canvas) { 179 final Rect bounds = getBounds(); 180 if (bounds.isEmpty()) { 181 return; 182 } 183 184 // Clip to path. 185 canvas.save(); 186 canvas.clipPath(mClipPath); 187 188 // Draw parent within path. 189 super.draw(canvas); 190 191 // Draw scrim on top of parent. 192 canvas.drawColor(mScrimColor); 193 194 // Draw flap. 195 float left = bounds.left + mBorderWidth / 2; 196 float top = bounds.top + mBorderWidth / 2; 197 float right = bounds.right - mBorderWidth / 2; 198 float bottom = bounds.bottom - mBorderWidth / 2; 199 RectF flapCornerRectF = sRectF; 200 flapCornerRectF.set(0, 0, mCornerFlapSide + mCornerRoundRadius, 201 mCornerFlapSide + mCornerRoundRadius); 202 203 if (mTopLeftCornerStyle == CORNER_STYLE_FLAP) { 204 flapCornerRectF.offsetTo(left, top); 205 canvas.drawRoundRect(flapCornerRectF, mCornerRoundRadius, 206 mCornerRoundRadius, mFlapPaint); 207 } 208 if (mTopRightCornerStyle == CORNER_STYLE_FLAP) { 209 flapCornerRectF.offsetTo(right - mCornerFlapSide, top); 210 canvas.drawRoundRect(flapCornerRectF, mCornerRoundRadius, 211 mCornerRoundRadius, mFlapPaint); 212 } 213 if (mBottomRightCornerStyle == CORNER_STYLE_FLAP) { 214 flapCornerRectF.offsetTo(right - mCornerFlapSide, bottom - mCornerFlapSide); 215 canvas.drawRoundRect(flapCornerRectF, mCornerRoundRadius, 216 mCornerRoundRadius, mFlapPaint); 217 } 218 if (mBottomLeftCornerStyle == CORNER_STYLE_FLAP) { 219 flapCornerRectF.offsetTo(left, bottom - mCornerFlapSide); 220 canvas.drawRoundRect(flapCornerRectF, mCornerRoundRadius, 221 mCornerRoundRadius, mFlapPaint); 222 } 223 224 canvas.restore(); 225 226 // Draw border around path. 227 canvas.drawPath(mClipPath, mBorderPaint); 228 } 229 230 protected Path getClipPath() { 231 return mClipPath; 232 } 233 234 private void recalculatePath() { 235 Rect bounds = getBounds(); 236 237 if (bounds.isEmpty()) { 238 return; 239 } 240 241 // Setup. 242 float left = bounds.left + mBorderWidth / 2; 243 float top = bounds.top + mBorderWidth / 2; 244 float right = bounds.right - mBorderWidth / 2; 245 float bottom = bounds.bottom - mBorderWidth / 2; 246 RectF roundedCornerRectF = sRectF; 247 roundedCornerRectF.set(0, 0, 2 * mCornerRoundRadius, 2 * mCornerRoundRadius); 248 mClipPath.rewind(); 249 250 switch (mTopLeftCornerStyle) { 251 case CORNER_STYLE_SHARP: 252 mClipPath.moveTo(left, top); 253 break; 254 case CORNER_STYLE_ROUND: 255 roundedCornerRectF.offsetTo(left, top); 256 mClipPath.arcTo(roundedCornerRectF, START_LEFT, QUARTER_CIRCLE); 257 break; 258 case CORNER_STYLE_FLAP: 259 mClipPath.moveTo(left, top - mCornerFlapSide); 260 mClipPath.lineTo(left + mCornerFlapSide, top); 261 break; 262 } 263 264 switch (mTopRightCornerStyle) { 265 case CORNER_STYLE_SHARP: 266 mClipPath.lineTo(right, top); 267 break; 268 case CORNER_STYLE_ROUND: 269 roundedCornerRectF.offsetTo(right - roundedCornerRectF.width(), top); 270 mClipPath.arcTo(roundedCornerRectF, START_TOP, QUARTER_CIRCLE); 271 break; 272 case CORNER_STYLE_FLAP: 273 mClipPath.lineTo(right - mCornerFlapSide, top); 274 mClipPath.lineTo(right, top + mCornerFlapSide); 275 break; 276 } 277 278 switch (mBottomRightCornerStyle) { 279 case CORNER_STYLE_SHARP: 280 mClipPath.lineTo(right, bottom); 281 break; 282 case CORNER_STYLE_ROUND: 283 roundedCornerRectF.offsetTo(right - roundedCornerRectF.width(), 284 bottom - roundedCornerRectF.height()); 285 mClipPath.arcTo(roundedCornerRectF, START_RIGHT, QUARTER_CIRCLE); 286 break; 287 case CORNER_STYLE_FLAP: 288 mClipPath.lineTo(right, bottom - mCornerFlapSide); 289 mClipPath.lineTo(right - mCornerFlapSide, bottom); 290 break; 291 } 292 293 switch (mBottomLeftCornerStyle) { 294 case CORNER_STYLE_SHARP: 295 mClipPath.lineTo(left, bottom); 296 break; 297 case CORNER_STYLE_ROUND: 298 roundedCornerRectF.offsetTo(left, bottom - roundedCornerRectF.height()); 299 mClipPath.arcTo(roundedCornerRectF, START_BOTTOM, QUARTER_CIRCLE); 300 break; 301 case CORNER_STYLE_FLAP: 302 mClipPath.lineTo(left + mCornerFlapSide, bottom); 303 mClipPath.lineTo(left, bottom - mCornerFlapSide); 304 break; 305 } 306 307 // Finish. 308 mClipPath.close(); 309 } 310} 311