进阶实战 Flutter for OpenHarmony:Transform 变换矩阵系统 - 高级视觉效果实现

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net


一、Transform 系统架构深度解析

在现代移动应用开发中,变换效果是提升用户界面视觉层次和交互体验的重要手段。Flutter 提供了强大的 Transform 组件,通过矩阵运算实现各种 2D 和 3D 变换效果。理解变换矩阵的数学原理,是掌握高级视觉效果的关键。

📱 1.1 Transform 核心概念

Transform 是 Flutter 中用于对子组件进行几何变换的 Widget。它通过修改绘制时的变换矩阵,实现平移、旋转、缩放、倾斜等效果,而不会影响子组件的实际布局位置。

Transform 与布局的关系:

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                    Transform 工作原理                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   布局阶段 (Layout Phase)                                       │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │  子组件按照原始尺寸和位置进行布局                         │   │
│   │  Transform 不影响子组件的布局空间                         │   │
│   └─────────────────────────────────────────────────────────┘   │
│                              │                                   │
│                              ▼                                   │
│   绘制阶段 (Paint Phase)                                         │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │  Transform 在绘制前应用变换矩阵                          │   │
│   │  子组件在变换后的坐标系中绘制                             │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Transform 核心属性:

属性 类型 说明 应用场景
transform Matrix4 变换矩阵 自定义复杂变换
origin Offset 变换原点 控制变换中心点
alignment Alignment 对齐方式 相对于子组件的变换原点
transformHitTests bool 是否变换点击测试 控制交互区域
child Widget 子组件 被变换的内容

🔬 1.2 变换矩阵数学原理

变换矩阵是线性代数在计算机图形学中的核心应用。在 2D 图形变换中,我们使用 3x3 齐次坐标矩阵;在 3D 变换中,使用 4x4 齐次坐标矩阵。

4x4 变换矩阵结构:

复制代码
┌                                              ┐
│  m00  m01  m02  m03  │  X轴缩放  Y轴倾斜  透视  X轴平移
│  m10  m11  m12  m13  │  X轴倾斜  Y轴缩放  透视  Y轴平移
│  m20  m21  m22  m23  │  透视X    透视Y    缩放  Z轴平移
│  m30  m31  m32  m33  │  透视W    透视W    透视  齐次坐标
└                                              ┘

基本变换矩阵:

dart 复制代码
// 平移矩阵
Matrix4.translation(Vector3(10, 20, 0))
// 等价于:
// [1  0  0  10]
// [0  1  0  20]
// [0  0  1  0 ]
// [0  0  0  1 ]

// 旋转矩阵(绕Z轴)
Matrix4.rotationZ(pi / 4)
// 等价于:
// [cos(θ)  -sin(θ)  0  0]
// [sin(θ)   cos(θ)  0  0]
// [0        0       1  0]
// [0        0       0  1]

// 缩放矩阵
Matrix4.diagonal3(Vector3(2, 2, 1))
// 等价于:
// [2  0  0  0]
// [0  2  0  0]
// [0  0  1  0]
// [0  0  0  1]

🎯 1.3 Matrix4 常用方法详解

Matrix4 是 Flutter 中表示 4x4 变换矩阵的类,提供了丰富的变换方法:

dart 复制代码
// 创建单位矩阵
Matrix4.identity()

// 创建平移矩阵
Matrix4.translation(Vector3(x, y, z))

// 创建旋转矩阵
Matrix4.rotationX(angle)  // 绕X轴旋转
Matrix4.rotationY(angle)  // 绕Y轴旋转
Matrix4.rotationZ(angle)  // 绕Z轴旋转

// 创建缩放矩阵
Matrix4.diagonal3(Vector3(x, y, z))
Matrix4.diagonal3Values(x, y, z)

// 创建组合矩阵
Matrix4.identity()
  ..translate(x, y, z)
  ..rotateZ(angle)
  ..scale(x, y, z)

// 矩阵运算
matrix.multiply(other)      // 矩阵乘法
matrix.invert()             // 矩阵求逆
matrix.transpose()          // 矩阵转置

矩阵组合顺序的重要性:

矩阵乘法不满足交换律,因此变换的顺序会影响最终结果。通常按照 缩放 -> 旋转 -> 平移 的顺序应用变换:

dart 复制代码
// 正确的组合顺序
Matrix4.identity()
  ..scale(2.0, 2.0, 1.0)    // 先缩放
  ..rotateZ(pi / 4)          // 再旋转
  ..translate(100, 0, 0);    // 最后平移

// 错误的组合顺序(结果不同)
Matrix4.identity()
  ..translate(100, 0, 0)     // 先平移
  ..rotateZ(pi / 4)          // 再旋转
  ..scale(2.0, 2.0, 1.0);    // 最后缩放

二、基础变换效果实现

掌握了 Transform 的基本原理后,让我们通过实际的代码示例来学习各种变换效果的实现方法。

👆 2.1 平移变换

平移变换是最基本的变换类型,它将组件从一个位置移动到另一个位置。Flutter 提供了多种实现平移变换的方式。

dart 复制代码
import 'dart:math';
import 'package:flutter/material.dart';

/// 平移变换示例
class TranslateDemo extends StatefulWidget {
  const TranslateDemo({super.key});

  @override
  State<TranslateDemo> createState() => _TranslateDemoState();
}

class _TranslateDemoState extends State<TranslateDemo> {
  double _offsetX = 0.0;
  double _offsetY = 0.0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('平移变换')),
      body: Column(
        children: [
          Expanded(
            child: Center(
              child: Stack(
                children: [
                  Container(
                    width: 100,
                    height: 100,
                    decoration: BoxDecoration(
                      color: Colors.grey.withOpacity(0.3),
                      borderRadius: BorderRadius.circular(12),
                      border: Border.all(color: Colors.grey, width: 2),
                    ),
                  ),
                  Transform.translate(
                    offset: Offset(_offsetX, _offsetY),
                    child: Container(
                      width: 100,
                      height: 100,
                      decoration: BoxDecoration(
                        color: Colors.blue.withOpacity(0.8),
                        borderRadius: BorderRadius.circular(12),
                        boxShadow: [
                          BoxShadow(
                            color: Colors.blue.withOpacity(0.3),
                            blurRadius: 10,
                            spreadRadius: 5,
                          ),
                        ],
                      ),
                      child: const Center(
                        child: Text(
                          '平移',
                          style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
                        ),
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ),
          Container(
            padding: const EdgeInsets.all(16),
            decoration: BoxDecoration(
              color: Colors.grey[100],
              borderRadius: const BorderRadius.vertical(top: Radius.circular(20)),
            ),
            child: Column(
              children: [
                Row(
                  children: [
                    const SizedBox(width: 60, child: Text('X轴:')),
                    Expanded(
                      child: Slider(
                        value: _offsetX,
                        min: -100,
                        max: 100,
                        divisions: 40,
                        activeColor: Colors.blue,
                        onChanged: (value) => setState(() => _offsetX = value),
                      ),
                    ),
                    SizedBox(width: 60, child: Text('${_offsetX.toStringAsFixed(1)}')),
                  ],
                ),
                Row(
                  children: [
                    const SizedBox(width: 60, child: Text('Y轴:')),
                    Expanded(
                      child: Slider(
                        value: _offsetY,
                        min: -100,
                        max: 100,
                        divisions: 40,
                        activeColor: Colors.green,
                        onChanged: (value) => setState(() => _offsetY = value),
                      ),
                    ),
                    SizedBox(width: 60, child: Text('${_offsetY.toStringAsFixed(1)}')),
                  ],
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

平移变换要点:

  • Transform.translate 是最简单的平移方式,直接指定偏移量
  • 平移不影响组件的布局位置,灰色虚线框显示了原始位置
  • 平移后的组件仍然可以响应点击事件(可通过 transformHitTests: false 禁用)

🔧 2.2 旋转变换

旋转变换可以使组件围绕指定点进行旋转。Flutter 支持绕 X、Y、Z 三个轴的旋转,可以创建 2D 和 3D 旋转效果。

dart 复制代码
/// 旋转变换示例
class RotateDemo extends StatefulWidget {
  const RotateDemo({super.key});

  @override
  State<RotateDemo> createState() => _RotateDemoState();
}

class _RotateDemoState extends State<RotateDemo> {
  double _angle = 0.0;
  Alignment _alignment = Alignment.center;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('旋转变换')),
      body: Column(
        children: [
          Expanded(
            child: Center(
              child: Stack(
                alignment: Alignment.center,
                children: [
                  Container(
                    width: 150,
                    height: 150,
                    decoration: BoxDecoration(
                      color: Colors.grey.withOpacity(0.2),
                      borderRadius: BorderRadius.circular(16),
                    ),
                  ),
                  Transform.rotate(
                    angle: _angle,
                    alignment: _alignment,
                    child: Container(
                      width: 150,
                      height: 150,
                      decoration: BoxDecoration(
                        gradient: const LinearGradient(
                          colors: [Colors.purple, Colors.pink],
                          begin: Alignment.topLeft,
                          end: Alignment.bottomRight,
                        ),
                        borderRadius: BorderRadius.circular(16),
                        boxShadow: [
                          BoxShadow(
                            color: Colors.purple.withOpacity(0.4),
                            blurRadius: 15,
                            offset: const Offset(0, 8),
                          ),
                        ],
                      ),
                      child: Center(
                        child: Text(
                          '${(_angle * 180 / pi).toStringAsFixed(0)}°',
                          style: const TextStyle(
                            color: Colors.white,
                            fontSize: 32,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                      ),
                    ),
                  ),
                  Positioned(
                    top: 0,
                    left: 0,
                    child: Container(
                      width: 12,
                      height: 12,
                      decoration: const BoxDecoration(
                        color: Colors.red,
                        shape: BoxShape.circle,
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ),
          Container(
            padding: const EdgeInsets.all(16),
            decoration: BoxDecoration(
              color: Colors.grey[100],
              borderRadius: const BorderRadius.vertical(top: Radius.circular(20)),
            ),
            child: Column(
              children: [
                Row(
                  children: [
                    const SizedBox(width: 60, child: Text('角度:')),
                    Expanded(
                      child: Slider(
                        value: _angle,
                        min: 0,
                        max: 2 * pi,
                        divisions: 36,
                        activeColor: Colors.purple,
                        onChanged: (value) => setState(() => _angle = value),
                      ),
                    ),
                    SizedBox(width: 60, child: Text('${(_angle * 180 / pi).toStringAsFixed(0)}°')),
                  ],
                ),
                const SizedBox(height: 8),
                Wrap(
                  spacing: 8,
                  children: [
                    _buildAlignmentChip('中心', Alignment.center),
                    _buildAlignmentChip('左上', Alignment.topLeft),
                    _buildAlignmentChip('右上', Alignment.topRight),
                    _buildAlignmentChip('左下', Alignment.bottomLeft),
                    _buildAlignmentChip('右下', Alignment.bottomRight),
                  ],
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildAlignmentChip(String label, Alignment alignment) {
    final isSelected = _alignment == alignment;
    return FilterChip(
      label: Text(label),
      selected: isSelected,
      selectedColor: Colors.purple.withOpacity(0.2),
      checkmarkColor: Colors.purple,
      onSelected: (_) => setState(() => _alignment = alignment),
    );
  }
}

旋转变换要点:

  • Transform.rotate 使用弧度制角度,angle: pi / 2 表示旋转 90 度
  • alignment 参数控制旋转中心点,默认为中心
  • 角度为正值时顺时针旋转,负值时逆时针旋转
  • 红色圆点标记了旋转中心位置

🎨 2.3 缩放变换

缩放变换可以改变组件的大小,支持均匀缩放和非均匀缩放。

dart 复制代码
/// 缩放变换示例
class ScaleDemo extends StatefulWidget {
  const ScaleDemo({super.key});

  @override
  State<ScaleDemo> createState() => _ScaleDemoState();
}

class _ScaleDemoState extends State<ScaleDemo> {
  double _scaleX = 1.0;
  double _scaleY = 1.0;
  Alignment _alignment = Alignment.center;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('缩放变换')),
      body: Column(
        children: [
          Expanded(
            child: Center(
              child: Stack(
                alignment: Alignment.center,
                children: [
                  Container(
                    width: 120,
                    height: 120,
                    decoration: BoxDecoration(
                      color: Colors.grey.withOpacity(0.2),
                      borderRadius: BorderRadius.circular(12),
                    ),
                  ),
                  Transform.scale(
                    scaleX: _scaleX,
                    scaleY: _scaleY,
                    alignment: _alignment,
                    child: Container(
                      width: 120,
                      height: 120,
                      decoration: BoxDecoration(
                        gradient: LinearGradient(
                          colors: [Colors.orange, Colors.deepOrange],
                          begin: Alignment.topLeft,
                          end: Alignment.bottomRight,
                        ),
                        borderRadius: BorderRadius.circular(12),
                        boxShadow: [
                          BoxShadow(
                            color: Colors.orange.withOpacity(0.4),
                            blurRadius: 15,
                            offset: const Offset(0, 8),
                          ),
                        ],
                      ),
                      child: Center(
                        child: Text(
                          '${_scaleX.toStringAsFixed(1)}x\n${_scaleY.toStringAsFixed(1)}x',
                          textAlign: TextAlign.center,
                          style: const TextStyle(
                            color: Colors.white,
                            fontSize: 24,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ),
          Container(
            padding: const EdgeInsets.all(16),
            decoration: BoxDecoration(
              color: Colors.grey[100],
              borderRadius: const BorderRadius.vertical(top: Radius.circular(20)),
            ),
            child: Column(
              children: [
                Row(
                  children: [
                    const SizedBox(width: 60, child: Text('X缩放:')),
                    Expanded(
                      child: Slider(
                        value: _scaleX,
                        min: 0.1,
                        max: 2.0,
                        divisions: 19,
                        activeColor: Colors.orange,
                        onChanged: (value) => setState(() => _scaleX = value),
                      ),
                    ),
                    SizedBox(width: 60, child: Text('${_scaleX.toStringAsFixed(1)}x')),
                  ],
                ),
                Row(
                  children: [
                    const SizedBox(width: 60, child: Text('Y缩放:')),
                    Expanded(
                      child: Slider(
                        value: _scaleY,
                        min: 0.1,
                        max: 2.0,
                        divisions: 19,
                        activeColor: Colors.deepOrange,
                        onChanged: (value) => setState(() => _scaleY = value),
                      ),
                    ),
                    SizedBox(width: 60, child: Text('${_scaleY.toStringAsFixed(1)}x')),
                  ],
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

缩放变换要点:

  • 缩放值为 1.0 表示原始大小,小于 1.0 表示缩小,大于 1.0 表示放大
  • scaleXscaleY 可以分别设置,实现非均匀缩放
  • 缩放值为负数时会产生镜像翻转效果
  • 缩放不影响组件的布局空间

三、3D 变换效果实现

3D 变换可以为应用增添立体感和深度感,是创建沉浸式用户体验的重要技术。

🌊 3.1 3D 透视旋转

3D 透视旋转通过调整视角,使组件看起来具有立体效果。Flutter 使用 Matrix4 实现 3D 变换。

dart 复制代码
/// 3D透视旋转示例
class PerspectiveDemo extends StatefulWidget {
  const PerspectiveDemo({super.key});

  @override
  State<PerspectiveDemo> createState() => _PerspectiveDemoState();
}

class _PerspectiveDemoState extends State<PerspectiveDemo> {
  double _rotateX = 0.0;
  double _rotateY = 0.0;
  double _perspective = 0.001;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('3D透视旋转')),
      body: Column(
        children: [
          Expanded(
            child: Center(
              child: Transform(
                transform: Matrix4.identity()
                  ..setEntry(3, 2, _perspective)
                  ..rotateX(_rotateX)
                  ..rotateY(_rotateY),
                alignment: Alignment.center,
                child: Container(
                  width: 200,
                  height: 200,
                  decoration: BoxDecoration(
                    gradient: LinearGradient(
                      colors: [Colors.indigo, Colors.purple],
                      begin: Alignment.topLeft,
                      end: Alignment.bottomRight,
                    ),
                    borderRadius: BorderRadius.circular(20),
                    boxShadow: [
                      BoxShadow(
                        color: Colors.indigo.withOpacity(0.5),
                        blurRadius: 30,
                        offset: const Offset(0, 15),
                      ),
                    ],
                  ),
                  child: Center(
                    child: Column(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        const Icon(
                          Icons.threed_rotation,
                          color: Colors.white,
                          size: 48,
                        ),
                        const SizedBox(height: 12),
                        const Text(
                          '3D变换',
                          style: TextStyle(
                            color: Colors.white,
                            fontSize: 24,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                        Text(
                          'X: ${(_rotateX * 180 / pi).toStringAsFixed(0)}° Y: ${(_rotateY * 180 / pi).toStringAsFixed(0)}°',
                          style: TextStyle(
                            color: Colors.white.withOpacity(0.8),
                            fontSize: 14,
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ),
            ),
          ),
          Container(
            padding: const EdgeInsets.all(16),
            decoration: BoxDecoration(
              color: Colors.grey[100],
              borderRadius: const BorderRadius.vertical(top: Radius.circular(20)),
            ),
            child: Column(
              children: [
                Row(
                  children: [
                    const SizedBox(width: 80, child: Text('X轴旋转:')),
                    Expanded(
                      child: Slider(
                        value: _rotateX,
                        min: -pi / 2,
                        max: pi / 2,
                        divisions: 36,
                        activeColor: Colors.indigo,
                        onChanged: (value) => setState(() => _rotateX = value),
                      ),
                    ),
                  ],
                ),
                Row(
                  children: [
                    const SizedBox(width: 80, child: Text('Y轴旋转:')),
                    Expanded(
                      child: Slider(
                        value: _rotateY,
                        min: -pi / 2,
                        max: pi / 2,
                        divisions: 36,
                        activeColor: Colors.purple,
                        onChanged: (value) => setState(() => _rotateY = value),
                      ),
                    ),
                  ],
                ),
                Row(
                  children: [
                    const SizedBox(width: 80, child: Text('透视强度:')),
                    Expanded(
                      child: Slider(
                        value: _perspective,
                        min: 0,
                        max: 0.01,
                        divisions: 20,
                        activeColor: Colors.teal,
                        onChanged: (value) => setState(() => _perspective = value),
                      ),
                    ),
                  ],
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

3D 变换关键技术:

  • setEntry(3, 2, value) 设置透视矩阵元素,值越大透视效果越强
  • rotateXrotateY 分别控制绕 X 轴和 Y 轴的旋转
  • 透视效果模拟了人眼观察物体时的近大远小现象

💬 3.2 翻转卡片效果

翻转卡片是 3D 变换的经典应用,常用于展示卡片正反两面内容。

dart 复制代码
/// 翻转卡片示例
class FlipCardDemo extends StatefulWidget {
  const FlipCardDemo({super.key});

  @override
  State<FlipCardDemo> createState() => _FlipCardDemoState();
}

class _FlipCardDemoState extends State<FlipCardDemo>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;
  bool _isFront = true;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(milliseconds: 600),
      vsync: this,
    );
    _animation = Tween<double>(begin: 0, end: pi).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  void _flip() {
    if (_isFront) {
      _controller.forward();
    } else {
      _controller.reverse();
    }
    setState(() => _isFront = !_isFront);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('翻转卡片')),
      body: Center(
        child: GestureDetector(
          onTap: _flip,
          child: AnimatedBuilder(
            animation: _animation,
            builder: (context, child) {
              final angle = _animation.value;
              final isFrontVisible = angle < pi / 2;
              
              return Transform(
                transform: Matrix4.identity()
                  ..setEntry(3, 2, 0.002)
                  ..rotateY(angle),
                alignment: Alignment.center,
                child: isFrontVisible
                    ? _buildFrontCard()
                    : Transform(
                        transform: Matrix4.rotationY(pi),
                        alignment: Alignment.center,
                        child: _buildBackCard(),
                      ),
              );
            },
          ),
        ),
      ),
    );
  }

  Widget _buildFrontCard() {
    return Container(
      width: 280,
      height: 180,
      decoration: BoxDecoration(
        gradient: const LinearGradient(
          colors: [Colors.blue, Colors.cyan],
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
        ),
        borderRadius: BorderRadius.circular(20),
        boxShadow: [
          BoxShadow(
            color: Colors.blue.withOpacity(0.4),
            blurRadius: 20,
            offset: const Offset(0, 10),
          ),
        ],
      ),
      child: const Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Icon(Icons.credit_card, color: Colors.white, size: 48),
            SizedBox(height: 12),
            Text(
              '点击翻转',
              style: TextStyle(
                color: Colors.white,
                fontSize: 20,
                fontWeight: FontWeight.bold,
              ),
            ),
            Text(
              '正面',
              style: TextStyle(color: Colors.white70, fontSize: 14),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildBackCard() {
    return Container(
      width: 280,
      height: 180,
      decoration: BoxDecoration(
        gradient: const LinearGradient(
          colors: [Colors.deepPurple, Colors.purple],
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
        ),
        borderRadius: BorderRadius.circular(20),
        boxShadow: [
          BoxShadow(
            color: Colors.purple.withOpacity(0.4),
            blurRadius: 20,
            offset: const Offset(0, 10),
          ),
        ],
      ),
      child: const Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Icon(Icons.info, color: Colors.white, size: 48),
            SizedBox(height: 12),
            Text(
              '背面内容',
              style: TextStyle(
                color: Colors.white,
                fontSize: 20,
                fontWeight: FontWeight.bold,
              ),
            ),
            Text(
              '再次点击翻转',
              style: TextStyle(color: Colors.white70, fontSize: 14),
            ),
          ],
        ),
      ),
    );
  }
}

翻转卡片实现要点:

  • 使用 AnimationController 控制翻转动画
  • 当角度超过 90 度时切换显示正反面
  • 背面需要额外旋转 180 度才能正确显示

四、组合变换效果实现

组合变换通过将多个变换效果叠加,创造出更复杂的视觉效果。

🔐 4.1 动画变换组合

将 Transform 与动画结合,可以创建动态的变换效果。

dart 复制代码
/// 动画变换组合示例
class AnimatedTransformDemo extends StatefulWidget {
  const AnimatedTransformDemo({super.key});

  @override
  State<AnimatedTransformDemo> createState() => _AnimatedTransformDemoState();
}

class _AnimatedTransformDemoState extends State<AnimatedTransformDemo>
    with TickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _rotationAnimation;
  late Animation<double> _scaleAnimation;
  late Animation<Offset> _positionAnimation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    )..repeat(reverse: true);

    _rotationAnimation = Tween<double>(begin: 0, end: 2 * pi).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
    );

    _scaleAnimation = Tween<double>(begin: 0.8, end: 1.2).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
    );

    _positionAnimation = Tween<Offset>(
      begin: Offset.zero,
      end: const Offset(0, -50),
    ).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('动画变换组合')),
      body: Center(
        child: AnimatedBuilder(
          animation: _controller,
          builder: (context, child) {
            return Transform(
              transform: Matrix4.identity()
                ..translate(
                  _positionAnimation.value.dx,
                  _positionAnimation.value.dy,
                )
                ..rotateZ(_rotationAnimation.value)
                ..scale(_scaleAnimation.value),
              alignment: Alignment.center,
              child: Container(
                width: 150,
                height: 150,
                decoration: BoxDecoration(
                  gradient: LinearGradient(
                    colors: [
                      Colors.primaries[(_controller.value * 10).floor() % Colors.primaries.length],
                      Colors.primaries[(_controller.value * 10 + 1).floor() % Colors.primaries.length],
                    ],
                    begin: Alignment.topLeft,
                    end: Alignment.bottomRight,
                  ),
                  borderRadius: BorderRadius.circular(20),
                  boxShadow: [
                    BoxShadow(
                      color: Colors.black.withOpacity(0.3),
                      blurRadius: 20,
                      offset: const Offset(0, 10),
                    ),
                  ],
                ),
                child: const Center(
                  child: Text(
                    '组合变换',
                    style: TextStyle(
                      color: Colors.white,
                      fontSize: 20,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
              ),
            );
          },
        ),
      ),
    );
  }
}

🎵 4.2 交互式变换控制器

创建一个可以实时调整各种变换参数的交互界面。

dart 复制代码
/// 交互式变换控制器
class InteractiveTransformDemo extends StatefulWidget {
  const InteractiveTransformDemo({super.key});

  @override
  State<InteractiveTransformDemo> createState() => _InteractiveTransformDemoState();
}

class _InteractiveTransformDemoState extends State<InteractiveTransformDemo> {
  double _translateX = 0;
  double _translateY = 0;
  double _rotateZ = 0;
  double _scale = 1.0;
  double _perspective = 0.001;
  double _rotateX = 0;
  double _rotateY = 0;

  void _resetAll() {
    setState(() {
      _translateX = 0;
      _translateY = 0;
      _rotateZ = 0;
      _scale = 1.0;
      _perspective = 0.001;
      _rotateX = 0;
      _rotateY = 0;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('交互式变换控制器'),
        actions: [
          IconButton(icon: const Icon(Icons.refresh), onPressed: _resetAll),
        ],
      ),
      body: Column(
        children: [
          Expanded(
            flex: 2,
            child: Center(
              child: Transform(
                transform: Matrix4.identity()
                  ..setEntry(3, 2, _perspective)
                  ..translate(_translateX, _translateY)
                  ..rotateX(_rotateX)
                  ..rotateY(_rotateY)
                  ..rotateZ(_rotateZ)
                  ..scale(_scale),
                alignment: Alignment.center,
                child: Container(
                  width: 160,
                  height: 160,
                  decoration: BoxDecoration(
                    gradient: const LinearGradient(
                      colors: [Colors.teal, Colors.cyan],
                      begin: Alignment.topLeft,
                      end: Alignment.bottomRight,
                    ),
                    borderRadius: BorderRadius.circular(20),
                    boxShadow: [
                      BoxShadow(
                        color: Colors.teal.withOpacity(0.5),
                        blurRadius: 25,
                        offset: const Offset(0, 12),
                      ),
                    ],
                  ),
                  child: const Center(
                    child: Column(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        Icon(Icons.transform, color: Colors.white, size: 40),
                        SizedBox(height: 8),
                        Text(
                          '变换预览',
                          style: TextStyle(
                            color: Colors.white,
                            fontSize: 18,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ),
            ),
          ),
          Expanded(
            flex: 3,
            child: Container(
              padding: const EdgeInsets.all(16),
              decoration: BoxDecoration(
                color: Colors.grey[100],
                borderRadius: const BorderRadius.vertical(top: Radius.circular(20)),
              ),
              child: ListView(
                children: [
                  _buildSliderRow('平移X', _translateX, -100, 100, (v) => _translateX = v),
                  _buildSliderRow('平移Y', _translateY, -100, 100, (v) => _translateY = v),
                  _buildSliderRow('旋转Z', _rotateZ, -pi, pi, (v) => _rotateZ = v, isAngle: true),
                  _buildSliderRow('缩放', _scale, 0.1, 2.0, (v) => _scale = v),
                  _buildSliderRow('透视', _perspective, 0, 0.01, (v) => _perspective = v),
                  _buildSliderRow('旋转X', _rotateX, -pi / 2, pi / 2, (v) => _rotateX = v, isAngle: true),
                  _buildSliderRow('旋转Y', _rotateY, -pi / 2, pi / 2, (v) => _rotateY = v, isAngle: true),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildSliderRow(
    String label,
    double value,
    double min,
    double max,
    Function(double) onChanged, {
    bool isAngle = false,
  }) {
    return Row(
      children: [
        SizedBox(width: 70, child: Text(label)),
        Expanded(
          child: Slider(
            value: value,
            min: min,
            max: max,
            divisions: 20,
            activeColor: Colors.teal,
            onChanged: (v) => setState(() => onChanged(v)),
          ),
        ),
        SizedBox(
          width: 60,
          child: Text(
            isAngle ? '${(value * 180 / pi).toStringAsFixed(0)}°' : value.toStringAsFixed(2),
            textAlign: TextAlign.right,
          ),
        ),
      ],
    );
  }
}

五、完整代码示例

以下是本文所有示例的完整代码,可以直接运行体验:

dart 复制代码
import 'dart:math';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
        useMaterial3: true,
      ),
      home: const TransformHomePage(),
    );
  }
}

class TransformHomePage extends StatelessWidget {
  const TransformHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('🔄 Transform 变换矩阵系统')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          _buildSectionCard(context, title: '平移变换', description: '移动组件位置', icon: Icons.open_with, color: Colors.blue, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const TranslateDemo()))),
          _buildSectionCard(context, title: '旋转变换', description: '旋转组件角度', icon: Icons.rotate_right, color: Colors.purple, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const RotateDemo()))),
          _buildSectionCard(context, title: '缩放变换', description: '改变组件大小', icon: Icons.zoom_out_map, color: Colors.orange, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const ScaleDemo()))),
          _buildSectionCard(context, title: '3D透视旋转', description: '立体旋转效果', icon: Icons.threed_rotation, color: Colors.indigo, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const PerspectiveDemo()))),
          _buildSectionCard(context, title: '翻转卡片', description: '正反面切换效果', icon: Icons.flip, color: Colors.cyan, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const FlipCardDemo()))),
          _buildSectionCard(context, title: '动画变换组合', description: '动态变换效果', icon: Icons.animation, color: Colors.pink, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const AnimatedTransformDemo()))),
          _buildSectionCard(context, title: '交互式变换控制器', description: '实时调整参数', icon: Icons.tune, color: Colors.teal, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const InteractiveTransformDemo()))),
        ],
      ),
    );
  }

  Widget _buildSectionCard(BuildContext context, {required String title, required String description, required IconData icon, required Color color, required VoidCallback onTap}) {
    return Card(
      margin: const EdgeInsets.only(bottom: 12),
      child: InkWell(
        onTap: onTap,
        borderRadius: BorderRadius.circular(12),
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Row(
            children: [
              Container(width: 56, height: 56, decoration: BoxDecoration(color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(12)), child: Icon(icon, color: color, size: 28)),
              const SizedBox(width: 16),
              Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [Text(title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), const SizedBox(height: 4), Text(description, style: TextStyle(fontSize: 13, color: Colors.grey[600]))])),
              Icon(Icons.chevron_right, color: Colors.grey[400]),
            ],
          ),
        ),
      ),
    );
  }
}

class TranslateDemo extends StatefulWidget {
  const TranslateDemo({super.key});
  @override
  State<TranslateDemo> createState() => _TranslateDemoState();
}

class _TranslateDemoState extends State<TranslateDemo> {
  double _offsetX = 0.0;
  double _offsetY = 0.0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('平移变换')),
      body: Column(
        children: [
          Expanded(
            child: Center(
              child: Stack(
                children: [
                  Container(width: 100, height: 100, decoration: BoxDecoration(color: Colors.grey.withOpacity(0.3), borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey, width: 2))),
                  Transform.translate(
                    offset: Offset(_offsetX, _offsetY),
                    child: Container(width: 100, height: 100, decoration: BoxDecoration(color: Colors.blue.withOpacity(0.8), borderRadius: BorderRadius.circular(12), boxShadow: [BoxShadow(color: Colors.blue.withOpacity(0.3), blurRadius: 10, spreadRadius: 5)]), child: const Center(child: Text('平移', style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold)))),
                  ),
                ],
              ),
            ),
          ),
          Container(
            padding: const EdgeInsets.all(16),
            decoration: BoxDecoration(color: Colors.grey[100], borderRadius: const BorderRadius.vertical(top: Radius.circular(20))),
            child: Column(
              children: [
                Row(children: [const SizedBox(width: 60, child: Text('X轴:')), Expanded(child: Slider(value: _offsetX, min: -100, max: 100, divisions: 40, activeColor: Colors.blue, onChanged: (value) => setState(() => _offsetX = value))), SizedBox(width: 60, child: Text('${_offsetX.toStringAsFixed(1)}'))]),
                Row(children: [const SizedBox(width: 60, child: Text('Y轴:')), Expanded(child: Slider(value: _offsetY, min: -100, max: 100, divisions: 40, activeColor: Colors.green, onChanged: (value) => setState(() => _offsetY = value))), SizedBox(width: 60, child: Text('${_offsetY.toStringAsFixed(1)}'))]),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

class RotateDemo extends StatefulWidget {
  const RotateDemo({super.key});
  @override
  State<RotateDemo> createState() => _RotateDemoState();
}

class _RotateDemoState extends State<RotateDemo> {
  double _angle = 0.0;
  Alignment _alignment = Alignment.center;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('旋转变换')),
      body: Column(
        children: [
          Expanded(
            child: Center(
              child: Stack(
                alignment: Alignment.center,
                children: [
                  Container(width: 150, height: 150, decoration: BoxDecoration(color: Colors.grey.withOpacity(0.2), borderRadius: BorderRadius.circular(16))),
                  Transform.rotate(
                    angle: _angle,
                    alignment: _alignment,
                    child: Container(
                      width: 150,
                      height: 150,
                      decoration: BoxDecoration(gradient: const LinearGradient(colors: [Colors.purple, Colors.pink], begin: Alignment.topLeft, end: Alignment.bottomRight), borderRadius: BorderRadius.circular(16), boxShadow: [BoxShadow(color: Colors.purple.withOpacity(0.4), blurRadius: 15, offset: const Offset(0, 8))]),
                      child: Center(child: Text('${(_angle * 180 / pi).toStringAsFixed(0)}°', style: const TextStyle(color: Colors.white, fontSize: 32, fontWeight: FontWeight.bold))),
                    ),
                  ),
                ],
              ),
            ),
          ),
          Container(
            padding: const EdgeInsets.all(16),
            decoration: BoxDecoration(color: Colors.grey[100], borderRadius: const BorderRadius.vertical(top: Radius.circular(20))),
            child: Column(
              children: [
                Row(children: [const SizedBox(width: 60, child: Text('角度:')), Expanded(child: Slider(value: _angle, min: 0, max: 2 * pi, divisions: 36, activeColor: Colors.purple, onChanged: (value) => setState(() => _angle = value))), SizedBox(width: 60, child: Text('${(_angle * 180 / pi).toStringAsFixed(0)}°'))]),
                const SizedBox(height: 8),
                Wrap(spacing: 8, children: [_buildAlignmentChip('中心', Alignment.center), _buildAlignmentChip('左上', Alignment.topLeft), _buildAlignmentChip('右上', Alignment.topRight), _buildAlignmentChip('左下', Alignment.bottomLeft), _buildAlignmentChip('右下', Alignment.bottomRight)]),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildAlignmentChip(String label, Alignment alignment) {
    final isSelected = _alignment == alignment;
    return FilterChip(label: Text(label), selected: isSelected, selectedColor: Colors.purple.withOpacity(0.2), checkmarkColor: Colors.purple, onSelected: (_) => setState(() => _alignment = alignment));
  }
}

class ScaleDemo extends StatefulWidget {
  const ScaleDemo({super.key});
  @override
  State<ScaleDemo> createState() => _ScaleDemoState();
}

class _ScaleDemoState extends State<ScaleDemo> {
  double _scaleX = 1.0;
  double _scaleY = 1.0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('缩放变换')),
      body: Column(
        children: [
          Expanded(
            child: Center(
              child: Stack(
                alignment: Alignment.center,
                children: [
                  Container(width: 120, height: 120, decoration: BoxDecoration(color: Colors.grey.withOpacity(0.2), borderRadius: BorderRadius.circular(12))),
                  Transform.scale(
                    scaleX: _scaleX,
                    scaleY: _scaleY,
                    child: Container(
                      width: 120,
                      height: 120,
                      decoration: BoxDecoration(gradient: const LinearGradient(colors: [Colors.orange, Colors.deepOrange], begin: Alignment.topLeft, end: Alignment.bottomRight), borderRadius: BorderRadius.circular(12), boxShadow: [BoxShadow(color: Colors.orange.withOpacity(0.4), blurRadius: 15, offset: const Offset(0, 8))]),
                      child: Center(child: Text('${_scaleX.toStringAsFixed(1)}x\n${_scaleY.toStringAsFixed(1)}x', textAlign: TextAlign.center, style: const TextStyle(color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold))),
                    ),
                  ),
                ],
              ),
            ),
          ),
          Container(
            padding: const EdgeInsets.all(16),
            decoration: BoxDecoration(color: Colors.grey[100], borderRadius: const BorderRadius.vertical(top: Radius.circular(20))),
            child: Column(
              children: [
                Row(children: [const SizedBox(width: 60, child: Text('X缩放:')), Expanded(child: Slider(value: _scaleX, min: 0.1, max: 2.0, divisions: 19, activeColor: Colors.orange, onChanged: (value) => setState(() => _scaleX = value))), SizedBox(width: 60, child: Text('${_scaleX.toStringAsFixed(1)}x'))]),
                Row(children: [const SizedBox(width: 60, child: Text('Y缩放:')), Expanded(child: Slider(value: _scaleY, min: 0.1, max: 2.0, divisions: 19, activeColor: Colors.deepOrange, onChanged: (value) => setState(() => _scaleY = value))), SizedBox(width: 60, child: Text('${_scaleY.toStringAsFixed(1)}x'))]),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

class PerspectiveDemo extends StatefulWidget {
  const PerspectiveDemo({super.key});
  @override
  State<PerspectiveDemo> createState() => _PerspectiveDemoState();
}

class _PerspectiveDemoState extends State<PerspectiveDemo> {
  double _rotateX = 0.0;
  double _rotateY = 0.0;
  double _perspective = 0.001;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('3D透视旋转')),
      body: Column(
        children: [
          Expanded(
            child: Center(
              child: Transform(
                transform: Matrix4.identity()..setEntry(3, 2, _perspective)..rotateX(_rotateX)..rotateY(_rotateY),
                alignment: Alignment.center,
                child: Container(
                  width: 200,
                  height: 200,
                  decoration: BoxDecoration(gradient: const LinearGradient(colors: [Colors.indigo, Colors.purple], begin: Alignment.topLeft, end: Alignment.bottomRight), borderRadius: BorderRadius.circular(20), boxShadow: [BoxShadow(color: Colors.indigo.withOpacity(0.5), blurRadius: 30, offset: const Offset(0, 15))]),
                  child: Center(
                    child: Column(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        const Icon(Icons.threed_rotation, color: Colors.white, size: 48),
                        const SizedBox(height: 12),
                        const Text('3D变换', style: TextStyle(color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold)),
                        Text('X: ${(_rotateX * 180 / pi).toStringAsFixed(0)}° Y: ${(_rotateY * 180 / pi).toStringAsFixed(0)}°', style: TextStyle(color: Colors.white.withOpacity(0.8), fontSize: 14)),
                      ],
                    ),
                  ),
                ),
              ),
            ),
          ),
          Container(
            padding: const EdgeInsets.all(16),
            decoration: BoxDecoration(color: Colors.grey[100], borderRadius: const BorderRadius.vertical(top: Radius.circular(20))),
            child: Column(
              children: [
                Row(children: [const SizedBox(width: 80, child: Text('X轴旋转:')), Expanded(child: Slider(value: _rotateX, min: -pi / 2, max: pi / 2, divisions: 36, activeColor: Colors.indigo, onChanged: (value) => setState(() => _rotateX = value)))]),
                Row(children: [const SizedBox(width: 80, child: Text('Y轴旋转:')), Expanded(child: Slider(value: _rotateY, min: -pi / 2, max: pi / 2, divisions: 36, activeColor: Colors.purple, onChanged: (value) => setState(() => _rotateY = value)))]),
                Row(children: [const SizedBox(width: 80, child: Text('透视强度:')), Expanded(child: Slider(value: _perspective, min: 0, max: 0.01, divisions: 20, activeColor: Colors.teal, onChanged: (value) => setState(() => _perspective = value)))]),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

class FlipCardDemo extends StatefulWidget {
  const FlipCardDemo({super.key});
  @override
  State<FlipCardDemo> createState() => _FlipCardDemoState();
}

class _FlipCardDemoState extends State<FlipCardDemo> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;
  bool _isFront = true;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(duration: const Duration(milliseconds: 600), vsync: this);
    _animation = Tween<double>(begin: 0, end: pi).animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut));
  }

  @override
  void dispose() { _controller.dispose(); super.dispose(); }

  void _flip() {
    if (_isFront) { _controller.forward(); } else { _controller.reverse(); }
    setState(() => _isFront = !_isFront);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('翻转卡片')),
      body: Center(
        child: GestureDetector(
          onTap: _flip,
          child: AnimatedBuilder(
            animation: _animation,
            builder: (context, child) {
              final angle = _animation.value;
              final isFrontVisible = angle < pi / 2;
              return Transform(
                transform: Matrix4.identity()..setEntry(3, 2, 0.002)..rotateY(angle),
                alignment: Alignment.center,
                child: isFrontVisible
                    ? _buildCard(Colors.blue, Colors.cyan, Icons.credit_card, '点击翻转', '正面')
                    : Transform(transform: Matrix4.rotationY(pi), alignment: Alignment.center, child: _buildCard(Colors.deepPurple, Colors.purple, Icons.info, '背面内容', '再次点击翻转')),
              );
            },
          ),
        ),
      ),
    );
  }

  Widget _buildCard(Color c1, Color c2, IconData icon, String title, String subtitle) {
    return Container(
      width: 280, height: 180,
      decoration: BoxDecoration(gradient: LinearGradient(colors: [c1, c2], begin: Alignment.topLeft, end: Alignment.bottomRight), borderRadius: BorderRadius.circular(20), boxShadow: [BoxShadow(color: c1.withOpacity(0.4), blurRadius: 20, offset: const Offset(0, 10))]),
      child: Center(child: Column(mainAxisSize: MainAxisSize.min, children: [Icon(icon, color: Colors.white, size: 48), const SizedBox(height: 12), Text(title, style: const TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold)), Text(subtitle, style: const TextStyle(color: Colors.white70, fontSize: 14))])),
    );
  }
}

class AnimatedTransformDemo extends StatefulWidget {
  const AnimatedTransformDemo({super.key});
  @override
  State<AnimatedTransformDemo> createState() => _AnimatedTransformDemoState();
}

class _AnimatedTransformDemoState extends State<AnimatedTransformDemo> with TickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _rotationAnimation;
  late Animation<double> _scaleAnimation;
  late Animation<Offset> _positionAnimation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(duration: const Duration(seconds: 2), vsync: this)..repeat(reverse: true);
    _rotationAnimation = Tween<double>(begin: 0, end: 2 * pi).animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut));
    _scaleAnimation = Tween<double>(begin: 0.8, end: 1.2).animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut));
    _positionAnimation = Tween<Offset>(begin: Offset.zero, end: const Offset(0, -50)).animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut));
  }

  @override
  void dispose() { _controller.dispose(); super.dispose(); }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('动画变换组合')),
      body: Center(
        child: AnimatedBuilder(
          animation: _controller,
          builder: (context, child) {
            return Transform(
              transform: Matrix4.identity()..translate(_positionAnimation.value.dx, _positionAnimation.value.dy)..rotateZ(_rotationAnimation.value)..scale(_scaleAnimation.value),
              alignment: Alignment.center,
              child: Container(
                width: 150, height: 150,
                decoration: BoxDecoration(gradient: LinearGradient(colors: [Colors.primaries[(_controller.value * 10).floor() % Colors.primaries.length], Colors.primaries[(_controller.value * 10 + 1).floor() % Colors.primaries.length]], begin: Alignment.topLeft, end: Alignment.bottomRight), borderRadius: BorderRadius.circular(20), boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.3), blurRadius: 20, offset: const Offset(0, 10))]),
                child: const Center(child: Text('组合变换', style: TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold))),
              ),
            );
          },
        ),
      ),
    );
  }
}

class InteractiveTransformDemo extends StatefulWidget {
  const InteractiveTransformDemo({super.key});
  @override
  State<InteractiveTransformDemo> createState() => _InteractiveTransformDemoState();
}

class _InteractiveTransformDemoState extends State<InteractiveTransformDemo> {
  double _translateX = 0, _translateY = 0, _rotateZ = 0, _scale = 1.0, _perspective = 0.001, _rotateX = 0, _rotateY = 0;

  void _resetAll() { setState(() { _translateX = 0; _translateY = 0; _rotateZ = 0; _scale = 1.0; _perspective = 0.001; _rotateX = 0; _rotateY = 0; }); }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('交互式变换控制器'), actions: [IconButton(icon: const Icon(Icons.refresh), onPressed: _resetAll)]),
      body: Column(
        children: [
          Expanded(
            flex: 2,
            child: Center(
              child: Transform(
                transform: Matrix4.identity()..setEntry(3, 2, _perspective)..translate(_translateX, _translateY)..rotateX(_rotateX)..rotateY(_rotateY)..rotateZ(_rotateZ)..scale(_scale),
                alignment: Alignment.center,
                child: Container(
                  width: 160, height: 160,
                  decoration: BoxDecoration(gradient: const LinearGradient(colors: [Colors.teal, Colors.cyan], begin: Alignment.topLeft, end: Alignment.bottomRight), borderRadius: BorderRadius.circular(20), boxShadow: [BoxShadow(color: Colors.teal.withOpacity(0.5), blurRadius: 25, offset: const Offset(0, 12))]),
                  child: const Center(child: Column(mainAxisSize: MainAxisSize.min, children: [Icon(Icons.transform, color: Colors.white, size: 40), SizedBox(height: 8), Text('变换预览', style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold))])),
                ),
              ),
            ),
          ),
          Expanded(
            flex: 3,
            child: Container(
              padding: const EdgeInsets.all(16),
              decoration: BoxDecoration(color: Colors.grey[100], borderRadius: const BorderRadius.vertical(top: Radius.circular(20))),
              child: ListView(
                children: [
                  _buildSliderRow('平移X', _translateX, -100, 100, (v) => _translateX = v),
                  _buildSliderRow('平移Y', _translateY, -100, 100, (v) => _translateY = v),
                  _buildSliderRow('旋转Z', _rotateZ, -pi, pi, (v) => _rotateZ = v, isAngle: true),
                  _buildSliderRow('缩放', _scale, 0.1, 2.0, (v) => _scale = v),
                  _buildSliderRow('透视', _perspective, 0, 0.01, (v) => _perspective = v),
                  _buildSliderRow('旋转X', _rotateX, -pi / 2, pi / 2, (v) => _rotateX = v, isAngle: true),
                  _buildSliderRow('旋转Y', _rotateY, -pi / 2, pi / 2, (v) => _rotateY = v, isAngle: true),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildSliderRow(String label, double value, double min, double max, Function(double) onChanged, {bool isAngle = false}) {
    return Row(children: [SizedBox(width: 70, child: Text(label)), Expanded(child: Slider(value: value, min: min, max: max, divisions: 20, activeColor: Colors.teal, onChanged: (v) => setState(() => onChanged(v)))), SizedBox(width: 60, child: Text(isAngle ? '${(value * 180 / pi).toStringAsFixed(0)}°' : value.toStringAsFixed(2), textAlign: TextAlign.right))]);
  }
}

六、最佳实践与注意事项

⚡ 6.1 性能优化建议

Transform 是一个高效的变换工具,但在使用时仍需注意以下几点:

1. 避免频繁重建

Transform 本身是高效的,但如果在动画中频繁创建新的 Matrix4 对象,会产生不必要的垃圾回收压力。建议使用 AnimatedBuilder 配合预计算的动画值。

2. 合理使用 transformHitTests

默认情况下,变换后的组件会变换其点击测试区域。如果变换后的组件不需要响应点击,可以设置 transformHitTests: false 来提升性能。

3. 注意变换顺序

矩阵乘法不满足交换律,变换顺序会影响最终结果。通常按照 缩放 -> 旋转 -> 平移 的顺序应用变换。

🔧 6.2 常见问题与解决方案

问题1:变换后点击位置不正确

解决方案:

  • 检查 transformHitTests 属性是否设置正确
  • 考虑使用 GestureDetector 包裹变换后的组件

问题2:3D 变换效果不明显

解决方案:

  • 调整透视强度 setEntry(3, 2, value)
  • 增大旋转角度范围
  • 确保变换原点设置正确

问题3:动画卡顿

解决方案:

  • 使用 AnimatedBuilder 而非 setState
  • 减少动画中的复杂计算
  • 考虑使用 RepaintBoundary 隔离重绘区域

七、总结

本文详细介绍了 Flutter 中 Transform 变换矩阵系统的使用方法,从基础的平移、旋转、缩放变换到高级的 3D 透视和组合变换,涵盖了以下核心内容:

  1. Transform 核心概念:理解变换矩阵的工作原理和数学基础
  2. 基础变换效果:平移变换、旋转变换、缩放变换的实现方法
  3. 3D 变换效果:透视旋转、翻转卡片等立体效果
  4. 组合变换:动画变换组合、交互式变换控制器
  5. 性能优化:合理使用 Transform,避免性能问题

Transform 是 Flutter 中实现高级视觉效果的核心工具,掌握其使用技巧能够帮助开发者创建更加丰富、生动的用户界面。在实际开发中,需要根据具体场景选择合适的变换方式,并注意性能优化。


参考资料

相关推荐
2501_921930832 小时前
进阶实战 Flutter for OpenHarmony:自定义仪表盘系统 - 高级数据可视化实现
flutter·信息可视化
2601_949593652 小时前
进阶实战 Flutter for OpenHarmony:InheritedWidget 组件实战 - 跨组件数据
flutter
阿林来了2 小时前
Flutter三方库适配OpenHarmony【flutter_speech】— 持续语音识别与长录音
flutter·语音识别·harmonyos
lili-felicity3 小时前
进阶实战 Flutter for OpenHarmony:mobile_device_identifier 第三方库实战 - 设备标识
flutter
松叶似针3 小时前
Flutter三方库适配OpenHarmony【secure_application】— 与 HarmonyOS 安全能力的深度集成
安全·flutter·harmonyos
lili-felicity4 小时前
进阶实战 Flutter for OpenHarmony:qr_flutter 第三方库实战 - 智能二维码生成系统
flutter
松叶似针4 小时前
Flutter三方库适配OpenHarmony【secure_application】— 自定义锁屏界面与品牌化设计
flutter
松叶似针5 小时前
Flutter三方库适配OpenHarmony【secure_application】— 敏感数据清除与安全增强
flutter