当前位置: 首页 > news >正文

入剖析 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 应用。

相关文章:

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

深入剖析 Android Compose 框架的关键帧动画&#xff08;keyframes、Animatable&#xff09; 引言 在当今的 Android 应用开发领域&#xff0c;用户体验已成为衡量一款应用成功与否的关键因素之一。而动画作为提升用户体验的重要手段&#xff0c;能够为应用增添生动性和交互性…...

java中的枚举类型和c,c++的有区别吗?c,c++的枚举,结构体,联合体,三种数据有什么区别和联系

Java 枚举类型与 C、C 枚举类型的区别 1. 类型安全 Java&#xff1a;Java 的枚举类型是类型安全的。枚举常量是枚举类型的实例&#xff0c;编译器会严格检查传递的参数是否为该枚举类型的有效常量。例如&#xff1a; java Apply enum Color { RED, GREEN, BLUE } // 编译器会检…...

详解Redis的持久化与数据可靠性

Redis持久化与数据可靠性详解&#xff08;结合实例&#xff09; Redis作为内存数据库&#xff0c;持久化是保证数据不丢失的核心机制。它通过将内存数据保存到磁盘&#xff0c;确保服务器重启后能恢复数据。Redis提供RDB、AOF和混合持久化三种方式&#xff0c;下面通过实例和操…...

1、mysql基础篇--概述

关系型数据库&#xff08;RDBMS&#xff09; 概念特点&#xff1a;数据模型&#xff1a; 概念 建立在关系模型基础上&#xff0c;有多张表相互连接的二维表组成的数据库 特点&#xff1a; 1、使用表存储&#xff0c;格式统一&#xff0c;便于维护 2、使用sql语言操作&#…...

【Tiny RDM】Redis客户端工具

Tiny RDM Tiny RDM是一款现代化、轻量级、跨平台的Redis客户端&#xff0c;支持Mac、Windows和Linux&#xff0c;目前在Github上已有10kStar。 Github 项目地址&#xff1a; https://github.com/tiny-craft/tiny-rdm 功能特性 极度轻量&#xff0c;基于Webview2&#xff0c…...

常见框架漏洞攻略-Shiro篇

漏洞名称 Shiro rememberMe反序列化漏洞 漏洞简介 Apache Shiro是⼀个强⼤易⽤的Java安全框架&#xff0c;提供了认证、授权、加密和会话管理等功能。Shiro框架直观、易⽤&#xff0c;同时也能提供健壮的安全性。 漏洞原理 在Shiro框架下&#xff0c;⽤户登陆成功后会⽣成…...

常见框架漏洞之一:Thinkphp5x

ThinkPHP是为了简化企业级应⽤开发和敏捷WEB应⽤开发⽽诞⽣的&#xff0c;是⼀个快速、兼容⽽且简单的轻量级国产PHP开发框架&#xff0c;诞⽣于2006年初&#xff0c;原名FCS&#xff0c;2007年元旦正式更名为 ThinkPHP&#xff0c;遵循Apache2开源协议发布&#xff0c;从Stru…...

MORL4PDEs:基于多目标优化与强化学习的数据驱动偏微分方程发现

摘要&#xff1a;本文提出了一种结合多目标优化与强化学习的数据驱动方法MORL4PDEs&#xff0c;用于从复杂系统观测数据中发现简洁的偏微分方程&#xff08;PDE&#xff09;。该方法无需预定义候选函数库&#xff0c;通过神经网络代理生成符号表达式&#xff0c;结合遗传算法优…...

UniApp和微信小程序中v-switch夜间模式动画开关

UniApp兼容版 <template><view><view class"main-container" :style"{ backgroundColor: value ? #45e3f9 : #20114c,transform:scale(${size})}" tap"onClick"><view class"content" :style"{ left: val…...

六十天Linux从0到项目搭建第四天(通配符命令、其他命令、压缩解压工具、shell的感性理解、linux权限解析)

通配符&#xff08;Wildcard&#xff09; 是 Shell 提供的特殊字符&#xff0c;用于 匹配文件名或路径名&#xff0c;可以代替一个或多个字符&#xff0c;使得命令能批量操作文件&#xff0c;而无需手动输入每个文件名。 典型用法 * 匹配任意字符 *.txt → 匹配所有 .txt 文…...

RAG优化:python从零实现自适应检索增强Adaptive Retrieval

开篇:当RAG遇上“自适应大脑”,检索从此不再“一根筋”!🧠 想象一下,你的AI助手是个超级聪明的“学霸”,但有时候却像个“一根筋”的机器人——无论你问它什么,它都用同一种方式去回答。问它“什么是XAI?”它给你一堆定义;问它“AI发展太快了吗?”它还是给你一堆定…...

C语言实现的冰墩墩

在windows系统下&#xff0c;vs 2022编译。 其中#include <graphics.h>需要自己下载安装。 环境配置没什么难度&#xff0c;直接上demo。 代码如下&#xff1a; #include <graphics.h> #include <conio.h> #include <math.h> #define PI acos(-1.0…...

【构建CV图像识别系统】从传统方法到深度学习

目录 1. 图像的基本概念1.1 像素与色彩1.2 过滤与卷积 2. 图像分类与检测3. 图像特征的提取3.1 全局特征3.2 局部特征3.2.1 边缘&#xff08;Edge&#xff09;3.2.2 角点&#xff08;Corner&#xff09;3.2.3 SIFT 特征 4. 传统方法与深度学习在图像识别中的应用4.1 基于传统方…...

在Centos 7环境下安装MySQL

前言&#xff1a;在安装与卸载MySQL时&#xff0c;用户需切换为root&#xff0c;这样安装之后&#xff0c;普通用户也能够使用。 Tips:我们在刚开始学习时&#xff0c;尽量全部使用root进行&#xff0c;适应mysql语句&#xff0c;后面学了用户管理&#xff0c;就可以考虑新建普…...

【机器学习基础 4】 Pandas库

一、Pandas库简介 Pandas 是一个开源的 Python 数据分析库&#xff0c;主要用于数据清洗、处理、探索与分析。其核心数据结构是 Series&#xff08;一维数据&#xff09;和 DataFrame&#xff08;二维表格数据&#xff09;&#xff0c;可以让我们高效地操作结构化数据。Pandas …...

干部监督预警系统的定义与功能

一、干部监督预警系统是什么&#xff1f; 干部监督预警系统是通过整合多源数据&#xff08;如干部档案、履职表现、廉政记录、舆情反馈等&#xff09;&#xff0c;利用大数据分析、人工智能等技术&#xff0c;对干部行为进行实时监测、风险评估和分级预警的数字化管理工具。 二…...

可视化图解算法:链表的奇偶重排(排序链表)

1. 题目 描述 给定一个单链表&#xff0c;请设定一个函数&#xff0c;将链表的奇数位节点和偶数位节点分别放在一起&#xff0c;重排后输出。 注意是节点的编号而非节点的数值。 数据范围&#xff1a;节点数量满足 0≤n≤105&#xff0c;节点中的值都满足 0≤val≤10000 要…...

获取小红书笔记详情接口的详细指南

一、引言 小红书作为一个集社交、购物、分享于一体的综合性平台&#xff0c;拥有海量的用户和丰富的笔记内容。小红书笔记详情API接口为开发者提供了一种高效获取笔记详细信息的方法&#xff0c;包括笔记的标题、正文、图片、视频、标签、点赞数、评论数等。这些数据可以帮助开…...

麒麟系统运维指令

麒麟系统运维指令 麒麟系统运维指令1、 查看系统版本2、查看系统信息3、用户与权限管理4. 网络相关命令5. 包管理6. 文件操作7. 进程管理 麒麟系统运维指令 1、 查看系统版本 目的指令查看操作系统版本信息cat /etc/os-release查看操作系统版本信息hostnamectl查看内核版本un…...

pyqt SQL Server 数据库查询

一、概述 本项目旨在开发一个基于 Python 和 PyQt6 的数据库查询工具&#xff0c;该工具能够连接到 SQL Server 数据库&#xff0c;显示数据库中的表名&#xff0c;支持用户输入 SQL 查询语句进行数据查询&#xff0c;并将查询结果展示在表格中。同时&#xff0c;为了提升用户…...

抓包软件【Fiddler】

我叫补三补四&#xff0c;很高兴见到大家&#xff0c;欢迎一起学习交流和进步 今天来讲一讲Fiddler 什么是Fidder Fiddler是一款非常流行并且实用的HTTP抓包工具&#xff0c;原理是在电脑上开启一个HTTP代理服务器&#xff0c;然后转发所有的HTTP请求和响应。是用C#开发的工具…...

SpringCould微服务架构之Docker(1)

项目中微服务比较多的时候&#xff0c;一个一个手动的部署太麻烦了&#xff0c;所以就需要用到Docker。 项目部署中的问题&#xff1a; Docker是一种快速交付应用、运行应用的技术。...

计算图(Computation Graph)

在强化学习中&#xff0c;TensorFlow的计算图&#xff08;Computation Graph&#xff09;是用于描述模型结构和训练流程的核心机制。 1. 计算图的基本概念 定义&#xff1a;计算图是TensorFlow中表示数学运算和数据流动的有向图。图中的节点&#xff08;Nodes&#xff09;代表…...

邮件营销:如何设置合适的发送频率

在邮件营销里&#xff0c;把握好发送频率特别关键&#xff0c;这直接关系到客户愿不愿意搭理你的邮件&#xff0c;以及邮件营销能不能达到预期效果。下面这几个步骤和建议&#xff0c;能帮你找到合适的邮件发送频率&#xff1a; 一、了解目标受众 分析客户行为&#xff1a;查…...

React项目中,递归写法获取tree的id集合

后端接口返回一个childrens的树&#xff0c;最后要拿到的是每个childrens下第一个对象的id集合&#xff0c;用于编辑页的回显 采用的是递归写法&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; const categoryIds: Array&…...

深入解析Linux网络、安全与容器技术

1. Netfilter&#xff1a;Linux内核的包处理框架 Netfilter 是Linux内核中用于控制网络数据包的核心机制&#xff0c;负责处理数据包的过滤、修改和转发。其核心功能包括&#xff1a; 包过滤&#xff08;Packet Filtering&#xff09;&#xff1a;根据规则允许或拒绝数据包通过…...

AF3 Rotation 类解读

Rotation 类(rigid_utils 模块)是 AlphaFold3 中用于 3D旋转 的核心组件,支持两种旋转表示: 1️⃣ 旋转矩阵 (3x3) 2️⃣ 四元数 (quaternion, 4元向量) 👉 设计目标: 允许灵活选择 旋转矩阵 或 四元数 封装了常用的 旋转操作(组合、逆旋转、应用到点上等) 像 torch.…...

数据预处理习题

简述常用的文本数据类型。 结构化文本&#xff1a;如数据库中的表格数据、JSON/XML格式数据&#xff0c;具有明确的字段和层级关系。非结构化文本&#xff1a;如自然语言文本&#xff08;新闻、社交媒体内容&#xff09;、长文档&#xff08;书籍、论文&#xff09;&#xff0…...

常见框架漏洞—中间件IIS

一.IIS6.x篇 1.在Windows server 2003中搭建网站 2.访问网站&#xff0c;并对该网站进行抓包 3.修改提交方式为PUT&#xff0c;然后写入木马 4.修改提交方式为MOVE&#xff0c;令将其更名为脚本⽂档后缀 5.我们在Windows server 2003中可以看到我们上传的shell.asp 6.我们在网…...

群体智能优化算法-蚁狮优化算法(Ant Lion Optimizer, ALO,含Matlab源代码)

一、文章摘要 蚁狮优化算法&#xff08;Ant Lion Optimizer&#xff0c;ALO&#xff09;是一种新颖的元启发式算法&#xff0c;由Mirjalili提出&#xff0c;其灵感来源于自然界中蚁狮幼虫构筑陷阱捕猎蚂蚁的行为。该算法通过模拟蚂蚁的随机游走、蚁狮的陷阱机制、陷阱缩小及精…...

【计算机视觉】数据增强

一、数据增强的意义 在深度学习中&#xff0c;数据集往往有限&#xff0c;而模型需要大量的样本来学习特征。数据增强技术通过对图像进行如下变换&#xff1a; 扩充样本数量&#xff1a;利用已有数据生成新的样本。提高模型鲁棒性&#xff1a;使模型适应不同的图像变换&#…...

BERT文本分类实战----美团外卖评论情绪分类

HuggingFace 提供了巨大的模型库&#xff0c;虽然其中的很多模型性能表现出色&#xff0c;但这些模型往往是在广义的数据集上训练的&#xff0c;缺乏针对特定数据集的优化&#xff0c;所以在获得一个合适的模型之后&#xff0c;往往还要针对具体任务的特定数据集进行二次训练&a…...

Chrome 133 版本开发者工具(DevTools)更新内容

Chrome 133 版本开发者工具&#xff08;DevTools&#xff09;更新内容 一、持久化的 AI 聊天记录 AI 助手面板会在本地持久化聊天记录&#xff0c;即使重新加载 DevTools 或 Chrome&#xff0c;也可以查看之前与 Gemini 的对话内容。 二、Performance 面板改进 此版本为 Per…...

大模型应用(Java)2025/3/24

大佬视频&#x1f449;使用Java实现一个基础的大模型RAG问答对话系统_哔哩哔哩_bilibili 需求 让大模型来理解知识库内容&#xff0c;并根据知识库回答。 通过本次应用我学到了&#xff1a; RAG工程的基本处理框架流程&#xff08;基于java&#xff09;向量数据库的基础使用…...

基于Sentinel-1A GRD洪涝淹没范围提取(SDWI阈值法和OSTU自动阈值法)

0 前言 两幅灾前和灾后的遥感影像经过SARscape配准、滤波、辐射定标预处理之后&#xff0c;使用GDAL库分别使用SDWI阈值法和OSTU自动阈值法提取洪涝淹没范围 1 ENVI 5.6和SARscape5.6安装 通过网盘分享的文件&#xff1a;ENVI5(1).6 链接: https://pan.baidu.com/s/1mKcEkC3…...

PPT 转高精度图片 API 接口

PPT 转高精度图片 API 接口 文件处理 / 图片处理&#xff0c;将 PPT 文件转换为图片序列。 1. 产品功能 支持将 PPT 文件转换为高质量图片序列&#xff1b;支持 .ppt 和 .pptx 格式&#xff1b;保持原始 PPT 的布局和样式&#xff1b;转换后的图片支持永久访问&#xff1b;全…...

IOS接入微信方法

导入SDK 和配置 SDK 的不做介绍&#xff1b; 1 在IOS 开发者中心 Identifiers 打开‘Associated Domains’ 2 建立一个文件&#xff08;不带后缀的&#xff09;apple-app-site-association, teamid在 IOS 开发者中心的会员找&#xff0c;appid在 xcode里面找 {"applin…...

隐式与显式等待的区别及混合使用

隐式等待&#xff08;Implicit Wait&#xff09;和显式等待&#xff08;Explicit Wait&#xff09;是 Selenium WebDriver 中两种不同的等待机制&#xff0c;用于处理动态加载的页面元素或异步操作。以下是它们的区别、作用范围以及混合使用的注意事项&#xff1a; 1. 核心区别…...

Selenium基本使用(三)隐藏框、获取文本、断言、切换窗口

1、定位文本框&#xff0c;密码框,按钮 案例一&#xff1a; 网站&#xff1a;过期更新 from selenium import webdriver import time dxwebdriver.Chrome() dx.get("过期更新") time.sleep(2) dx.find_element_by_name("userAccount").send_keys("…...

蓝桥杯,利用 Vue.js 构建简易任务管理器

在日常开发中&#xff0c;我们经常需要处理各种任务和计划。一个简单且高效的任务管理器可以帮助我们更好地组织和安排时间。今天&#xff0c;我将向大家展示如何使用 Vue.js 构建一个简易的任务管理器。这个项目不仅能够帮助我们更好地理解 Vue.js 的基本语法和功能&#xff0…...

vmwaretools解压失败|vmware tools distrib cannot mkdir read only file system|bug汇总

最简单的一条路线&#xff1a;你的解压命令用sudo了吗&#xff1f; 这个方法不能解决的话就看下面内容。本文提供给你全过程思路。 如需转载&#xff0c;标记出处 背景&#xff1a; 之前虚拟机和主机的复制黏贴还能用&#xff0c;今天突然用不了&#xff0c;重新下载安装包&am…...

类与对象(中)(详解)

【本节目标】 1. 类的6个默认成员函数 2. 构造函数 3. 析构函数 4. 拷贝构造函数 5. 赋值运算符重载 6. const成员函数 7. 取地址及const取地址操作符重载 1.类的6个默认成员函数 如果一个类中什么成员都没有&#xff0c;简称为空类。 空类中真的什么都没有吗&…...

2025-03-21 Unity 网络基础3——TCP网络通信准备知识

文章目录 1 IP/端口类1.1 IPAddress1.2 IPEndPoint 2 域名解析2.1 IPHostEntry2.2 Dns 3 序列化与反序列化3.1 序列化3.1.1 内置类型 -> 字节数组3.1.2 字符串 -> 字节数组3.1.3 类对象 -> 字节数组 3.2 反序列化3.2.1 字节数组 -> 内置类型3.2.2 字节数组 -> 字…...

练习8-8 移动字母

练习8-8 移动字母 day 8 void Shift( char s[] ){int lenstrlen(s);int a[3];for(int i0;i<3;i){a[i]s[i];}for(int i3;i<len;i){s[i-3]s[i];}s[len-3]a[0];s[len-2]a[1];s[len-1]a[2]; }...

BigEvent项目后端学习笔记(二)文章分类模块 | 文章分类增删改查全流程解析(含优化)

&#x1f4d6; 模块概述 文章分类模块包括 新增文章分类、文章分类列表、获取文章分类详情、更新文章分类、删除文章分类 功能。本篇对于原项目进行了代码优化&#xff0c;将原先写在 Controller 层的业务逻辑代码迁移至了 Service 层。 &#x1f6e0;️ 技术实现要点 分组校…...

蓝桥杯,冬奥大抽奖

在日常的网页开发中&#xff0c;抽奖功能是一种常见的交互设计&#xff0c;它可以增加用户的参与感和趣味性。今天&#xff0c;我将分享一个简单的抽奖转盘实现&#xff0c;它使用了HTML、CSS和JavaScript&#xff0c;非常适合初学者学习和理解前端开发的基本概念。 一、项目背…...

scNET:整合scRNA-seq和PPI用于学习基因和细胞的embedding

scRNA-seq 技术的最新进展为深入了解各种组织的异质性提供了前所未有的视角。然而&#xff0c;仅靠基因表达数据往往无法捕捉和识别细胞通路和复合物的变化&#xff0c;因为这些变化在蛋白质水平上更容易被察觉。此外&#xff0c;由于scRNA-seq数据存在高噪声水平和零膨胀等固有…...

第四天 开始Unity Shader的学习之旅之Unity中的基础光照

Unity Shader的学习笔记 第四天 开始Unity Shader的学习之旅之Unity中的基础光照 文章目录 Unity Shader的学习笔记前言一、我们是如何看到这个世界的1. 光源2.吸收和散射3.着色 二、标准光照模型1. 自发光2. 高光反射① Phong模型② Blinn-Phong模型 3.漫反射4.环境光 总结 前…...

Lustre 语言的 Rust 生成相关的工作

目前 Lustre V6 编译器支持编译生成的语言为C语言。但也注意到&#xff0c;以 Rust 语言为生成目标语言&#xff0c;也存在若干相关工作。 rustre&#xff08;elegaanz&#xff09; 该项工作为 Lustre v6 语言的解析器&#xff0c;使用 Rust 语言实现。生成 Lustre AST。 项…...

std::endl为什么C++ 智能提示是函数?

在使用vscode 的C智能提示后&#xff0c;输入endl 后&#xff0c;提示的却是std::endl(basic_ostream<CharT, Traits> &os), 感觉比较奇怪&#xff0c;各种代码里都是直接用的std::endl 啊&#xff0c; 这里怎么变成函数了呢&#xff1f; 在 C 中&#xff0c;std::en…...