本文已授权微信公众号:鸿洋(
hongyangAndroid
)在微信公众号平台原创首发。转载请注明出处
引言
蒙层引导在我们项目中一直的做法都是让UI
直接切一整张静态图,这样的做法虽然省事,但带来的后果就是适配性太差,还会出现引导图和下面真正的界面不符的情况,让用户感到莫名其妙。因此,就有必要自定义一个蒙层引导视图来解决这个问题。本篇文章主要是对核心原理实现的剖析。
核心原理分析
自定义引导视图(GuideView
)其实最主要的是需要解决三个问题:
- 引导视图应该是按需加载,在需要展示时浮在整个页面上,展示完毕后从页面视图中移除
- 确定需要引导的视图位置,并将之高亮
- 支持自定义提示视图,并根据高亮视图的位置摆放
对于第一个问题,很容易想到GuideView
应该继承自帧布局,这样可以保证浮在整个页面上。按需加载的话,可以在需要展示引导时,添加到页面的根视图(即DecorView
)上,展示完毕后再从根视图中移除即可解决。并且为了让蒙层布满全屏,应该在添加时,指定宽高为Decorview
的宽高。
对于第二个问题,高亮视图的位置,可以通过系统提供的获取屏幕位置的API
来解决
1 | public static RectF getRectOnScreen(View view) { |
获取到高亮位置后,下一步就是如何高亮?通常的做法就是将高亮位置从整个屏幕当中挖出来,改变屏幕其它位置的背景。如下图所示:
要实现这样的效果,就需要知道画布裁剪的知识。
裁剪共分为:裁剪路径、裁剪矩形、裁剪区域。裁剪后,只能编辑该区域,其它的区域并没有消失!
这里,我们可以选择裁剪路径。因为一般的引导库为了能够更好地引导用户,会在高亮区周围会绘制一些内容。
而路径(path
)很好地封装了由直线和曲线构的几何图形,如添加圆形、矩形、圆角矩形、椭圆形甚至一些复杂图形。因此在确定好高亮区位置后,我们也可以根据位置信息通过path
完成一些图片的绘制,如圆形:
1 | mPath.addCircle(mDrawRect.centerX(), mDrawRect.centerY(), radius, Path.Direction.CW); |
mDrawRect
就是高亮区的位置。注意:这里只是在path中描述了图形的轮廓。
继续说裁剪路径。裁剪路径的API
有以下两个。
1 | // 方法1 |
方法1默认调用了方法2,只是第二个参数传的值是 Region.Op.INTERSECT
1 | /** |
下面一张图解释下Region.Op 这个参数。它的作用就是在裁剪下多个区域时,当这些区域有重叠的时候,决定重叠部分该如何处理,多次裁剪之后究竟获得了哪个区域。
这里我们需要的是先裁剪出高亮区之外的区域,因为要绘制蒙层背景,但不能绘制到高亮区。所以选择的参数应该是Region.Op.DIFFERENCE
。
裁剪后,绘制需要的背景。
绘制完背景后,如果有需要,还可通过画布可以将上面的path中
描述的图形再绘制出来。
1 |
|
注意:ViewGroup
默认是不绘制的,因此这里要绘制的话,需要将开关打开。
1 | //需要重写onDraw 设置为false |
第三个问题,外部可以传入布局Id
,也可以直接传入View
,然后将提示视图添加到自定义的GuideView
中。如果传入的是ID
,我们内部可以使用布局解析器解析出来,然后再添加。
添加之后,如何摆放呢?这里我们可以通过Margin和Gravity来确定。具体规则如下:
到此,一个引导库的核心原理已经全部分析完毕。
其它功能分析
多个引导分步骤如何实现?
可以先定义一个
Java Bean
对象,用来封装需要的一些参数,如需要引导的视图,提示视图等。然后用集合来保存,每次从集合中取第一个元素中的提示视图,添加到GuideView
中,每次添加时,GuideView
都要移除所有子视图,保证每次只显示一个引导。同时,添加完毕后,从集合中移除这个元素。这里应该能够想到集合应该用个队列来实现,Java的集合体系中,支持队列的有Deque
接口,所有这里可以使用ArrayDeque
或者LinkedList
。如何控制引导的显示次数?
这个很容易想到用SP来控制。每次往SP中存取次数,默认只允许一次。当然还可以设定次数上限,或者永久显示用于调试。