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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,585评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,398评论 25 707
  • 静殊像往常一样坐在办公桌前,都阳走了进来。都阳是静殊的同事。静殊身边除了亲人只有两种认识的人,一种密无间,一种淡如...
    沙扬海云阅读 305评论 0 0
  • 今天来辆凯美瑞保养第一次进店 跟客户报机油机滤的价钱 检查的什么问题 多少钱都跟客户说了 后来做了机油三滤 跟客...
    不够穷没有野心阅读 174评论 0 0
  • 昨天刚开学,大家都还没有从假期的乐活劲缓过来,老师就交给我们宿舍一个任务:做一个介绍金砖五国的PPT。 嗨,这还不...
    心如采薇阅读 265评论 0 3