# spine-android 运行时文档
>
> **Licensing**
>
> 将官方的Spine运行时整合到你的应用程序之前, 请仔细阅读 [Spine运行时许可页面](/spine-runtimes-license).

# 简介

spine-android 运行时基于 [spine-libgdx](/git/runtimes/spine-libgdx) 构建. 它支持加载、播放和操作 Spine 创建的动画.

## 安装

它最低支持的 SDK 版本为 24 (Android 7.0, Nougat). 请确保您的项目至少为该 SDK 版本, 以便利用 Spine 的功能.

如需在你的 Android 应用中使用 spine-android, 首先应将 mavenCentral 和 SonaType 快照仓库添加进你的 Gradle 项目中：

```gradle
repositories {
    mavenCentral()
    maven {
        url = uri("https://oss.sonatype.org/content/repositories/snapshots")
    }
}
```

接下来需要将 spine-android 依赖项添加到应用的 `build.gradle` 文件中:

```gradle
dependencies {
    implementation(""com.esotericsoftware.spine:spine-android:4.2.+"")
}
```

该配置将拉取与 Spine Editor 4.2 兼容的最新 spine-android. 一般来说拉取的就是 [spine-runtimes](https://github.com/esotericsoftware/spine-runtimes) 仓库中最新提交的快照. 可以认为我们的快照是稳定版本, 且保持经常更新. 如果你仍然希望或需要使用发布版本而非快照版本, 你可以在 [Maven Central](https://central.sonatype.com/artifact/com.esotericsoftware.spine/spine-android) 上找到与 Spine Editor对应的最新发布版本.

应确保spine-haxe的 `major.minor` 版本与用来导出资产的Spine编辑器的 `major.minor` 版本一致. 更多详情请见 [同步版本号](/spine-versioning#同步版本) 一节.

## 示例

spine-android 运行时内置了数个用于功能展示的示例. 你可以按照以下步骤运行示例项目:

1. 安装 [Android Studio](https://developer.android.com/studio)
2. 克隆 spine-runtimes 仓库: `git clone https://github.com/esotericsoftware/spine-runtimes`
3. 在 Android Studio 中打开 `spine-android/` 文件夹.
4. 运行 `app` 配置.

所有示例都是使用 Kotlin 结合 [Jetpack Compose](https://developer.android.com/develop/ui/compose) 编写的, 但 spine-android 也兼容 `Java` 和原生 Android 视图. 示例项目中包含了以下示例:

* [`SimpleAnimation.kt`](/git/spine-runtimes/spine-android/app/src/main/java/com/esotericsoftware/spine/SimpleAnimation.kt): 演示了如何使用基本的 `SpineView` 和 `SpineController` 加载 Spine skeleton、在视图中显示它, 并播放特定动画.
* [`PlayPause.kt`](/git/spine-runtimes/spine-android/app/src/main/java/com/esotericsoftware/spine/PlayPause.kt): 演示了如何暂停和恢复动画.
* [`AnimationStateEvents.kt`](/git/spine-runtimes/spine-android/app/src/main/java/com/esotericsoftware/spine/AnimationStateEvents.kt): 演示了如何设置槽位的颜色、如何队列多个动画以及如何监听动画状态事件.
* [`DebugRendering.kt`](/git/spine-runtimes/spine-android/app/src/main/java/com/esotericsoftware/spine/DebugRendering.kt): 演示了如何通过 `SpineController` 的 `onAfterPaint` 回调在 skeleton 上实现自定义渲染.
* [`DressUp.kt`](/git/spine-runtimes/spine-android/app/src/main/java/com/esotericsoftware/spine/DressUp.kt): 演示了 Spine 的皮肤功能, 以及如何将 skeleton 渲染为缩略图以用于角色创建 UI.
* [`IKFollowing.kt`](/git/spine-runtimes/spine-android/app/src/main/java/com/esotericsoftware/spine/IKFollowing.kt): 演示了如何让用户通过鼠标或触摸输入拖动 skeleton 的骨骼.
* [`DisableRendering.kt`](/git/spine-runtimes/spine-android/app/src/main/java/com/esotericsoftware/spine/DisableRendering.kt): 演示了当 `SpineView` 移出屏幕后如何停止渲染. 在需要节省 CPU/GPU 资源的时候该功能至关重要.
* [`SimpleAnimationActivity.java`](/git/spine-runtimes/spine-android/app/src/main/java/com/esotericsoftware/spine/SimpleAnimationActivity.java): 演示了如何在基于 `XML` 的相对布局中使用 `Java` 和 `SpineView`.

## 更新 spine-android 运行时

在更新项目中的Spine iOS运行时前, 请先查阅[Spine 编辑器和运行时版本管理指南](/spine-runtime-architecture#版本控制).

只需在 `build.gradle` 依赖中修改 spine-android 包的版本字符串便可更新 spine-android 运行时.

> **注意:** 如果你更改了 spine-android 包的 `major.minor` 版本, 则必须使用同版本的 Spine Editor 重新导出 Spine skeleton !

# 使用 spine-android

spine-android 运行时基于 [spine-libgdx](/git/runtimes/spine-libgdx) 构建, 后者依赖于 [libGDX](https://github.com/libgdx/libgdx). 它支持加载、播放和操作 Spine 动画. spine-android 运行时使用 `spine-libgdx`, 以便轻松地在 Android 上显示 Spine skeleton 并与之交互.

## 资产管理

### 导出适用于 spine-android 的 Spine 资产

![](/img/spine-runtimes-guide/spine-ue4/export.png)
请按 Spine 用户指南中的说明进行操作来:

1. [导出skeleton和动画数据](/spine-export)
2. [导出包含skeleton图像的texture atlases](/spine-texture-packer)

导出的 skeleton 数据和 texture atlas 将产生以下文件:

![](/img/spine-runtimes-guide/spine-ue4/exported-files.png)

1. `skeleton-name.json` 或 `skeleton-name.skel` 文件, 包含了 skeleton 和动画数据.
2. `skeleton-name.atlas`, 包含了 texture atlas 的相关信息.
3. 单个或数个 `.png` 文件, texture atlas 中的一页就对应着这样一个文件, 而 texture atlas 则保存着 skeleton 所需的图片包.

> **注意:** 你应该优先选择导出 skeleton 的二进制文件而非 JSON 格式, 因为二进制文件体积更小, 加载速度更快.

可以通过如 `AndroidTextureAtlas`, `SkeletonDataUtils`, `AndroidSkeletonDrawable`, `SpineView` 的 spine-android 类来加载这些文件.

> **注意:** 由于 Android 的技术限制, spine-android 运行时目前不支持使用预乘(pre-multiplied) alpha 导出的 atlas. Android 的渲染引擎只能确保不出现常见的非预乘 alpha 伪影.

### 更新 Spine 资产

在开发过程中可能需要频繁更新 Spine skeleton 数据和 texture atlas 文件. 直接覆盖这些原始文件(`.json`, `.skel`, `.atlas`, `.png`)即可完成资产更新, 具体做法是从 Spine Editor 中重新导出文件并替换 Android 项目中已有的文件.

在此过程中应该确保 spine-android 的 `major.minor` 版本与导出文件的 Spine 编辑器的 `major.minor` 版本一致. 有关更多信息, 请参阅 [Spine 版本控制](/spine-versioning#同步版本) 一节.

## 核心类

spine-android API 基于 [spine-libgdx](/git/runtimes/spine-libgdx) 构建, 后者提供了加载、查询、修改和动画化 Spine skeleton 的核心类和算法.

本节将简要讨论在日常使用 spine-android 时会接触到的关键核心类. 有关 Spine 运行时架构、核心类和 API 使用的详细概述, 请查阅 [Spine Runtimes Guide](/spine-runtimes-guide).

### spine-android 类

[`AndroidSkeletonDrawable`](/git/spine-runtimes/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/AndroidTextureAtlas.java) 类将加载和更新 `AndroidTextureAtlas`, `Skeleton`, 以及 `AnimationState` 的功能集成到一个易于使用的类中.

[`AndroidTextureAtlas`](/git/spine-runtimes/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/AndroidTextureAtlas.java) 类存储从 `.atlas` 文件及其对应的 `.png` 图像文件加载的数据.

[`SkeletonDataUtils`](/git/spine-runtimes/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/util/SkeletonDataUtils.java) 类公开了从 skeleton 文件中加载 `SkeletonData` 的静态函数.

[`SkeletonRenderer`](/git/spine-runtimes/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/SkeletonRenderer.java) 类负责将 `Skeleton` 的当前 pose 转换为 `SkeletonRenderer.RenderCommand` 命令并将其渲染到 `Canvas` 上.

这些类既是 spine-android 的一部分, 也是添加了 Android 特定功能的 `spine-libgdx` 扩展.

### spine-libgdx 类

[`SkeletonData`](/git/spine-runtimes/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonData.java) 类存储了从 `.json` 或 `.skel` 文件加载的 skeleton 数据. Skeleton 数据包含了骨骼层次结构、槽位、附件、约束、皮肤和动画的相关信息. 通常是通过一个 `Atlas` 类来实例化 `SkeletonData` 实例, 实例便可从 `Atlas` 中获取skeleton所使用的图像. 该类是创建 `Skeleton` 实例的蓝图. 可以从同一个atlas和skeleton数据实例化多个skeleton, 从而共用已加载的数据, 这样可最大限度地降低运行时的加载时长和内存消耗.

[`Skeleton`](/git/spine-runtimes/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonData.java) 类存储了从 `SkeletonData` 实例创建的skeleton实例. Skeleton实例存储了其当前pose, 即当前的骨骼位置和槽位设置、附件和活动皮肤. 当前pose可以通过手动修改骨骼层次结构来得到, 而更常见做法的是通过 `AnimationState` 来应用动画, 以此改变当前pose.

[`AnimationState`](/git/spine-runtimes/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java) 类负责跟踪应应用于 skeleton 的动画, 并基于最后渲染帧和当前渲染帧间的时差来推进和 mixing 这些动画, 最后将动画应用于skeleton实例从而设置其当前 pose. `AnimationState` 会查询 [`AnimationStateData`](/git/spine-runtimes/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationStateData.java) 实例来检索动画间的 mixing 时长, 若没有设置 mixing 时长则会使用默认 mixing 时长.

spine-android 运行时正是构建在这些核心类之上.

## SpineView

![/img/spine-runtimes-guide/spine-android/simple-animation.png](/img/spine-runtimes-guide/spine-android/simple-animation.png)

[`SpineView`](/git/spine-runtimes/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SpineView.java) 是一个 Android [视图(View)](https://developer.android.com/reference/android/view/View), 负责加载和显示 Spine skeleton. 视图至少需要知道从哪里加载 skeleton 和 atlas 文件, 并接受传入一个 `SpineController` 实例, 该实例负责修改视图状态, 例如设置动画或更改 skeleton 皮肤.

使用 `Jetpack Compose` 时, 可以在 `AndroidView` 中实例化 `SpineView`:

```kotlin
AndroidView(
    factory = { context ->
        SpineView.loadFromAssets(
            "spineboy.atlas",
            "spineboy-pro.json",
            context,
            SpineController {
                it.animationState.setAnimation(0, "walk", true)
            }
        )
    }
)
```

在实例化过程中, `SpineView` 将异步加载指定的文件并从中构造底层核心类实例, 即 `AndroidTextureAtlas`, `SkeletonData`, `Skeleton`, `AnimationStateData` 和 `AnimationState` 的实例.

加载完成后, 将调用 `SpineController` 的构造函数回调 `onInitialized`, 它可以修改视图的状态, 例如设置一个或多个动画、调整骨骼层次结构或修改 skeleton 皮肤. 请参见下面文中的 `SpineController` 一节.

`SpineView` 类提供了多个静态工厂方法, 用于从不同来源加载 skeleton 和 atlas 文件:

* `SpineView.loadFromAsset()` 从主包或指定的包中加载文件.
* `SpineView.loadFromFile()` 从文件系统加载文件.
* `SpineView.loadFromHttp()` 从 URL 加载文件.
* `SpineView.loadFromDrawable()` 从 `AndroidSkeletonDrawable` 构造视图. 当需要预加载、缓存和/或在 `SpineView` 实例间共享 skeleton 数据时, 这一选项时非常有用. 详情请参阅 [`DisableRendering.kt`](/git/spine-runtimes/spine-android/app/src/main/java/com/esotericsoftware/spine/DisableRendering.kt) 示例.

当你需要进一步调整 Spine skeleton 在部件(widget)中如何缩放和对齐时, 可以使用 `SpineView` 的 setter 方法:

```kotlin
val spineView = SpineView.loadFromAssets("dragon.atlas", "dragon-ess.skel", ctx, controller)
spineView.setContentMode(ContentMode.FILL)
spineView.setAlignment(Alignment.BOTTOM_CENTER)
spineView.setBoundsProvider(SkinAndAnimationBounds("flying"))
```

也可以使用 `SpineView.Builder`:

```kotlin
SpineView.Builder(ctx, controller)
 .setLoadFromAssets("dragon.atlas", "dragon-ess.skel")
 .setContentMode(ContentMode.FILL)
 .setAlignment(Alignment.BOTTOM_CENTER)
 .setBoundsProvider(SkinAndAnimationBounds("flying"))
 .build()
```

* `setContentMode` 设置用于将 skeleton 适配于视图的 [`ContentMode`](/git/spine-runtimes/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/bounds/ContentMode.java).
* `setAlignment` 设置用于将 skeleton 对齐于视图的 [`Alignment`](/git/spine-runtimes/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/bounds/Alignment.java).
* `setBoundsProvider` 设置用于计算边界框的像素尺寸的 [`BoundsProvider`](/git/spine-runtimes/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/bounds/BoundsProvider.java). 默认使用 skeleton 的setup pose 边界框. 更多信息请参见类文档 [`SetupPoseBounds`](/git/spine-runtimes/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/bounds/SetupPoseBounds.java) 和 [`SkinAndAnimationBounds`](/git/spine-runtimes/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/bounds/SkinAndAnimationBounds.java).

`SpineView` 包含一对 getter/setter 方法 `isRendering/setRendering`, 它可以禁用渲染. 要在 `Jetpack Compose` 中如 `isRendering` 般更新 `SpineView` 的属性, 可以添加 `AndroidView.update` 参数:

```kotlin
AndroidView(
    factory = ...
    update = { spineView ->
        spineView.isRendering = isSpineBoyVisible.value
    }
)
```

更多详情请见 [`DisableRendering.kt`](/git/spine-runtimes/spine-android/app/src/main/java/com/esotericsoftware/spine/DisableRendering.kt) 示例.

## SpineController

[`SpineController`](/git/spine-runtimes/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/SpineController.java) 控制如何动画化和渲染 `SpineView` 中的 skeleton. 控制器可以通过方法注入或 [`SpineController.Builder`](/git/spine-runtimes/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/SpineController.java) 类传入一组回调. `SpineView` 生命周期中的特定时刻将会调用这些回调.

控制器通过返回 Spine 运行时 API 对象 (如 `AndroidTextureAtlas`, `SkeletonData`, `Skeleton` 和 `AnimationState`) 的 getter 暴露 skeleton 状态, 使用这些对象便可调整 skeleton 状态. 更多信息请参阅 [Spine 运行时指南](/spine-runtimes-guide) 和相关类文档.

`SpineView` 初始化时会调用一次控制器的 `onInitialized()` 回调方法. 此方法可用于设置需播放的初始动画, 也可以设置 skeleton 皮肤..

初始化完成后, `SpineView` 将以屏幕刷新率不断渲染 skeleton. 在每一帧中都会根据当前队列的动画来更新 `AnimationState`, 并将动画应用于 `Skeleton`.

接下来会按需调用 `onBeforeUpdateWorldTransforms()` 回调, 它可以在 `Skeleton.updateWorldTransform()` 计算 skeleton 的当前 pose 前修改 skeleton. 可以通过 `SpineController.setOnBeforeUpdateWorldTransforms()` 或 `SpineController.Builder.setOnBeforeUpdateWorldTransforms()` 设置该回调.

计算出当前 pose 后会按需调用 `onAfterUpdateWorldTransforms()` 回调, 它可以在渲染 skeleton 前进一步修改当前 pose. 这是一个手动调整骨骼位置的好时机. 可以通过 `SpineController.setOnAfterUpdateWorldTransforms()` 或 `SpineController.Builder.setOnAfterUpdateWorldTransforms()` 设置该回调.

在 `SpineView` 渲染 skeleton 之前可以按需调用 `onBeforePaint()` 回调, 它可以在 [`Canvas`](https://developer.android.com/reference/android/graphics/Canvas) 上渲染 skeleton 背景或其他下层的对象. 可以通过 `SpineController.setOnBeforePaint()` 或 `SpineController.Builder.setOnBeforePaint()` 设置该回调.

`SpineView` 将当前 skeleton pose 渲染到 `Canvas` 后可以按需调用 `onAfterPaint()` 回调, 它可以在 skeleton 之上渲染其他对象. 可以通过 `SpineController.setOnAfterPaint()` 或者 `SpineController.Builder.setOnAfterPaint()` 设置该回调.

默认情况下, 视图每帧都会更新和渲染 skeleton. `SpineController.pause()` 方法用于暂停 skeleton 的更新和渲染. `SpineController.resume()` 方法则会恢复更新和渲染. `SpineController.isPlaying()` 的 getter 会返回当前播放状态. 更多详细信息请参阅 [`PlayPause.kt`](/git/spine-runtimes/spine-android/app/src/main/java/com/esotericsoftware/spine/PlayPause.kt) 示例.

## AndroidSkeletonDrawable

`AndroidSkeletonDrawable` 将加载、存储、更新和渲染 `Skeleton` 及其关联的 `AnimationState` 类等功能打包到一个易于使用的类中. 该类可用作实现自定义视图的基类. `SpineView` 通过 `AndroidSkeletonDrawable` 实例封装了 skeleton 状态.

用 `fromAsset()`, `fromFile()` 或 `fromHttp()` 等静态函数可以从文件构造出 `AndroidSkeletonDrawable`. 如需在多个 `AndroidSkeletonDrawable` 实例间共享 `AndroidTextureAtlas` 和 `SkeletonData`, 应该通调用构造函数来实例化多个 drawable, 并将相同的 atlas 和 skeleton 数据传入各个实例.

`AndroidSkeletonDrawable` 公开 `Skeleton` 和 `AnimationState` 以便查询、修改和动画化 skeleton. 它同时公开了用于构造 skeleton 和动画状态的 `AndroidTextureAtlas` 和 `SkeletonData`.

如需动画化 skeleton, 可以通过 `AnimationState` API(如 `AnimationState.setAnimation()` 或 `AnimationState.addAnimation()`)在一条或多条轨道上队列动画.

若要更新动画状态, 将其应用于 skeleton, 然后更新当前的 skeleton pose, 请调用 `AndroidSkeletonDrawable.update()` 方法, 并提供以秒计的时长参数以便播放动画.

## 应用动画

`SpineController` 回调中的 `AnimationState` 可将动画应用于 `SpineView` 中的 skeleton.

> **注意:** 关于动画轨道和动画队列的详情请参阅 Spine 运行时指南中的 [应用动画](/spine-applying-animations#AnimationState-API)一节.

调用 `AnimationState.setAnimation()` 可在轨道 0 上设置一段动画:

```kotlin
 SpineController { initializedController ->
  // Set the walk animation on track 0, let it loop
    initializedController.animationState.setAnimation(0, "walk", true)
}
```

其首个参数指定轨道编号, 第二个参数是动画名称, 第三个参数设置是否循环播放动画.

可以用这种方式队列多个动画:

```kotlin
controller.animationState.setAnimation(0, "walk", true)
controller.animationState.addAnimation(0, "jump", false, 2f)
controller.animationState.addAnimation(0, "run", true, 0f)
```

`addAnimationByName()` 的第一个参数也是轨道号. 第二个参数仍是动画名称. 第三个参数还是循环动画的开关. 但最后一个参数表示播放延迟(以秒为单位), 这段延迟之后该轨道中的前一段动画将被这段动画替换.

在上文示例中, 运行时会首先播放 `"walk"` 动画. 2 秒后播放一次 `"jump"` 动画, 然后过渡到循环播放的 `"run"` 动画.

从一段动画过渡到另一段动画时, `AnimationState` 将在指定的持续时长内 mix 这两段动画. 这些 mix 时长定义在 `AnimationStateData` 实例中, `AnimationState` 要用的时候会从中检索 mix 时长.

`AnimationStateData` 实例可通过控制器实例访问. 你既可以设置默认 mix 时长, 也可以给某对动画设置 mix 时长:

```kotlin
controller.animationStateData.setDefaultMix(0.2f)
controller.animationStateData.setMix("walk", "jump", 0.1f)
```

设置或添加动画后会返回一个 `AnimationState.TrackEntry` 对象, 通过这个对象能进一步定制该动画的播放行为. 例如可以设置轨道条目的 `reverse` 属性来倒放某段动画:

```kotlin
val entry = controller.animationState.setAnimation(0, "walk", true)
entry.setReverse(true)
```

其他选项请参阅 [`AnimationState.TrackEntry` 类文档](/git/spine-runtimes/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java).

> **注意:** 不要在使用它们的函数外保留 `AnimationState.TrackEntry` 实例. 函数内部会重用轨道条目, 一旦它所代表的动画播放完后, 这些实例就会成为无效实例.

可以在动画轨道上设置或队列空动画, 就能平滑地将skeleton置为其 setup pose:

```kotlin
controller.animationState.setEmptyAnimation(0, 0.5f)
controller.animationState.addEmptyAnimation(0, 0.5f, 0.5f)
```

`setEmptyAnimation()` 的第一个参数指定轨道号. 第二个参数指定 mix 时长(以秒为单位), 这个时长表示淡出前一个动画并淡入"空"动画所用的时间.

`addEmptyAnimation()` 的第一个参数指定轨道号. 第二个参数指定 mix 时长. 第三个参数则是空动画的播放延迟(以秒为单位), 这段延迟之后将用空动画替换掉该轨道中的前一个动画.

`AnimationState.clearTrack()` 可以立即清空轨道上的所有动画. 用 `AnimationState.clearTracks()` 则会一次清空所有轨道. 这时 skeleton 将保持最后一个 pose.

调用 `Skeleton.setToSetupPose()` 就能将 skeleton 重置为 setup pose:

```kotlin
controller.skeleton.setToSetupPose()
```

如果需要将 skeleton 的骨骼和槽位均重置为 setup pose 设置, 则应调用 `Skeleton.setSlotsToSetupPose()`.

## AnimationState 事件

`AnimationState` 在动画播放的生命周期中会触发事件. 监听这些事件便能根据需要做出响应. Spine Runtimes API 定义了 [`AnimationState.AnimationStateListener`](/git/spine-runtimes/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java) 接口, 其中包含以下事件的回调:

* `start`: 当动画开始时触发.
* `interrupt`: 当清空动画轨道或设置了新动画时触发.
* `end`: 当不再应用动画时触发.
* `dispose`: 当销毁动画的轨道条目时触发.
* `complete`: 当动画播放完一个循环时触发.
* `event`: 在发生用户自定义的 [事件](/spine-events#Events) 时触发.

在 `AnimationState` 上注册一个 [`AnimationState.AnimationStateListener`](/git/spine-runtimes/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java) 回调便可监听所有动画的事件; 也可以用 `AnimationState.TrackEntry` 来监听某个队列中的动画:

```kotlin
val entry = controller.animationState.setAnimation(0, "walk", true)
entry.setListener(object : AnimationState.AnimationStateListener {
    override fun start(entry: AnimationState.TrackEntry?) {}

    override fun interrupt(entry: AnimationState.TrackEntry?) {}

    override fun end(entry: AnimationState.TrackEntry?) {}

    override fun dispose(entry: AnimationState.TrackEntry?) {}

    override fun complete(entry: AnimationState.TrackEntry?) {}

    override fun event(entry: AnimationState.TrackEntry?, event: Event?) {
        if (event != null) {
            print("User defined event: ${event.data.name}");
        }
    }
})
controller.animationState.addListener(object : AnimationState.AnimationStateListener {
    override fun start(entry: AnimationState.TrackEntry?) {}

    override fun interrupt(entry: AnimationState.TrackEntry?) {}

    override fun end(entry: AnimationState.TrackEntry?) {}

    override fun dispose(entry: AnimationState.TrackEntry?) {}

    override fun complete(entry: AnimationState.TrackEntry?) {}

    override fun event(entry: AnimationState.TrackEntry?, event: Event?) {
        if (event != null) {
            print("Animation state event $event")
        }
    }
})
```

更多细节请参阅 [`AnimationStateEvents.kt`](/git/spine-runtimes/spine-android/app/src/main/java/com/esotericsoftware/spine/AnimationStateEvents.kt) 示例.

## 皮肤

![/img/spine-runtimes-guide/spine-android/skins.png](/img/spine-runtimes-guide/spine-android/skins.png)

许多应用程序和游戏可以让用户组合各种独立部件(例如头发、眼睛、裤子或耳环、包等配饰)来创建自定义形象. Spine可以通过[皮肤混搭](/spine-examples-mix-and-match)来实现这一功能.

下例是从其他皮肤中重组出一套自定义皮肤的做法:

```kotlin
val data = controller.skeletonData
val skeleton = controller.skeleton
val customSkin = Skin("custom-skin");
customSkin.addSkin(data.findSkin("skin-base"))
customSkin.addSkin(data.findSkin("nose/short"))
customSkin.addSkin(data.findSkin("eyelids/girly"))
customSkin.addSkin(data.findSkin("eyes/violet"))
customSkin.addSkin(data.findSkin("hair/brown"))
customSkin.addSkin(data.findSkin("clothes/hoodie-orange"))
customSkin.addSkin(data.findSkin("legs/pants-jeans"))
customSkin.addSkin(data.findSkin("accessories/bag"))
customSkin.addSkin(data.findSkin("accessories/hat-red-yellow"))
skeleton.setSkin(customSkin)
skeleton.setSlotsToSetupPose()
```

构造函数 `Skin()` 可以创建自定义皮肤.

然后从控制器中获取 `SkeletonData`. 通过 `SkeletonData.findSkin()` 便能按名称查找皮肤.

`Skin.addSkin()` 会将指定的皮肤组件添加到新建的自定义皮肤中.

最后在 `Skeleton` 上设置新皮肤, 并调用 `Skeleton.setSlotsToSetupPose()` 以确保没有原先的皮肤和/或动画附件残留在 Skeleton 上.

详情请参阅 [`DressUp.kt`](/git/spine-runtimes/spine-android/app/src/main/java/com/esotericsoftware/spine/DressUp.kt) 示例, 该示例还演示了如何使用 `AndroidSkeletonDrawable` 渲染皮肤的预览缩略图.

## 设置骨骼变换

![/img/spine-runtimes-guide/spine-android/simple-animation.png](/img/spine-runtimes-guide/spine-android/bone-transform.png)

在 Spine Editor 中创作 skeleton 时, skeleton 使用的是所谓的 skeleton 坐标系. 这个坐标系可能与用来渲染 skeleton 的 `SpineView` 坐标系并不一致. 因此触控操作使用的 `SpineView` 坐标需要转换为 skeleton 坐标系才便于使用(例如, 需要用户通过触控操作来移动骨骼).

`SpineController` 为此提供了 `toSkeletonCoordinates()` 方法, 该方法可以传入一个 `SpineView` 中的 [`Point`](https://developer.android.com/reference/android/graphics/Point) 坐标, 再将其转换为 skeleton 坐标.

请参阅 [`IKFollowing.kt`](/git/spine-runtimes/spine-android/app/src/main/java/com/esotericsoftware/spine/IKFollowing.kt) 示例以了解更多信息.

## 使用 SkeletonRenderer 渲染

如果你需要在 `SpineView` 外渲染 skeleton, 比如将其渲染到 `Cavas` 或 `Bitmap`, 那么可以使用 [`SkeletonRenderer`](/git/spine-runtimes/spine-android/app/src/main/java/com/esotericsoftware/spine/SkeletonRenderer.kt).

使用渲染方法 `SkeletonDrawable.render()` 创建一个  [`SkeletonRenderer.RenderCommand`](/git/spine-runtimes/spine-android/app/src/main/java/com/esotericsoftware/spine/SkeletonRenderer.kt) 对象列表便可渲染 skeleton 的当前 pose. 通过这些对象, 使用 `SkeletonDrawable.renderToCanvas()` 可以将 skeleton 渲染到 `Canvas`, 而使用 `SkeletonDrawable.renderToBitmap()` 则会渲染到 `Bitmap`.

# 访问 Spine 运行时 API

spine-android API 基于 [spine-libgdx](/git/runtimes/spine-libgdx) 构建, 因此你可以使用其完整 API. 请参阅 [Spine 运行时指南](/spine-runtimes-guide) 以获取更多信息.
