遮罩引导层是很常用的一个功能,实现方法呢也是多种多样。今天就自己造一个轮子便于日后使用。
思路
要实现在当前视图上增加一层视图,仔细一想这不是FrameLayout
的特性嘛,再仔细一想,我们的Activity
的根布局其实就是Window
对象持有的DecorView
.而这个DeecorView
就是一个FrameLayout
.想到这问题就简单了。需要实现的就是自定义一个View
了。
效果
怎么使用:
new GuideView.Builder()
.arrowBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.guide_arrow))
.guideText("我是引导文字~~~~你好吗?、、\n哈哈哈")
.lightShapeType(GuideView.RECT)
.targetView(testTarget)
.build(this)
.addToActivity(this);
复制代码
代码
首先我们我们需要知道我们要引导的View
是谁,于是来一个方法:
public void setTargetView(View targetView) {
this.targetView = targetView;
targetView.getLocationOnScreen(targetViewOut);
targetViewWidth = targetView.getWidth();
targetViewHeight = targetView.getHeight();
invalidate();
}
复制代码
上述方法获取了我们要引导的View
并且获取了目标控件在屏幕上的位置,以及目标控件的大小。方便我们绘制时候使用。
接下来我们开始绘制我们需要的东西:箭头,文字,目标控件高亮显示。 看一下OnDraw
方法
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//设置背景颜色为透明
setBackgroundColor(Color.TRANSPARENT);
if (targetView != null) {
int bitmapW = arrow.getWidth();
int bitmapH = arrow.getHeight();
int saved = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG);
//绘制半透明的底色
canvas.drawColor(bgColor);
//根据targetView位置,判断在屏幕的方位 绘制箭头
float arrowLeft, arrowTop, textTranslateY;
if (targetViewOut[1] > screenHeight / 2) { //屏幕下半区
arrowLeft = targetViewOut[0] - bitmapW;
arrowTop = targetViewOut[1] - bitmapH - arrowMargin;
textTranslateY = arrowTop - textHeight - arrowMargin;
} else {//屏幕上半区
arrowLeft = targetViewOut[0] + targetViewWidth;
arrowTop = targetViewOut[1] + targetViewHeight + arrowMargin;
textTranslateY = arrowTop + bitmapH + arrowMargin;
}
canvas.drawBitmap(arrow, arrowLeft, arrowTop, mPaint);
//绘制引导文字
canvas.save();
// canvas.translate(screenWidth / 2 - maxLineWidth / 2, screenHeight / 2);
canvas.translate(screenWidth / 2, textTranslateY);
staticLayout.draw(canvas);
canvas.restore();
//绘制高亮区域
mPaint.setXfermode(porterDuffXfermode);
if (highLightType == CIRCLE) {
int cx = targetViewOut[0] + targetViewWidth / 2;
int cy = targetViewOut[1] + targetViewHeight / 2;
canvas.drawCircle(cx, cy, Math.max(targetViewWidth, targetViewHeight) / 2, mPaint);
} else if (highLightType == RECT) {
canvas.drawRect(targetViewOut[0], targetViewOut[1], targetViewOut[0] + targetViewWidth,
targetViewOut[1] + targetViewHeight, mPaint);
}
mPaint.setXfermode(null);
canvas.restoreToCount(saved);
}
}
复制代码
OnDraw
方法还是很清晰明了的,注意两个地方:
绘制文字使用的是:staticLayout.draw(canvas);
而不是 canvas.drawText(()
这是因为直接使用 canvas 的方法绘制文字是不能自动换行的,所以我们这里使用StaticLayout
来绘制,看一下这个对象的构造方法
//构造方法定义
StaticLayout(CharSequence source, TextPaint paint, int width, Layout.Alignment align, float spacingmult, float spacingadd, boolean includepad)
//创建一个staticLayout
staticLayout = new StaticLayout(guideText, textPaint, (int) screenWidth, Layout.Alignment.ALIGN_NORMAL,
1.5f, 0, true);
复制代码
乍一看参数很多,但其实也很简单
- source:需要绘制的文字,可以包含换行符
- paint:画笔对象
- width :希望在多少宽度范围内进行绘制,超过这个宽度就会自动换行。这里我们传的是整个屏幕的宽度
- align:文字的对齐方式。这里我们使用普通方式
- spacingmult:行距,表示文字的每一行间隔1.5倍标准行距
- spacingadd: 是行间距的额外增加值,通常情况下填 0 就好;
- includepad: 是指是否在文字上下添加额外的空间,来避免某些过高的字符的绘制出现越界。
第二个注意点:
porterDuffXfermode = new PorterDuffXfermode(PorterDuff.Mode.CLEAR);
/。。some code。。/
//绘制高亮区域
mPaint.setXfermode(porterDuffXfermode);
/。。some code。。/
canvas.drawCircle(cx, cy, Math.max(targetViewWidth, targetViewHeight) / 2, mPaint);
/。。some code。。/
canvas.drawRect(targetViewOut[0], targetViewOut[1], targetViewOut[0] + targetViewWidth,
targetViewOut[1] + targetViewHeight, mPaint);
mPaint.setXfermode(null);
复制代码
这里我们绘制高亮区域时候,为画笔设置了一个 PorterDuffXfermode
对象,并且传入的参数是PorterDuff.Mode.CLEAR
。
这其实是在设置绘制中图像相交区域的混合模式。具体效果请参照官方文档
我们这里使用了简单的 PorterDuff.Mode.CLEAR
意思是 在两个图形相交的区域什么也不绘制。这会达到什么效果呢,我们之前为整个View绘制了一个半透明的背景颜色,这里我们选择 CLEAR 模式绘制了一个圆/矩形,这其实就是把我们绘制的 圆/矩形 这块区域清空了,什么也没有,就露出了下层的要展示的View了。
ok,整个View的绘制就完成了。最后看一看如何将我们的遮罩层添加到Activity
中:
public void addToActivity(Activity activity) {
this.activity = activity;
FrameLayout dec = (FrameLayout) activity.getWindow().getDecorView();
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
dec.addView(this, params);
}
复制代码
就是找到我们的DecorView
,添加进去就好了。
结束
整个引导控件已经完成,使用起来也很方便。最后完整代码已经上传到Github