NativeScript 动画系统 #
动画概述 #
NativeScript 提供了强大的动画系统,支持属性动画、关键帧动画和 CSS 动画。
text
┌─────────────────────────────────────────────────────────────┐
│ 动画类型 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 属性动画 │
│ ├── 基本属性动画 │
│ ├── 组合动画 │
│ └── 动画序列 │
│ │
│ 关键帧动画 │
│ ├── 多关键帧 │
│ └── 自定义缓动 │
│ │
│ CSS 动画 │
│ ├── 过渡动画 │
│ └── 关键帧动画 │
│ │
└─────────────────────────────────────────────────────────────┘
属性动画 #
基本动画 #
typescript
import { Animation } from '@nativescript/core';
const view = page.getViewById('myView');
// 单属性动画
view.animate({
translate: { x: 100, y: 0 },
duration: 300
});
// 多属性动画
view.animate({
translate: { x: 100, y: 50 },
scale: { x: 1.5, y: 1.5 },
rotate: 45,
opacity: 0.5,
duration: 500
});
可动画属性 #
| 属性 | 类型 | 说明 |
|---|---|---|
| translate | 平移 | |
| scale | 缩放 | |
| rotate | number | 旋转(角度) |
| opacity | number | 透明度 |
| backgroundColor | Color | 背景色 |
| width | number | 宽度 |
| height | number | 高度 |
动画选项 #
typescript
view.animate({
translate: { x: 100, y: 0 },
duration: 1000, // 持续时间(毫秒)
delay: 500, // 延迟时间
iterations: 3, // 重复次数
curve: 'easeInOut', // 缓动函数
origin: { x: 0.5, y: 0.5 } // 变换原点
});
缓动函数 #
typescript
import { CoreTypes } from '@nativescript/core';
// 预定义缓动函数
view.animate({
translate: { x: 100, y: 0 },
curve: CoreTypes.AnimationCurve.easeIn
});
// 可用缓动函数
CoreTypes.AnimationCurve.easeIn
CoreTypes.AnimationCurve.easeOut
CoreTypes.AnimationCurve.easeInOut
CoreTypes.AnimationCurve.linear
CoreTypes.AnimationCurve.spring
CoreTypes.AnimationCurve.bounce
// 自定义贝塞尔曲线
view.animate({
translate: { x: 100, y: 0 },
curve: CoreTypes.AnimationCurve.cubicBezier(0.1, 0.8, 0.2, 1)
});
动画回调 #
typescript
view.animate({
translate: { x: 100, y: 0 },
duration: 500
})
.then(() => {
console.log('Animation finished');
})
.catch((error) => {
console.error('Animation error:', error);
});
取消动画 #
typescript
const animation = view.createAnimation({
translate: { x: 100, y: 0 },
duration: 5000
});
animation.play();
// 取消动画
animation.cancel();
组合动画 #
并行动画 #
typescript
import { Animation } from '@nativescript/core';
const view1 = page.getViewById('view1');
const view2 = page.getViewById('view2');
const animationSet = new Animation([
{ target: view1, translate: { x: 100, y: 0 }, duration: 500 },
{ target: view2, translate: { x: -100, y: 0 }, duration: 500 }
]);
animationSet.play();
序列动画 #
typescript
import { Animation } from '@nativescript/core';
const view = page.getViewById('myView');
const animations = [
{ target: view, translate: { x: 100, y: 0 }, duration: 300 },
{ target: view, rotate: 45, duration: 300 },
{ target: view, scale: { x: 1.5, y: 1.5 }, duration: 300 }
];
const animationSet = new Animation(animations);
animationSet.play();
链式动画 #
typescript
const view = page.getViewById('myView');
view.animate({ translate: { x: 100, y: 0 }, duration: 300 })
.then(() => view.animate({ rotate: 45, duration: 300 }))
.then(() => view.animate({ scale: { x: 1.5, y: 1.5 }, duration: 300 }))
.then(() => view.animate({ opacity: 0.5, duration: 300 }));
关键帧动画 #
基本关键帧 #
typescript
import { KeyframeAnimation } from '@nativescript/core';
const keyframeAnimation = KeyframeAnimation.keyframeAnimationFromInfo([
{ value: 0, duration: 0 },
{ value: 100, duration: 500 },
{ value: 50, duration: 500 },
{ value: 100, duration: 500 }
], 'translateX');
keyframeAnimation.play(view);
多属性关键帧 #
typescript
const keyframeAnimation = KeyframeAnimation.keyframeAnimationFromInfo([
{
value: { translate: { x: 0, y: 0 }, scale: { x: 1, y: 1 } },
duration: 0
},
{
value: { translate: { x: 100, y: 0 }, scale: { x: 1.2, y: 1.2 } },
duration: 500
},
{
value: { translate: { x: 100, y: 100 }, scale: { x: 1, y: 1 } },
duration: 500
}
]);
keyframeAnimation.play(view);
CSS 动画 #
过渡动画 #
css
/* 定义过渡 */
.button {
background-color: #3498db;
transition: background-color 0.3s ease, transform 0.3s ease;
}
.button:active {
background-color: #2980b9;
transform: scale(0.95);
}
CSS 关键帧动画 #
css
/* 定义关键帧 */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slideIn {
from { transform: translateX(-100); }
to { transform: translateX(0); }
}
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-20); }
}
/* 应用动画 */
.fade-in {
animation-name: fadeIn;
animation-duration: 0.5s;
animation-timing-function: ease;
}
.slide-in {
animation-name: slideIn;
animation-duration: 0.3s;
}
.bounce {
animation-name: bounce;
animation-duration: 0.5s;
animation-iteration-count: infinite;
}
CSS 动画属性 #
| 属性 | 说明 |
|---|---|
| animation-name | 动画名称 |
| animation-duration | 持续时间 |
| animation-timing-function | 缓动函数 |
| animation-delay | 延迟时间 |
| animation-iteration-count | 重复次数 |
| animation-direction | 动画方向 |
| animation-fill-mode | 填充模式 |
常用动画效果 #
淡入淡出 #
typescript
// 淡入
function fadeIn(view: View, duration: number = 300): Promise<void> {
view.opacity = 0;
view.visibility = 'visible';
return view.animate({ opacity: 1, duration });
}
// 淡出
function fadeOut(view: View, duration: number = 300): Promise<void> {
return view.animate({ opacity: 0, duration })
.then(() => {
view.visibility = 'collapsed';
});
}
滑动效果 #
typescript
// 从左侧滑入
function slideInFromLeft(view: View, duration: number = 300): Promise<void> {
view.translateX = -view.width;
view.visibility = 'visible';
return view.animate({ translate: { x: 0, y: 0 }, duration });
}
// 从右侧滑入
function slideInFromRight(view: View, duration: number = 300): Promise<void> {
view.translateX = view.width;
view.visibility = 'visible';
return view.animate({ translate: { x: 0, y: 0 }, duration });
}
// 滑出
function slideOut(view: View, direction: 'left' | 'right', duration: number = 300): Promise<void> {
const x = direction === 'left' ? -view.width : view.width;
return view.animate({ translate: { x, y: 0 }, duration })
.then(() => {
view.visibility = 'collapsed';
});
}
缩放效果 #
typescript
// 放大进入
function scaleIn(view: View, duration: number = 300): Promise<void> {
view.scaleX = 0;
view.scaleY = 0;
view.visibility = 'visible';
return view.animate({ scale: { x: 1, y: 1 }, duration });
}
// 缩小退出
function scaleOut(view: View, duration: number = 300): Promise<void> {
return view.animate({ scale: { x: 0, y: 0 }, duration })
.then(() => {
view.visibility = 'collapsed';
});
}
弹跳效果 #
typescript
function bounce(view: View): Promise<void> {
return view.animate({
translate: { x: 0, y: -30 },
duration: 200,
curve: CoreTypes.AnimationCurve.easeOut
})
.then(() => view.animate({
translate: { x: 0, y: 0 },
duration: 200,
curve: CoreTypes.AnimationCurve.bounce
}));
}
摇晃效果 #
typescript
function shake(view: View): Promise<void> {
return view.animate({ translate: { x: -10, y: 0 }, duration: 50 })
.then(() => view.animate({ translate: { x: 10, y: 0 }, duration: 50 }))
.then(() => view.animate({ translate: { x: -10, y: 0 }, duration: 50 }))
.then(() => view.animate({ translate: { x: 10, y: 0 }, duration: 50 }))
.then(() => view.animate({ translate: { x: 0, y: 0 }, duration: 50 }));
}
动画服务 #
typescript
// services/animation.service.ts
import { Injectable } from '@angular/core';
import { View, CoreTypes } from '@nativescript/core';
@Injectable({
providedIn: 'root'
})
export class AnimationService {
fadeIn(view: View, duration: number = 300): Promise<void> {
view.opacity = 0;
view.visibility = 'visible';
return view.animate({ opacity: 1, duration });
}
fadeOut(view: View, duration: number = 300): Promise<void> {
return view.animate({ opacity: 0, duration })
.then(() => { view.visibility = 'collapsed'; });
}
slideIn(view: View, direction: 'left' | 'right' | 'top' | 'bottom', duration: number = 300): Promise<void> {
const translations = {
left: { x: -view.width, y: 0 },
right: { x: view.width, y: 0 },
top: { x: 0, y: -view.height },
bottom: { x: 0, y: view.height }
};
view.translateX = translations[direction].x;
view.translateY = translations[direction].y;
view.visibility = 'visible';
return view.animate({ translate: { x: 0, y: 0 }, duration });
}
slideOut(view: View, direction: 'left' | 'right' | 'top' | 'bottom', duration: number = 300): Promise<void> {
const translations = {
left: { x: -view.width, y: 0 },
right: { x: view.width, y: 0 },
top: { x: 0, y: -view.height },
bottom: { x: 0, y: view.height }
};
return view.animate({ translate: translations[direction], duration })
.then(() => { view.visibility = 'collapsed'; });
}
scaleIn(view: View, duration: number = 300): Promise<void> {
view.scaleX = 0;
view.scaleY = 0;
view.visibility = 'visible';
return view.animate({ scale: { x: 1, y: 1 }, duration });
}
scaleOut(view: View, duration: number = 300): Promise<void> {
return view.animate({ scale: { x: 0, y: 0 }, duration })
.then(() => { view.visibility = 'collapsed'; });
}
bounce(view: View): Promise<void> {
return view.animate({
translate: { x: 0, y: -30 },
duration: 200,
curve: CoreTypes.AnimationCurve.easeOut
})
.then(() => view.animate({
translate: { x: 0, y: 0 },
duration: 200,
curve: CoreTypes.AnimationCurve.bounce
}));
}
shake(view: View): Promise<void> {
const shakeStep = (x: number) =>
view.animate({ translate: { x, y: 0 }, duration: 50 });
return shakeStep(-10)
.then(() => shakeStep(10))
.then(() => shakeStep(-10))
.then(() => shakeStep(10))
.then(() => shakeStep(0));
}
pulse(view: View, scale: number = 1.1, duration: number = 200): Promise<void> {
return view.animate({ scale: { x: scale, y: scale }, duration })
.then(() => view.animate({ scale: { x: 1, y: 1 }, duration }));
}
}
性能优化 #
使用 will-change #
css
.animated-element {
will-change: transform, opacity;
}
避免布局动画 #
typescript
// 不推荐:动画布局属性
view.animate({ width: 200, height: 200 });
// 推荐:使用 transform
view.animate({ scale: { x: 2, y: 2 } });
合理使用硬件加速 #
typescript
// 使用 transform 触发硬件加速
view.animate({
translate: { x: 100, y: 0 },
duration: 300
});
最佳实践 #
动画设计原则 #
text
┌─────────────────────────────────────────────────────────────┐
│ 动画设计原则 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 保持简洁 │
│ 动画应该简洁明了,不要过度使用 │
│ │
│ 2. 一致性 │
│ 整个应用使用一致的动画风格 │
│ │
│ 3. 性能优先 │
│ 使用 transform 和 opacity 而非布局属性 │
│ │
│ 4. 用户体验 │
│ 动画应该增强用户体验,而非分散注意力 │
│ │
│ 5. 可访问性 │
│ 考虑用户的动画偏好设置 │
│ │
└─────────────────────────────────────────────────────────────┘
下一步 #
现在你已经掌握了动画系统,接下来学习 手势处理,了解如何处理用户交互!
最后更新:2026-03-29