入剖析 Android Compose 框架的关键帧动画(keyframes、Animatable)(二十三)

深入剖析 Android Compose 框架的关键帧动画(keyframes、Animatable)

引言

在当今的 Android 应用开发领域,用户体验已成为衡量一款应用成功与否的关键因素之一。而动画作为提升用户体验的重要手段,能够为应用增添生动性和交互性,使界面更加吸引人。Android Compose 作为新一代的 Android UI 工具包,以其声明式编程模型和简洁的 API 为开发者提供了强大的动画支持。其中,关键帧动画是一种非常灵活且强大的动画方式,它允许开发者精确控制动画在不同时间点的状态,从而创建出丰富多样的动画效果。本文将深入分析 Android Compose 框架中的关键帧动画,重点关注 keyframes 和 Animatable 这两个核心组件,从源码级别进行详细剖析,帮助开发者更好地理解和运用这些工具来创建高质量的动画。

一、Compose 框架关键帧动画概述

1.1 动画在 Android 开发中的重要性

在 Android 应用中,动画可以用于多种场景,从而显著提升用户体验。以下是一些常见的应用场景:

  • 界面过渡:当用户在不同的界面之间切换时,使用动画可以使过渡更加平滑和自然。例如,在打开一个新的 Activity 或 Fragment 时,通过淡入淡出、缩放或滑动等动画效果,让用户感受到界面的流畅切换,而不是生硬的跳转。
  • 元素交互:为界面元素添加动画可以增强用户与元素之间的交互感。比如,当用户点击一个按钮时,按钮可以通过缩放或颜色变化等动画效果来反馈用户的操作,让用户明确知道自己的点击已经被应用接收。
  • 引导用户注意力:动画可以吸引用户的注意力,引导用户关注特定的内容或操作。例如,通过闪烁、跳动等动画效果,突出显示新的消息通知或重要的提示信息。
  • 增强视觉效果:复杂而精美的动画可以为应用增添独特的视觉魅力,使应用在众多竞品中脱颖而出。例如,一些游戏类应用或设计精美的工具类应用,会使用大量的动画来营造出炫酷的视觉效果。

1.2 Compose 框架简介

Android Compose 是 Google 推出的用于构建 Android UI 的现代工具包,它采用声明式编程模型,与传统的基于 XML 和 Java 的视图系统相比,具有以下显著特点和优势:

  • 声明式编程:在 Compose 中,开发者通过编写简单的函数来描述 UI 的外观和行为,而不是像传统方式那样通过手动操作视图对象来构建 UI。这种声明式的方式使得代码更加简洁、易于理解和维护。例如,以下是一个简单的 Compose 函数来创建一个文本组件:

kotlin

// 定义一个名为 SimpleText 的 Composable 函数
@Composable
fun SimpleText() {// 使用 Text 组件显示文本内容Text(text = "Hello, Compose!") 
}
  • 高效的性能:Compose 采用了智能的重组机制,只有当数据发生变化时,才会对受影响的 UI 部分进行重新绘制,从而减少了不必要的计算和绘制操作,提高了应用的性能。
  • 可组合性:Compose 鼓励将 UI 拆分成多个小的可组合函数,这些函数可以像搭积木一样组合在一起,形成复杂的 UI 界面。这种可组合性使得代码的复用性更高,开发效率也得到了显著提升。

1.3 关键帧动画的基本概念

关键帧动画是一种动画技术,它允许开发者指定动画在特定时间点的状态(即关键帧),然后动画系统会自动计算这些关键帧之间的过渡状态,从而实现平滑的动画效果。在 Android Compose 中,keyframes 和 Animatable 是实现关键帧动画的核心组件。

  • keyframes:用于定义动画的关键帧和过渡规则。通过 keyframes,开发者可以指定动画在不同时间点的目标值和插值方式,从而精确控制动画的行为。

  • Animatable:用于管理动画的状态和值。它提供了一系列方法来启动、暂停、恢复和停止动画,并且可以实时获取动画的当前值。

下面是一个简单的使用 keyframes 和 Animatable 实现关键帧动画的示例:

kotlin

import androidx.compose.animation.core.*
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember// 定义一个名为 KeyframeAnimationExample 的 Composable 函数
@Composable
fun KeyframeAnimationExample() {// 使用 remember 函数创建一个 Animatable 对象,初始值为 50.dpval size by remember { Animatable(50.dp) }.run {// 启动一个协程来执行动画LaunchedEffect(Unit) {// 使用 keyframes 定义动画的关键帧animateTo(targetValue = 200.dp,animationSpec = keyframes {// 设置动画的总时长为 2000 毫秒durationMillis = 2000// 在 500 毫秒时,目标值为 100.dp,使用线性插值器100.dp at 500 with LinearEasing// 在 1500 毫秒时,目标值为 150.dp,使用加速插值器150.dp at 1500 with FastOutSlowInEasing})}// 返回 Animatable 的当前值this.asState()}// 创建一个 Box 组件,设置其大小和背景颜色Box(modifier = Modifier.size(size).background(Color.Blue))
}

在这个示例中,我们使用 Animatable 来管理 Box 组件的大小,并通过 keyframes 定义了动画在不同时间点的目标值和插值方式。动画开始时,Box 的大小为 50.dp,在 500 毫秒时,大小变为 100.dp,在 1500 毫秒时,大小变为 150.dp,最终在 2000 毫秒时,大小变为 200.dp。

二、keyframes 源码分析

2.1 keyframes 的基本定义与结构

在 Android Compose 中,keyframes 是一个函数,用于创建一个关键帧动画规范。下面是 keyframes 函数的定义:

kotlin

// 定义一个名为 keyframes 的函数,用于创建关键帧动画规范
fun <T> keyframes(// 配置关键帧的函数block: KeyframesSpecConfig<T>.() -> Unit
): KeyframesSpec<T> {// 创建一个 KeyframesSpecConfig 对象val config = KeyframesSpecConfig<T>()// 调用配置函数来配置关键帧block(config)// 根据配置创建 KeyframesSpec 对象return KeyframesSpec(keyframes = config.keyframes,durationMillis = config.durationMillis,delayMillis = config.delayMillis,initialVelocity = config.initialVelocity)
}

从这段代码可以看出,keyframes 函数接受一个 block 参数,该参数是一个 KeyframesSpecConfig<T> 类型的函数,用于配置关键帧的具体信息。在函数内部,首先创建了一个 KeyframesSpecConfig<T> 对象,然后调用 block 函数对其进行配置,最后根据配置信息创建一个 KeyframesSpec<T> 对象并返回。

KeyframesSpecConfig<T> 类的定义如下:

kotlin

// 定义一个名为 KeyframesSpecConfig 的类,用于配置关键帧动画的参数
class KeyframesSpecConfig<T> internal constructor() {// 存储关键帧信息的列表internal val keyframes = mutableListOf<Keyframe<T>>()// 动画的总时长,默认为 300 毫秒internal var durationMillis: Int = 300// 动画的延迟时间,默认为 0 毫秒internal var delayMillis: Int = 0// 动画的初始速度,默认为 0internal var initialVelocity: T = 0 as T// 定义一个名为 at 的函数,用于添加关键帧infix fun T.at(millis: Int): KeyframeAtBuilder<T> {// 创建一个 KeyframeAtBuilder 对象return KeyframeAtBuilder(this, millis, this@KeyframesSpecConfig)}
}

KeyframesSpecConfig<T> 类包含了关键帧动画的一些基本配置信息,如关键帧列表、动画总时长、延迟时间和初始速度等。其中,at 函数用于添加关键帧,它接受一个时间参数 millis,表示关键帧的时间点,并返回一个 KeyframeAtBuilder<T> 对象,用于进一步配置关键帧的插值方式。

2.2 关键帧的添加与配置源码解读

在 KeyframesSpecConfig<T> 类中,at 函数用于添加关键帧。下面是 at 函数的具体实现:

kotlin

// 定义一个名为 at 的函数,用于添加关键帧
infix fun T.at(millis: Int): KeyframeAtBuilder<T> {// 创建一个 KeyframeAtBuilder 对象return KeyframeAtBuilder(this, millis, this@KeyframesSpecConfig)
}

at 函数接受一个时间参数 millis,表示关键帧的时间点,并将当前值 this 作为关键帧的值。然后,它创建一个 KeyframeAtBuilder<T> 对象,并将关键帧的值、时间点和 KeyframesSpecConfig<T> 对象传递给该对象。

KeyframeAtBuilder<T> 类的定义如下:

kotlin

// 定义一个名为 KeyframeAtBuilder 的类,用于构建关键帧
class KeyframeAtBuilder<T> internal constructor(// 关键帧的值private val value: T,// 关键帧的时间点private val millis: Int,// 关键帧动画的配置对象private val config: KeyframesSpecConfig<T>
) {// 定义一个名为 with 的函数,用于指定关键帧的插值器infix fun with(easing: Easing): KeyframeAtBuilder<T> {// 创建一个 Keyframe 对象val keyframe = Keyframe(value = value,fraction = millis.toFloat() / config.durationMillis,easing = easing)// 将 Keyframe 对象添加到配置对象的关键帧列表中config.keyframes.add(keyframe)// 返回当前的 KeyframeAtBuilder 对象return this}
}

KeyframeAtBuilder<T> 类用于构建关键帧,它包含了关键帧的值、时间点和配置对象。with 函数用于指定关键帧的插值器,它接受一个 Easing 类型的参数,表示插值器。在 with 函数内部,首先创建一个 Keyframe 对象,将关键帧的值、时间点(转换为动画总时长的比例)和插值器传递给该对象,然后将该 Keyframe 对象添加到 KeyframesSpecConfig<T> 对象的关键帧列表中。

2.3 动画插值与计算源码深入分析

在 KeyframesSpec<T> 类中,负责根据动画的进度计算当前值的方法是 getValueFromFraction。下面是该方法的具体实现:

kotlin

// 定义一个名为 getValueFromFraction 的函数,用于根据动画进度计算当前值
override fun getValueFromFraction(// 动画的初始值initialValue: T,// 动画的目标值targetValue: T,// 动画的进度,范围从 0 到 1fraction: Float,// 类型转换器typeConverter: TwoWayConverter<T, AnimationVector>
): T {// 查找当前进度所在的关键帧区间val (startKeyframe, endKeyframe) = findKeyframeRange(fraction)// 如果当前进度小于第一个关键帧的时间点if (startKeyframe == null) {// 计算初始值到第一个关键帧值的插值return typeConverter.convertFromVector(typeConverter.convertToVector(initialValue) * (1 - fraction) +typeConverter.convertToVector(keyframes.first().value) * fraction)}// 如果当前进度大于最后一个关键帧的时间点if (endKeyframe == null) {// 计算最后一个关键帧值到目标值的插值return typeConverter.convertFromVector(typeConverter.convertToVector(keyframes.last().value) * (1 - fraction) +typeConverter.convertToVector(targetValue) * fraction)}// 计算当前进度在关键帧区间内的相对进度val localFraction = (fraction - startKeyframe.fraction) / (endKeyframe.fraction - startKeyframe.fraction)// 根据关键帧的插值器计算插值val easedFraction = endKeyframe.easing.transform(localFraction)// 计算当前值return typeConverter.convertFromVector(typeConverter.convertToVector(startKeyframe.value) * (1 - easedFraction) +typeConverter.convertToVector(endKeyframe.value) * easedFraction)
}

该方法接受动画的初始值、目标值、动画进度和类型转换器作为参数,返回当前动画进度对应的具体值。具体步骤如下:

  1. 查找关键帧区间:调用 findKeyframeRange 方法查找当前进度所在的关键帧区间,返回起始关键帧和结束关键帧。
  2. 处理边界情况:如果当前进度小于第一个关键帧的时间点,则计算初始值到第一个关键帧值的插值;如果当前进度大于最后一个关键帧的时间点,则计算最后一个关键帧值到目标值的插值。
  3. 计算相对进度:如果当前进度在关键帧区间内,则计算当前进度在该区间内的相对进度。
  4. 应用插值器:根据结束关键帧的插值器对相对进度进行转换,得到经过插值处理后的进度。
  5. 计算当前值:根据插值后的进度,在起始关键帧值和结束关键帧值之间进行线性插值,得到当前动画进度对应的具体值。

2.4 keyframes 使用示例与代码解析

下面是一个完整的使用 keyframes 实现关键帧动画的示例代码:

kotlin

import androidx.compose.animation.core.*
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember// 定义一个名为 KeyframeAnimationExample 的 Composable 函数
@Composable
fun KeyframeAnimationExample() {// 使用 remember 函数创建一个 Animatable 对象,初始值为 50.dpval size by remember { Animatable(50.dp) }.run {// 启动一个协程来执行动画LaunchedEffect(Unit) {// 使用 keyframes 定义动画的关键帧animateTo(targetValue = 200.dp,animationSpec = keyframes {// 设置动画的总时长为 2000 毫秒durationMillis = 2000// 在 500 毫秒时,目标值为 100.dp,使用线性插值器100.dp at 500 with LinearEasing// 在 1500 毫秒时,目标值为 150.dp,使用加速插值器150.dp at 1500 with FastOutSlowInEasing})}// 返回 Animatable 的当前值this.asState()}// 创建一个 Box 组件,设置其大小和背景颜色Box(modifier = Modifier.size(size).background(Color.Blue))
}

代码解析:

  1. 创建 Animatable 对象:使用 remember 函数创建一个 Animatable 对象,初始值为 50.dp。
  2. 启动动画协程:使用 LaunchedEffect 启动一个协程,在协程中调用 animateTo 方法启动动画。
  3. 定义关键帧动画规范:使用 keyframes 函数定义动画的关键帧,设置动画的总时长为 2000 毫秒,并指定在 500 毫秒时目标值为 100.dp,使用线性插值器;在 1500 毫秒时目标值为 150.dp,使用加速插值器。
  4. 更新 UI:在 Box 组件中,使用 Animatable 的当前值来设置组件的大小,从而实现动画效果。

三、Animatable 源码分析

3.1 Animatable 的基本原理与功能

Animatable 是 Android Compose 中用于管理动画状态和值的核心类。它允许开发者创建一个可动画化的值,并通过一系列方法来控制该值的动画过程。Animatable 的基本原理是通过记录当前值、目标值、动画进度等信息,在每一帧中根据动画规范计算出新的值,并更新自身的状态。其主要功能包括:

  • 管理动画值Animatable 可以存储和更新动画的当前值,开发者可以通过 value 属性获取当前值。
  • 启动动画:提供了 animateTo 方法,用于启动一个从当前值到目标值的动画。
  • 暂停和恢复动画:支持暂停和恢复动画的功能,开发者可以通过 pauseAnimation 和 resumeAnimation 方法来控制动画的暂停和恢复。
  • 快照更新:提供了 snapTo 方法,用于立即将动画的值更新到指定的值,而不进行动画过渡。

3.2 Animatable 的创建与初始化源码剖析

Animatable 的构造函数如下:

kotlin

// 定义 Animatable 类,用于管理动画状态
class Animatable<T, V : AnimationVector>(// 动画的初始值initialValue: T,// 类型转换器,用于在 T 类型和 AnimationVector 类型之间进行转换typeConverter: TwoWayConverter<T, V> = DefaultTypeConverter as TwoWayConverter<T, V>,// 初始速度initialVelocity: T = typeConverter.convertFromVector(typeConverter.getZeroVector())
) {// 存储当前动画值private var _value: T = initialValue// 存储动画的初始速度private var _velocity: T = initialVelocity// 存储类型转换器internal val typeConverter: TwoWayConverter<T, V> = typeConverter// 存储动画状态private var _animationState: AnimationState<T, V> = AnimationState.Idle// 存储上一帧的时间戳private var lastFrameTimeNanos: Long = 0L// 公开的属性,用于获取当前动画值val value: Tget() = _value// 公开的属性,用于获取当前动画速度val velocity: Tget() = _velocity// 公开的属性,用于获取当前动画状态val isRunning: Booleanget() = _animationState is AnimationState.Running// 其他方法和逻辑...
}

在构造函数中,接受三个参数:

  • initialValue:动画的初始值,即动画开始时的值。

  • typeConverter:类型转换器,用于在 T 类型和 AnimationVector 类型之间进行转换。默认使用 DefaultTypeConverter

  • initialVelocity:动画的初始速度,默认值为 0。

在构造函数内部,将传入的参数赋值给相应的私有变量,并将动画状态初始化为 AnimationState.Idle,表示动画处于空闲状态。

3.3 动画操作方法源码解析(animateTo、snapTo 等)

animateTo 方法

animateTo 方法用于启动一个从当前值到目标值的动画。其源码如下:

kotlin

// 定义一个挂起函数 animateTo,用于启动动画
suspend fun animateTo(// 动画的目标值targetValue: T,// 动画规范,定义了动画的行为,如持续时间、插值器等animationSpec: AnimationSpec<T> = spring(),// 可选的回调函数,在动画结束时调用block: (Animatable<T, V>.() -> Unit)? = null
) {// 根据当前值、目标值和动画规范创建一个动画实例val animation = animationSpec.createAnimation(initialValue = value,targetValue = targetValue,typeConverter = typeConverter)// 更新动画状态为运行中_animationState = AnimationState.Running(animation)try {// 在每一帧中更新动画withFrameNanos { frameTimeNanos ->// 计算自上一帧以来经过的时间val elapsedTimeNanos = frameTimeNanos - lastFrameTimeNanos// 如果是第一帧,记录上一帧的时间戳if (lastFrameTimeNanos == 0L) {lastFrameTimeNanos = frameTimeNanos}// 根据动画实例计算当前的动画进度val fraction = animation.calculateCurrentFraction(elapsedTimeNanos = elapsedTimeNanos,initialTimeNanos = lastFrameTimeNanos)// 根据动画进度计算当前的动画值val updatedValue = animation.calculateValue(fraction)// 更新当前动画值_value = updatedValue// 更新上一帧的时间戳lastFrameTimeNanos = frameTimeNanos// 执行可选的回调函数block?.invoke(this)// 如果动画结束,更新动画状态为结束if (animation.isFinished(fraction)) {_animationState = AnimationState.Finishedreturn@withFrameNanos false}// 继续下一帧的更新true}} finally {// 如果动画在异常情况下结束,更新动画状态为取消if (_animationState is AnimationState.Running) {_animationState = AnimationState.Canceled}}
}

animateTo 方法的执行步骤如下:

  1. 创建动画实例:根据当前值、目标值和动画规范创建一个动画实例。
  2. 更新动画状态:将动画状态更新为 AnimationState.Running,表示动画开始运行。
  3. 逐帧更新动画:使用 withFrameNanos 函数在每一帧中更新动画。在每一帧中,计算自上一帧以来经过的时间,根据动画实例计算当前的动画进度和值,并更新 _value 属性。
  4. 处理动画结束:如果动画结束,将动画状态更新为 AnimationState.Finished;如果动画在异常情况下结束,将动画状态更新为 AnimationState.Canceled
snapTo 方法

snapTo 方法用于立即将动画的值更新到指定的值,而不进行动画过渡。其源码如下:

kotlin

// 定义一个函数 snapTo,用于立即更新动画值
fun snapTo(targetValue: T) {// 立即更新当前动画值_value = targetValue// 将动画速度重置为 0_velocity = typeConverter.convertFromVector(typeConverter.getZeroVector())// 更新动画状态为空闲_animationState = AnimationState.Idle
}

snapTo 方法的执行步骤如下:

  1. 更新当前值:将 _value 属性更新为指定的目标值。
  2. 重置速度:将动画速度重置为 0。
  3. 更新动画状态:将动画状态更新为 AnimationState.Idle,表示动画处于空闲状态。

3.4 Animatable 的类型转换与兼容性源码分析

Animatable 通过 typeConverter 进行类型转换,确保可以处理不同类型的动画值。TwoWayConverter 接口定义了类型转换的方法:

kotlin

// 定义一个接口 TwoWayConverter,用于在 T 类型和 V 类型之间进行双向转换
interface TwoWayConverter<T, V : AnimationVector> {// 将 T 类型的值转换为 V 类型的向量fun convertToVector(value: T): V// 将 V 类型的向量转换为 T 类型的值fun convertFromVector(vector: V): T// 获取 V 类型的零向量fun getZeroVector(): V
}

Animatable 在内部使用 typeConverter 进行值的转换,例如在 animateTo 方法中:

kotlin

// 根据当前值、目标值和动画规范创建一个动画实例
val animation = animationSpec.createAnimation(initialValue = value,targetValue = targetValue,typeConverter = typeConverter
)

通过 typeConverterAnimatable 可以处理不同类型的动画值,如 IntFloatDp 等。同时,它也确保了动画计算过程中使用的向量类型与实际值类型之间的兼容性。

3.5 Animatable 使用示例与代码解析

下面是一个使用 Animatable 实现简单动画的示例代码:

kotlin

import androidx.compose.animation.core.*
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember// 定义一个名为 AnimatableExample 的 Composable 函数
@Composable
fun AnimatableExample() {// 使用 remember 函数创建一个 Animatable 对象,初始值为 50.dpval sizeAnimatable = remember { Animatable(50.dp) }// 启动一个协程来执行动画LaunchedEffect(Unit) {// 启动一个从 50.dp 到 200.dp 的动画,使用弹簧动画规范sizeAnimatable.animateTo(targetValue = 200.dp,animationSpec = spring())}// 创建一个 Box 组件,设置其大小和背景颜色Box(modifier = Modifier.size(sizeAnimatable.value).background(Color.Red))
}

代码解析:

  1. 创建 Animatable 对象:使用 remember 函数创建一个 Animatable 对象,初始值为 50.dp。
  2. 启动动画协程:使用 LaunchedEffect 启动一个协程,在协程中调用 animateTo 方法启动动画,目标值为 200.dp,使用弹簧动画规范。
  3. 更新 UI:在 Box 组件中,使用 Animatable 的当前值来设置组件的大小,从而实现动画效果。

四、keyframes 与 Animatable 的对比与结合

4.1 keyframes 与 Animatable 的功能对比

  • 功能侧重点

    • keyframes:主要侧重于定义动画的关键帧和过渡规则。它允许开发者精确控制动画在不同时间点的状态,通过指定关键帧的值和插值方式,创建出复杂多样的动画效果。例如,在一个物体的移动动画中,可以使用 keyframes 定义物体在不同时间点的位置,实现不规则的移动轨迹。
    • Animatable:主要用于管理动画的状态和值。它提供了一系列方法来启动、暂停、恢复和停止动画,并且可以实时获取动画的当前值。Animatable 更关注动画的执行过程和状态管理,开发者可以通过它来控制动画的开始和结束,以及在动画过程中进行一些操作。
  • 使用场景

    • keyframes:适用于需要精确控制动画过程的场景,如创建复杂的转场动画、模拟物理效果等。例如,在一个游戏中,角色的攻击动画可能需要在不同的时间点展示不同的动作姿态,这时可以使用 keyframes 来定义这些关键帧,实现逼真的动画效果。
    • Animatable:适用于各种需要动画效果的场景,尤其是那些需要动态控制动画的场景。例如,在一个交互式界面中,用户点击按钮时触发动画,这时可以使用 Animatable 来启动动画,并根据用户的操作动态调整动画的目标值。

4.2 如何在项目中结合使用 keyframes 与 Animatable

在实际项目中,通常需要将 keyframes 和 Animatable 结合使用,以充分发挥它们的优势。下面是一个结合使用的示例代码:

kotlin

import androidx.compose.animation.core.*
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember// 定义一个名为 CombinedAnimationExample 的 Composable 函数
@Composable
fun CombinedAnimationExample() {// 使用 remember 函数创建一个 Animatable 对象,初始值为 50.dpval sizeAnimatable = remember { Animatable(50.dp) }// 启动一个协程来执行动画LaunchedEffect(Unit) {// 使用 keyframes 定义动画的关键帧sizeAnimatable.animateTo(targetValue = 200.dp,animationSpec = keyframes {// 设置动画的总时长为 2000 毫秒durationMillis = 2000// 在 500 毫秒时,目标值为 100.dp,使用线性插值器100.dp at 500 with LinearEasing// 在 1500 毫秒时,目标值为 150.dp,使用加速插值器150.dp at 1500 with FastOutSlowInEasing})}// 创建一个 Box 组件,设置其大小和背景颜色Box(modifier = Modifier.size(sizeAnimatable.value).background(Color.Green))
}

代码解析:

  1. 创建 Animatable 对象:使用 remember 函数创建一个 Animatable 对象,初始值为 50.dp。

  2. 定义关键帧动画规范:使用 keyframes 函数定义动画的关键帧,设置动画的总时长为 2000 毫秒,并指定在 500 毫秒时目标值为 100.dp,使用线性插值器;在 1500 毫秒时目标值为 150.dp,使用加速插值器。

  3. 启动动画:在协程中调用 Animatable 的 animateTo 方法,将定义好的关键帧动画规范传递给该方法,启动动画。

  4. 更新 UI:在 Box 组件中,使用 Animatable 的当前值来设置组件的大小,从而实现动画效果。

通过结合使用 keyframes 和 Animatable,可以创建出既具有精确控制又能灵活管理的动画效果。

五、关键帧动画的应用场景与案例分析

5.1 常见的应用场景举例

  • 界面过渡动画

    • 在应用的不同界面之间切换时,使用关键帧动画可以实现平滑、自然的过渡效果。例如,在一个新闻应用中,从新闻列表页切换到新闻详情页时,可以使用关键帧动画实现页面的淡入淡出、缩放或滑动效果,让用户感受到界面的流畅切换。

    • 示例代码:

kotlin

import androidx.compose.animation.core.*
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember// 定义一个名为 ScreenTransitionAnimation 的 Composable 函数
@Composable
fun ScreenTransitionAnimation() {// 使用 remember 函数创建一个 Animatable 对象,初始值为 0fval alphaAnimatable = remember { Animatable(0f) }// 启动一个协程来执行动画LaunchedEffect(Unit) {// 使用 keyframes 定义动画的关键帧alphaAnimatable.animateTo(targetValue = 1f,animationSpec = keyframes {// 设置动画的总时长为 500 毫秒durationMillis = 500// 在 200 毫秒时,目标值为 0.5f,使用线性插值器0.5f at 200 with LinearEasing})}// 创建一个 Box 组件,设置其大小、背景颜色和透明度Box(modifier = Modifier.fillMaxSize().background(Color.Blue).alpha(alphaAnimatable.value))
}
  • 元素交互动画

    • 为界面元素添加交互动画可以增强用户与元素之间的交互感。例如,当用户点击一个按钮时,按钮可以通过缩放、旋转或颜色变化等动画效果来反馈用户的操作。

    • 示例代码:

kotlin

import androidx.compose.animation.core.*
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember// 定义一个名为 ButtonInteractionAnimation 的 Composable 函数
@Composable
fun ButtonInteractionAnimation() {// 使用 remember 函数创建一个 Animatable 对象,初始值为 1fval scaleAnimatable = remember { Animatable(1f) }// 定义一个状态变量,用于记录按钮是否被点击var isClicked by remember { mutableStateOf(false) }// 当 isClicked 状态改变时,启动动画LaunchedEffect(isClicked) {if (isClicked) {// 使用 keyframes 定义动画的关键帧scaleAnimatable.animateTo(targetValue = 1.2f,animationSpec = keyframes {// 设置动画的总时长为 300 毫秒durationMillis = 300// 在 150 毫秒时,目标值为 1.1f,使用线性插值器1.1f at 150 with LinearEasing
元素交互动画

kotlin

import androidx.compose.animation.core.*
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember// 定义一个名为 ButtonInteractionAnimation 的 Composable 函数
@Composable
fun ButtonInteractionAnimation() {// 使用 remember 函数创建一个 Animatable 对象,初始值为 1fval scaleAnimatable = remember { Animatable(1f) }// 定义一个状态变量,用于记录按钮是否被点击var isClicked by remember { mutableStateOf(false) }// 当 isClicked 状态改变时,启动动画LaunchedEffect(isClicked) {if (isClicked) {// 使用 keyframes 定义动画的关键帧scaleAnimatable.animateTo(targetValue = 1.2f,animationSpec = keyframes {// 设置动画的总时长为 300 毫秒durationMillis = 300// 在 150 毫秒时,目标值为 1.1f,使用线性插值器1.1f at 150 with LinearEasing})} else {// 当按钮未被点击时,恢复到初始状态scaleAnimatable.animateTo(targetValue = 1f,animationSpec = keyframes {// 设置动画的总时长为 300 毫秒durationMillis = 300// 在 150 毫秒时,目标值为 1.05f,使用线性插值器1.05f at 150 with LinearEasing})}}// 创建一个按钮组件Button(onClick = {// 点击按钮时,切换 isClicked 状态isClicked =!isClicked},modifier = Modifier.size(100.dp).scale(scaleAnimatable.value)) {// 按钮文本Text(text = "Click me")}
}

在这个示例中,我们创建了一个按钮,当用户点击按钮时,按钮会通过 scaleAnimatable 进行缩放动画。使用 keyframes 定义了动画的关键帧,在点击时先放大到 1.2 倍,未点击时恢复到初始大小。通过 LaunchedEffect 监听 isClicked 状态的变化,根据不同状态启动不同的动画。

引导用户注意力动画

在一些应用中,需要引导用户关注特定的元素或操作,这时可以使用关键帧动画来实现闪烁、跳动等效果。

kotlin

import androidx.compose.animation.core.*
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember// 定义一个名为 AttentionGuideAnimation 的 Composable 函数
@Composable
fun AttentionGuideAnimation() {// 使用 remember 函数创建一个 Animatable 对象,初始值为 1fval alphaAnimatable = remember { Animatable(1f) }// 启动一个协程来执行动画LaunchedEffect(Unit) {while (true) {// 使用 keyframes 定义动画的关键帧,实现闪烁效果alphaAnimatable.animateTo(targetValue = 0.2f,animationSpec = keyframes {// 设置动画的总时长为 500 毫秒durationMillis = 500// 在 250 毫秒时,目标值为 0.6f,使用线性插值器0.6f at 250 with LinearEasing})// 再使用 keyframes 定义动画的关键帧,恢复到初始透明度alphaAnimatable.animateTo(targetValue = 1f,animationSpec = keyframes {// 设置动画的总时长为 500 毫秒durationMillis = 500// 在 250 毫秒时,目标值为 0.6f,使用线性插值器0.6f at 250 with LinearEasing})}}// 创建一个 Box 组件,设置其大小、背景颜色和透明度Box(modifier = Modifier.size(50.dp).background(Color.Yellow).alpha(alphaAnimatable.value))
}

在这个例子中,我们创建了一个黄色的 Box 组件,通过 alphaAnimatable 控制其透明度,实现闪烁效果。使用 while (true) 循环不断执行动画,让 Box 组件在透明度 1 和 0.2 之间不断切换,吸引用户的注意力。

5.2 实际项目案例分析

案例一:电商应用商品详情页的展开动画

在电商应用的商品详情页中,通常会有一些隐藏的商品信息,当用户点击 “展开” 按钮时,这些信息会以动画的形式展开显示。以下是一个简化的示例代码:

kotlin

import androidx.compose.animation.core.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember// 定义一个名为 ProductDetailAnimation 的 Composable 函数
@Composable
fun ProductDetailAnimation() {// 使用 remember 函数创建一个 Animatable 对象,初始高度为 0.dpval heightAnimatable = remember { Animatable(0.dp) }// 定义一个状态变量,用于记录是否展开详情var isExpanded by remember { mutableStateOf(false) }// 当 isExpanded 状态改变时,启动动画LaunchedEffect(isExpanded) {if (isExpanded) {// 使用 keyframes 定义展开动画的关键帧heightAnimatable.animateTo(targetValue = 200.dp,animationSpec = keyframes {// 设置动画的总时长为 300 毫秒durationMillis = 300// 在 150 毫秒时,目标高度为 100.dp,使用线性插值器100.dp at 150 with LinearEasing})} else {// 使用 keyframes 定义收缩动画的关键帧heightAnimatable.animateTo(targetValue = 0.dp,animationSpec = keyframes {// 设置动画的总时长为 300 毫秒durationMillis = 300// 在 150 毫秒时,目标高度为 100.dp,使用线性插值器100.dp at 150 with LinearEasing})}}// 创建一个 Column 组件,包含按钮和详情内容Column(modifier = Modifier.fillMaxWidth().background(Color.White).padding(16.dp)) {// 展开/收缩按钮Button(onClick = {// 点击按钮时,切换 isExpanded 状态isExpanded =!isExpanded},modifier = Modifier.align(Alignment.CenterHorizontally)) {// 按钮文本Text(text = if (isExpanded) "收缩详情" else "展开详情")}// 详情内容,高度根据 heightAnimatable 的值动态变化Box(modifier = Modifier.fillMaxWidth().height(heightAnimatable.value).background(Color.LightGray).padding(16.dp)) {// 详情文本Text(text = "这里是商品的详细信息...")}}
}

分析

  • 动画设计:通过 heightAnimatable 控制详情内容的高度,使用 keyframes 定义展开和收缩动画的关键帧,使动画更加平滑自然。
  • 交互逻辑:使用 isExpanded 状态变量记录详情是否展开,点击按钮时切换状态,从而触发相应的动画。
  • 用户体验:动画效果增强了用户与界面的交互感,让用户更直观地看到详情内容的展开和收缩过程。
案例二:社交应用的消息提示动画

在社交应用中,当有新消息时,消息图标可能会通过动画来提示用户。以下是一个简单的实现示例:

kotlin

import androidx.compose.animation.core.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.Icon
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Mail
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember// 定义一个名为 MessageAlertAnimation 的 Composable 函数
@Composable
fun MessageAlertAnimation() {// 使用 remember 函数创建一个 Animatable 对象,初始缩放比例为 1fval scaleAnimatable = remember { Animatable(1f) }// 定义一个状态变量,用于记录是否有新消息var hasNewMessage by remember { mutableStateOf(true) }// 当 hasNewMessage 状态改变时,启动动画LaunchedEffect(hasNewMessage) {if (hasNewMessage) {// 使用 keyframes 定义闪烁动画的关键帧while (hasNewMessage) {scaleAnimatable.animateTo(targetValue = 1.2f,animationSpec = keyframes {// 设置动画的总时长为 300 毫秒durationMillis = 300// 在 150 毫秒时,目标缩放比例为 1.1f,使用线性插值器1.1f at 150 with LinearEasing})scaleAnimatable.animateTo(targetValue = 1f,animationSpec = keyframes {// 设置动画的总时长为 300 毫秒durationMillis = 300// 在 150 毫秒时,目标缩放比例为 1.1f,使用线性插值器1.1f at 150 with LinearEasing})}}}// 创建一个 Box 组件,包含消息图标Box(modifier = Modifier.size(40.dp).background(Color.Blue).scale(scaleAnimatable.value).align(Alignment.CenterHorizontally)) {// 消息图标Icon(imageVector = Icons.Default.Mail,contentDescription = "新消息",tint = Color.White)}
}

分析

  • 动画设计:通过 scaleAnimatable 控制消息图标的缩放比例,使用 keyframes 定义闪烁动画的关键帧,使图标在缩放过程中更加自然。
  • 交互逻辑:使用 hasNewMessage 状态变量记录是否有新消息,当有新消息时,动画会不断循环执行,直到消息被处理。
  • 用户体验:闪烁的动画效果能够吸引用户的注意力,让用户及时发现新消息。

六、性能优化与注意事项

6.1 关键帧动画的性能优化策略

合理设置动画时长和帧率

动画的时长和帧率对性能有直接影响。如果动画时长过短,可能会导致动画过于急促,用户体验不佳;如果动画时长过长,会增加 CPU 和 GPU 的负担。帧率方面,过高的帧率会使动画过于流畅,但也会消耗更多的资源。一般来说,将动画帧率控制在 60fps 左右可以在性能和流畅度之间取得较好的平衡。

kotlin

// 设置动画时长为 300 毫秒,帧率接近 60fps
val animationSpec = keyframes {durationMillis = 300// 关键帧设置...
}
减少不必要的关键帧

过多的关键帧会增加动画计算的复杂度,从而影响性能。在设计动画时,应尽量减少不必要的关键帧,只保留那些对动画效果有重要影响的关键帧。例如,在一个简单的平移动画中,如果物体的移动轨迹比较简单,可以只设置起始点和终点的关键帧,让系统自动计算中间的过渡帧。

kotlin

// 只设置起始和终点关键帧
val animationSpec = keyframes {durationMillis = 500startValue at 0 with LinearEasingendValue at 500 with LinearEasing
}
使用合适的插值器

不同的插值器会对动画的计算复杂度产生影响。一些复杂的插值器,如弹簧插值器,虽然可以实现更自然的动画效果,但计算成本较高。在性能要求较高的场景中,可以选择一些简单的插值器,如线性插值器。

kotlin

// 使用线性插值器
val animationSpec = keyframes {durationMillis = 300targetValue at 150 with LinearEasing
}
避免在动画过程中进行大量的计算

在动画的每一帧中,尽量避免进行大量的计算或复杂的逻辑处理。如果需要进行一些计算,可以在动画开始前进行预计算,并将结果存储起来,在动画过程中直接使用。例如,在一个颜色渐变动画中,如果需要根据某个公式计算颜色值,可以在动画开始前计算好所有可能的颜色值,然后在动画过程中直接获取。

6.2 使用过程中的常见问题与解决方法

动画卡顿问题
  • 原因:动画卡顿通常是由于 CPU 或 GPU 负担过重导致的。可能是动画计算过于复杂,或者在动画过程中进行了大量的 UI 绘制操作。

  • 解决方法

    • 优化动画计算逻辑,减少不必要的计算。
    • 降低动画的帧率或时长,减轻 CPU 和 GPU 的负担。
    • 避免在动画过程中进行大量的 UI 绘制操作,可以将一些静态的 UI 元素提前绘制好。
动画闪烁问题
  • 原因:动画闪烁可能是由于动画的帧率不稳定、插值器设置不合理或 UI 元素的重绘问题导致的。

  • 解决方法

    • 确保动画的帧率稳定,可以通过设置合适的动画时长和插值器来实现。
    • 检查插值器的设置,避免使用过于复杂或不合适的插值器。
    • 检查 UI 元素的重绘逻辑,确保在动画过程中不会频繁重绘。
动画不按预期执行问题
  • 原因:动画不按预期执行可能是由于关键帧设置错误、动画规范配置不当或状态管理问题导致的。

  • 解决方法

    • 仔细检查关键帧的设置,确保关键帧的时间点和值符合预期。
    • 检查动画规范的配置,如动画时长、延迟时间、插值器等,确保配置正确。
    • 检查状态管理逻辑,确保动画的启动和停止条件正确,避免状态混乱。

七、总结与展望

7.1 对 Android Compose 关键帧动画的总结

Android Compose 框架为开发者提供了强大而灵活的关键帧动画支持,主要通过 keyframes 和 Animatable 这两个核心组件来实现。

keyframes 允许开发者精确控制动画在不同时间点的状态,通过定义关键帧的值和插值方式,可以创建出复杂多样的动画效果。它提供了一种直观的方式来描述动画的过程,使得开发者能够根据具体需求定制动画的细节。例如,在界面过渡、元素交互等场景中,keyframes 可以帮助实现平滑、自然的动画效果。

Animatable 则负责管理动画的状态和值,提供了一系列方法来启动、暂停、恢复和停止动画。它与 keyframes 结合使用,可以方便地实现动画的执行和控制。通过 Animatable,开发者可以实时获取动画的当前值,并根据需要进行动态调整。

在实际应用中,结合使用 keyframes 和 Animatable 可以充分发挥它们的优势,创建出既具有精确控制又能灵活管理的动画效果。同时,通过合理设置动画参数、优化性能和处理常见问题,可以确保动画在各种设备上都能流畅运行,提升用户体验。

7.2 未来发展趋势与可能的改进方向

更丰富的动画效果和预设

未来,Android Compose 可能会提供更多丰富的动画效果和预设,进一步降低开发者创建动画的难度。例如,增加更多基于物理模拟的动画效果,如重力、弹性、摩擦力等,让动画更加逼真和自然。同时,提供更多的预设动画模板,开发者可以直接使用这些模板来快速实现常见的动画效果,提高开发效率。

与其他技术的深度融合

随着 Android 技术的不断发展,Android Compose 关键帧动画可能会与其他技术进行更深度的融合。例如,与 AR/VR 技术结合,为用户带来更加沉浸式的动画体验;与机器学习技术结合,实现智能动画效果,根据用户的行为和环境自动调整动画。

性能优化和资源管理的进一步提升

在性能优化方面,未来的 Android Compose 可能会进一步优化动画的计算和渲染机制,减少资源消耗,提高动画的流畅度。例如,采用更高效的算法来计算动画值,减少 CPU 和 GPU 的负担;优化内存管理,避免动画过程中的内存泄漏问题。

跨平台兼容性的增强

随着跨平台开发的需求不断增加,Android Compose 关键帧动画可能会进一步增强跨平台兼容性。使得开发者可以在不同的平台(如 Android、iOS、Web 等)上使用相同的代码实现一致的动画效果,降低开发成本和维护难度。

总之,Android Compose 关键帧动画在未来有着广阔的发展前景,将为开发者提供更多的可能性,帮助他们创建出更加出色的 Android 应用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/39498.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android Studio编译问题

文章目录 GradleJDK版本不兼容 Gradle JDK版本不兼容 Incompatible because this component declares an API of a component compatible with Java 11 and the consumer needed a runtime of a component compatible with Java 8 查看module内gradle文件是否设置jdk版本&…

Four.meme是什么,一篇文章读懂

一、什么是Four.meme&#xff1f; Four.meme 是一个运行在 BNB 链的去中心化平台旨在为 meme 代币供公平启动服务。它允许用户以极低的成本创建和推出 meme 代币&#xff0c;无需预售或团队分配&#xff0c;它消除了传统的预售、种子轮和团队分配&#xff0c;确保所有参与者有…

解决PHP内存溢出问题的讨论和分析

PHP作为一种广泛使用的服务器端脚本语言&#xff0c;在处理大量数据或复杂任务时&#xff0c;常常会遇到内存溢出的问题。内存溢出不仅会导致程序崩溃&#xff0c;还可能影响服务器的稳定性。本文将探讨解决PHP内存溢出问题的最佳实践&#xff0c;并通过代码示例进行详细说明。…

git,openpnp - 根据安装程序打包名称找到对应的源码版本

文章目录 git,openpnp - 根据安装程序打包名称找到对应的源码版本概述笔记备注 - 提交时间不可以作为查找提交记录的依据END git,openpnp - 根据安装程序打包名称找到对应的源码版本 概述 想在openpnp官方最新稳定版上改一改&#xff0c;首先就得知道官方打包的安装程序对应的…

基于Spring Boot的停车场管理系统的设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

基于Spring Boot + Vue的银行管理系统设计与实现

基于Spring Boot Vue的银行管理系统设计与实现 一、引言 随着金融数字化进程加速&#xff0c;传统银行业务向线上化转型成为必然趋势。本文设计并实现了一套基于Spring Boot Vue的银行管理系统&#xff0c;通过模块化架构满足用户、银行职员、管理员三类角色的核心业务需求…

Unity | Tag、Layer常量类生成工具

在项目开发中我们可以对诸如Layer、Tag等编辑器数据进行常量生成&#xff0c;来代替在代码中通过输入字符串生成常量的形式以提高开发效率。 Layer的生成可以通过LayerMask.LayerToName获取层名称&#xff08;也可以从TagManager.asset中获得 &#xff09;&#xff0c;Tag的生成…

两个手机都用流量,IP地址会一样吗?深入解析

在日常生活中&#xff0c;我们常常会同时使用多台手机设备上网&#xff0c;尤其是在流量充足的情况下。你是否曾好奇过&#xff0c;当两台手机同时使用流量上网时&#xff0c;它们的IP地址会是一样的吗&#xff1f;这个问题看似简单&#xff0c;却涉及移动网络的技术原理。本文…

后端——AOP异步日志

需求分析 在SpringBoot系统中&#xff0c;一般会对访问系统的请求做日志记录的需求&#xff0c;确保系统的安全维护以及查看接口的调用情况&#xff0c;可以使用AOP对controller层的接口进行增强&#xff0c;作日志记录。日志保存在数据库当中&#xff0c;为了避免影响接口的响…

Qt的内存管理机制

在Qt中&#xff0c;显式使用new创建的对象通常不需要显式调用delete来释放内存&#xff0c;这是因为Qt提供了一种基于对象树(Object Tree)和父子关系(Parent-Child Relationship)的内存管理机制。这种机制可以自动管理对象的生命周期&#xff0c;确保在适当的时候释放内存&…

React:React主流组件库对比

1、Material-UI | 官网 | GitHub | GitHub Star: 94.8k Material-UI 是一个实现了 Google Material Design 规范的 React 组件库。 Material UI 包含了大量预构建的 Material Design 组件&#xff0c;覆盖导航、滑块、下拉菜单等各种常用组件&#xff0c;并都提供了高度的可定制…

排序算法(插入,希尔,选择,冒泡,堆,快排,归并)

1.插入排序 插入排序的主要思想是额外申请一个空间cur&#xff0c;让cur一开始等于数组的第1号位置,设置i1&#xff0c;让i-1的元素与其比较&#xff0c;如果arr[i-1]>arr[i]&#xff0c;就让arr[i1] arr[i]&#xff0c;当进行到最后一次对比结束&#xff0c;i-1,再让arr[…

python学习笔记--实现简单的爬虫(二)

任务&#xff1a;爬取B站上最爱欢迎的编程课程 网址&#xff1a;编程-哔哩哔哩_bilibili 打开网页的代码模块&#xff0c;如下图&#xff1a; 标题均位于class_"bili-video-card__info--tit"的h3标签中&#xff0c;下面通过代码来实现&#xff0c;需要说明的是URL中…

Vue3 实现pdf预览

1.使用到的插件 vue3-pdf-app 以及预览效果 2.下载依赖 // 可以使用npm 以及pnpm // 下载版本1.0.3 pnpm install vue3-pdf-app^1.0.3 3.封装pdfModel组件复用 <template><VuePdfApp :page-scale"pageScale" :theme"theme" :style"width: …

SpringBoot集成Elasticsearch 7.x spring-boot-starter-data-elasticsearch 方式

SpringBoot集成Elasticsearch 7.x | spring-boot-starter-data-elasticsearch 方式 前言添加maven依赖配置application.properties测试实体类 方式一&#xff1a;继承 ElasticsearchRepository&#xff08;适合简单查询&#xff09; 直接使用想自定义自己的Repository接口 方式…

【Clang AST】基于 Clang 获取分析 AST

The Clang AST AST&#xff08;Abstract Syntax Tree&#xff09;抽象语法树 AST是什么 抽象语法树&#xff08;Abstract Syntax Tree, AST&#xff09;是源代码的抽象表示&#xff0c;广泛用于编译器和分析工具中。 AST将源代码的语法结构转换为树形结构&#xff0c;其中每…

onedav一为导航批量自动化导入网址(完整教程)

OneNav作为一个功能强大的导航工具,支持后台管理、加密链接、浏览器书签批量导入等功能,能够帮助用户轻松打造专属的导航页面。今天,我将为大家详细介绍如何实现OneNav导航站的批量自动化导入网址。 1、建立要批量导入的表格 格局需要创建表格,表格的要求是一定要有需要,…

文档处理控件Aspose.Words 教程:.NET版中增强的 AI 文档摘要功能

Aspose.Words是一个功能强大的 Word 文档处理库。它可以帮助开发人员自动编辑、转换和处理文档。 自 24.11 版以来&#xff0c;Aspose.Words for .NET 提供了 AI 驱动的文档摘要功能&#xff0c;使用户能够从冗长的文本中快速提取关键见解。在 25.2 版中&#xff0c;我们通过使…

AI本地部署之dify

快捷目录 Windows 系统一、环境准备:首先windows 需要准备docker 环1. 安装Docker desktop2. 安装Docker3. 配置Docker 镜像路径4. 配置Docker 下载镜像源5. 重启Docker服务二、Dify 下载和安装1. Dify下载2. Dify 配置3. Dify 安装附件知识:4. Dify创建账号三、下载Ollama d…

DDS协议(二)

一、DDS发布订阅机制 基于发布/订阅的数据分发服务为各种虚拟机载设备之间的通信提供了统一的底层支撑 其技术原理如图所示&#xff1a; 发布方应用程序和订阅方应用程序分别同发布/订阅中间件通信&#xff0c; 而数据的实际分发是由发布/订阅中间件来处理。 发布方将包含主题…