mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-15 19:26:42 +08:00
fix(tabs): delay loadView when animation runs (#8353)
* fix(tabs): delay loadView when animation runs * chore: update api.md * chore: remove unnecessary casting * test: Added disabled test for changing tabs
This commit is contained in:
@ -772,6 +772,9 @@ export class Frame extends View {
|
|||||||
|
|
||||||
animated: boolean;
|
animated: boolean;
|
||||||
|
|
||||||
|
// (undocumented)
|
||||||
|
_animationInProgress: boolean;
|
||||||
|
|
||||||
backStack: Array<BackstackEntry>;
|
backStack: Array<BackstackEntry>;
|
||||||
|
|
||||||
canGoBack(): boolean;
|
canGoBack(): boolean;
|
||||||
@ -2931,6 +2934,7 @@ export abstract class ViewBase extends Observable {
|
|||||||
// (undocumented)
|
// (undocumented)
|
||||||
_setupAsRootView(context: any): void;
|
_setupAsRootView(context: any): void;
|
||||||
_setupUI(context: any /* android.content.Context */, atIndex?: number): void;
|
_setupUI(context: any /* android.content.Context */, atIndex?: number): void;
|
||||||
|
_shouldDelayLoad(): boolean;
|
||||||
showModal(moduleName: string, modalOptions: ShowModalOptions): ViewBase;
|
showModal(moduleName: string, modalOptions: ShowModalOptions): ViewBase;
|
||||||
showModal(view: ViewBase, modalOptions: ShowModalOptions): ViewBase;
|
showModal(view: ViewBase, modalOptions: ShowModalOptions): ViewBase;
|
||||||
public readonly style: Style;
|
public readonly style: Style;
|
||||||
|
3
e2e/ui-tests-app/app/tabs/frame-in-tabs-inner-page-1.xml
Normal file
3
e2e/ui-tests-app/app/tabs/frame-in-tabs-inner-page-1.xml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<Page class="page" >
|
||||||
|
<Label text="Inner label 1" backgroundColor="blue"/>
|
||||||
|
</Page>
|
3
e2e/ui-tests-app/app/tabs/frame-in-tabs-inner-page-2.xml
Normal file
3
e2e/ui-tests-app/app/tabs/frame-in-tabs-inner-page-2.xml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<Page class="page">
|
||||||
|
<Label text="Inner label 2" backgroundColor="red"/>
|
||||||
|
</Page>
|
3
e2e/ui-tests-app/app/tabs/frame-in-tabs-inner-page-3.xml
Normal file
3
e2e/ui-tests-app/app/tabs/frame-in-tabs-inner-page-3.xml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<Page class="page">
|
||||||
|
<Label text="Inner label 3" backgroundColor="orange"/>
|
||||||
|
</Page>
|
3
e2e/ui-tests-app/app/tabs/frame-in-tabs-inner-page-4.xml
Normal file
3
e2e/ui-tests-app/app/tabs/frame-in-tabs-inner-page-4.xml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<Page class="page">
|
||||||
|
<Label text="Inner label 4" backgroundColor="green"/>
|
||||||
|
</Page>
|
3
e2e/ui-tests-app/app/tabs/frame-in-tabs.ts
Normal file
3
e2e/ui-tests-app/app/tabs/frame-in-tabs.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export function onItemTap(args) {
|
||||||
|
console.log(`Item with index: ${args.index} tapped`);
|
||||||
|
}
|
24
e2e/ui-tests-app/app/tabs/frame-in-tabs.xml
Normal file
24
e2e/ui-tests-app/app/tabs/frame-in-tabs.xml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<Page class="page">
|
||||||
|
<StackLayout>
|
||||||
|
<Tabs highlightColor="red" offscreenTabLimit="1">
|
||||||
|
<TabStrip highlightColor="green" itemTap="onItemTap">
|
||||||
|
<TabStripItem title="1"></TabStripItem>
|
||||||
|
<TabStripItem title="2"></TabStripItem>
|
||||||
|
<TabStripItem title="3"></TabStripItem>
|
||||||
|
<TabStripItem title="4"></TabStripItem>
|
||||||
|
</TabStrip>
|
||||||
|
<TabContentItem>
|
||||||
|
<Frame defaultPage="tabs/frame-in-tabs-inner-page-1"></Frame>
|
||||||
|
</TabContentItem>
|
||||||
|
<TabContentItem>
|
||||||
|
<Frame defaultPage="tabs/frame-in-tabs-inner-page-2"></Frame>
|
||||||
|
</TabContentItem>
|
||||||
|
<TabContentItem>
|
||||||
|
<Frame defaultPage="tabs/frame-in-tabs-inner-page-3"></Frame>
|
||||||
|
</TabContentItem>
|
||||||
|
<TabContentItem>
|
||||||
|
<Frame defaultPage="tabs/frame-in-tabs-inner-page-4"></Frame>
|
||||||
|
</TabContentItem>
|
||||||
|
</Tabs>
|
||||||
|
</StackLayout>
|
||||||
|
</Page>
|
@ -283,4 +283,22 @@ describe(`${imagePrefix}-suite`, async function () {
|
|||||||
assert.isTrue(driver.imageHelper.hasImageComparisonPassed());
|
assert.isTrue(driver.imageHelper.hasImageComparisonPassed());
|
||||||
await tabsViewBasePage.navigateBackToSuitMainPage();
|
await tabsViewBasePage.navigateBackToSuitMainPage();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// it(`${imagePrefix}-frame-in-tabs`, async function () {
|
||||||
|
// await tabsViewBasePage.navigateToSample("frame-in-tabs");
|
||||||
|
// await driver.imageHelper.compareScreen();
|
||||||
|
|
||||||
|
// // go through the tabs and check that they are loaded
|
||||||
|
// await tabsViewBasePage.tabOnItem(1);
|
||||||
|
// await driver.imageHelper.compareScreen();
|
||||||
|
|
||||||
|
// await tabsViewBasePage.tabOnItem(2);
|
||||||
|
// await driver.imageHelper.compareScreen();
|
||||||
|
|
||||||
|
// await tabsViewBasePage.tabOnItem(3);
|
||||||
|
// await driver.imageHelper.compareScreen();
|
||||||
|
|
||||||
|
// assert.isTrue(driver.imageHelper.hasImageComparisonPassed());
|
||||||
|
// await tabsViewBasePage.navigateBackToSuitMainPage();
|
||||||
|
// });
|
||||||
});
|
});
|
||||||
|
@ -470,6 +470,12 @@ export abstract class ViewBase extends Observable {
|
|||||||
*/
|
*/
|
||||||
_setupAsRootView(context: any): void;
|
_setupAsRootView(context: any): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When returning true the callLoaded method will be run in setTimeout
|
||||||
|
* Method is intended to be overridden by inheritors and used as "protected"
|
||||||
|
*/
|
||||||
|
_shouldDelayLoad(): boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
|
@ -615,10 +615,18 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
|
|||||||
|
|
||||||
public loadView(view: ViewBase): void {
|
public loadView(view: ViewBase): void {
|
||||||
if (view && !view.isLoaded) {
|
if (view && !view.isLoaded) {
|
||||||
view.callLoaded();
|
if (this._shouldDelayLoad()) {
|
||||||
|
setTimeout(() => view.callLoaded());
|
||||||
|
} else {
|
||||||
|
view.callLoaded();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public _shouldDelayLoad(): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public unloadView(view: ViewBase): void {
|
public unloadView(view: ViewBase): void {
|
||||||
if (view && view.isLoaded) {
|
if (view && view.isLoaded) {
|
||||||
view.callUnloaded();
|
view.callUnloaded();
|
||||||
@ -1053,7 +1061,7 @@ export const classNameProperty = new Property<ViewBase, string>({
|
|||||||
cssClasses.clear();
|
cssClasses.clear();
|
||||||
|
|
||||||
if (shouldAddModalRootViewCssClasses) {
|
if (shouldAddModalRootViewCssClasses) {
|
||||||
cssClasses.add(MODAL_ROOT_VIEW_CSS_CLASS);
|
cssClasses.add(MODAL_ROOT_VIEW_CSS_CLASS);
|
||||||
} else if (shouldAddRootViewCssClasses) {
|
} else if (shouldAddRootViewCssClasses) {
|
||||||
cssClasses.add(ROOT_VIEW_CSS_CLASS);
|
cssClasses.add(ROOT_VIEW_CSS_CLASS);
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,7 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
|
|||||||
|
|
||||||
public actionBarVisibility: "auto" | "never" | "always";
|
public actionBarVisibility: "auto" | "never" | "always";
|
||||||
public _currentEntry: BackstackEntry;
|
public _currentEntry: BackstackEntry;
|
||||||
|
public _animationInProgress = false;
|
||||||
public _executingContext: NavigationContext;
|
public _executingContext: NavigationContext;
|
||||||
public _isInFrameStack = false;
|
public _isInFrameStack = false;
|
||||||
public static defaultAnimatedNavigation = true;
|
public static defaultAnimatedNavigation = true;
|
||||||
|
14
nativescript-core/ui/frame/frame.d.ts
vendored
14
nativescript-core/ui/frame/frame.d.ts
vendored
@ -147,6 +147,10 @@ export class Frame extends View {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
navigationBarHeight: number;
|
navigationBarHeight: number;
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_animationInProgress: boolean;
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
@ -218,21 +222,21 @@ export function setFragmentClass(clazz: any): void;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Use Frame.getFrameById() instead.
|
* @deprecated Use Frame.getFrameById() instead.
|
||||||
*
|
*
|
||||||
* Gets a frame by id.
|
* Gets a frame by id.
|
||||||
*/
|
*/
|
||||||
export function getFrameById(id: string): Frame;
|
export function getFrameById(id: string): Frame;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Use Frame.topmost() instead.
|
* @deprecated Use Frame.topmost() instead.
|
||||||
*
|
*
|
||||||
* Gets the topmost frame in the frames stack. An application will typically has one frame instance. Multiple frames handle nested (hierarchical) navigation scenarios.
|
* Gets the topmost frame in the frames stack. An application will typically has one frame instance. Multiple frames handle nested (hierarchical) navigation scenarios.
|
||||||
*/
|
*/
|
||||||
export function topmost(): Frame;
|
export function topmost(): Frame;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Use Frame.goBack() instead.
|
* @deprecated Use Frame.goBack() instead.
|
||||||
*
|
*
|
||||||
* Navigates back using the navigation hierarchy (if any). Updates the Frame stack as needed.
|
* Navigates back using the navigation hierarchy (if any). Updates the Frame stack as needed.
|
||||||
* This method will start from the topmost Frame and will recursively search for an instance that has the canGoBack operation available.
|
* This method will start from the topmost Frame and will recursively search for an instance that has the canGoBack operation available.
|
||||||
*/
|
*/
|
||||||
@ -241,7 +245,7 @@ export function goBack();
|
|||||||
//@private
|
//@private
|
||||||
/**
|
/**
|
||||||
* @deprecated Use Frame._stack() instead.
|
* @deprecated Use Frame._stack() instead.
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
export function _stack(): Array<Frame>;
|
export function _stack(): Array<Frame>;
|
||||||
@ -487,7 +491,7 @@ export function setActivityCallbacks(activity: any /*androidx.appcompat.app.AppC
|
|||||||
//@private
|
//@private
|
||||||
/**
|
/**
|
||||||
* @deprecated Use Frame.reloadPage() instead.
|
* @deprecated Use Frame.reloadPage() instead.
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
export function reloadPage(context?: ModuleContext): void;
|
export function reloadPage(context?: ModuleContext): void;
|
||||||
|
@ -93,7 +93,7 @@ class UIViewControllerImpl extends UIViewController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const frame = this.navigationController ? (<any>this.navigationController).owner : null;
|
const frame = (this.navigationController ? (<any>this.navigationController).owner : null);
|
||||||
const newEntry = this[ENTRY];
|
const newEntry = this[ENTRY];
|
||||||
|
|
||||||
// Don't raise event if currentPage was showing modal page.
|
// Don't raise event if currentPage was showing modal page.
|
||||||
@ -199,7 +199,7 @@ class UIViewControllerImpl extends UIViewController {
|
|||||||
if (!owner) {
|
if (!owner) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache presentedViewController if any. We don't want to raise
|
// Cache presentedViewController if any. We don't want to raise
|
||||||
// navigation events in case of presenting view controller.
|
// navigation events in case of presenting view controller.
|
||||||
if (!owner._presentedViewController) {
|
if (!owner._presentedViewController) {
|
||||||
@ -230,7 +230,6 @@ class UIViewControllerImpl extends UIViewController {
|
|||||||
if (!page || page.modal || page._presentedViewController) {
|
if (!page || page.modal || page._presentedViewController) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forward navigation does not remove page from frame so we raise unloaded manually.
|
// Forward navigation does not remove page from frame so we raise unloaded manually.
|
||||||
if (page.isLoaded) {
|
if (page.isLoaded) {
|
||||||
page.callUnloaded();
|
page.callUnloaded();
|
||||||
@ -349,6 +348,10 @@ export class Page extends PageBase {
|
|||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public _shouldDelayLoad(): boolean {
|
||||||
|
return this._frame && this._frame._animationInProgress;
|
||||||
|
}
|
||||||
|
|
||||||
public onLoaded(): void {
|
public onLoaded(): void {
|
||||||
super.onLoaded();
|
super.onLoaded();
|
||||||
if (this.hasActionBar) {
|
if (this.hasActionBar) {
|
||||||
|
@ -1091,7 +1091,18 @@ export class Tabs extends TabsBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._currentNativeSelectedIndex = value;
|
this._currentNativeSelectedIndex = value;
|
||||||
|
|
||||||
|
let itemControllerOwner = null;
|
||||||
|
if (itemController._owner) {
|
||||||
|
let itemControllerOwner = <Frame>itemController._owner.get();
|
||||||
|
// do not load new views while the animation is in progress https://stackoverflow.com/a/47031524/613113
|
||||||
|
itemControllerOwner._animationInProgress = true;
|
||||||
|
}
|
||||||
|
|
||||||
this.viewController.setViewControllersDirectionAnimatedCompletion(controllers, navigationDirection, true, (finished: boolean) => {
|
this.viewController.setViewControllersDirectionAnimatedCompletion(controllers, navigationDirection, true, (finished: boolean) => {
|
||||||
|
if (itemControllerOwner) {
|
||||||
|
itemControllerOwner._animationInProgress = false;
|
||||||
|
}
|
||||||
if (finished) {
|
if (finished) {
|
||||||
// HACK: UIPageViewController fix; see https://stackoverflow.com/a/17330606
|
// HACK: UIPageViewController fix; see https://stackoverflow.com/a/17330606
|
||||||
invokeOnRunLoop(() => this.viewController.setViewControllersDirectionAnimatedCompletion(controllers, navigationDirection, false, null));
|
invokeOnRunLoop(() => this.viewController.setViewControllersDirectionAnimatedCompletion(controllers, navigationDirection, false, null));
|
||||||
|
Reference in New Issue
Block a user