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