引言
再次热烈欢迎回到 每天一个Flutter开发小项目 系列博客! 历经九篇博客的沉淀,相信您已不再是 Flutter 开发的“新手村”玩家,而是掌握了扎实基础、具备一定项目经验的“进阶开发者”。 我们一路走来,从 UI 布局、状态管理、数据交互到持久化存储,已经构建了不少实用的功能型应用。
但一个优秀的应用,除了功能完备,更要拥有卓越的用户体验。 而动画 (Animation) 和 精美的 UI 设计 (UI Enhancement) 正是提升用户体验、赋予应用灵魂的关键所在。 生动流畅的动画能够引导用户操作、增强视觉反馈、提升应用趣味性; 精美的 UI 设计则能带来赏心悦目的视觉享受,提升应用品质感和专业度。 今天,我们将聚焦 Flutter 应用的 “颜值” 和 “动感” —— 动画与 UI 增强,并构建一个炫酷的动态天气应用,让您掌握 Flutter 应用“内外兼修”、打造卓越用户体验的秘诀。
通过本篇博客,您将深入学习:
AnimatedContainer
, AnimatedOpacity
, AnimatedPositioned
, Hero
, AnimatedBuilder
, TweenAnimationBuilder
等,掌握它们的特性和使用场景。AnimationController
精细化控制动画的播放、暂停、反向播放、重复播放等,打造更复杂的动画效果。Tween
定义动画起始值和结束值,使用 Curve
定义动画运动曲线,实现更丰富的动画效果。AnimatedBuilder
和 CustomPainter
构建自定义动画,实现更独特、更个性化的动画效果。项目简介: 动态天气应用
我们的动态天气应用将围绕以下核心功能和动效展开:
通过构建动态天气应用,我们将重点实践:
AnimatedContainer
, AnimatedOpacity
, AnimatedPositioned
等隐式动画组件,实现简单的 UI 元素动画效果 (例如,颜色渐变、透明度渐变、位置移动等)。AnimationController
精细化控制动画的播放、暂停、反向播放、重复播放等,实现更复杂的动画效果 (例如,循环播放的天气图标动画、动态背景动画)。Tween
定义动画起始值和结束值,使用 Curve
定义动画运动曲线,实现更丰富的动画效果 (例如,加速运动、减速运动、弹簧效果等)。AnimatedBuilder
和 CustomPainter
(可选,如果时间允许) 构建自定义动画,实现更独特、更个性化的动画效果 (例如,复杂的云朵飘动动画、更真实的雨滴飘落动画)。Flutter 动画核心概念与分类
在深入动画实战之前,我们先来深入理解 Flutter 动画的核心概念和分类,为后续的动画开发打牢理论基础。
Flutter 动画体系概览: Flutter 动画体系非常强大且灵活,它基于 Widget 重绘机制 和 Animation API 构建。 Flutter 动画的核心思想是在每一帧 (frame) 改变 Widget 的属性值,从而产生动画效果。 Flutter 动画主要分为以下几种类型:
隐式动画 (Implicit Animations): 最简单的动画类型,只需要修改 Widget 的某个属性值,Flutter 框架会自动为属性值的变化添加动画过渡效果。 隐式动画组件通常以 Animated
开头命名,例如,AnimatedContainer
, AnimatedOpacity
, AnimatedPositioned
, AnimatedDefaultTextStyle
等。 优点: 使用简单,代码简洁,易于上手。 缺点: 动画效果相对简单,只能实现属性值之间的线性过渡,无法进行更复杂的动画控制。 适用场景: 简单的 UI 元素动画,例如,颜色渐变、大小缩放、透明度渐变、位置移动等。
显式动画 (Explicit Animations): 更高级、更灵活的动画类型,需要手动创建和控制动画控制器 (AnimationController),并使用动画值 (Animation) 来驱动 Widget 属性值的变化。 显式动画可以实现更复杂、更精细的动画效果,例如,曲线运动、组合动画、自定义动画等。 优点: 动画效果丰富,可以实现各种复杂的动画效果,动画控制灵活,可以精确控制动画的播放、暂停、反向播放、重复播放等。 缺点: 使用相对复杂,代码量较多,学习曲线稍陡峭。 适用场景: 复杂的 UI 元素动画,例如,曲线运动、弹簧效果、路径动画、自定义动画等,以及需要精确控制动画播放流程的场景。
Hero 动画 (Hero Animations): 专门用于页面之间元素过渡的动画类型,可以在页面切换时,将一个 Widget “飞”到新的页面,实现平滑的页面过渡效果。 Hero 动画通常用于实现共享元素过渡动画,例如,在列表页点击图片进入详情页时,将列表页的图片“飞”到详情页的顶部。 优点: 页面过渡效果自然流畅,提升用户体验,实现共享元素过渡动画非常方便。 缺点: 主要用于页面过渡动画,不适用于 Widget 内部的动画。 适用场景: 页面之间元素过渡动画,例如,共享元素过渡动画、页面展开动画等。
物理动画 (Physics-based Animations): 基于物理引擎的动画类型,模拟真实的物理运动效果,例如,弹簧效果、阻尼效果、惯性滚动等。 物理动画可以使动画效果更加自然、生动、符合物理规律。 Flutter 提供了 SpringSimulation
, FrictionSimulation
等类来创建物理动画。 优点: 动画效果自然生动,符合物理规律,用户体验更佳。 缺点: 实现相对复杂,需要了解一定的物理知识。 适用场景: 需要模拟真实物理运动效果的动画,例如,拖拽动画、滚动动画、弹簧按钮等。
自定义动画 (Custom Animations): 最高级的动画类型,可以完全自定义动画的实现方式,不受 Flutter 框架提供的动画组件的限制。 自定义动画通常使用 AnimatedBuilder
和 CustomPainter
结合实现,可以实现各种天马行空的动画效果。 优点: 动画效果无限可能,可以实现任何想象力所及的动画效果。 缺点: 实现难度最高,代码量最大,需要深入理解 Flutter 动画原理和绘图机制。 适用场景: 需要实现非常独特、个性化的动画效果,或者 Flutter 框架提供的动画组件无法满足需求的场景。
动画控制器 (AnimationController): 显式动画的核心组件,用于控制动画的播放、暂停、反向播放、重复播放等。 AnimationController
继承自 Animation
,自身也是一个 Animation 对象,其值通常在 0.0 到 1.0 之间变化,表示动画的进度。 AnimationController
需要手动创建和 dispose,并在 Widget 的生命周期中进行管理。
动画值 (Animation): 动画的核心数据,表示动画在每一帧的值。 Animation
是一个泛型类,T
表示动画值的类型。 常用的动画值类型包括:
Animation
: 最常用的动画值类型,值在 0.0 到 1.0 之间变化,通常用于驱动 Widget 的数值属性动画,例如,透明度、缩放比例、旋转角度、位置偏移等。Animation
: 颜色动画值类型,值在两种颜色之间渐变,通常用于驱动 Widget 的颜色属性动画,例如,背景颜色、文字颜色、边框颜色等。Animation
: 尺寸动画值类型,值在两种尺寸之间渐变,通常用于驱动 Widget 的尺寸属性动画,例如,宽度、高度、字体大小等。Animation
: 偏移量动画值类型,值在两个偏移量之间渐变,通常用于驱动 Widget 的位置属性动画,例如,位置偏移、路径动画等。Tween 动画: 补间动画 (In-betweening Animation),是动画的一种常用技术,指定义动画的起始值和结束值,中间的值由系统自动计算 (补间)。 在 Flutter 中,Tween
类用于定义动画的起始值和结束值,Tween
对象与 Animation
结合使用,可以生成在起始值和结束值之间平滑变化的动画值。
Curve 曲线: 动画运动曲线,用于控制动画值变化的速率。 默认情况下,动画值是线性变化的 (匀速运动)。 使用 Curve
可以定义非线性运动曲线,例如,加速运动、减速运动、弹簧效果、回弹效果等,使动画效果更加自然生动。 Flutter 提供了多种预定义的 Curve 曲线,例如,Curves.linear
, Curves.easeIn
, Curves.easeOut
, Curves.easeInOut
, Curves.bounceIn
, Curves.bounceOut
等,也可以自定义 Curve 曲线。
实战步骤: 构建动态天气应用
接下来,我们将一步步使用 Flutter 动画技巧构建我们的动态天气应用。
步骤 1: 创建新的 Flutter 项目并添加必要的依赖
首先,创建一个新的 Flutter 项目,命名为 dynamic_weather_app
。
然后在 pubspec.yaml
文件中添加 http
, intl
和 cached_network_image
依赖 (如果需要缓存网络图片):
dependencies:
flutter:
sdk: flutter
http: ^0.13.0 # 网络请求
intl: ^0.18.0 # 日期时间格式化
cached_network_image: ^3.2.0 # 缓存网络图片 (可选,如果需要缓存天气图标等网络图片)
运行 flutter pub get
命令获取依赖。
步骤 2: 选择天气 API 接口并定义数据模型
选择一个免费的天气 API 接口,例如 OpenWeatherMap (需要注册并获取 API Key,免费版足够使用) 或 和风天气 (也提供免费 API)。
https://openweathermap.org/
注册后在 API 页面获取 API Key,免费版提供当前天气、5天天气预报等 API。 API 文档: https://openweathermap.org/current
https://www.qweather.com/
注册后在控制台创建项目,获取 API Key,免费版提供当前天气、3天天气预报等 API。 API 文档: https://dev.qweather.com/docs/api/weather/weather-now/
本篇博客示例将使用 OpenWeatherMap API。 您需要在 OpenWeatherMap 网站注册账号并获取 API Key,然后在代码中替换
为您自己的 API Key。
根据 OpenWeatherMap API 返回的 JSON 数据结构,定义天气数据模型 (例如,Weather
, CurrentWeather
, WeatherCondition
, Temperature
, Wind
, City
等类)。 数据模型需要包含应用需要展示的天气信息,例如,城市名称、天气状况、天气描述、温度、体感温度、最高温度、最低温度、湿度、风速、风向、天气图标代码等。
创建 lib/models/weather.dart
文件,定义天气数据模型类,例如 (示例数据模型,请根据实际 API 返回数据结构进行调整):
class Weather {
final String cityName; // 城市名称
final String description; // 天气描述 (例如,Clear sky, Mist)
final String iconCode; // 天气图标代码 (例如,01d, 50n)
final double temperature; // 温度 (摄氏度)
final double feelsLike; // 体感温度 (摄氏度)
final double humidity; // 湿度 (%)
final double windSpeed; // 风速 (米/秒)
const Weather({
// 使用 const 构造函数
required this.cityName,
required this.description,
required this.iconCode,
required this.temperature,
required this.feelsLike,
required this.humidity,
required this.windSpeed,
});
factory Weather.fromJson(Map<String, dynamic> json) {
// 工厂构造函数,从 JSON 数据创建 Weather 对象
final weatherCondition = json['weather'][0]; // 天气状况信息 (数组的第一个元素)
final main = json['main']; // 主要天气信息
final wind = json['wind']; // 风力信息
return Weather(
cityNa