/* * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.drawee.drawable; import java.util.Arrays; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; import com.facebook.common.internal.Preconditions; import com.facebook.common.internal.VisibleForTesting; /** * Drawable that draws underlying drawable with rounded corners. */ public class RoundedCornersDrawable extends ForwardingDrawable { public enum Type { /** * Draws rounded corners on top of the underlying drawable by overlaying a solid color which * is specified by {@code setOverlayColor}. This option should only be used when the * background beneath the underlying drawable is static and of the same solid color. */ OVERLAY_COLOR, /** * Clips the drawable to be rounded. This option is not supported right now but is expected to * be made available in the future. */ CLIPPING } @VisibleForTesting Type mType = Type.OVERLAY_COLOR; @VisibleForTesting final float[] mRadii = new float[8]; @VisibleForTesting final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); @VisibleForTesting boolean mIsCircle = false; @VisibleForTesting float mBorderWidth = 0; @VisibleForTesting int mBorderColor = Color.TRANSPARENT; @VisibleForTesting int mOverlayColor = Color.TRANSPARENT; private final Path mPath = new Path(); private final RectF mTempRectangle = new RectF(); /** * Creates a new RoundedCornersDrawable with given underlying drawable. * * @param drawable underlying drawable */ public RoundedCornersDrawable(Drawable drawable) { super(Preconditions.checkNotNull(drawable)); } /** * Sets the type of rounding process * * @param type type of rounding process */ public void setType(Type type) { mType = type; invalidateSelf(); } /** * Sets whether to round as circle. * * @param isCircle whether or not to round as circle */ public void setCircle(boolean isCircle) { mIsCircle = isCircle; updatePath(); invalidateSelf(); } /** * Sets radius to be used for rounding * * @param radius corner radius in pixels */ public void setRadius(float radius) { Arrays.fill(mRadii, radius); updatePath(); invalidateSelf(); } /** * Sets radii values to be used for rounding. * Each corner receive two radius values [X, Y]. The corners are ordered * top-left, top-right, bottom-right, bottom-left * * @param radii Array of 8 values, 4 pairs of [X,Y] radii */ public void setRadii(float[] radii) { if (radii == null) { Arrays.fill(mRadii, 0); } else { Preconditions.checkArgument(radii.length == 8, "radii should have exactly 8 values"); System.arraycopy(radii, 0, mRadii, 0, 8); } updatePath(); invalidateSelf(); } /** * Sets the overlay color. * * @param overlayColor the color to filled outside the rounded corners */ public void setOverlayColor(int overlayColor) { mOverlayColor = overlayColor; invalidateSelf(); } /** * Sets the border * @param color of the border * @param width of the border */ public void setBorder(int color, float width) { mBorderColor = color; mBorderWidth = width; updatePath(); invalidateSelf(); } @Override protected void onBoundsChange(Rect bounds) { super.onBoundsChange(bounds); updatePath(); } private void updatePath() { mPath.reset(); mTempRectangle.set(getBounds()); mTempRectangle.inset(mBorderWidth/2, mBorderWidth/2); if (mIsCircle) { mPath.addCircle( mTempRectangle.centerX(), mTempRectangle.centerY(), Math.min(mTempRectangle.width(), mTempRectangle.height())/2, Path.Direction.CW); } else { mPath.addRoundRect(mTempRectangle, mRadii, Path.Direction.CW); } mTempRectangle.inset(-mBorderWidth/2, -mBorderWidth/2); } @Override public void draw(Canvas canvas) { Rect bounds = getBounds(); switch (mType) { case CLIPPING: // clip, note: doesn't support anti-aliasing int saveCount = canvas.save(); mPath.setFillType(Path.FillType.EVEN_ODD); canvas.clipPath(mPath); super.draw(canvas); canvas.restoreToCount(saveCount); break; case OVERLAY_COLOR: super.draw(canvas); mPaint.setColor(mOverlayColor); mPaint.setStyle(Paint.Style.FILL); mPath.setFillType(Path.FillType.INVERSE_EVEN_ODD); canvas.drawPath(mPath, mPaint); if (mIsCircle) { // INVERSE_EVEN_ODD will only draw inverse circle within its bounding box, so we need to // fill the rest manually if the bounds are not square. float paddingH = (bounds.width() - bounds.height() + mBorderWidth) / 2f; float paddingV = (bounds.height() - bounds.width() + mBorderWidth) / 2f; if (paddingH > 0) { canvas.drawRect(bounds.left, bounds.top, bounds.left + paddingH, bounds.bottom, mPaint); canvas.drawRect( bounds.right - paddingH, bounds.top, bounds.right, bounds.bottom, mPaint); } if (paddingV > 0) { canvas.drawRect(bounds.left, bounds.top, bounds.right, bounds.top + paddingV, mPaint); canvas.drawRect( bounds.left, bounds.bottom - paddingV, bounds.right, bounds.bottom, mPaint); } } break; } if (mBorderColor != Color.TRANSPARENT) { mPaint.setStyle(Paint.Style.STROKE); mPaint.setColor(mBorderColor); mPaint.setStrokeWidth(mBorderWidth); mPath.setFillType(Path.FillType.EVEN_ODD); canvas.drawPath(mPath, mPaint); } } }