19
19
import android .graphics .PathEffect ;
20
20
import android .graphics .Rect ;
21
21
import android .graphics .RectF ;
22
+ import android .graphics .Region ;
22
23
import android .graphics .drawable .Drawable ;
23
24
import android .os .Build ;
24
25
import com .facebook .react .common .annotations .VisibleForTesting ;
30
31
import javax .annotation .Nullable ;
31
32
32
33
/**
33
- * A subclass of {@link Drawable} used for background of {@link ReactViewGroup}. It supports
34
- * drawing background color and borders (including rounded borders) by providing a react friendly
35
- * API (setter for each of those properties).
34
+ * A subclass of {@link Drawable} used for background of {@link ReactViewGroup}. It supports drawing
35
+ * background color and borders (including rounded borders) by providing a react friendly API
36
+ * (setter for each of those properties).
36
37
*
37
- * The implementation tries to allocate as few objects as possible depending on which properties are
38
- * set. E.g. for views with rounded background/borders we allocate {@code mPathForBorderRadius} and
39
- * {@code mTempRectForBorderRadius }. In case when view have a rectangular borders we allocate
40
- * {@code mBorderWidthResult} and similar. When only background color is set we won't allocate any
41
- * extra/unnecessary objects.
38
+ * <p> The implementation tries to allocate as few objects as possible depending on which properties
39
+ * are set. E.g. for views with rounded background/borders we allocate {@code
40
+ * mInnerClipPathForBorderRadius} and {@code mInnerClipTempRectForBorderRadius }. In case when view
41
+ * have a rectangular borders we allocate {@code mBorderWidthResult} and similar. When only
42
+ * background color is set we won't allocate any extra/unnecessary objects.
42
43
*/
43
44
public class ReactViewBackgroundDrawable extends Drawable {
44
45
@@ -83,10 +84,12 @@ private static enum BorderStyle {
83
84
84
85
/* Used for rounded border and rounded background */
85
86
private @ Nullable PathEffect mPathEffectForBorderStyle ;
86
- private @ Nullable Path mPathForBorderRadius ;
87
+ private @ Nullable Path mInnerClipPathForBorderRadius ;
88
+ private @ Nullable Path mOuterClipPathForBorderRadius ;
87
89
private @ Nullable Path mPathForBorderRadiusOutline ;
88
90
private @ Nullable Path mPathForBorder ;
89
- private @ Nullable RectF mTempRectForBorderRadius ;
91
+ private @ Nullable RectF mInnerClipTempRectForBorderRadius ;
92
+ private @ Nullable RectF mOuterClipTempRectForBorderRadius ;
90
93
private @ Nullable RectF mTempRectForBorderRadiusOutline ;
91
94
private boolean mNeedUpdatePathForBorderRadius = false ;
92
95
private float mBorderRadius = YogaConstants .UNDEFINED ;
@@ -169,8 +172,13 @@ public void setBorderWidth(int position, float width) {
169
172
}
170
173
if (!FloatUtil .floatsEqual (mBorderWidth .getRaw (position ), width )) {
171
174
mBorderWidth .set (position , width );
172
- if (position == Spacing .ALL ) {
173
- mNeedUpdatePathForBorderRadius = true ;
175
+ switch (position ) {
176
+ case Spacing .ALL :
177
+ case Spacing .LEFT :
178
+ case Spacing .BOTTOM :
179
+ case Spacing .RIGHT :
180
+ case Spacing .TOP :
181
+ mNeedUpdatePathForBorderRadius = true ;
174
182
}
175
183
invalidateSelf ();
176
184
}
@@ -266,44 +274,87 @@ public int getColor() {
266
274
267
275
private void drawRoundedBackgroundWithBorders (Canvas canvas ) {
268
276
updatePath ();
277
+ canvas .save ();
278
+
269
279
int useColor = ColorUtil .multiplyColorAlpha (mColor , mAlpha );
270
280
if (Color .alpha (useColor ) != 0 ) { // color is not transparent
271
281
mPaint .setColor (useColor );
272
282
mPaint .setStyle (Paint .Style .FILL );
273
- canvas .drawPath (mPathForBorderRadius , mPaint );
283
+ canvas .drawPath (mInnerClipPathForBorderRadius , mPaint );
274
284
}
275
- // maybe draw borders?
276
- float fullBorderWidth = getFullBorderWidth ();
277
- if (fullBorderWidth > 0 ) {
285
+
286
+ final float borderWidth = getBorderWidthOrDefaultTo (0 , Spacing .ALL );
287
+ final float borderTopWidth = getBorderWidthOrDefaultTo (borderWidth , Spacing .TOP );
288
+ final float borderBottomWidth = getBorderWidthOrDefaultTo (borderWidth , Spacing .BOTTOM );
289
+ final float borderLeftWidth = getBorderWidthOrDefaultTo (borderWidth , Spacing .LEFT );
290
+ final float borderRightWidth = getBorderWidthOrDefaultTo (borderWidth , Spacing .RIGHT );
291
+
292
+ if (borderTopWidth > 0
293
+ || borderBottomWidth > 0
294
+ || borderLeftWidth > 0
295
+ || borderRightWidth > 0 ) {
278
296
int borderColor = getFullBorderColor ();
279
297
mPaint .setColor (ColorUtil .multiplyColorAlpha (borderColor , mAlpha ));
280
- mPaint .setStyle (Paint .Style .STROKE );
281
- mPaint .setStrokeWidth (fullBorderWidth );
282
- canvas .drawPath (mPathForBorderRadius , mPaint );
298
+ mPaint .setStyle (Paint .Style .FILL );
299
+
300
+ // Draw border
301
+ canvas .clipPath (mOuterClipPathForBorderRadius , Region .Op .INTERSECT );
302
+ canvas .clipPath (mInnerClipPathForBorderRadius , Region .Op .DIFFERENCE );
303
+ canvas .drawRect (getBounds (), mPaint );
283
304
}
305
+
306
+ canvas .restore ();
284
307
}
285
308
286
309
private void updatePath () {
287
310
if (!mNeedUpdatePathForBorderRadius ) {
288
311
return ;
289
312
}
313
+
290
314
mNeedUpdatePathForBorderRadius = false ;
291
- if (mPathForBorderRadius == null ) {
292
- mPathForBorderRadius = new Path ();
293
- mTempRectForBorderRadius = new RectF ();
315
+
316
+ if (mInnerClipPathForBorderRadius == null ) {
317
+ mInnerClipPathForBorderRadius = new Path ();
318
+ }
319
+
320
+ if (mOuterClipPathForBorderRadius == null ) {
321
+ mOuterClipPathForBorderRadius = new Path ();
322
+ }
323
+
324
+ if (mPathForBorderRadiusOutline == null ) {
294
325
mPathForBorderRadiusOutline = new Path ();
326
+ }
327
+
328
+ if (mInnerClipTempRectForBorderRadius == null ) {
329
+ mInnerClipTempRectForBorderRadius = new RectF ();
330
+ }
331
+
332
+ if (mOuterClipTempRectForBorderRadius == null ) {
333
+ mOuterClipTempRectForBorderRadius = new RectF ();
334
+ }
335
+
336
+ if (mTempRectForBorderRadiusOutline == null ) {
295
337
mTempRectForBorderRadiusOutline = new RectF ();
296
338
}
297
339
298
- mPathForBorderRadius .reset ();
340
+ mInnerClipPathForBorderRadius .reset ();
341
+ mOuterClipPathForBorderRadius .reset ();
299
342
mPathForBorderRadiusOutline .reset ();
300
343
301
- mTempRectForBorderRadius .set (getBounds ());
344
+ mInnerClipTempRectForBorderRadius .set (getBounds ());
345
+ mOuterClipTempRectForBorderRadius .set (getBounds ());
302
346
mTempRectForBorderRadiusOutline .set (getBounds ());
303
- float fullBorderWidth = getFullBorderWidth ();
304
- if (fullBorderWidth > 0 ) {
305
- mTempRectForBorderRadius .inset (fullBorderWidth * 0.5f , fullBorderWidth * 0.5f );
306
- }
347
+
348
+ final float borderWidth = getBorderWidthOrDefaultTo (0 , Spacing .ALL );
349
+ final float borderTopWidth = getBorderWidthOrDefaultTo (borderWidth , Spacing .TOP );
350
+ final float borderBottomWidth = getBorderWidthOrDefaultTo (borderWidth , Spacing .BOTTOM );
351
+ final float borderLeftWidth = getBorderWidthOrDefaultTo (borderWidth , Spacing .LEFT );
352
+ final float borderRightWidth = getBorderWidthOrDefaultTo (borderWidth , Spacing .RIGHT );
353
+
354
+ mInnerClipTempRectForBorderRadius .top += borderTopWidth ;
355
+ mInnerClipTempRectForBorderRadius .bottom -= borderBottomWidth ;
356
+ mInnerClipTempRectForBorderRadius .left += borderLeftWidth ;
357
+ mInnerClipTempRectForBorderRadius .right -= borderRightWidth ;
307
358
308
359
final float borderRadius = getFullBorderRadius ();
309
360
final float topLeftRadius =
@@ -315,8 +366,22 @@ private void updatePath() {
315
366
final float bottomRightRadius =
316
367
getBorderRadiusOrDefaultTo (borderRadius , BorderRadiusLocation .BOTTOM_RIGHT );
317
368
318
- mPathForBorderRadius .addRoundRect (
319
- mTempRectForBorderRadius ,
369
+ mInnerClipPathForBorderRadius .addRoundRect (
370
+ mInnerClipTempRectForBorderRadius ,
371
+ new float [] {
372
+ Math .max (topLeftRadius - borderLeftWidth , 0 ),
373
+ Math .max (topLeftRadius - borderTopWidth , 0 ),
374
+ Math .max (topRightRadius - borderRightWidth , 0 ),
375
+ Math .max (topRightRadius - borderTopWidth , 0 ),
376
+ Math .max (bottomRightRadius - borderRightWidth , 0 ),
377
+ Math .max (bottomRightRadius - borderBottomWidth , 0 ),
378
+ Math .max (bottomLeftRadius - borderLeftWidth , 0 ),
379
+ Math .max (bottomLeftRadius - borderBottomWidth , 0 ),
380
+ },
381
+ Path .Direction .CW );
382
+
383
+ mOuterClipPathForBorderRadius .addRoundRect (
384
+ mOuterClipTempRectForBorderRadius ,
320
385
new float [] {
321
386
topLeftRadius ,
322
387
topLeftRadius ,
@@ -329,6 +394,7 @@ private void updatePath() {
329
394
},
330
395
Path .Direction .CW );
331
396
397
+
332
398
float extraRadiusForOutline = 0 ;
333
399
334
400
if (mBorderWidth != null ) {
@@ -350,6 +416,20 @@ private void updatePath() {
350
416
Path .Direction .CW );
351
417
}
352
418
419
+ public float getBorderWidthOrDefaultTo (final float defaultValue , final int spacingType ) {
420
+ if (mBorderWidth == null ) {
421
+ return defaultValue ;
422
+ }
423
+
424
+ final float width = mBorderWidth .getRaw (spacingType );
425
+
426
+ if (YogaConstants .isUndefined (width )) {
427
+ return defaultValue ;
428
+ }
429
+
430
+ return width ;
431
+ }
432
+
353
433
/**
354
434
* Set type of border
355
435
*/
0 commit comments