Skip to content

Commit 9a7d3ec

Browse files
rigor789NathanWalker
authored andcommitted
feat: implement BoxShadowDrawable
1 parent a822f2a commit 9a7d3ec

File tree

7 files changed

+235
-143
lines changed

7 files changed

+235
-143
lines changed

apps/toolbox/src/pages/box-shadow.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ export function navigatingTo(args: EventData) {
77
}
88

99
export class BoxShadowModel extends Observable {
10-
private _selectedComponentType: string;
10+
private _selectedComponentType: string = 'buttons';
1111
private _selectedBackgroundType: string;
1212
private _selectedBorderType: string;
1313
private _selectedAnimation: string;
14-
private _boxShadow: string = '5 5 1 1 rgba(255, 0, 0, .9)';
14+
private _boxShadow: string = '0 10 15 -3 rgba(200, 0, 0, 0.4)';
15+
// private _boxShadow: string = '5 5 1 1 rgba(255, 0, 0, .9)';
16+
// private _boxShadow: string = '5 5 5 10 rgba(255, 0, 0, .9)';
1517

1618
background: string;
1719
borderWidth: number;

apps/toolbox/src/pages/box-shadow.xml

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
</Page.actionBar>
66

77
<GridLayout rows="*, auto, *" class="box-shadow-demo">
8-
<StackLayout backgroundColor="#ededed" row="0" padding="20" id="boxShadowDemo">
8+
<StackLayout backgroundColor="#ededed" row="0" id="boxShadowDemo">
99
<!-- layouts -->
1010
<ScrollView height="100%" visibility="{{ selectedComponentType === 'layouts' ? 'visible' : 'collapsed' }}">
11-
<StackLayout>
11+
<StackLayout padding="20">
1212
<StackLayout
1313
width="300"
1414
height="100"
@@ -74,22 +74,6 @@
7474
<Label text="FlexboxLayout"></Label>
7575
</FlexboxLayout>
7676

77-
<GridLayout
78-
width="300"
79-
height="100"
80-
padding="4"
81-
boxShadow="{{ appliedBoxShadow }}"
82-
tap="{{ toggleAnimation }}"
83-
>
84-
<StackLayout
85-
borderWidth="4"
86-
borderRadius="20"
87-
backgroundColor="white"
88-
>
89-
<Label text="BorderRadius + BoxShadow on parent container"></Label>
90-
</StackLayout>
91-
</GridLayout>
92-
9377
</StackLayout>
9478
</ScrollView>
9579

1.73 KB
Binary file not shown.

packages/core/ui/styling/background.android.ts

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@ import { parse } from '../../css-value';
66
import { path, knownFolders } from '../../file-system';
77
import * as application from '../../application';
88
import { profile } from '../../profiling';
9-
import { Color } from '../../color';
10-
import { Screen } from '../../platform';
119
import { CSSShadow } from './css-shadow';
10+
import { Length, LengthType } from './style-properties';
1211
export * from './background-common';
1312

1413
interface AndroidView {
@@ -28,8 +27,12 @@ export namespace ad {
2827
}
2928

3029
function isSetColorFilterOnlyWidget(nativeView: android.view.View): boolean {
30+
// prettier-ignore
3131
return (
32-
nativeView instanceof android.widget.Button || (nativeView instanceof androidx.appcompat.widget.Toolbar && getSDK() >= 21) // There is an issue with the DrawableContainer which was fixed for API version 21 and above: https://code.google.com/p/android/issues/detail?id=60183
32+
nativeView instanceof android.widget.Button
33+
|| (nativeView instanceof androidx.appcompat.widget.Toolbar && getSDK() >= 21)
34+
// There is an issue with the DrawableContainer which was fixed
35+
// for API version 21 and above: https://code.google.com/p/android/issues/detail?id=60183
3336
);
3437
}
3538

@@ -48,7 +51,15 @@ export namespace ad {
4851
androidView._cachedDrawable = constantState || drawable;
4952
}
5053
const isBorderDrawable = drawable instanceof org.nativescript.widgets.BorderDrawable;
51-
const onlyColor = !background.hasBorderWidth() && !background.hasBorderRadius() && !background.clipPath && !background.image && !!background.color;
54+
55+
// prettier-ignore
56+
const onlyColor = !background.hasBorderWidth()
57+
&& !background.hasBorderRadius()
58+
&& !background.hasBoxShadow()
59+
&& !background.clipPath
60+
&& !background.image
61+
&& !!background.color;
62+
5263
if (!isBorderDrawable && drawable instanceof android.graphics.drawable.ColorDrawable && onlyColor) {
5364
drawable.setColor(background.color.android);
5465
drawable.invalidateSelf();
@@ -71,13 +82,19 @@ export namespace ad {
7182
// this is the fastest way to change only background color
7283
nativeView.setBackgroundColor(background.color.android);
7384
} else if (!background.isEmpty()) {
74-
let backgroundDrawable = drawable as org.nativescript.widgets.BorderDrawable;
75-
if (!isBorderDrawable) {
76-
backgroundDrawable = new org.nativescript.widgets.BorderDrawable(layout.getDisplayDensity(), view.toString());
85+
let backgroundDrawable = drawable;
86+
87+
if (drawable instanceof org.nativescript.widgets.BoxShadowDrawable) {
88+
// if we have BoxShadow's we have to get the underlying drawable
89+
backgroundDrawable = drawable.getWrappedDrawable();
90+
}
91+
92+
if (backgroundDrawable instanceof org.nativescript.widgets.BorderDrawable) {
7793
refreshBorderDrawable(view, backgroundDrawable);
78-
nativeView.setBackground(backgroundDrawable);
7994
} else {
95+
backgroundDrawable = new org.nativescript.widgets.BorderDrawable(layout.getDisplayDensity(), view.toString());
8096
refreshBorderDrawable(view, backgroundDrawable);
97+
nativeView.setBackground(backgroundDrawable);
8198
}
8299
} else {
83100
const cachedDrawable = androidView._cachedDrawable;
@@ -228,24 +245,19 @@ function createNativeCSSValueArray(css: string): androidNative.Array<org.natives
228245
}
229246

230247
function drawBoxShadow(nativeView: android.view.View, view: View, boxShadow: CSSShadow) {
231-
const color = boxShadow.color;
232-
const shadowOpacity = color.a;
233-
const shadowColor = new Color(shadowOpacity, color.r, color.g, color.b);
234-
const cornerRadius = view.borderRadius; // this should be applied to the main view as well (try 20 with a transparent background on the xml to see the effect)
235248
const config = {
236-
shadowColor: shadowColor.android,
237-
cornerRadius: cornerRadius,
238-
spreadRadius: boxShadow.spreadRadius,
239-
blurRadius: boxShadow.blurRadius,
240-
offsetX: boxShadow.offsetX,
241-
offsetY: boxShadow.offsetY,
242-
scale: Screen.mainScreen.scale,
249+
shadowColor: boxShadow.color.android,
250+
cornerRadius: Length.toDevicePixels(view.borderRadius as LengthType, 0.0),
251+
spreadRadius: Length.toDevicePixels(boxShadow.spreadRadius, 0.0),
252+
blurRadius: Length.toDevicePixels(boxShadow.blurRadius, 0.0),
253+
offsetX: Length.toDevicePixels(boxShadow.offsetX, 0.0),
254+
offsetY: Length.toDevicePixels(boxShadow.offsetY, 0.0),
243255
};
244256
org.nativescript.widgets.Utils.drawBoxShadow(nativeView, JSON.stringify(config));
245257
}
246258

247259
function clearBoxShadow(nativeView: android.view.View) {
248-
// org.nativescript.widgets.Utils.clearBoxShadow(nativeView);
260+
org.nativescript.widgets.Utils.clearBoxShadow(nativeView);
249261
}
250262

251263
export enum CacheMode {
@@ -279,13 +291,13 @@ export function initImageCache(context: android.content.Context, mode = CacheMod
279291
imageFetcher.initCache();
280292
}
281293

282-
function onLivesync(args): void {
294+
function onLiveSync(args): void {
283295
if (imageFetcher) {
284296
imageFetcher.clearCache();
285297
}
286298
}
287299

288-
global.NativeScriptGlobals.events.on('livesync', onLivesync);
300+
global.NativeScriptGlobals.events.on('livesync', onLiveSync);
289301

290302
global.NativeScriptGlobals.addEventWiring(() => {
291303
application.android.on('activityStarted', (args) => {

packages/types-android/src/lib/android/org.nativescript.widgets.d.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,16 @@
22
module nativescript {
33
module widgets {
44

5-
export class Utils {
6-
public static drawBoxShadow(view: android.view.View, value: string);
7-
}
5+
export class Utils {
6+
public static drawBoxShadow(view: android.view.View, value: string): void;
7+
public static clearBoxShadow(view: android.view.View): void;
8+
}
9+
10+
export class BoxShadowDrawable {
11+
public constructor(drawable: android.graphics.drawable.Drawable, value: string);
12+
public getWrappedDrawable(): android.graphics.drawable.Drawable;
13+
public toString(): string;
14+
}
815

916
export class CustomTransition extends androidx.transition.Visibility {
1017
constructor(animatorSet: android.animation.AnimatorSet, transitionName: string);
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
package org.nativescript.widgets;
2+
3+
import android.graphics.BlurMaskFilter;
4+
import android.graphics.Color;
5+
import android.graphics.drawable.Drawable;
6+
import android.graphics.drawable.LayerDrawable;
7+
import android.graphics.drawable.ShapeDrawable;
8+
import android.graphics.drawable.shapes.RectShape;
9+
import android.graphics.drawable.shapes.RoundRectShape;
10+
import android.os.Build;
11+
import android.util.Log;
12+
13+
import androidx.annotation.RequiresApi;
14+
15+
import org.json.JSONException;
16+
import org.json.JSONObject;
17+
18+
import java.util.Arrays;
19+
20+
@RequiresApi(api = Build.VERSION_CODES.M)
21+
public class BoxShadowDrawable extends LayerDrawable {
22+
// Static parameters
23+
protected final static int DEFAULT_BACKGROUND_COLOR = Color.WHITE;
24+
protected final static String TAG = "BoxShadowDrawable";
25+
26+
// BoxShadow Parameters
27+
protected int offsetX = 0;
28+
protected int offsetY = 0;
29+
protected int blurRadius = 0;
30+
protected int spreadRadius = 0;
31+
protected int shadowColor = Color.BLACK;
32+
33+
// Layers
34+
protected final ShapeDrawable shadowLayer;
35+
protected final ShapeDrawable overlayLayer;
36+
protected final Drawable wrappedLayer;
37+
38+
protected float[] currentCornerRadii;
39+
40+
public BoxShadowDrawable(Drawable wrappedDrawable, String value) {
41+
super(new Drawable[]{});
42+
43+
Log.d(TAG, "Constructing BoxShadowDrawable!");
44+
45+
this.shadowLayer = new ShapeDrawable(new RectShape());
46+
this.overlayLayer = this.createOverlayLayer();
47+
this.wrappedLayer = wrappedDrawable;
48+
49+
// add our layers
50+
this.addLayer(shadowLayer);
51+
this.addLayer(overlayLayer);
52+
this.addLayer(wrappedLayer);
53+
54+
this.setValue(value);
55+
}
56+
57+
// to allow applying any bg changes on original Drawable
58+
public Drawable getWrappedDrawable() {
59+
return this.wrappedLayer;
60+
}
61+
62+
public void setValue(String value) {
63+
try {
64+
JSONObject config = new JSONObject(value);
65+
offsetX = config.getInt("offsetX");
66+
offsetY = config.getInt("offsetY");
67+
blurRadius = config.getInt("blurRadius");
68+
spreadRadius = config.getInt("spreadRadius");
69+
shadowColor = config.getInt("shadowColor");
70+
71+
float[] outerRadius;
72+
73+
// if we are wrapping a BorderDrawable - we can get the radii from it
74+
if(wrappedLayer instanceof BorderDrawable) {
75+
BorderDrawable b = (BorderDrawable) wrappedLayer;
76+
outerRadius = new float[]{
77+
b.getBorderTopLeftRadius(),
78+
b.getBorderTopLeftRadius(),
79+
80+
b.getBorderTopRightRadius(),
81+
b.getBorderTopRightRadius(),
82+
83+
b.getBorderBottomRightRadius(),
84+
b.getBorderBottomRightRadius(),
85+
86+
b.getBorderBottomLeftRadius(),
87+
b.getBorderBottomLeftRadius(),
88+
};
89+
} else {
90+
int cornerRadius = 0;
91+
try {
92+
cornerRadius = config.getInt("cornerRadius");
93+
} catch (JSONException ignore) {}
94+
95+
outerRadius = new float[8];
96+
Arrays.fill(outerRadius, cornerRadius);
97+
}
98+
99+
if(!Arrays.equals(outerRadius, currentCornerRadii)) {
100+
Log.d(TAG, "Update layer shape");
101+
shadowLayer.setShape(new RoundRectShape(outerRadius, null, null));
102+
overlayLayer.setShape(new RoundRectShape(outerRadius, null, null));
103+
104+
// update current
105+
currentCornerRadii = outerRadius;
106+
}
107+
108+
// apply new shadow parameters
109+
this.applyShadow();
110+
} catch (JSONException exception) {
111+
Log.d(TAG, "Caught JSONException...");
112+
exception.printStackTrace();
113+
}
114+
}
115+
116+
private void applyShadow() {
117+
Log.d(TAG, "applyShadow: " + this);
118+
119+
// apply boxShadow
120+
shadowLayer.getPaint().setColor(shadowColor);
121+
shadowLayer.getPaint().setMaskFilter(new BlurMaskFilter(
122+
Float.MIN_VALUE + blurRadius,
123+
BlurMaskFilter.Blur.NORMAL
124+
));
125+
shadowLayer.getPaint().setAntiAlias(true);
126+
127+
// apply insets that mimic offsets/spread to the shadowLayer
128+
int inset = -spreadRadius;
129+
Log.d(TAG, "Insets:"
130+
+ "\n l: " + (inset + offsetX)
131+
+ "\n t: " + (inset + offsetY)
132+
+ "\n r: " + (inset - offsetX)
133+
+ "\n b: " + (inset - offsetY)
134+
);
135+
this.setLayerInset(0,
136+
inset + offsetX,
137+
inset + offsetY,
138+
inset - offsetX,
139+
inset - offsetY
140+
);
141+
}
142+
143+
private ShapeDrawable createOverlayLayer() {
144+
ShapeDrawable shapeDrawable = new ShapeDrawable(new RectShape());
145+
shapeDrawable.getPaint().setColor(DEFAULT_BACKGROUND_COLOR);
146+
147+
return shapeDrawable;
148+
}
149+
150+
@Override
151+
public String toString() {
152+
return "BoxShadowDrawable { oX:" + offsetX + " oY:" + offsetY + " br:" + blurRadius + " sr:" + spreadRadius + " c:" + shadowColor + " }";
153+
}
154+
}

0 commit comments

Comments
 (0)