Angular5开发移动——App过场动画
众所周知,Angular是目前比较火的一个前端框架。与React和Vue基本齐名。
因工作需要,要开发一款hybired的混合式app。公司将此大任交付与我,因是安卓和java出身,经过几天的对比,决定用Angular。因为typescript语言与java很接近,容易上手。
- Angular 1.x 我习惯称为AngularJs
- Angular 2.x 3 4.x 5.x 我习惯称为Angular。
两者可以看做两种框架学习。Angular5有自己想项目结构,用cli来管理项目。
具体基本知识,不在此文的探讨中,本文主要阐述Angular5的过场动画
就是移动端常见的过场动画。打开子页面,父级页面左滑退出,子页面右滑进入。退出时相反。
通过几天的研究,发现Angular5的动画可以自定义动画状态,但在路由切换前后,dom元素要卸载和挂载,会重新走生命周期。并且,dom元素(界面)在没挂载前,和卸载后的状态都只能用void这个特殊状态表示。
Angular5的动画基础知识参见 https://www.angular.cn/guide/animations
受上边基础教程中“范例:从不同的状态下进场和离场”启发,我本来是想自定义一个boolen或者状态字符标志,通过
进入方式两种(一个是第一次的时候左边进,子页面退出的时候,右边进)
void-->active
void -->inactive
退出方式两种(同上类似)
active -->void
inactive -->void
而改变状态的方法,是通过路由传值。事实证明这种方案行不通。因为路由传值,在构造方法中获得传过来的值的时候,页面已经加载完毕,动画也完了,所以没效果。
最后我的思路是通过路由的钩子方法,在页面打开前,静态注入状态值的。
当然也是参考互联网上的代码。详见 https://embed.plnkr.co/cOeDfXCetaYuXFaZ7WeO/
我自己的Demo已经上传,地址详见:https://gitee.com/null_555_2102/Angular5_AppDemo
上我的代码吧!
//动画文件horizontalslideanim.ts 注释掉的是原版的angular2的代码,不适合angular5
import {animate, state, style, transition, trigger} from '@angular/animations';
// const statesSlidedIn = [
// state('fromLeft' , style({})),
// state('fromRight' , style({}))
// ];
// const styleSlidedLeft = style({transform: 'translateX(-100%)', display: 'none'});
// const styleSlidedRight = style({transform: 'translateX(100%)', display: 'none'});
// const stateSlidedLeft = state('left', styleSlidedLeft);
// const stateSlidedRight = state('right', styleSlidedRight);
// const transitionsSlideLeft = [
// transition('fromLeft => void', animate('.3s ease-out', styleSlidedRight)),
// transition('void => fromLeft', [styleSlidedLeft, animate('.3s ease-out')])
// ];
// const transitionsSlideRight = [
// transition('fromRight => void', animate('.3s ease-out', styleSlidedLeft)),
// transition('void => fromRight', [styleSlidedRight, animate('.3s ease-out')])
// ];
// export const slideHorizontal = trigger('slideHorizontal', [
// ...statesSlidedIn,
// stateSlidedLeft,
// stateSlidedRight,
// ...transitionsSlideLeft,
// ...transitionsSlideRight
// ]);
export const slideHorizontal = trigger('slideHorizontal', [
// * 表示任何状态
state('*', style({ position: 'fixed', 'width': '100%', 'height': '100%' })),
// 定义void表示空状态下
state('void', style({ position: 'fixed', 'width': '100%', 'height': '100%' })),
state('fromLeft', style({
position: 'fixed', 'width': '100%', 'height': '100%',
})),
state('fromRight', style({
position: 'fixed', 'width': '100%', 'height': '100%',
})),
transition('void => fromLeft', [
style({transform: 'translate3d(-100%,0,0) '}),
animate('0.5s ease-in-out',style({transform: 'translate3d(0,0,0)'}))
]),
transition('fromLeft => void', [
style({transform: 'translate3d(0,0,0) '}),
animate('0.5s ease-in-out',style({transform: 'translate3d(100%,0,0)'}))
]),
transition('void => fromRight', [
style({transform: 'translate3d(100%,0,0) '}),
animate('0.5s ease-in-out')
]),
transition('fromRight => void', [
style({transform: 'translate3d(0,0,0)'}),
animate('0.5s ease-in-out', style({transform: 'translate3d(-100%,0,0)'}))
])
]);
路由文件
export const appRoutes=[
{
path:'home',
component:HomepageComponent,
data: {
slideIndex: 1,
},
canDeactivate: [CanDeactivateAfterChangeDetectionGuard]
},
{
path:'cutdowntime',
component:CutdowntimeComponent,
data: {
slideIndex: 2,
},
canDeactivate: [CanDeactivateAfterChangeDetectionGuard]
},
{
path:'calc',
component:CalculatorpageComponent,
data: {
slideIndex: 2,
},
canDeactivate: [CanDeactivateAfterChangeDetectionGuard]
},
{
path:'help',
component:HelppageComponent,
data: {
slideIndex: 3,
},
canDeactivate: [CanDeactivateAfterChangeDetectionGuard]
},
{
path:'**',//fallback router must in the last
redirectTo:'home'
// loadChildren:'./page/home.module#HomeModule'
}
];
@NgModule({
imports: [RouterModule.forRoot(appRoutes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
不要嫌麻烦,这个功能就是不好实现,继续
两个service
import { Injectable } from '@angular/core';
import {WaitForChangeDetection} from './routeslidedirectionservice.service';
import {CanDeactivate} from '@angular/router';
@Injectable()
export class CanDeactivateAfterChangeDetectionGuard implements CanDeactivate<WaitForChangeDetection> {
canDeactivate(component: WaitForChangeDetection): Promise<boolean> {
return component.waitForChangeDetection();
}
}
//分割线,两个文件
import { Injectable } from '@angular/core';
@Injectable()
export class RouteSlideDirectionService {
constructor() { }
direction: string;
setDirection(direction: string) {
this.direction = direction;
}
getDirection(): string {
return this.direction;
}
}
export declare abstract class WaitForChangeDetection {
abstract waitForChangeDetection(): Promise<boolean>;
}
两个父类
import {ChangeDetectorRef, Component, HostBinding} from '@angular/core';
import {RouteSlideDirectionService} from '../../service/routeslidedirectionservice.service';
import {WaitForChangeDetectionImpl} from '../waitforchangedetectionimpl/waitforchangedetectionimpl.component';
@Component({
selector: 'slidable',
templateUrl: './slidable.html',
styleUrls: ['./slidable.css']
})
export class Slidable extends WaitForChangeDetectionImpl {
constructor(protected cdRef: ChangeDetectorRef, private routeSlideDirectionService: RouteSlideDirectionService){
super(cdRef);
}
@HostBinding('@slideHorizontal')
get slideHorizontal(){
let slideDirection = this.routeSlideDirectionService.getDirection();
if(slideDirection){
slideDirection = (slideDirection==="right"?"fromRight" :"fromLeft");
console.log("slideDirection:"+slideDirection);
return slideDirection;
}
return null;
}
}
//分割线 两个文件
import {AfterViewChecked, ChangeDetectorRef, Component} from '@angular/core';
import {WaitForChangeDetection} from '../../service/routeslidedirectionservice.service';
import {Subject} from 'rxjs/Subject';
@Component({
selector: 'wait',
templateUrl: './wait.html',
styleUrls: ['./wait.css']
})
export class WaitForChangeDetectionImpl implements AfterViewChecked, WaitForChangeDetection {
constructor(protected cdRef: ChangeDetectorRef){
this.viewChecked$ = new Subject<void>();
}
viewChecked$: Subject<void>;
waitForChangeDetection(): Promise<boolean>{
this.cdRef.detectChanges();
return new Promise((resolve) => this.viewChecked$.subscribe(() => resolve(true)));
}
ngAfterViewChecked(){
this.viewChecked$.next();
}
}
在module和app.component中的配置
import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {CutDowntimeviewViewComponent} from './component/cutdowntimeview/app.cutdowntimeview';
import {TabviewComponent} from './component/tabview/tabview.component';
import {DrawerviewComponent} from './component/drawerview/drawerview.component';
import {HomepageComponent} from './page/homepage/homepage.component';
import {CutdowntimeComponent} from './page/cutdowntime/cutdowntime.component';
import {appRoutes, AppRoutingModule} from './app.routes';
import {AppComponent} from './app.component';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {HeaderComponent} from './component/header/header.component';
import {CalculatorpageComponent} from './page/calculatorpage/calculatorpage.component';
import {FeedbackpageComponent} from './page/feedbackpage/feedbackpage.component';
import {HelppageComponent} from './page/helppage/helppage.component';
import { WaitForChangeDetectionImpl } from './component/waitforchangedetectionimpl/waitforchangedetectionimpl.component';
import { Slidable } from './component/slidable/slidable.component';
import {RouterModule} from '@angular/router';
import {CanDeactivateAfterChangeDetectionGuard} from './service/candeactivateafterchangedetectionguard.service';
import {RouteSlideDirectionService} from './service/routeslidedirectionservice.service';
@NgModule({
declarations: [
AppComponent,
CutDowntimeviewViewComponent,
TabviewComponent,
DrawerviewComponent,
HomepageComponent,
CutdowntimeComponent,
HeaderComponent,
CalculatorpageComponent,
FeedbackpageComponent,
HelppageComponent,
WaitForChangeDetectionImpl,
Slidable
],
imports: [
BrowserModule,
AppRoutingModule,
BrowserAnimationsModule,// 引入动画模块
RouterModule.forRoot(appRoutes),
],
providers: [
RouteSlideDirectionService,
CanDeactivateAfterChangeDetectionGuard
],
bootstrap: [AppComponent]
})
export class AppModule {
}
//分割线
import {Component} from '@angular/core';
import {RouteSlideDirectionService} from './service/routeslidedirectionservice.service';
import {ActivatedRoute, Router, RoutesRecognized} from '@angular/router';
import * as _ from 'lodash';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
constructor(private router: Router, private route: ActivatedRoute, private routeSlideDirectionService: RouteSlideDirectionService){
this.router.events.subscribe((event) => {
if (event instanceof RoutesRecognized) {
let leavingSlideIndex = _.get(event, 'state.root.firstChild.data.slideIndex');
let enteringSlideIndex = _.get(this.route, 'snapshot.firstChild.data.slideIndex');
// console.log("event: "+event);
//
// console.log("leavingSlideIndex :"+leavingSlideIndex);
// console.log("enteringSlideIndex :"+enteringSlideIndex);
if(leavingSlideIndex && enteringSlideIndex){
this.routeSlideDirectionService.setDirection(leavingSlideIndex > enteringSlideIndex ? 'right' : 'left');
} else {
this.routeSlideDirectionService.setDirection(null);
}
}
});
}
}
最后,在两个页面上,这样用
//页面1
import {Component, HostBinding, OnInit} from '@angular/core';
import {slideHorizontal} from '../../animates/horizontalslideanim';
import {Slidable} from '../../component/slidable/slidable.component';
@Component({
selector: 'app-homepage',
templateUrl: './homepage.component.html',
styleUrls: ['./homepage.component.css'],
animations:[
slideHorizontal
]
})
export class HomepageComponent extends Slidable{
}
//页面1 的html
<div class="content">
<div class="content-padded grid-demo">
<div class="row" style="margin-top: 1rem;">
<a class="col-33 box" routerLink="/cutdowntime"><img src="../../../favicon.ico"/></a>
<div class="col-33 box">33%</div>
<div class="col-33 box">33%</div>
</div>
<div class="row" style="margin-top: 1rem;">
<a class="col-33 box" routerLink="/calc"><img src="../../../favicon.ico"/></a>
<div class="col-33 box">33%</div>
<div class="col-33 box">33%</div>
</div>
<div class="row" style="margin-top: 1rem;">
<div class="col-33 box"><img src="../../../favicon.ico"/></div>
<div class="col-33 box">33%</div>
</div>
</div>
</div>
//页面2
@Component({
selector: 'app-calculatorpage',
templateUrl: './calculatorpage.component.html',
styleUrls: ['./calculatorpage.component.css'],
animations: [
slideHorizontal
]
})
export class CalculatorpageComponent extends Slidable{
mTitle:string="计算器";
mHref:string="/home";
}
//页面2 的html
<div class="content native-scroll">
<app-header [mTitle]="mTitle" [mHref]="mHref"></app-header>
<div class="content-block">
<a routerLink="/help">打开子页面</a>
</div>
</div>
最后,把项目地址,贴到这里,大家可以参考一下。https://gitee.com/null_555_2102/Angular5_AppDemo