如何自定义 UIScrollView 的减速系数

decelerationRate属性用来定义用户滑动UIScrollView,手指离开UIScrollView后,UIScrollView减速所受到的阻力。

decelerationRate是一个浮点数类型,系统提供了预先定义的两个值:

  • UIScrollViewDecelerationRateNormal
  • UIScrollViewDecelerationRateFast

其中UIScrollViewDecelerationRateNormal是默认值。

1 Normal 和 Fast 的值

UIScrollViewDecelerationRateNormalUIScrollViewDecelerationRateFast的值,打印如下:

bash 复制代码
# UIScrollViewDecelerationRateNormal
(lldb) po @import UIKit
(lldb) p (UIScrollViewDecelerationRate)UIScrollViewDecelerationRateNormal
(UIScrollViewDecelerationRate) 0.998

# UIScrollViewDecelerationRateFast
(lldb) p (UIScrollViewDecelerationRate)UIScrollViewDecelerationRateFast
(UIScrollViewDecelerationRate) 0.98999999999999999

decelerationRate的数值越小,减速越快。

UIScrollViewDecelerationRateFast就是浮点数0.99

由于计算机对浮点数表示精度问题,显示为0.98999999999999999

2 decelerationRate setter 的实现

decelerationRate的设置函数代码比较短,直接贴出来:

c 复制代码
UIKitCore`-[UIScrollView setDecelerationRate:]:
->  0x1a2a4998c <+0>:  adrp   x8, 5091
    // 1. 读取阈值到 d1
    0x1a2a49990 <+4>:  ldr    d1, [x8, #0x558]
    // 2. 将要设置的值与阈值比较
    0x1a2a49994 <+8>:  fcmp   d0, d1
    // 3. 如果要设置的值 < 阈值,w8 置为 1,否则,w8 置为 0
    0x1a2a49998 <+12>: cset   w8, lt
    0x1a2a4999c <+16>: adrp   x9, 5091
    // 4. x9 存储 UIScrollViewDecelerationRateNormal 和 UIScrollViewDecelerationRateFast 组成的数组首地址
    0x1a2a499a0 <+20>: add    x9, x9, #0x5a0            ; UIScrollTestParametersLengthAutomatic + 128
    // 5. 将 UIScrollViewDecelerationRateNormal 或者 UIScrollViewDecelerationRateFast 读取到 d0
    0x1a2a499a4 <+24>: ldr    d0, [x9, w8, uxtw #3]
    0x1a2a499a8 <+28>: adrp   x8, 416465
    0x1a2a499ac <+32>: ldrsw  x8, [x8, #0xf9c]
    0x1a2a499b0 <+36>: add    x8, x0, x8
    // 6. 存储 decelerationRate 到 UIScrollView 对象
    0x1a2a499b4 <+40>: stp    d0, d0, [x8]
    0x1a2a499b8 <+44>: ret 

注释1,读取阈值到寄存器d1

代码后面会将要设置的值与阈值比较。

打印阈值为:

bash 复制代码
(lldb) p $d1
(double) 0.99399999999999999

可以看到,阈值就是0.994

这个阈值,正好是UIScrollViewDecelerationRateNormalUIScrollViewDecelerationRateFast的中间值。

同样由于计算机浮点数精度问题,表示为0.99399999999999999

注释2,注释3,将要设置的值与阈值进行比较。

如果要设置的值比阈值小,那么寄存器w8被置为1,否则,w8被置为0

寄存器w8的值,后面会看到成为数组索引。

注释4,将UIScrollViewDecelerationRateNormalUIScrollViewDecelerationRateFast组成的数组首地址,读入寄存器x9

可以打印这个数组的内容:

bash 复制代码
(lldb) x/2fg $x9
0x1a3e2c5a0: 0.998
0x1a3e2c5a8: 0.98999999999999999

从打印内容可以看到,数组第一项,是UIScrollViewDecelerationRateNormal,数组第二项是UIScrollViewDecelerationRateFast

注释5,根据寄存器w8做索引,读入相应的值到寄存器d0

如果要设置的值比阈值大,那么就读入UIScrollViewDecelerationRateNormal,否则,读入UIScrollViewDecelerationRateFast

注释6,将最终的值存入到UIScrollView对象中。

存储decelerationRate的实例变量相对于UIScrollView首地址偏移量存储在寄存器x8,打印为:

bash 复制代码
(lldb) p/x $x8
(unsigned long) 0x0000000000000358

通过_ivarDescription方法打印UIScrollView的所有实例变量,经过尝试,可以发现,偏移量0x358处的实例变量为_decelerationFactor:

bash 复制代码
(lldb) po class_getInstanceVariable(UIScrollView.class, "_decelerationFactor")
0x000000020a6adbd8
(lldb) po ivar_getOffset(0x000000020a6adbd8)
0x0000000000000358

_decelerationFactor是一个结构体,存储这水平和垂直减速比例值:

c 复制代码
_decelerationFactor (struct CGSize): {0.998, 0.998}

总结一下,如果设置UIScrollViewdecelerationRate属性,系统会将设置的值与阈值0.994进行比较:

如果设置的值大于阈值,那么系统会选择UIScrollViewDecelerationRateNormal;

如果设置的值小于阈值,那么系统会选择UIScrollViewDecelerationRateFast

也就是说,无论外界设置何种值,系统只会在UIScrollViewDecelerationRateNormalUIScrollViewDecelerationRateFast中进行选择。

3 decelerationRate getter 的实现

decelerationRategetter方法也很短,代码如下:

c 复制代码
UIKitCore`-[UIScrollView decelerationRate]:
    0x1a3ba2354 <+0>:  adrp   x8, 412024
    0x1a3ba2358 <+4>:  ldrsw  x8, [x8, #0xf9c]
    // 1. 读取 _decelerationFactor 的水平值
->  0x1a3ba235c <+8>:  ldr    d0, [x0, x8]
    0x1a3ba2360 <+12>: ret  

注释1,寄存器x8存储这_decelerationFactor结构体相对于UIScrollView首地址的偏移量:

bash 复制代码
(lldb) p/x $x8
(unsigned long) 0x0000000000000358

这里将_decelerationFactor的水平值读取出来,作为结果返回。

4 _decelerationFactor 结构体

如果我们直接设置UIScrollViewdecelerationRate属性,系统始终会把设置的值调整为预定义的UIScrollViewDecelerationRateNormal或者UIScrollViewDecelerationRateFast

那么,我们能否直接设置_decelerationFactor结构体,从而使用不同的减速系数呢?

通过调用_methodDescription查看UIScrollView的所有方法,可以找到如下2个私有方法:

c 复制代码
- (void) setHorizontalScrollDecelerationFactor:(double)arg1; (0x138bd7d00)
- (void) setVerticalScrollDecelerationFactor:(double)arg1; (0x138bd7d70)

这两个方法,这是设置_decelerationFactor结构体的水平值和垂直值。

我们先看setHorizontalScrollDecelerationFactor:方法的实现,垂直方法的实现类似:

c 复制代码
UIKitCore`-[UIScrollView setHorizontalScrollDecelerationFactor:]:
->  0x1a3ba2378 <+0>:  adrp   x8, 412024
    0x1a3ba237c <+4>:  ldrsw  x8, [x8, #0xf9c]
    // 1. 将设置的值直接存入到 _decelerationFactor 结构体的水平分量
    0x1a3ba2380 <+8>:  str    d0, [x0, x8]
    0x1a3ba2384 <+12>: ret  

注释1,将设置的值直接存入到_decelerationFactor结构体的水平分量。

因此,通过上面两个私有方法,就可以设置decelerationRate的值为[0, 1)直接的值。

注意,不能通过上面2个私有方法,将decelerationRate的值设置为1.0,否则滚动时会发生崩溃。

比如如果我们调用setVerticalScrollDecelerationFactor:方法,将垂直减速系数设置为0.999999,那么就会发现拖动UIScrollView之后的减速非常缓慢。

这里需要注意,如果设置了_decelerationFactor的垂直分量,最好一起设置水平分量,因为decelerationRategetter方法,只返回水平分量的值。