自定义View之遮罩引导层超简单实现方法

遮罩引导层是很常用的一个功能,实现方法呢也是多种多样。今天就自己造一个轮子便于日后使用。

思路

要实现在当前视图上增加一层视图,仔细一想这不是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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值