1 /* 2 * Copyright (C) 2007 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 17 package android.graphics.drawable.shapes; 18 19 import android.annotation.Nullable; 20 import android.graphics.Canvas; 21 import android.graphics.Outline; 22 import android.graphics.Paint; 23 import android.graphics.Path; 24 import android.graphics.RectF; 25 26 import java.util.Arrays; 27 import java.util.Objects; 28 29 /** 30 * Creates a rounded-corner rectangle. Optionally, an inset (rounded) rectangle 31 * can be included (to make a sort of "O" shape). 32 * <p> 33 * The rounded rectangle can be drawn to a Canvas with its own draw() method, 34 * but more graphical control is available if you instead pass 35 * the RoundRectShape to a {@link android.graphics.drawable.ShapeDrawable}. 36 */ 37 public class RoundRectShape extends RectShape { 38 private float[] mOuterRadii; 39 private RectF mInset; 40 private float[] mInnerRadii; 41 42 private RectF mInnerRect; 43 private Path mPath; // this is what we actually draw 44 45 /** 46 * RoundRectShape constructor. 47 * <p> 48 * Specifies an outer (round)rect and an optional inner (round)rect. 49 * 50 * @param outerRadii An array of 8 radius values, for the outer roundrect. 51 * The first two floats are for the top-left corner 52 * (remaining pairs correspond clockwise). For no rounded 53 * corners on the outer rectangle, pass {@code null}. 54 * @param inset A RectF that specifies the distance from the inner 55 * rect to each side of the outer rect. For no inner, pass 56 * {@code null}. 57 * @param innerRadii An array of 8 radius values, for the inner roundrect. 58 * The first two floats are for the top-left corner 59 * (remaining pairs correspond clockwise). For no rounded 60 * corners on the inner rectangle, pass {@code null}. If 61 * inset parameter is {@code null}, this parameter is 62 * ignored. 63 */ RoundRectShape(@ullable float[] outerRadii, @Nullable RectF inset, @Nullable float[] innerRadii)64 public RoundRectShape(@Nullable float[] outerRadii, @Nullable RectF inset, 65 @Nullable float[] innerRadii) { 66 if (outerRadii != null && outerRadii.length < 8) { 67 throw new ArrayIndexOutOfBoundsException("outer radii must have >= 8 values"); 68 } 69 if (innerRadii != null && innerRadii.length < 8) { 70 throw new ArrayIndexOutOfBoundsException("inner radii must have >= 8 values"); 71 } 72 mOuterRadii = outerRadii; 73 mInset = inset; 74 mInnerRadii = innerRadii; 75 76 if (inset != null) { 77 mInnerRect = new RectF(); 78 } 79 mPath = new Path(); 80 } 81 82 @Override draw(Canvas canvas, Paint paint)83 public void draw(Canvas canvas, Paint paint) { 84 canvas.drawPath(mPath, paint); 85 } 86 87 @Override getOutline(Outline outline)88 public void getOutline(Outline outline) { 89 if (mInnerRect != null) return; // have a hole, can't produce valid outline 90 91 float radius = 0; 92 if (mOuterRadii != null) { 93 radius = mOuterRadii[0]; 94 for (int i = 1; i < 8; i++) { 95 if (mOuterRadii[i] != radius) { 96 // can't call simple constructors, use path 97 outline.setConvexPath(mPath); 98 return; 99 } 100 } 101 } 102 103 final RectF rect = rect(); 104 outline.setRoundRect((int) Math.ceil(rect.left), (int) Math.ceil(rect.top), 105 (int) Math.floor(rect.right), (int) Math.floor(rect.bottom), radius); 106 } 107 108 @Override onResize(float w, float h)109 protected void onResize(float w, float h) { 110 super.onResize(w, h); 111 112 RectF r = rect(); 113 mPath.reset(); 114 115 if (mOuterRadii != null) { 116 mPath.addRoundRect(r, mOuterRadii, Path.Direction.CW); 117 } else { 118 mPath.addRect(r, Path.Direction.CW); 119 } 120 if (mInnerRect != null) { 121 mInnerRect.set(r.left + mInset.left, r.top + mInset.top, 122 r.right - mInset.right, r.bottom - mInset.bottom); 123 if (mInnerRect.width() < w && mInnerRect.height() < h) { 124 if (mInnerRadii != null) { 125 mPath.addRoundRect(mInnerRect, mInnerRadii, Path.Direction.CCW); 126 } else { 127 mPath.addRect(mInnerRect, Path.Direction.CCW); 128 } 129 } 130 } 131 } 132 133 @Override clone()134 public RoundRectShape clone() throws CloneNotSupportedException { 135 final RoundRectShape shape = (RoundRectShape) super.clone(); 136 shape.mOuterRadii = mOuterRadii != null ? mOuterRadii.clone() : null; 137 shape.mInnerRadii = mInnerRadii != null ? mInnerRadii.clone() : null; 138 shape.mInset = new RectF(mInset); 139 shape.mInnerRect = new RectF(mInnerRect); 140 shape.mPath = new Path(mPath); 141 return shape; 142 } 143 144 @Override equals(Object o)145 public boolean equals(Object o) { 146 if (this == o) { 147 return true; 148 } 149 if (o == null || getClass() != o.getClass()) { 150 return false; 151 } 152 if (!super.equals(o)) { 153 return false; 154 } 155 RoundRectShape that = (RoundRectShape) o; 156 return Arrays.equals(mOuterRadii, that.mOuterRadii) 157 && Objects.equals(mInset, that.mInset) 158 && Arrays.equals(mInnerRadii, that.mInnerRadii) 159 && Objects.equals(mInnerRect, that.mInnerRect) 160 && Objects.equals(mPath, that.mPath); 161 } 162 163 @Override hashCode()164 public int hashCode() { 165 int result = Objects.hash(super.hashCode(), mInset, mInnerRect, mPath); 166 result = 31 * result + Arrays.hashCode(mOuterRadii); 167 result = 31 * result + Arrays.hashCode(mInnerRadii); 168 return result; 169 } 170 } 171