Flutter物理動畫的淺析

Flutter物理動畫的資料實在太少了,等不及大佬們的博客,我就本身研究源碼了。spring

在咱們平時寫的ListView和PageView中,所給定的ClampingScrollPhysics與BouncingScrollPhysics等都有這一系列的物理動畫。bash

ListView慣性滑動的運動模型

一個木板在不光滑的水平面上,給它一個初速度,它會受滑動摩擦力逐漸中止,Flutter中ClampingScrollPhysics這個physics更近似的模擬了這個動畫,而BouncingScrollPhysics其實並不近似於這個動畫,拋開ListView達到頂部的動畫差別,咱們用力滑動BouncingScrollPhysics的ListView,就會發現,無論給它多大的初速度,它的最大滑動速度遠不及ClampingScrollPhysics,在較長的列表中能夠表現出來。ide

從物理的角度自定義簡單的動畫

這部分應該徹底來自高中物理,典型的汽車剎車運動模型。post

  • 這塊木板的名字是ListView動畫

  • 假設屏幕無限長,且不光滑,存在必定係數的動摩擦因數,ListView有它的寬度,咱們把它畫厚一點。ui

  • 設手指離開屏幕時,ListView向右滑動this

受力分析--->spa

因此它在接下來的運動中只受到來自屏幕給它的摩擦力,方向水平向左。 由動力學公式code

f = μN
N = mg
F = m*a
複製代碼

f=μN=μmg=macdn

便可得出這個ListView的加速度。因此在水平運動時加速度與物體的質量是無關的。

滑動摩擦力存在的大前提是物體相對有壓力

物體在滑動中滑動摩擦力爲恆力,此時物體水平協力F即爲f,在設備中,μ是動摩擦係數,天然是模擬的一個固定數值,因此整個滑動摩擦力f與加速度都是模擬的。

有如下兩個公式

公式中的初末爲下標

1.v末=v初+at
2.x=v初*t+0.5*a*t*t
複製代碼

因爲ListView末速度爲0,因此第一個公式可得時間t,代入第二個公式可得它運動的位移x。

通常這種狀況,咱們每每用一個快捷的公式

3.v末*v末-v初*v初=2ax
複製代碼

因此經過一個公式算出時間,另外兩個之一可得出位移。

有了物體還須要運動的時間,跟運動的位移,動畫就好寫了。再經過公式2算出動畫執行時的每一個瞬間物體應該位移到的距離。

代碼的簡單實現

class SimulationWidget extends StatefulWidget {
  @override
  _SimulationWidgetState createState() => _SimulationWidgetState();
}

class _SimulationWidgetState extends State<SimulationWidget>
    with SingleTickerProviderStateMixin {
  AnimationController animationController;
  double friction = 9; //動摩擦因素
  double distance = 0.0; //運動位移
  double startVelocity = 200; //初速度
  double time; //運動所需時間
  double acceleration; //加速度
  double g=9.8;
  @override
  void initState() {
    super.initState();
    acceleration = friction * g;
    time = -startVelocity / -acceleration;
    animationController = AnimationController(
        vsync: this, duration: Duration(milliseconds: time.toInt() * 1000));
    animationController.addListener(() {
      double tmpTime = time * animationController.value;
      distance = startVelocity * tmpTime - 0.5 * acceleration * tmpTime * tmpTime;
      setState(() {});
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          animationController.reset();
          animationController.forward();
        },
      ),
      body: Center(
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Padding(
              padding: EdgeInsets.only(left: distance),
              child: Container(
                width: 20,
                height: 20,
                color: Colors.green,
              ),
            )
          ],
        ),
      ),
    );
  }
}
複製代碼

效果以下

建立Flutter自定義的物理動畫

不使用任何的physics,關於physics中的Simulation能夠參考貓大的掘金

Flutter完整開發實戰詳解(十8、 神奇的ScrollPhysics與Simulation)

咱們先寫一個簡單的ListView

預覽

將physics設置爲禁止滑動

physics: NeverScrollableScrollPhysics(),
複製代碼

使用GestureDetector接收滑動事件

簡單計算出手指從按下的點到滑動的點產生的位移並及時控制ListView的位置

GestureDetector(
    onPanDown: (details){
        preOffset=scrollController.offset;
        onPanDownOffset=details.globalPosition.dx;
    },
    onHorizontalDragUpdate: (details){
        scrollController.jumpTo(preOffset - (details.globalPosition.dx - onPanDownOffset));
    },
        child: Container(
            color: Colors.red,
            height: 200.0,
            width: MediaQuery.of(context).size.width,
        ),
),
複製代碼

預覽

能夠看到它如今沒有任何的慣性

建立ClampingScrollPhysics效果的慣性動畫

構建Simulation對象

ClampingScrollSimulation對象須要三個參數:

  • position:運動的起始位移
  • velocity:手指離開屏幕每秒的偏移量
  • tolerance:一個公差,屬性有運動的距離,時間,每一時刻的速度,這個玩意個人理解不深入,因爲我想要與原ListView的慣性效果同樣(不考慮到達邊界),因此我直接copy的官方源碼。

建立動畫控制器

這兒也有一個須要注意的點

須要用

AnimationController(
  vsync: this,
  value: 0,
  lowerBound: double.negativeInfinity,
  upperBound: double.infinity,
);
複製代碼

來實例化這個控制器,否則它的默認上下界爲0~1

監聽動畫控制器

animationController.addListener(() {
  scrollController.jumpTo(animationController.value);
});
複製代碼

經過Simulation來執行動畫

animationController.animateWith(simulation);
複製代碼

這一節的完整代碼,須要放在手指離開屏幕的回調中.

final Tolerance tolerance = Tolerance(
  velocity: 1.0 /
      (0.050 *
          WidgetsBinding.instance.window
              .devicePixelRatio), // logical pixels per second
  distance: 1.0 /
      WidgetsBinding
          .instance.window.devicePixelRatio, // logical pixels
);
double start = scrollController.offset;
ClampingScrollSimulation clampingScrollSimulation = ClampingScrollSimulation(
  position: start,
  velocity: -details.velocity.pixelsPerSecond.dx,
  tolerance: tolerance,
);
animationController = AnimationController(
  vsync: this,
  value: 0,
  lowerBound: double.negativeInfinity,
  upperBound: double.infinity,
);
animationController.reset();
animationController.addListener(() {
  scrollController.jumpTo(animationController.value);
});
animationController.animateWith(clampingScrollSimulation);
複製代碼

預覽

能夠看到,咱們如今不借助physics就讓ListView有了慣性滾動的效果

把PageView的彈性效果搬來是怎樣呢?🧐

PageView的彈簧效果

double velocity = -details.velocity.pixelsPerSecond.dx;
double maxSize = 10000;
double itemDimension = MediaQuery.of(context).size.width;
double _getPixels(double page, double leading) {
  return (page * itemDimension) - leading;
}

double _getPage(double pixels, double leading) {
  return (pixels + leading) / itemDimension;
}

double _getTargetPixels(
  double pixels,
  Tolerance tolerance,
  double velocity,
  double leading,
) {
  double page = _getPage(pixels, leading);

  if (pixels < 0) {
    return 0;
  }

  if (pixels >= maxSize) {
    return maxSize;
  }
  if (pixels > 0) {
    if (velocity < -tolerance.velocity) {
      page -= 0.5;
    } else if (velocity > tolerance.velocity) {
      page += 0.5;
    }
    return _getPixels(page.roundToDouble(), leading);
  }
}
double start = scrollController.offset;
double target =
    _getTargetPixels(start, tolerance, velocity, 0);
var spring = SpringDescription.withDampingRatio(
  mass: 0.5,
  stiffness: 100.0,
  ratio: 1.1,
);
ScrollSpringSimulation scrollSpringSimulation = ScrollSpringSimulation(
    spring, start, target, velocity,
    tolerance: tolerance);
animationController = AnimationController(
  vsync: this,
  value: 0,
  lowerBound: double.negativeInfinity,
  upperBound: double.infinity,
);

animationController.reset();
animationController.addListener(() {
  scrollController.jumpTo(animationController.value);
});
animationController.animateWith(scrollSpringSimulation);
複製代碼

預覽

這就實現了PageView中的彈簧動畫

關於滑動到邊界時候的動畫處理仍是須要參考physics的源碼。

結語

  • 有的時候仍是須要本身揣摩一下原理,本身多動手,不能一直組別人的輪子
  • 數學跟物理在動畫中始終有着很大的做用
  • 高考物理差班上第一名兩分始終記着🤣

下一篇是利用本章知識解決各類複雜的滑動聯動問題(應該吧🤪 )。

相關文章
相關標籤/搜索