mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-15 19:26:42 +08:00
feat(core): box-shadow support (#9161)
This commit is contained in:
@ -7,12 +7,31 @@ components that have the btn class name.
|
|||||||
*/
|
*/
|
||||||
.btn {
|
.btn {
|
||||||
font-size: 18;
|
font-size: 18;
|
||||||
|
/* box-shadow: -5 -5 10 10 navy; */
|
||||||
|
box-shadow: -5 -5 rgba(0,0,0,0.5);
|
||||||
|
background-color: #add8e6;
|
||||||
|
color: navy;
|
||||||
|
/* TODO: adding border radius breaks shadow */
|
||||||
|
/* border-radius: 10; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.bold{
|
.bold{
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sample-animation {
|
||||||
|
animation-name: rotate-expand;
|
||||||
|
animation-duration: 5s;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotate-expand {
|
||||||
|
0%, 50% { background-color: red; width: 200; transform: rotate(0) scale(1,1); }
|
||||||
|
25%, 75% { background-color: yellow; width: 100; transform: rotate(180) scale(1.2,1.2); }
|
||||||
|
100% { background-color: red; width: 200; transform: rotate(0) scale(1,1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.icon-label{
|
.icon-label{
|
||||||
font-size: 22;
|
font-size: 22;
|
||||||
font-family: "ns-playground-font";
|
font-family: "ns-playground-font";
|
||||||
|
@ -3,18 +3,49 @@
|
|||||||
<ActionBar title="Dev Toolbox" icon="" class="action-bar">
|
<ActionBar title="Dev Toolbox" icon="" class="action-bar">
|
||||||
</ActionBar>
|
</ActionBar>
|
||||||
</Page.actionBar>
|
</Page.actionBar>
|
||||||
|
<ScrollView>
|
||||||
<StackLayout class="p-t-20 p-x-20">
|
<StackLayout class="p-t-20 p-x-20">
|
||||||
<Label text="Tap the button" class="h1 text-center"/>
|
|
||||||
<Button text="TAP" tap="{{ onTap }}" class="btn btn-primary btn-active"/>
|
<!-- <Button text="Button with html boxShadow" tap="{{ onTap }}" height="50" class="btn btn-primary btn-active" boxShadow="5 5 10 navy"/>
|
||||||
<Label text="{{ message }}" class="h2 text-center" textWrap="true"/>
|
|
||||||
<StackLayout class="c-bg-grey w-full m-y-5" height="1"></StackLayout>
|
<Button text="Button with css boxShadow (rgba)" tap="{{ onTap }}" height="50" marginTop="50" class="btn btn-primary btn-active"/>
|
||||||
<ScrollView class="h-full">
|
|
||||||
<StackLayout>
|
<Button text="Button with boxShadow 3 props" tap="{{ onTap }}" height="50" marginTop="50" boxShadow="5 5 navy" class="btn btn-primary btn-active"/>
|
||||||
<Label text="More things to tool around with:" class="t-12 text-center font-italic m-t-10"/>
|
<Button text="Button with boxShadow 4 props" tap="{{ onTap }}" height="50" marginTop="50" boxShadow="5 5 10 navy" class="btn btn-primary btn-active"/>
|
||||||
<Button text="View List" tap="{{ viewList }}" class="btn"/>
|
<Button text="Button with boxShadow 5 props" tap="{{ onTap }}" height="50" marginTop="50" boxShadow="5 5 10 10 navy" class="btn btn-primary btn-active"/>
|
||||||
<!-- add more examples below as desired to test and play around -->
|
|
||||||
|
<StackLayout boxShadow="10 10 rgba(0,0,0,1)" marginTop="50">
|
||||||
|
<Button text="Button with css boxShadow" tap="{{ onTap }}" height="50" borderRadius="10"/>
|
||||||
|
</StackLayout> -->
|
||||||
|
|
||||||
|
<!-- TODO: if backgroundColor is not set, it won't call background.ios.ts hence not applying the boxShadow -->
|
||||||
|
<!-- <StackLayout boxShadow="5 5 10 10 red" height="100" backgroundColor="transparent" padding="10" margin="20">
|
||||||
|
<Label text="StackLayout with transparent background"></Label>
|
||||||
|
</StackLayout> -->
|
||||||
|
|
||||||
|
<GridLayout boxShadow="10 -10 10 10 rgba(0,0,0,0.5)" height="100" backgroundColor="lightblue" padding="10" margin="20" tap="{{ toggleAnimation }}">
|
||||||
|
<Label text="GridLayout"></Label>
|
||||||
|
</GridLayout>
|
||||||
|
|
||||||
|
<StackLayout boxShadow="5 10 10 20 #000" height="100" backgroundColor="lightblue" padding="10" margin="20" tap="{{ toggleAnimation }}">
|
||||||
|
<Label text="StackLayout"></Label>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</ScrollView>
|
|
||||||
|
<AbsoluteLayout boxShadow="5 15 10 20 green" height="100" backgroundColor="lightblue" padding="10" margin="20" tap="{{ toggleAnimation }}">
|
||||||
|
<Label text="AbsoluteLayout"></Label>
|
||||||
|
</AbsoluteLayout>
|
||||||
|
|
||||||
|
<!-- note: the 3rd number in box shadow is currently being ignored, only the 1st, 2nd, and 4th, and color are being used-->
|
||||||
|
<FlexboxLayout boxShadow="0 0 10 25 red" height="100" backgroundColor="lightblue" padding="10" margin="20" tap="{{ toggleAnimation }}">
|
||||||
|
<Label text="FlexboxLayout"></Label>
|
||||||
|
</FlexboxLayout>
|
||||||
|
|
||||||
|
<FlexboxLayout boxShadow="15 10 10 20 #000" height="100" backgroundColor="transparent" padding="10" margin="20" tap="{{ toggleAnimation }}">
|
||||||
|
<Label text="FlexboxLayout (transparent background)"></Label>
|
||||||
|
</FlexboxLayout>
|
||||||
|
|
||||||
|
<Button marginTop="30" boxShadow="0 0 10 8 #000" backgroundColor="transparent" text="button" padding="20"></Button>
|
||||||
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
|
</ScrollView>
|
||||||
</Page>
|
</Page>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Observable, Frame } from '@nativescript/core';
|
import { Observable, Frame, StackLayout } from '@nativescript/core';
|
||||||
|
|
||||||
export class HelloWorldModel extends Observable {
|
export class HelloWorldModel extends Observable {
|
||||||
private _counter: number;
|
private _counter: number;
|
||||||
@ -23,6 +23,15 @@ export class HelloWorldModel extends Observable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleAnimation(args) {
|
||||||
|
const layout = args.object as StackLayout;
|
||||||
|
if (!layout.className) {
|
||||||
|
layout.className = 'sample-animation';
|
||||||
|
} else {
|
||||||
|
layout.className = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onTap() {
|
onTap() {
|
||||||
this._counter--;
|
this._counter--;
|
||||||
this.updateMessage();
|
this.updateMessage();
|
||||||
|
6
packages/core/ui/core/view/index.d.ts
vendored
6
packages/core/ui/core/view/index.d.ts
vendored
@ -6,6 +6,7 @@ import { Animation, AnimationDefinition, AnimationPromise } from '../../animatio
|
|||||||
import { HorizontalAlignment, VerticalAlignment, Visibility, Length, PercentLength } from '../../styling/style-properties';
|
import { HorizontalAlignment, VerticalAlignment, Visibility, Length, PercentLength } from '../../styling/style-properties';
|
||||||
import { GestureTypes, GestureEventData, GesturesObserver } from '../../gestures';
|
import { GestureTypes, GestureEventData, GesturesObserver } from '../../gestures';
|
||||||
import { LinearGradient } from '../../styling/gradient';
|
import { LinearGradient } from '../../styling/gradient';
|
||||||
|
import { BoxShadow } from '../../styling/box-shadow';
|
||||||
|
|
||||||
// helpers (these are okay re-exported here)
|
// helpers (these are okay re-exported here)
|
||||||
export * from './view-helper';
|
export * from './view-helper';
|
||||||
@ -250,6 +251,11 @@ export abstract class View extends ViewBase {
|
|||||||
*/
|
*/
|
||||||
backgroundImage: string | LinearGradient;
|
backgroundImage: string | LinearGradient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets or sets the box shadow of the view.
|
||||||
|
*/
|
||||||
|
boxShadow: string | BoxShadow;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets or sets the minimum width the view may grow to.
|
* Gets or sets the minimum width the view may grow to.
|
||||||
*/
|
*/
|
||||||
|
@ -22,6 +22,7 @@ import { LinearGradient } from '../../styling/linear-gradient';
|
|||||||
import { TextTransform } from '../../text-base';
|
import { TextTransform } from '../../text-base';
|
||||||
|
|
||||||
import * as am from '../../animation';
|
import * as am from '../../animation';
|
||||||
|
import { BoxShadow } from '../../styling/box-shadow';
|
||||||
|
|
||||||
// helpers (these are okay re-exported here)
|
// helpers (these are okay re-exported here)
|
||||||
export * from './view-helper';
|
export * from './view-helper';
|
||||||
@ -581,6 +582,13 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
|
|||||||
this.style.backgroundRepeat = value;
|
this.style.backgroundRepeat = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get boxShadow(): BoxShadow {
|
||||||
|
return this.style.boxShadow;
|
||||||
|
}
|
||||||
|
set boxShadow(value: BoxShadow) {
|
||||||
|
this.style.boxShadow = value;
|
||||||
|
}
|
||||||
|
|
||||||
get minWidth(): Length {
|
get minWidth(): Length {
|
||||||
return this.style.minWidth;
|
return this.style.minWidth;
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ export class LayoutBase extends LayoutBaseCommon {
|
|||||||
|
|
||||||
_setNativeClipToBounds() {
|
_setNativeClipToBounds() {
|
||||||
if (this.clipToBounds) {
|
if (this.clipToBounds) {
|
||||||
|
// TODO: temporarily setting this to false as it crops the shadow
|
||||||
this.nativeViewProtected.clipsToBounds = true;
|
this.nativeViewProtected.clipsToBounds = true;
|
||||||
} else {
|
} else {
|
||||||
super._setNativeClipToBounds();
|
super._setNativeClipToBounds();
|
||||||
|
@ -6,6 +6,9 @@ import { parse } from '../../css-value';
|
|||||||
import { path, knownFolders } from '../../file-system';
|
import { path, knownFolders } from '../../file-system';
|
||||||
import * as application from '../../application';
|
import * as application from '../../application';
|
||||||
import { profile } from '../../profiling';
|
import { profile } from '../../profiling';
|
||||||
|
import { BoxShadow } from './box-shadow';
|
||||||
|
import { Color } from '../../color';
|
||||||
|
import { Screen } from '../../platform';
|
||||||
export * from './background-common';
|
export * from './background-common';
|
||||||
|
|
||||||
interface AndroidView {
|
interface AndroidView {
|
||||||
@ -90,6 +93,11 @@ export namespace ad {
|
|||||||
nativeView.setBackground(defaultDrawable);
|
nativeView.setBackground(defaultDrawable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const boxShadow = view.style.boxShadow;
|
||||||
|
if (boxShadow) {
|
||||||
|
drawBoxShadow(nativeView, boxShadow);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Can we move BorderWidths as separate native setter?
|
// TODO: Can we move BorderWidths as separate native setter?
|
||||||
// This way we could skip setPadding if borderWidth is not changed.
|
// This way we could skip setPadding if borderWidth is not changed.
|
||||||
const leftPadding = Math.ceil(view.effectiveBorderLeftWidth + view.effectivePaddingLeft);
|
const leftPadding = Math.ceil(view.effectiveBorderLeftWidth + view.effectivePaddingLeft);
|
||||||
@ -218,6 +226,24 @@ function createNativeCSSValueArray(css: string): native.Array<org.nativescript.w
|
|||||||
return nativeArray;
|
return nativeArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function drawBoxShadow(nativeView: android.view.View, boxShadow: BoxShadow) {
|
||||||
|
const color = boxShadow.color;
|
||||||
|
const shadowOpacity = color.a;
|
||||||
|
const shadowColor = new Color(shadowOpacity, color.r, color.g, color.b);
|
||||||
|
// TODO: corner radius here should reflect the view's corner radius
|
||||||
|
const cornerRadius = 0; // this should be applied to the main view as well (try 20 with a transparent background on the xml to see the effect)
|
||||||
|
const config = {
|
||||||
|
shadowColor: shadowColor.android,
|
||||||
|
cornerRadius,
|
||||||
|
spreadRadius: boxShadow.spreadRadius,
|
||||||
|
blurRadius: boxShadow.blurRadius,
|
||||||
|
offsetX: boxShadow.offsetX,
|
||||||
|
offsetY: boxShadow.offsetY,
|
||||||
|
scale: Screen.mainScreen.scale,
|
||||||
|
};
|
||||||
|
org.nativescript.widgets.Utils.drawBoxShadow(nativeView, JSON.stringify(config));
|
||||||
|
}
|
||||||
|
|
||||||
export enum CacheMode {
|
export enum CacheMode {
|
||||||
none,
|
none,
|
||||||
memory,
|
memory,
|
||||||
|
2
packages/core/ui/styling/background.d.ts
vendored
2
packages/core/ui/styling/background.d.ts
vendored
@ -2,6 +2,7 @@ import { Color } from '../../color';
|
|||||||
import { View } from '../core/view';
|
import { View } from '../core/view';
|
||||||
import { BackgroundRepeat } from '../../css/parser';
|
import { BackgroundRepeat } from '../../css/parser';
|
||||||
import { LinearGradient } from '../styling/linear-gradient';
|
import { LinearGradient } from '../styling/linear-gradient';
|
||||||
|
import { BoxShadow } from './box-shadow';
|
||||||
|
|
||||||
export enum CacheMode {
|
export enum CacheMode {
|
||||||
none,
|
none,
|
||||||
@ -29,6 +30,7 @@ export declare class Background {
|
|||||||
public borderBottomRightRadius: number;
|
public borderBottomRightRadius: number;
|
||||||
public borderBottomLeftRadius: number;
|
public borderBottomLeftRadius: number;
|
||||||
public clipPath: string;
|
public clipPath: string;
|
||||||
|
public boxShadow: string | BoxShadow;
|
||||||
|
|
||||||
public withColor(value: Color): Background;
|
public withColor(value: Color): Background;
|
||||||
public withImage(value: string | LinearGradient): Background;
|
public withImage(value: string | LinearGradient): Background;
|
||||||
|
@ -7,6 +7,9 @@ import { Color } from '../../color';
|
|||||||
import { isDataURI, isFileOrResourcePath, layout } from '../../utils';
|
import { isDataURI, isFileOrResourcePath, layout } from '../../utils';
|
||||||
import { ImageSource } from '../../image-source';
|
import { ImageSource } from '../../image-source';
|
||||||
import { CSSValue, parse as cssParse } from '../../css-value';
|
import { CSSValue, parse as cssParse } from '../../css-value';
|
||||||
|
import { BoxShadow } from './box-shadow';
|
||||||
|
import { Screen } from '../../platform';
|
||||||
|
import { StackLayout } from '../layouts/stack-layout';
|
||||||
|
|
||||||
export * from './background-common';
|
export * from './background-common';
|
||||||
|
|
||||||
@ -24,6 +27,7 @@ interface NativeView extends UIView {
|
|||||||
leftBorderLayer: CALayer;
|
leftBorderLayer: CALayer;
|
||||||
|
|
||||||
gradientLayer: CAGradientLayer;
|
gradientLayer: CAGradientLayer;
|
||||||
|
boxShadowLayer: CALayer;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Rect {
|
interface Rect {
|
||||||
@ -85,6 +89,15 @@ export namespace ios {
|
|||||||
} else {
|
} else {
|
||||||
setUIColorFromImage(view, nativeView, callback, flip);
|
setUIColorFromImage(view, nativeView, callback, flip);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const boxShadow = view.style.boxShadow;
|
||||||
|
if (boxShadow) {
|
||||||
|
|
||||||
|
// this is required (if not, shadow will get cutoff at parent's dimensions)
|
||||||
|
// nativeView.clipsToBounds doesn't work
|
||||||
|
view.setProperty('clipToBounds', false);
|
||||||
|
drawBoxShadow(nativeView, boxShadow, background);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -706,6 +719,46 @@ function drawNoRadiusNonUniformBorders(nativeView: NativeView, background: Backg
|
|||||||
nativeView.hasNonUniformBorder = hasNonUniformBorder;
|
nativeView.hasNonUniformBorder = hasNonUniformBorder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: use sublayer if its applied to a layout
|
||||||
|
function drawBoxShadow(nativeView: NativeView, boxShadow: BoxShadow, background: BackgroundDefinition, useSubLayer: boolean = false) {
|
||||||
|
const layer: CALayer = nativeView.layer;
|
||||||
|
|
||||||
|
layer.masksToBounds = false;
|
||||||
|
nativeView.clipsToBounds = false;
|
||||||
|
|
||||||
|
if (!background.color.a) {
|
||||||
|
// add white background if view has a transparent background
|
||||||
|
layer.backgroundColor = UIColor.whiteColor.CGColor;
|
||||||
|
}
|
||||||
|
// shadow opacity is handled on the shadow's color instance
|
||||||
|
layer.shadowOpacity = 1;
|
||||||
|
layer.shadowRadius = boxShadow.spreadRadius;
|
||||||
|
layer.shadowColor = boxShadow.color.ios.CGColor;
|
||||||
|
|
||||||
|
// / 2 here since ios's shadow offset is bigger than android
|
||||||
|
// TODO: this is just for experimenting with the amount of offset,
|
||||||
|
// need to use some real calculation here to gain parity with android's
|
||||||
|
// implementation
|
||||||
|
const adjustedShadowOffset = {
|
||||||
|
x: boxShadow.offsetX / 2,
|
||||||
|
y: boxShadow.offsetY / 2,
|
||||||
|
};
|
||||||
|
layer.shadowOffset = CGSizeMake(adjustedShadowOffset.x, adjustedShadowOffset.y);
|
||||||
|
|
||||||
|
// this should match the view's border radius
|
||||||
|
const cornerRadius = 0;
|
||||||
|
// This doesn't handle the offsets properly
|
||||||
|
// factor in shadowRadius and the offsets so shadow don't spread too far
|
||||||
|
// layer.shadowPath = UIBezierPath.bezierPathWithRoundedRectCornerRadius(CGRectMake(
|
||||||
|
// nativeView.bounds.origin.x + boxShadow.spreadRadius + adjustedShadowOffset.x,
|
||||||
|
// nativeView.bounds.origin.y + boxShadow.spreadRadius + adjustedShadowOffset.y,
|
||||||
|
// nativeView.bounds.size.width - boxShadow.spreadRadius - adjustedShadowOffset.x,
|
||||||
|
// nativeView.bounds.size.height - boxShadow.spreadRadius - adjustedShadowOffset.y), cornerRadius).CGPath;
|
||||||
|
|
||||||
|
// This has the nice glow with box shadow of 0,0
|
||||||
|
layer.shadowPath = UIBezierPath.bezierPathWithRoundedRectCornerRadius(nativeView.bounds, cornerRadius).CGPath;
|
||||||
|
}
|
||||||
|
|
||||||
function drawGradient(nativeView: NativeView, gradient: LinearGradient) {
|
function drawGradient(nativeView: NativeView, gradient: LinearGradient) {
|
||||||
const gradientLayer = CAGradientLayer.layer();
|
const gradientLayer = CAGradientLayer.layer();
|
||||||
gradientLayer.frame = nativeView.bounds;
|
gradientLayer.frame = nativeView.bounds;
|
||||||
|
9
packages/core/ui/styling/box-shadow.ts
Normal file
9
packages/core/ui/styling/box-shadow.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { Color } from '../../color';
|
||||||
|
|
||||||
|
export class BoxShadow {
|
||||||
|
public offsetX: number;
|
||||||
|
public offsetY: number;
|
||||||
|
public blurRadius: number;
|
||||||
|
public spreadRadius: number;
|
||||||
|
public color: Color;
|
||||||
|
}
|
@ -17,6 +17,7 @@ import { Trace } from '../../trace';
|
|||||||
|
|
||||||
import * as parser from '../../css/parser';
|
import * as parser from '../../css/parser';
|
||||||
import { LinearGradient } from './linear-gradient';
|
import { LinearGradient } from './linear-gradient';
|
||||||
|
import { BoxShadow } from './box-shadow';
|
||||||
|
|
||||||
export type LengthDipUnit = { readonly unit: 'dip'; readonly value: dip };
|
export type LengthDipUnit = { readonly unit: 'dip'; readonly value: dip };
|
||||||
export type LengthPxUnit = { readonly unit: 'px'; readonly value: px };
|
export type LengthPxUnit = { readonly unit: 'px'; readonly value: px };
|
||||||
@ -451,6 +452,51 @@ export const verticalAlignmentProperty = new CssProperty<Style, VerticalAlignmen
|
|||||||
});
|
});
|
||||||
verticalAlignmentProperty.register(Style);
|
verticalAlignmentProperty.register(Style);
|
||||||
|
|
||||||
|
function parseBoxShadowProperites(value: string): BoxShadow {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
let arr;
|
||||||
|
let colorRaw;
|
||||||
|
if (value.indexOf('rgb') > -1) {
|
||||||
|
arr = value.split(' ');
|
||||||
|
colorRaw = arr.pop();
|
||||||
|
} else {
|
||||||
|
arr = value.split(/[ ,]+/);
|
||||||
|
colorRaw = arr.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
let offsetX: number;
|
||||||
|
let offsetY: number;
|
||||||
|
let blurRadius: number; // not currently in use
|
||||||
|
let spreadRadius: number; // maybe rename this to just radius
|
||||||
|
let color: Color = new Color(colorRaw);
|
||||||
|
|
||||||
|
if (arr.length === 2) {
|
||||||
|
offsetX = parseFloat(arr[0]);
|
||||||
|
offsetY = parseFloat(arr[1]);
|
||||||
|
} else if (arr.length === 3) {
|
||||||
|
offsetX = parseFloat(arr[0]);
|
||||||
|
offsetY = parseFloat(arr[1]);
|
||||||
|
blurRadius = parseFloat(arr[2]);
|
||||||
|
} else if (arr.length === 4) {
|
||||||
|
offsetX = parseFloat(arr[0]);
|
||||||
|
offsetY = parseFloat(arr[1]);
|
||||||
|
blurRadius = parseFloat(arr[2]);
|
||||||
|
spreadRadius = parseFloat(arr[3]);
|
||||||
|
} else {
|
||||||
|
throw new Error('Expected 3, 4 or 5 parameters. Actual: ' + value);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
offsetX: offsetX,
|
||||||
|
offsetY: offsetY,
|
||||||
|
blurRadius: blurRadius,
|
||||||
|
spreadRadius: spreadRadius,
|
||||||
|
color: color,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface Thickness {
|
interface Thickness {
|
||||||
top: string;
|
top: string;
|
||||||
right: string;
|
right: string;
|
||||||
@ -1275,6 +1321,18 @@ export const borderBottomLeftRadiusProperty = new CssProperty<Style, Length>({
|
|||||||
});
|
});
|
||||||
borderBottomLeftRadiusProperty.register(Style);
|
borderBottomLeftRadiusProperty.register(Style);
|
||||||
|
|
||||||
|
const boxShadowProperty = new CssProperty<Style, BoxShadow>({
|
||||||
|
name: 'boxShadow',
|
||||||
|
cssName: 'box-shadow',
|
||||||
|
valueChanged: (target, oldValue, newValue) => {
|
||||||
|
target.boxShadow = newValue;
|
||||||
|
},
|
||||||
|
valueConverter: (value) => {
|
||||||
|
return parseBoxShadowProperites(value);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
boxShadowProperty.register(Style);
|
||||||
|
|
||||||
function isNonNegativeFiniteNumber(value: number): boolean {
|
function isNonNegativeFiniteNumber(value: number): boolean {
|
||||||
return isFinite(value) && !isNaN(value) && value >= 0;
|
return isFinite(value) && !isNaN(value) && value >= 0;
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import { Observable } from '../../../data/observable';
|
|||||||
import { FlexDirection, FlexWrap, JustifyContent, AlignItems, AlignContent, Order, FlexGrow, FlexShrink, FlexWrapBefore, AlignSelf } from '../../layouts/flexbox-layout';
|
import { FlexDirection, FlexWrap, JustifyContent, AlignItems, AlignContent, Order, FlexGrow, FlexShrink, FlexWrapBefore, AlignSelf } from '../../layouts/flexbox-layout';
|
||||||
import { Trace } from '../../../trace';
|
import { Trace } from '../../../trace';
|
||||||
import { TextAlignment, TextDecoration, TextTransform, WhiteSpace } from '../../text-base';
|
import { TextAlignment, TextDecoration, TextTransform, WhiteSpace } from '../../text-base';
|
||||||
|
import { BoxShadow } from '../box-shadow';
|
||||||
|
|
||||||
export interface CommonLayoutParams {
|
export interface CommonLayoutParams {
|
||||||
width: number;
|
width: number;
|
||||||
@ -137,6 +138,8 @@ export class Style extends Observable implements StyleDefinition {
|
|||||||
public borderBottomRightRadius: Length;
|
public borderBottomRightRadius: Length;
|
||||||
public borderBottomLeftRadius: Length;
|
public borderBottomLeftRadius: Length;
|
||||||
|
|
||||||
|
public boxShadow: BoxShadow;
|
||||||
|
|
||||||
public fontSize: number;
|
public fontSize: number;
|
||||||
public fontFamily: string;
|
public fontFamily: string;
|
||||||
public fontStyle: FontStyle;
|
public fontStyle: FontStyle;
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
declare module org {
|
declare module org {
|
||||||
module nativescript {
|
module nativescript {
|
||||||
module widgets {
|
module widgets {
|
||||||
|
|
||||||
|
export class Utils {
|
||||||
|
public static drawBoxShadow(view: android.view.View, value: string);
|
||||||
|
}
|
||||||
|
|
||||||
export class CustomTransition extends androidx.transition.Visibility {
|
export class CustomTransition extends androidx.transition.Visibility {
|
||||||
constructor(animatorSet: android.animation.AnimatorSet, transitionName: string);
|
constructor(animatorSet: android.animation.AnimatorSet, transitionName: string);
|
||||||
public setResetOnTransitionEnd(resetOnTransitionEnd: boolean): void;
|
public setResetOnTransitionEnd(resetOnTransitionEnd: boolean): void;
|
||||||
|
@ -0,0 +1,122 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package org.nativescript.widgets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author triniwiz
|
||||||
|
*/
|
||||||
|
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.graphics.drawable.LayerDrawable;
|
||||||
|
import android.graphics.drawable.ShapeDrawable;
|
||||||
|
import android.graphics.drawable.shapes.RoundRectShape;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
public class Utils {
|
||||||
|
public static void drawBoxShadow(View view, String value) {
|
||||||
|
try {
|
||||||
|
JSONObject config = new JSONObject(value);
|
||||||
|
int shadowColor = config.getInt("shadowColor");
|
||||||
|
int cornerRadius = config.getInt("cornerRadius");
|
||||||
|
int spreadRadius = config.getInt("spreadRadius");
|
||||||
|
int blurRadius = config.getInt("blurRadius");
|
||||||
|
int configOffsetX = config.getInt("offsetX");
|
||||||
|
int configOffsetY = config.getInt("offsetY");
|
||||||
|
int scale = config.getInt("scale");
|
||||||
|
|
||||||
|
|
||||||
|
float cornerRadiusValue = cornerRadius * scale;
|
||||||
|
|
||||||
|
float shadowSpread = spreadRadius * scale;
|
||||||
|
|
||||||
|
// Set shadow layer
|
||||||
|
float[] outerRadius = {cornerRadiusValue, cornerRadiusValue, cornerRadiusValue, cornerRadiusValue, cornerRadiusValue, cornerRadiusValue, cornerRadiusValue, cornerRadiusValue};
|
||||||
|
|
||||||
|
// Default background for transparent/semi-transparent background so it doesn't see through the shadow
|
||||||
|
int defaultBackgroundColor = Color.WHITE;
|
||||||
|
RoundRectShape backgroundRectShape = new RoundRectShape(outerRadius, null, null);
|
||||||
|
ShapeDrawable backgroundDrawable = new ShapeDrawable(backgroundRectShape);
|
||||||
|
backgroundDrawable.getPaint().setColor(defaultBackgroundColor);
|
||||||
|
|
||||||
|
// shadow layer setup
|
||||||
|
RoundRectShape shadowRectShape = new RoundRectShape(outerRadius, null, null);
|
||||||
|
ShapeDrawable shadowShapeDrawable = new ShapeDrawable(shadowRectShape);
|
||||||
|
shadowShapeDrawable.getPaint().setShadowLayer(shadowSpread, 0, 0, shadowColor);
|
||||||
|
shadowShapeDrawable.getPaint().setAntiAlias(true);
|
||||||
|
|
||||||
|
// set shadow direction
|
||||||
|
Drawable[] drawableArray = new Drawable[3];
|
||||||
|
drawableArray[0] = shadowShapeDrawable;
|
||||||
|
drawableArray[1] = backgroundDrawable;
|
||||||
|
drawableArray[2] = view.getBackground();
|
||||||
|
LayerDrawable drawable = new LayerDrawable(drawableArray);
|
||||||
|
|
||||||
|
// workaround to show shadow offset (similar to ios's offsets)
|
||||||
|
int shadowInsetsLeft;
|
||||||
|
int shadowInsetsTop;
|
||||||
|
int shadowInsetsRight;
|
||||||
|
int shadowInsetsBottom;
|
||||||
|
|
||||||
|
float offsetX = configOffsetX - spreadRadius;
|
||||||
|
// ignore the following line, this is similar to the adjustedShadowOffset on ios.
|
||||||
|
// it is just used to experiment the amount of insets that need to be applied based
|
||||||
|
// on the offset provided. Need to use some real calculation to gain parity (ask Osei)
|
||||||
|
float insetScaleFactor = 4f / 5f;
|
||||||
|
|
||||||
|
if (configOffsetX == 0) {
|
||||||
|
shadowInsetsLeft = 0;
|
||||||
|
shadowInsetsRight = 0;
|
||||||
|
} else if (configOffsetX > 0) {
|
||||||
|
shadowInsetsLeft = (int) (shadowSpread * insetScaleFactor);
|
||||||
|
shadowInsetsRight = (int) ((offsetX < 0 ? 0 : offsetX) * scale * insetScaleFactor);
|
||||||
|
} else {
|
||||||
|
shadowInsetsLeft = (int) ((offsetX < 0 ? 0 : offsetX) * scale * insetScaleFactor);
|
||||||
|
shadowInsetsRight = (int) (shadowSpread * insetScaleFactor);
|
||||||
|
}
|
||||||
|
float offsetY = configOffsetY - spreadRadius;
|
||||||
|
if (configOffsetY == 0) {
|
||||||
|
shadowInsetsTop = 0;
|
||||||
|
shadowInsetsBottom = 0;
|
||||||
|
} else if (configOffsetY >= 0) {
|
||||||
|
shadowInsetsTop = (int) (shadowSpread * insetScaleFactor);
|
||||||
|
shadowInsetsBottom = (int) ((offsetY < 0 ? 0 : offsetY) * scale * insetScaleFactor);
|
||||||
|
} else {
|
||||||
|
shadowInsetsTop = (int) ((offsetY < 0 ? 0 : offsetY) * scale * insetScaleFactor);
|
||||||
|
shadowInsetsBottom = (int) (shadowSpread * insetScaleFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: this isn't really a shadow offset per se, but just having the some layer
|
||||||
|
// drawable layer have an inset to mimic an offset (feels very hacky ugh)
|
||||||
|
drawable.setLayerInset(0, shadowInsetsLeft, shadowInsetsTop, shadowInsetsRight, shadowInsetsBottom);
|
||||||
|
|
||||||
|
// this is what it shadows look like without offsets - uncomment the following line,
|
||||||
|
// and comment out line above to see what the shadow without any inset modification looks like
|
||||||
|
// on android
|
||||||
|
// drawable.setLayerInset(0, shadowSpread, shadowSpread, shadowSpread, shadowSpread);
|
||||||
|
|
||||||
|
// make sure parent doesn't clip the shadows
|
||||||
|
int count = 0;
|
||||||
|
View nativeView = view;
|
||||||
|
while (view.getParent() != null && view.getParent() instanceof ViewGroup) {
|
||||||
|
count++;
|
||||||
|
ViewGroup parent = (ViewGroup) view.getParent();
|
||||||
|
parent.setClipChildren(false);
|
||||||
|
parent.setClipToPadding(false);
|
||||||
|
// removing clipping from all breaks the ui
|
||||||
|
if (count == 1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
nativeView = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
nativeView.setBackground(drawable);
|
||||||
|
} catch (JSONException ignore) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user