Skip to content

feat(core): RootLayout option with api to fluidly handle dynamic layers #8980

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
eeb9e06
chore: webpack adjustments and config for core
NathanWalker Aug 28, 2020
8bbb3fe
chore: use nativescript.config with apps
NathanWalker Aug 30, 2020
92d93fd
chore: dep bump
NathanWalker Aug 30, 2020
e64652c
fix(android): #8733
triniwiz Aug 31, 2020
e053105
Merge pull request #8800 from NativeScript/triniwiz-patch-1
NathanaelA Aug 31, 2020
57e646f
chore: cleanup types
NathanWalker Sep 1, 2020
4f88b66
Merge branch 'feat/ns7-finishing-touches' of https://github.com/Nativ…
NathanWalker Sep 1, 2020
ee0c8e7
chore: bump
NathanWalker Sep 1, 2020
d490b36
feat(layout): add basic root layout
williamjuan027 Sep 1, 2020
b078011
feat(layout): add shade options and error handling
williamjuan027 Sep 6, 2020
0107c06
feat(layouts): better animation and more configurable properties
williamjuan027 Sep 13, 2020
d109371
feat(layouts): add shadecover transitions for ios
williamjuan027 Sep 20, 2020
928be3d
feat(layout): shade cover exit animation
williamjuan027 Sep 20, 2020
f805a95
feat(layout): add shadecover transitions for android
williamjuan027 Sep 27, 2020
140c7d6
feat(layout): refactore shade animation handling
williamjuan027 Sep 27, 2020
70c16ec
feat(layouts): update transition animation API
williamjuan027 Oct 6, 2020
a17b6db
feat(layout): add trace messages to root layout
williamjuan027 Oct 6, 2020
3c7afb7
feat(layout): remove shade when last popup is closed
williamjuan027 Oct 14, 2020
9e251cb
merge upstream
williamjuan027 Oct 22, 2020
821cabd
cleanup
williamjuan027 Oct 22, 2020
0dbb798
Merge branch 'release/8.0.0' into feat/rootlayout-options
NathanWalker Jan 29, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 70 additions & 35 deletions apps/toolbox/src/main-page.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,49 +3,84 @@
<ActionBar title="Dev Toolbox" icon="" class="action-bar">
</ActionBar>
</Page.actionBar>
<ScrollView>
<StackLayout class="p-t-20 p-x-20">

<GridLayout rows="2*, *">
<!-- background color transparent here to hide children overflow -->
<AbsoluteLayout row="0" backgroundColor="transparent">
<!-- Root layout demo -->
<RootLayout height="100%" width="100%">
<GridLayout height="100%" backgroundColor="#232652">
<Label verticalAlignment="center" textAlignment="center" fontWeight="bold" color="#fff" text="ROOT LAYOUT CONTENT"></Label>
</GridLayout>
</RootLayout>
</AbsoluteLayout>

<!-- <Button text="Button with html boxShadow" tap="{{ onTap }}" height="50" class="btn btn-primary btn-active" boxShadow="5 5 10 navy"/>
<!-- Root layout controls -->
<StackLayout row="1">
<ScrollView height="100%">
<StackLayout class="p-15">
<Label color="#b4b6b9" fontSize="25" fontWeight="bold" text="ORANGE"></Label>
<FlexboxLayout flexDirection="row" justifyContent="space-between">
<Button flexGrow="1" text="open" tap="{{ open }}" popupIndex="0" class="btn btn-primary btn-active"/>
<Button flexGrow="1" text="front" tap="{{ bringToFront }}" popupIndex="0" class="btn btn-primary btn-active"/>
<Button flexGrow="1" text="close" tap="{{ close }}" popupIndex="0" class="btn btn-primary btn-active"/>
</FlexboxLayout>

<Button text="Button with css boxShadow (rgba)" tap="{{ onTap }}" height="50" marginTop="50" class="btn btn-primary btn-active"/>

<Button text="Button with boxShadow 3 props" tap="{{ onTap }}" height="50" marginTop="50" boxShadow="5 5 navy" class="btn btn-primary btn-active"/>
<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="Button with boxShadow 5 props" tap="{{ onTap }}" height="50" marginTop="50" boxShadow="5 5 10 10 navy" class="btn btn-primary btn-active"/>
<Label color="#b4b6b9" fontSize="25" fontWeight="bold" text="NAVY"></Label>
<FlexboxLayout flexDirection="row">
<Button flexGrow="1" text="open" tap="{{ open }}" popupIndex="1" class="btn btn-primary btn-active"/>
<Button flexGrow="1" text="front" tap="{{ bringToFront }}" popupIndex="1" class="btn btn-primary btn-active"/>
<Button flexGrow="1" text="close" tap="{{ close }}" popupIndex="1" class="btn btn-primary btn-active"/>
</FlexboxLayout>

<StackLayout boxShadow="10 10 rgba(0,0,0,1)" marginTop="50">
<Button text="Button with css boxShadow" tap="{{ onTap }}" height="50" borderRadius="10"/>
</StackLayout> -->
<Label color="#b4b6b9" fontSize="25" fontWeight="bold" text="GRAY"></Label>
<FlexboxLayout flexDirection="row">
<Button flexGrow="1" text="open" tap="{{ open }}" popupIndex="2" class="btn btn-primary btn-active"/>
<Button flexGrow="1" text="front" tap="{{ bringToFront }}" popupIndex="2" class="btn btn-primary btn-active"/>
<Button flexGrow="1" text="close" tap="{{ close }}" popupIndex="2" class="btn btn-primary btn-active"/>
</FlexboxLayout>

<!-- <Button text="Button with html boxShadow" tap="{{ onTap }}" height="50" class="btn btn-primary btn-active" boxShadow="5 5 10 navy"/>

<!-- 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> -->
<Button text="Button with css boxShadow (rgba)" tap="{{ onTap }}" height="50" marginTop="50" class="btn btn-primary btn-active"/>

<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>
<Button text="Button with boxShadow 3 props" tap="{{ onTap }}" height="50" marginTop="50" boxShadow="5 5 navy" class="btn btn-primary btn-active"/>
<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="Button with boxShadow 5 props" tap="{{ onTap }}" height="50" marginTop="50" boxShadow="5 5 10 10 navy" class="btn btn-primary btn-active"/>

<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 boxShadow="5 10 10 20 #000" height="100" backgroundColor="lightblue" padding="10" margin="20" tap="{{ toggleAnimation }}">
<Label text="StackLayout"></Label>
</StackLayout>

<AbsoluteLayout boxShadow="5 15 10 20 green" height="100" backgroundColor="lightblue" padding="10" margin="20" tap="{{ toggleAnimation }}">
<Label text="AbsoluteLayout"></Label>
</AbsoluteLayout>
<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>
<!-- 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>
<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>
</ScrollView>
<Button marginTop="30" boxShadow="0 0 10 8 #000" backgroundColor="transparent" text="button" padding="20"></Button>
</StackLayout>
</ScrollView>
</StackLayout>
</GridLayout>
</Page>
120 changes: 119 additions & 1 deletion apps/toolbox/src/main-view-model.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Observable, Frame, StackLayout } from '@nativescript/core';
import { Observable, Frame, View, StackLayout, getRootLayout, EventData, RootLayout, RootLayoutOptions } from '@nativescript/core';
import { AnimationCurve } from '@nativescript/core/ui/enums';

export class HelloWorldModel extends Observable {
private _counter: number;
Expand Down Expand Up @@ -37,6 +38,123 @@ export class HelloWorldModel extends Observable {
this.updateMessage();
}

popupViews: { view: View; options: RootLayoutOptions; extra?: any }[] = [
{
view: this.getPopup('#EA5936', 110, -30),
options: {
shadeCover: {
color: '#FFF',
opacity: 0.7,
tapToClose: true,
},
animation: {
enterFrom: {
opacity: 0,
translateY: 500,
duration: 500,
},
exitTo: {
opacity: 0,
duration: 300,
},
},
},
extra: {
customExitAnimation: {
opacity: 0,
translate: { x: 0, y: -500 },
},
},
},
{
view: this.getPopup('#232652', 110, 0),
options: {
shadeCover: {
color: 'pink',
opacity: 0.7,
tapToClose: false,
animation: {
exitTo: {
scaleX: 0,
},
},
},
},
},
{
view: this.getPopup('#E1E4E8', 110, 30),
options: {
shadeCover: {
color: '#ffffdd',
opacity: 0.5,
tapToClose: true,
ignoreShadeRestore: true,
animation: {
enterFrom: {
translateX: -1000,
duration: 500,
},
exitTo: {
rotate: -180,
duration: 500,
},
},
},
animation: {
enterFrom: {
rotate: 180,
duration: 300,
},
exitTo: {
rotate: 180,
opacity: 0,
duration: 300,
curve: AnimationCurve.spring,
},
},
},
},
];

open(args: EventData): void {
getRootLayout()
.open(this.popupViews[(<any>args.object).popupIndex].view, this.popupViews[(<any>args.object).popupIndex].options)
.then(() => console.log('opened'))
.catch((ex) => console.error(ex));
}

bringToFront(args: EventData): void {
getRootLayout()
.bringToFront(this.popupViews[(<any>args.object).popupIndex].view, true)
.then(() => console.log('brought to front'))
.catch((ex) => console.error(ex));
}

close(args: EventData): void {
if (this.popupViews[(<any>args.object).popupIndex]?.extra?.customExitAnimation) {
getRootLayout()
.close(this.popupViews[(<any>args.object).popupIndex].view, this.popupViews[(<any>args.object).popupIndex].extra.customExitAnimation)
.then(() => console.log('closed with custom exit animation'))
.catch((ex) => console.error(ex));
} else {
getRootLayout()
.close(this.popupViews[(<any>args.object).popupIndex].view)
.then(() => console.log('closed'))
.catch((ex) => console.error(ex));
}
}

getPopup(color: string, size: number, offset: number): View {
const layout = new StackLayout();
layout.height = size;
layout.width = size;
layout.marginTop = offset;
layout.marginLeft = offset;
layout.backgroundColor = color;
layout.borderRadius = 10;
return layout;
}

viewList() {
Frame.topmost().navigate({
moduleName: 'list-page',
Expand Down
3 changes: 3 additions & 0 deletions packages/core/global-types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ declare namespace NodeJS {
isIOS?: boolean;
isAndroid?: boolean;
__requireOverride?: (name: string, dir: string) => any;

// used to get the rootlayout instance to add/remove childviews
rootLayout: any;
}
}

Expand Down
1 change: 1 addition & 0 deletions packages/core/ui/layouts/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export { AbsoluteLayout } from './absolute-layout';
export { DockLayout } from './dock-layout';
export { FlexboxLayout } from './flexbox-layout';
export { GridLayout, GridUnitType, ItemSpec } from './grid-layout';
export { RootLayout, getRootLayout, RootLayoutOptions, ShadeCoverOptions } from './root-layout';
export { StackLayout } from './stack-layout';
export { WrapLayout } from './wrap-layout';
export { LayoutBase } from './layout-base';
2 changes: 2 additions & 0 deletions packages/core/ui/layouts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ export { AbsoluteLayout } from './absolute-layout';
export { DockLayout } from './dock-layout';
export { FlexboxLayout } from './flexbox-layout';
export { GridLayout, GridUnitType, ItemSpec } from './grid-layout';
export { RootLayout, getRootLayout } from './root-layout';
export type { RootLayoutOptions, ShadeCoverOptions } from './root-layout';
export { StackLayout } from './stack-layout';
export { WrapLayout } from './wrap-layout';
export { LayoutBase } from './layout-base';
102 changes: 102 additions & 0 deletions packages/core/ui/layouts/root-layout/index.android.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { Color } from '../../../color';
import { View } from '../../core/view';
import { RootLayoutBase, defaultShadeCoverOptions } from './root-layout-common';
import { TransitionAnimation, ShadeCoverOptions } from '.';

export * from './root-layout-common';

export class RootLayout extends RootLayoutBase {
constructor() {
super();
}

protected _bringToFront(view: View) {
(<android.view.View>view.nativeViewProtected).bringToFront();
}

protected _initShadeCover(view: View, shadeOptions: ShadeCoverOptions): void {
const initialState = <TransitionAnimation>{
...defaultShadeCoverOptions.animation.enterFrom,
...shadeOptions?.animation?.enterFrom,
};
this._playAnimation(this._getAnimationSet(view, initialState));
}

protected _updateShadeCover(view: View, shadeOptions: ShadeCoverOptions): Promise<void> {
const options = <ShadeCoverOptions>{
...defaultShadeCoverOptions,
...shadeOptions,
};
const duration = options.animation?.enterFrom?.duration || defaultShadeCoverOptions.animation.enterFrom.duration;
return this._playAnimation(
this._getAnimationSet(
view,
{
translateX: 0,
translateY: 0,
scaleX: 1,
scaleY: 1,
rotate: 0,
opacity: options.opacity,
},
options.color
),
duration
);
}

protected _closeShadeCover(view: View, shadeOptions: ShadeCoverOptions): Promise<void> {
const exitState = <TransitionAnimation>{
...defaultShadeCoverOptions.animation.exitTo,
...shadeOptions?.animation?.exitTo,
};
return this._playAnimation(this._getAnimationSet(view, exitState), exitState?.duration);
}

private _getAnimationSet(view: View, shadeCoverAnimation: TransitionAnimation, backgroundColor: string = defaultShadeCoverOptions.color): Array<android.animation.Animator> {
const animationSet = Array.create(android.animation.Animator, 7);
animationSet[0] = android.animation.ObjectAnimator.ofFloat(view.nativeViewProtected, 'translationX', [shadeCoverAnimation.translateX]);
animationSet[1] = android.animation.ObjectAnimator.ofFloat(view.nativeViewProtected, 'translationY', [shadeCoverAnimation.translateY]);
animationSet[2] = android.animation.ObjectAnimator.ofFloat(view.nativeViewProtected, 'scaleX', [shadeCoverAnimation.scaleX]);
animationSet[3] = android.animation.ObjectAnimator.ofFloat(view.nativeViewProtected, 'scaleY', [shadeCoverAnimation.scaleY]);
animationSet[4] = android.animation.ObjectAnimator.ofFloat(view.nativeViewProtected, 'rotation', [shadeCoverAnimation.rotate]);
animationSet[5] = android.animation.ObjectAnimator.ofFloat(view.nativeViewProtected, 'alpha', [shadeCoverAnimation.opacity]);
animationSet[6] = this._getBackgroundColorAnimator(view, backgroundColor);
return animationSet;
}

private _getBackgroundColorAnimator(view: View, backgroundColor: string): android.animation.ValueAnimator {
const nativeArray = Array.create(java.lang.Object, 2);
nativeArray[0] = view.backgroundColor ? java.lang.Integer.valueOf((<Color>view.backgroundColor).argb) : java.lang.Integer.valueOf(-1);
nativeArray[1] = java.lang.Integer.valueOf(new Color(backgroundColor).argb);
const backgroundColorAnimator = android.animation.ValueAnimator.ofObject(new android.animation.ArgbEvaluator(), nativeArray);
backgroundColorAnimator.addUpdateListener(
new android.animation.ValueAnimator.AnimatorUpdateListener({
onAnimationUpdate(animator: android.animation.ValueAnimator) {
let argb = (<java.lang.Integer>animator.getAnimatedValue()).intValue();
view.backgroundColor = new Color(argb);
},
})
);
return backgroundColorAnimator;
}

private _playAnimation(animationSet: Array<android.animation.Animator>, duration: number = 0): Promise<void> {
return new Promise((resolve) => {
const animatorSet = new android.animation.AnimatorSet();
animatorSet.playTogether(animationSet);
animatorSet.setDuration(duration);
animatorSet.addListener(
new android.animation.Animator.AnimatorListener({
onAnimationStart: function (animator: android.animation.Animator): void {},
onAnimationEnd: function (animator: android.animation.Animator): void {
resolve();
},
onAnimationRepeat: function (animator: android.animation.Animator): void {},
onAnimationCancel: function (animator: android.animation.Animator): void {},
})
);
animatorSet.start();
});
}
}
Loading