Android自定义View-引导蒙版

2,820 阅读3分钟

引导蒙版

引导蒙版分为三部分

1、高亮显示的目标布局

2、围绕着目标布局的围栏布局

3、用于提示的蒙版布局

DEMO地址:github.com/chaozhouzha…

GuideMask guideMask = new GuideMask.Builder(this)
        //蒙版所在activity
        .setMaskActivity(this)
        //目标布局
        .setTargetView(mIvTest)
        //蒙版背景颜色包括透明度
        .setBgColor(Color.parseColor("#40000000"))
        //蒙版布局
        .setMaskLayout(R.layout.layout_mask)
        //关闭蒙版的按钮
        .setMaskCloseId(R.id.btn_close)
        //围栏与目标布局的距离
        .setFencePadding(0,0,0,0)
        //围栏布局四角的角度
        .setFenceRadius(100)
        .build();

GuideMaskSet guideMaskSet = new GuideMaskSet();
//添加蒙版
guideMaskSet.addGuide(guideMask);
//显示所有蒙版
guideMaskSet.show();

1、自定义FrameLayout

在ViewGroup中,初始化时设置了WILL_NOT_DRAW,设置WILL_NOT_DRAW之后,onDraw()不会被调用,目的是略过绘制的过程,优化了性能。所以,在写自定义ViewGroup布局时,如果需要调用onDraw()进行绘制,则需要在初始化时候,调用setWillNotDraw(false)。

setWillNotDraw(false);

2、使用LayoutInflater解析蒙版布局,并添加到当前自定义布局中

配置root为当前自定义布局,配置attachToRoot为true,也就是解析蒙版布局结束后,直接添加到当前自定义布局中。

LayoutInfalter的使用与源码解析可以看本公众号的文章:

mp.weixin.qq.com/s?__biz=MzU…

mMaskLayoutView = LayoutInflater.from(mMaskActivity).inflate(mMaskLayout, this, true);

3、将自定义布局添加到activity的content布局中

添加:

mContentParent.post(new Runnable() {
    @Override
    public void run() {
        //显示蒙版,也就是将当前蒙版加到mContentParent的FrameLayout布局上
        mContentParent.addView(GuideMask.this, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
    }
});

找到关闭蒙版的ID控件,点击后将自定义布局从activity的content布局中移除:

if (mCloseId != 0) {
    //在蒙版布局中找到点击关闭蒙版的控件
    mMaskLayoutView.findViewById(mCloseId).setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            dismiss();
            if (mOnDismissListener != null) {
                mOnDismissListener.onDismiss();
            }
        }
    });
}

移除:

public void dismiss() {
    mContentParent.post(new Runnable() {
        @Override
        public void run() {
            //关闭蒙版,也就是将当前蒙版从mContentParent的FrameLayout布局上移除
            mContentParent.removeView(GuideMask.this);
        }
    });
}

4、绘制围栏布局,以突出目标布局

4.1、设置围栏的画笔

图像过渡模式,设置为清除模式,用于显示目标布局:

//首先xfermode绘图需要两部分,DST,SRC 两种。可以理解为DST 在下边,SRC在上面。也就是说DST先绘制,SRC 后绘制。
//PorterDuff.Mode.CLEAR:清除模式,[0, 0],即图像中所有像素点的alpha和颜色值均为0。
PorterDuff.Mode mode = PorterDuff.Mode.CLEAR;
mFenceClearMode = new PorterDuffXfermode(mode);
//这个方法用于设置图像的过渡模式,所谓过渡是指图像的饱和度、颜色值等参数的计算结果的图像表现。
//设置围栏区域为清除模式,以达到显示目标布局的目的
mFencePaint.setXfermode(mFenceClearMode);

设置画笔遮罩滤镜,用于凸出目标布局:

//设置画笔遮罩滤镜,传入BlurMaskFilter或EmbossMaskFilter,前者为模糊遮罩滤镜而后者为浮雕遮罩滤镜。
//如果应用启用了硬件加速,将看不到任何阴影效果。
mFencePaint.setMaskFilter(new BlurMaskFilter(10, BlurMaskFilter.Blur.INNER));
//关闭当前View的硬件加速。
setLayerType(LAYER_TYPE_SOFTWARE, null);

4.2、onDraw绘制围栏

获取目标布局和content布局的位置坐标:

/**
 * 获取当前蒙版所目标的布局的坐标位置。
 */
mTargetView.getGlobalVisibleRect(mTargetRect);
/**
 * getGlobalVisibleRect()是View可见区域相对与屏幕来说的坐标位置。
 * getLocalVisibleRect()是View可见区域想对于自己坐标的位置。
 * 获取当前蒙版所在activity根布局的坐标位置。
 */
mContentParent.getGlobalVisibleRect(mContentRect);

计算围栏布局的位置坐标:

/**
 * 需要绘制的坐标位置
 */
int topMargin = mContentRect.top;
int left = mTargetRect.left - mPaddingLeft;
mFenceRectF.left = left;
int right = mTargetRect.right + mPaddingRight;
mFenceRectF.right = right;
int top = mTargetRect.top - mPaddingTop - topMargin;
mFenceRectF.top = top;
int bottom = mTargetRect.bottom + mPaddingBottom - topMargin;
mFenceRectF.bottom = bottom;
/**
 * rx:x方向上的圆角半径。
 * ry:y方向上的圆角半径。
 */
canvas.drawRoundRect(mFenceRectF, mRadius, mRadius, mFencePaint);

5、建造者模式构建蒙版

GuideMask guideMask = new GuideMask.Builder(this)
        //蒙版所在activity
        .setMaskActivity(this)
        //目标布局
        .setTargetView(mIvTest)
        //蒙版背景颜色包括透明度
        .setBgColor(Color.parseColor("#40000000"))
        //蒙版布局
        .setMaskLayout(R.layout.layout_mask)
        //关闭蒙版的按钮
        .setMaskCloseId(R.id.btn_close)
        //围栏与目标布局的距离
        .setFencePadding(5,5,5,5)
        //围栏布局四角的角度
        .setFenceRadius(100)
        .build();

6、整合蒙版集合,关闭蒙版后显示下一个蒙版

/**
 * 添加蒙版
 * @param guideMask
 */
public void addGuide(GuideMask guideMask) {
    mGuideMasks.add(guideMask);
    guideMask.setOnDismissListener(new GuideMask.OnDismissListener() {
        @Override
        public void onDismiss() {//关闭后显示下一个蒙版
            int nextPosition = mPosition + 1;
            if (mGuideMasks.size() > nextPosition) {
                GuideMask next = mGuideMasks.get(nextPosition);
                mPosition++;
                next.show();
            }
        }
    });
}

欢迎关注公众号,Android技术堆栈: