Flutter 研究:基于 lerp 的线性过渡

一、基础概念

在 Flutter 中,lerp 是 'linear interpolation' 的缩写,中文通常称为线性插值。线性插值是用于在两个值之间进行平滑过渡的一种方法。它在动画、颜色过渡、尺寸变化等许多场景中广泛使用。lerp " "的基本原理是通过给定的参数 t,在两个值 a 和 b 之间进行插值。参数 t 通常在 0 到 1 的范围内,其中 t = 0 时返回 a,t = 1 时返回 b,而 t 在 0 和 1 之间时返回一个介于 a 和 b 之间的值。

二、示例

less 复制代码
//
//  LerpDemo.dart
//  flutter_templet_project
//
//  Created by shang on 2024/8/6 12:09.
//  Copyright © 2024/8/6 shang. All rights reserved.
//

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_templet_project/basicWidget/n_section_header.dart';
import 'package:flutter_templet_project/basicWidget/n_slider.dart';
import 'package:flutter_templet_project/extension/ddlog.dart';
import 'package:get/get.dart';

class LerpDemo extends StatefulWidget {
  const LerpDemo({
    super.key,
    this.arguments,
  });

  final Map<String, dynamic>? arguments;

  @override
  State<LerpDemo> createState() => _LerpDemoState();
}

class _LerpDemoState extends State<LerpDemo>
    with SingleTickerProviderStateMixin {
  final _scrollController = ScrollController();

  Map<String, dynamic> arguments = Get.arguments ?? <String, dynamic>{};

  late final AnimationController _controller = AnimationController(
    duration: Duration(seconds: 2),
    vsync: this,
  )..repeat(reverse: true);

  late final tween = ColorTween(
    begin: Colors.red,
    end: Colors.blue,
  );
  late final Animation<Color?> _animation = tween.animate(_controller);

  final String desc = r"在 Flutter 中,lerp 是 'linear interpolation' "
      "的缩写,中文通常称为线性插值。线性插值是用于在两个值之间进行平滑过渡的一种方法。它在动画、颜色过渡、尺寸变化等许多场景中广泛使用。lerp "
      "的基本原理是通过给定的参数 t,在两个值 a 和 b 之间进行插值。参数 t 通常在 0 到 1 的范围内,其中 t = 0 时返回 a,t = 1 时返回 b,而 t 在 0 和 1 之间时返回一个介于 a 和 b 之间的值。";

  @override
  void initState() {
    super.initState();
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("$widget"),
      ),
      body: buildBody(),
    );
  }

  Widget buildBody() {
    return Scrollbar(
      controller: _scrollController,
      child: SingleChildScrollView(
        controller: _scrollController,
        child: Container(
          padding: EdgeInsets.all(12),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              NSectionHeader(
                text: Text(desc),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.stretch,
                  children: [
                    Text("其数学公式为:"),
                    Padding(
                      padding: const EdgeInsets.symmetric(vertical: 8.0),
                      child: Text(
                        "lerp(a,b,t)=a+(b−a)×t",
                        style: TextStyle(
                          fontSize: 26,
                        ),
                      ),
                    ),
                  ],
                ),
              ),
              NSectionHeader(
                text: Text(
                  "二. 随滑块变化的颜色和尺寸",
                  style: TextStyle(
                    fontWeight: FontWeight.w600,
                  ),
                ),
                child: buildLerpColor(),
              ),
              NSectionHeader(
                text: Text(
                  "三. 基于 ColorTween 的动画",
                  style: TextStyle(
                    fontWeight: FontWeight.w600,
                  ),
                ),
                child: ValueListenableBuilder(
                  valueListenable: _animation,
                  builder: (context, value, child) {
                    return Container(
                      width: 100,
                      height: 100,
                      color: _animation.value,
                      child: Column(
                        children: [
                          Text("a: Colors.red"),
                          Text("b: Colors.blue"),
                        ]
                            .map((e) => Padding(
                                  padding: EdgeInsets.symmetric(vertical: 4),
                                  child: e,
                                ))
                            .toList(),
                      ),
                    );
                  },
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Widget buildLerpColor({
    double min = 0,
    double max = 1,
    double current = 0,
  }) {
    return StatefulBuilder(
      builder: (context, setState) {
        Color a = Colors.red;
        Color b = Colors.blue;
        Color? color = Color.lerp(a, b, current);

        Size sizeStart = Size(240, 150);
        Size sizeEnd = Size(300, 200);
        Size? size = Size.lerp(sizeStart, sizeEnd, current);

        final desc = "t: ${current.toStringAsFixed(2)}";

        return Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Row(
              children: [
                Expanded(
                  child: Slider(
                    value: current,
                    onChanged: (double value) {
                      current = value;
                      setState(() {});
                    },
                    min: 0,
                    max: 1,
                    divisions: 100,
                  ),
                ),
                Text(desc),
              ],
            ),
            Container(
              width: size?.width,
              height: size?.height,
              alignment: Alignment.center,
              decoration: BoxDecoration(
                color: color,
                border: Border.all(color: Colors.blue),
                borderRadius: BorderRadius.all(Radius.circular(0)),
              ),
              child: Column(
                children: [
                  Text("a: Colors.red"),
                  Text("b: Colors.blue"),
                  Text("sizeStart: $sizeStart"),
                  Text("sizeEnd: $sizeEnd"),
                ]
                    .map((e) => Padding(
                          padding: EdgeInsets.symmetric(vertical: 4),
                          child: e,
                        ))
                    .toList(),
              ),
            ),
          ],
        );
      },
    );
  }
}

最后

1、凡是遵循以下格式的静态方法的类型均支持线性变化

dart 复制代码
static T? lerp(T? a, T? b, double t) {

2、lerp公式函数有SDK底层支持

dart 复制代码
double? lerpDouble(num? a, num? b, double t) {
  if (a == b || (a?.isNaN ?? false) && (b?.isNaN ?? false)) {
    return a?.toDouble();
  }
  a ??= 0.0;
  b ??= 0.0;
  return a * (1.0 - t) + b * t;
}

double _lerpDouble(double a, double b, double t) {
  return a * (1.0 - t) + b * t;
}


double _lerpInt(int a, int b, double t) {
  return a + (b - a) * t;
}

github

相关推荐
张文君几秒前
ubuntu26.04坏块分区隔离急速版
java·前端·python
两只羊ovo3 分钟前
给 Agent 装上手脚:手写 mini-Cursor,从建项目到跑起来
前端
Hive_MOM14 分钟前
制造企业库存精益化管理数字化:从“库存压资金、找货靠人翻“到“库存水位实时可见、资金周转提速“
前端·制造
光影少年22 分钟前
react navite调试方案:Flipper、远程调试
前端·javascript·react native·react.js·前端框架
计算机魔术师30 分钟前
智谱开源 GLM-5.3 模型权重,主打智能体编程与网络防御
前端
百变梦仔32 分钟前
多个 Skill 同时出现时,Codex 前端任务我会先定优先级
前端
IT_陈寒44 分钟前
SpringBoot自动配置失效?这个隐式依赖坑了我三天
前端·人工智能·后端
码视野1 小时前
基于 Spring Boot + Vue3 的【城市雨污水管网液位淤积溯源与立交桥下穿隧洞防汛排涝智控中台】设计与实现(含PRD/三端高保真源码/大屏)
java·前端·人工智能·spring boot·后端
雪芽蓝域zzs1 小时前
第五节:Vue‑Router4 路由配置,布局嵌套路由
前端·javascript·vue.js
雪芽蓝域zzs1 小时前
第十二节:完整用户管理 CRUD 页面(表格分页、新增 / 编辑弹窗、删除、Mock 接口)
前端·javascript·vue.js