从0开始打造UI框架:动态化框架Scrollview物理学算法解析

时间:2022-07-27
本文章向大家介绍从0开始打造UI框架:动态化框架Scrollview物理学算法解析,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

| 导语   动态化是APP未来的趋势,腾讯成立了动态化框架中台,打造腾讯自研的动态化框架解决方案。ScrollView是动态化框架UI组件的核心之一,而物理学算法可能是其中最重要的部分之一了,好的物理学算法能给用户带来最优秀的体验。最初iOS就是以丝滑而自然的滚动体验,征服了许多用户的心。 而对于从0开始打造UI框架的动态化框架来说,这也是最重要的部分之一。用户评判一个应用是否流畅的第一反应,可能就是在页面上划一划试试,因此物理学算法的好坏,将直接影响到用动态化框架打造的应用的体验。 本文将主要分析物理学算法在ScrollView中的应用及实现方法。

一、前言

在ScrollView中,物理学算法可能是其中最重要的部分之一了,好的物理学算法能给用户带来最优秀的体验。最初iOS就是以丝滑而自然的滚动体验,征服了许多用户的心。

而对于从0开始打造UI框架的动态化框架来说,这也是最重要的部分之一。用户评判一个应用是否流畅的第一反应,可能就是在页面上划一划试试,因此物理学算法的好坏,将直接影响到用动态化框架打造的应用的体验。

本文将主要分析物理学算法在ScrollView中的应用及实现方法

二、物理学算法的相关物理属性

动画&滚动中涉及到的物理学算法属于力学算法。在本文涉及到的动画&滚动中,主要涉及到滑动摩擦和粘性阻尼两种场景。

这两种阻尼的力学运算一般涉及到以下属性。

Mass:

质量,是物体所具有的一种物理属性,是物质的量的量度,它是一个正的标量。质量分为惯性质量和引力质量。这里主要谈的是惯性质量。惯性质量是物体惯性的量度:对于m越大的物体,就越难改变其运动状态(速度)。

Velocity:

速度。表示物体运动的快慢和方向

Acceleration:

是速度变化量与发生这一变化所用时间的比值Δv/Δt,是描述物体速度变化快慢的物理量

Offset:

偏移量(位置)。弹性阻尼使用

Stiffness:

刚度。刚度是指材料或结构在受力时抵抗弹性变形的能力。是材料或结构弹性变形难易程度的表征。在宏观弹性范围内,刚度是零件荷载与位移成正比的比例系数,即引起单位位移所需的力。

Damping:

阻尼系数。在物理学和工程学上,阻尼的力学模型一般是一个与振动速度大小成正比,与振动速度方向相反的力,该模型称为粘性(或黏性)阻尼模型,是工程中应用最广泛的阻尼模型。

三、滚动过程的力学模拟分析

  • A:滚动,但是没有滚动到底部,速度逐步减小最终停止
  • B:滚动,最终会超过底部,回弹并最终停止
  • C:已经超过底部,直接回弹,但并不会反复弹,不像普通弹簧

1.  场景A

最容易想到的肯定是滑动摩擦,ScrollView由手指滑动带来了初始的速度,由于惯性的原因,ScrollView倾向于保持原有速度继续滚动,而施加的摩擦力使得滚动速度慢慢减少,最终停下来。

然而。从上面的运动曲线可以看出,滑动摩擦的匀减速运动,速度变化固定,在滚动快结束的时候其实是非常生硬的,体验并不是很好。

因此,此处也应该是采用粘性阻尼的运算方法,减速和速度挂钩,速度慢的时候减速幅度也会更小。

但是值得注意的是,如果滑动超过了边界,导致了回弹,这就不再是这种场景了。

因此该场景的判断条件为

  • Offset介于中间区域:

LeadingExtent < Offset < TrailingExtent

  • 滑动终止位置在中间区域

LeadingExtent < FinalOffset < TrailingExtent

2. 场景B&场景C

场景B核心点是如果滚动很快并超过底部边界,最符合用户预期的应该是回弹停止到最底部,而不是像弹簧一样弹回来甚至反复弹。

场景C也一样,希望最终能回到边界,而不是弹力过强直接大幅度回弹。

这其实就是典型的粘性阻尼的场景了。数学的推导并不是本文的重点,因此略过。

最典型的弹簧震子运动方程是一个微分方程:

使得在参数不同的时候有不同的解。

  • 临界阻尼

当阻尼比=1时,方程的解为一对重实根,此时系统的阻尼形式称为临界阻尼。现实生活中,许多大楼内房间或卫生间的门上在装备自动关门的扭转弹簧的同时,都相应地装有阻尼铰链,使得门的阻尼接近临界阻尼,这样人们关门或门被风吹动时就不会造成太大的声响。

  • 过阻尼

当阻尼比>1时,方程的解为一对互异实根,此时系统的阻尼形式称为过阻尼。当自动门上安装的阻尼铰链使门的阻尼达到过阻尼时,自动关门需要更长的时间。如记忆枕。

  • 欠阻尼

当阻尼比 <1时,方程的解的解为一对共轭虚根,此时系统的阻尼形式称为欠阻尼。在欠阻尼的情况下,系统将以圆频率相对平衡位置作往复振动。

如上图所示,平时大家常见弹簧就属于欠阻尼的情况,会反复弹动。而对于列表滚动来说,场景B和场景C一般选择欠阻尼的模式。使得超过边界的情况下,能回弹,但又不会反复回弹,最终回到边界区域。

四、物理学模块的设计和实现

根据滑动情况选择不同的物理算法

void SpringScrollPhysics::Start(float offset, float velocity, float leadingExtent,

                                float tailingExtent) {
  ScrollPhysics::Start(offset, velocity, leadingExtent, tailingExtent);
  if (offset < leadingExtent) {
    scroll_type = ScrollPhysicsType::LeadingOverscroll;
    calculator_ = SpringCalculator::Create(spring_, offset - leadingExtent, velocity);
  } else if (offset > tailingExtent) {
    scroll_type = ScrollPhysicsType::TailingOverscroll;
    calculator_ = SpringCalculator::Create(spring_, offset - tailingExtent, velocity);
  } else {
    auto c = FrictionCalculator::Create(offset, velocity);
    if (velocity > 0 && c->FinalOffset() > tailingExtent) {
      scroll_type = ScrollPhysicsType::TailingOverscroll;
      calculator_ = SpringCalculator::Create(spring_, offset - tailingExtent, velocity);
    } else if (velocity < 0 && c->FinalOffset() < leadingExtent) {
      scroll_type = ScrollPhysicsType::LeadingOverscroll;
      calculator_ = SpringCalculator::Create(spring_, offset - leadingExtent, velocity);
    } else {
      scroll_type = ScrollPhysicsType::NoOverscroll;
      calculator_ = c;
    }
  }
}

float SpringScrollPhysics::Offset(float time) {
  if (scroll_type == ScrollPhysicsType::LeadingOverscroll) {
    return calculator_->Offset(time) + leadingExtent_;
  } else if (scroll_type == ScrollPhysicsType::TailingOverscroll) {
    return calculator_->Offset(time) + tailingExtent_;
  } else {
    return calculator_->Offset(time);
  }
}
float SpringScrollPhysics::Velocity(float time) {
  return calculator_->Velocity(time);
}
bool SpringScrollPhysics::IsDone(float time) { return std::fabs(Velocity(time))
< MinimumVelocity; }

阻尼计算

shared_ptr<SpringCalculator> SpringCalculator::Create(SpringDescrition spring, float offset,

                                                      float velocity) {
  float cmk = spring.damping * spring.damping - 4 * spring.mass * spring.stiffness;
  if (cmk == 0.0)
    return dynamic_pointer_cast<SpringCalculator>(
        make_shared<CriticalSpringCalculator>(spring, offset, velocity));
  if (cmk > 0.0)
    return dynamic_pointer_cast<SpringCalculator>(
        make_shared<OverdampedSpringCalculator>(spring, offset, velocity));
  return dynamic_pointer_cast<SpringCalculator>(
      make_shared<UnderdampedSpringCalculator>(spring, offset, velocity));
}

SpringCalculator::SpringCalculator(SpringDescrition spring, float offset, float velocity)
    : spring_(spring), PhysicsCalculator(offset, velocity) {}

CriticalSpringCalculator::CriticalSpringCalculator(SpringDescrition spring, float offset,
                                                   float velocity)
    : SpringCalculator(spring, offset, velocity) {
  r_ = -spring.damping / (2.0 * spring.mass);
  c1_ = offset;
  c2_ = velocity / (r_ * offset);
}
float CriticalSpringCalculator::Offset(float time) {
  return (c1_ + c2_ * time) * std::powf(number::e, r_ * time);
}

float CriticalSpringCalculator::Velocity(float time) {
  float power = std::powf(number::e, r_ * time);
  return r_ * (c1_ + c2_ * time) * power + c2_ * power;
}

OverdampedSpringCalculator::OverdampedSpringCalculator(SpringDescrition spring,
float offset,
                                                       float velocity)
    : SpringCalculator(spring, offset, velocity) {
  float cmk = spring.damping * spring.damping - 4 * spring.mass * spring.stiffness;
  r1_ = (-spring.damping - std::sqrtf(cmk)) / (2.0 * spring.mass);
  r2_ = (-spring.damping + std::sqrtf(cmk)) / (2.0 * spring.mass);
  c2_ = (velocity - r1_ * offset) / (r2_ - r1_);
  c1_ = offset - c2_;
}
float OverdampedSpringCalculator::Offset(float time) {
  return c1_ * std::powf(number::e, r1_ * time) + c2_ * std::powf(number::e, r2_ * time);
}

float OverdampedSpringCalculator::Velocity(float time) {
  return c1_ * r1_ * std::powf(number::e, r1_ * time) +
         c2_ * r2_ * std::powf(number::e, r2_ * time);
}

UnderdampedSpringCalculator::UnderdampedSpringCalculator(SpringDescrition spring, float offset,
                                                         float velocity)
    : SpringCalculator(spring, offset, velocity) {
  w_ = std::sqrtf(4.0 * spring.mass * spring.stiffness - spring.damping * spring.damping) /
       (2.0 * spring.mass);
  r_ = -(spring.damping / 2.0 * spring.mass);
  c1_ = offset;
  c2_ = (velocity - r_ * offset) / w_;
}
float UnderdampedSpringCalculator::Offset(float time) {
  return std::powf(number::e, r_ * time) *
         (c1_ * std::cosf(w_ * time) + c2_ * std::sinf(w_ * time));
}

float UnderdampedSpringCalculator::Velocity(float time) {
  float power = std::powf(number::e, r_ * time);
  float cosine = std::cosf(w_ * time);
  float sine = std::sinf(w_ * time);
  return power * (c2_ * w_ * cosine - c1_ * w_ * sine) + r_ * power *
 (c2_ * sine + c1_ * cosine);
}

如何做用户运营体系的推导思考

Automl框架katib浅析

算力时代将至——我们是否已经做好准备