browser高亮显示(HitTest)

   在点击一些诸如链接、按钮之类的地方的时候,browser一般会高亮反色显示。这个功能是怎样实现的呢?我们分层次来解析一下(基于android4.1 browser的代码)。

1. framework层

在framework层来看整个过程分为3步。

a. 识别手势

 识别出这是一个单击或者长按手势(具体手势还要看代码)是该功能的基本条件(参见WebViewInputDispatcher::updateStateTrackersLocked)。如果手势符合,则设置WebViewClassic的标识位mShowTapHighlight为true。

b. 计算高亮区域

 通过WebViewCore::nativeHitTest来计算需要高亮的区域,该函数会通过jni调用下去。结果就是返回一个类WebKitHitTest。该类中包含了高亮区域。找到该高亮区域后调用WebViewClassic::setTouchHighlightRects,来设置高亮区域。该函数会对高亮区域进一步的判断,比如如果高亮区域过大,则不会再显示高亮,以迎合用户体验。

c. 绘制

 在WebViewClassic::onDraw的过程中绘制高亮区域


WebViewCore::nativeHitTest函数比较关键,它实现了计算高亮区域的功能,而涉及的其他几个函数都比较简单,下面来看一下WebViewCore::nativeHitTest的调用过程。


2. native层

native层是WebViewCore::hitTestAtPoint这个函数,它用这么几步来计算高亮区域

a. 调用WebCore::EventHandler::hitTestResultAtPoint来计算HitTestResult。这一步就是WebCore所提供的HitTest功能,在后文详解。其中hitTestResult.rectBasedTestResult()中包含了一系列HitTest所击中的节点,并且它们按z轴有序排列,z值越高,越靠前

b. 对上述击中节点按正序做一个循环(即从最上层的节点开始向下),循环中做这么几件事。一、使用nodeIsClickableOrFocusable来保证该节点是可点击或者可获得焦点的,否则找其父节点,直至document节点。也就是说如果击中了一个普通的文字节点,向上也没有节点符合条件的话,在这里会被pass掉。二、如果和已存在候选节点重复,则pass掉。三、如果getAbsoluteBoundingBox得不到该节点的值,则查找子节点,如果得不到有效值则pass。四、这些测试都通过的节点加入候选节点列表。注意,由于循环是有序的,所以此时候选节点列表也是有序的。

c. 对候选节点列表按序做循环,循环中做一件事,将节点与点击区域相交,如果结果比最终候选节点大的话,那该节点成为最终候选节点。通俗的讲,取与点击区域重合最大的节点。如果重合一样,自然是取前面的节点,也就是z值更大的节点。

d. 此时最终节点已经获取了,进行一些设置后返回上层


3. RenderLayer层

上述WebCore::EventHandler::hitTestResultAtPoint会一直调用到WebCore::RenderLayer::hitTestLayer函数来实现功能。主要做了这几个事

a. 对位置进行一些变换

b. 对层列表调用hitTestList, hitTestList和hitTestLayer是递归调用,递归的终点是某一层已经没有子层。层列表有几种,m_posZOrderList(大于等于0的层列表),m_negZOrderList(小于0的层列表)和m_normalFlowList。它们已经按z值排序好,也就是说这是一个深度优先搜索遍历,遍历的结果是各个层按z值的顺序。

c. 如果本层与点击位置有重叠的话,对本层调用hitTestContents,会一直调用到RenderLayer所对应的根节点的RenderBlock::nodeAtPoint函数,由该函数来测试本层哪些节点被击中。注意,并不是所有层都会走到这一步,有的层,如果它的子层已经被击中的话,那么它也不必走到这一步了。

d. 最终会通过HitTestResult::append将各个层的result结果累加起来,形成前文所说的m_rectBasedTestResult列表。但是m_innerNode则是取第一个击中结果的值,也就是最上层被击中的层的hitTest结果。这个结果是由RenderBlock::nodeAtPoint来进一步计算的


4. RenderBlock层

对于每一个RenderLayer而言,都有根RenderBlock节点,那么从该节点开始计算具体击中了哪个节点

a. RenderBlock::nodeAtPoint主要的一步是如果有子节点的话则调用到RenderBlock::hitTestContents,该函数中会倒序遍历子节点,并调用子节点的nodeAtPoint函数。显然,如果子节点也是RenderBlock类型的话,那这又是在递归调用。如果子节点并不是一个RenderBlock类型的话,那就会调用到RenderBox::nodeAtPoint。

b. 所以最终的计算就发生在两处。如果叶子节点是RenderBlock类型,那在RenderBlock::nodeAtPoint中计算。如果叶子节点不是RenderBlock类型,那在RenderBox::nodeAtPoint中计算。

c. 另外需要注意的是,这一步中的倒序遍历,在找到第一个result后就退出了。也就是说最终只找到了击中的第一个节点。


总体来说HitTest的过程还是相当复杂的。就分析来看HitTest的最终m_innerNode是击中的最上层layer的最上节点。而m_rectBasedTestResult则是各层被击中节点的按序排列。

由于水平有限,其中难免有一些个人推断和不正确之处,仅供参考


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值