FadingEdge 分析

最近设计需求要有边缘透明的淡入淡出,完成后发现其中的原理挺有意思的,于是分析一下系统是怎么生成边缘淡化 (FadingEdge)。

相关代码参见 FadingEdgeResearch

一. 系统滑动控件

使用 RecyclerView / ScrollView 等系统提供的可滑动控件,需要边缘淡化很简单,在 xml 中加入 android:requiresFadingEdge 指定需要淡化的方向即可。

跟踪 android:requiresFadingEdge 初始化设定的位置,是在最基本的 View 控件内。当 View 绘制时满足特定条件就会用内部的 ScrollabilityCache 绘制滚动条、边缘淡化等滑动用的元素。

抛开 ScrollabilityCache ,看看 View 的 draw(Canvas canvas) 方法,源代码里写得很清楚:

/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
*      1. Draw the background
*      2. If necessary, save the canvas' layers to prepare for fading
*      3. Draw view's content
*      4. Draw children
*      5. If necessary, draw the fading edges and restore layers
*      6. Draw decorations (scrollbars for instance)
*/

顺便回顾一下系统基本的绘制流程,一共分为 6 步。 通常不需要边缘淡化时,会按照 1 -> 3 -> 4 -> 6 的流程绘制。 其中第 3 步就是自定义控件中常用的 onDraw 方法。

当设置过 android:requiresFadingEdge,就会按照完整的顺序绘制了。

二. 自定义控件

因为边缘淡化是在 View 中处理的, 理论上所有自定义控件都能启用这个特性。 代码调用 setHorizontalFadingEdgeEnabled(boolean)setVerticalFadingEdgeEnabled(boolean) 可以开启水平竖直方向的淡化效果。

再回头看 View 绘制的第 2 步。边缘淡化的判断是由 drawTop 、 drawBottom 、 drawLeft 、 drawRight 这 4 个变量控制。

if (verticalEdges) {  
    topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
    drawTop = topFadeStrength * fadeHeight > 1.0f;
    bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
    drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
}

if (horizontalEdges) {  
    leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
    drawLeft = leftFadeStrength * fadeHeight > 1.0f;
    rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
    drawRight = rightFadeStrength * fadeHeight > 1.0f;
}

而判断的关键在于 getTopFadingEdgeStrength()getBottomFadingEdgeStrength()getLeftFadingEdgeStrength()getRightFadingEdgeStrength() 这4个方法。 在自定义控件中重写对应方向的方法就能使边缘淡化了,不过实际使用中还需要动态改变并刷新 View 来精确控制效果。

三. 手动绘制透明渐变

了解系统绘制边缘淡化的步骤后,如果需要自己绘制淡化效果,我们就可以直接对着源代码抄了😂…… 注意:这里特定指透明淡化。如果背景底色是纯色,我们可以很简单的在上层绘制出和背景色一致的渐变遮罩达到视觉上的淡化过度。

具体步骤如下:

PS 图示

  1. 创建用于颜色混合的图层。 即 View 绘制的第 2 步。
    使用 canvas.saveLayer(left, top, right, bottom, null, Canvas.HAS_ALPHA_LAYER_SAVE_FLAG) 创建出带有 Alpha 通道的图层。 用 Photoshop 来说明,相当于在原图层上添加一个图层蒙版。

  2. 绘制内容。

  3. 绘制渐变。 例如创建好 FadingPaint 的画笔,颜色只能是黑色到透明区间,并且需要设置 Xfermode 模式为 PorterDuff.Mode.DST_OUT 。 在 Canvas 上绘制出需要渐变的区域。

    这里有个误区: 一般介绍 Xfermode 都会说因为兼容性等需要关闭硬件加速。 Xfermode 使用的情景有很多种,在搭配 canvas.saveLayer 使用的情况下,不用关闭硬件加速。

  4. 合并图层。 最后使用 canvas.restoreToCount 混合图层,就能得到透明的边缘淡化了。 与前面 FadingPaint 的颜色相反,FadingPaint 的黑色区域是最后透明的区域。

具体的代码可以参考 DrawFadingEdgeViewDrawCircularFadingView

Author

Relex

Android Developer