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