美文网首页Android潜修者Androidandroid实用技术
推荐一个好用小巧的Android引导蒙版(浮层)库

推荐一个好用小巧的Android引导蒙版(浮层)库

作者: 胡奚冰 | 来源:发表于2017-07-30 14:00 被阅读4634次

更新:目前该库已更新v2.0版本,修改了调用api,详细使用可以看:可能是最好用的Android引导层库

前言

每当一个项目开发一个新功能,总会想办法及时让用户得知有这样一个新功能,这时通常会采用引导页或者蒙版(浮层)的方式提心用户,这里有需要关注的新内容。
遇到这种需求,最简单的想法就是将引导的布局直接写在对应的页面中,在首次打开时显示,之后隐藏。但是用这种做法来显示只会出现一次的布局,显然有些浪费资源。而且很low,完全体现不出OOP的编程思想。我们的项目中原来使用的是:http://www.jianshu.com/p/5aa96683d0dc
可以看到这是一个非常好的思路,通过DecorView来添加引导层,引导层的相关代码就可以从activity中抽离出来。高亮则是通过画笔的setXfermode来实现。作者也进行了一定的封装,使用的效果也挺好,但是我始终对调用的方法感觉不舒服:
每次使用时要判断是否显示过

if(NewbieGuideManager.isNeverShowed(this, NewbieGuideManager.TYPE_COLLECT)) {
      new NewbieGuideManager(this, NewbieGuideManager.TYPE_COLLECT).addView
              (mCollect, HoleBean.TYPE_CIRCLE).addView(mTitleTv, HoleBean
              .TYPE_RECTANGLE).show();
  }

通过下列方法显示出引导层

new NewbieGuideManager(MainActivity.this,
                                  NewbieGuideManager.TYPE_LIST).addView(view
                                  .getChildAt(0).findViewById(R.id.logo), HoleBean
                                  .TYPE_RECTANGLE).show();

这里还涉及到一个常量:NewbieGuideManager.TYPE_LIST,需要在manager类中定义,并且设置对应的布局:

mNewbieGuide.setEveryWhereTouchable(false).addIndicateImg(R.drawable.left_arrow, ScreenUtils.dpToPx(mActivity,
                                60), ScreenUtils.dpToPx(mActivity, 110)).addMsgAndKnowTv("这个listview滚动到item6后出现新手引导浮层,\n只有点击我知道啦才会想消失",
                                -ScreenUtils.dpToPx(mActivity, 250)).show();

并且每增加一种引导浮层就要重复上述3步的过程。

优化的NewbieGuide

由于非常介意上述的调用方式,于是我抽空在上述的思路上自己实现一个小巧的库,主要对调用方式进行了封装,通过链式调用,一行代码就可以实现引导层的实现。
Github:https://github.com/huburt-Hu/NewbieGuide

导入

先来看下如何导入项目中:
项目的build.gradle添加

allprojects {
        repositories {
            ...
            maven { url 'https://jitpack.io' }
        }
    }

module的build.gradle添加

dependencies {
     compile 'com.github.huburt-Hu:NewbieGuide:v1.0.2'
   }

使用

使用的话非常方便,在需要引导层的地方添加如下代码:

NewbieGuide.with(this)//传入activity
               .setLabel("guide1")//设置引导层标示,用于区分不同引导层,必传!否则报错
               .addHighLight(view, HighLight.Type.RECTANGLE)//添加需要高亮的view
               .setLayoutRes(R.layout.view_guide)//自定义的提示layout,不要添加背景色,引导层背景色通过setBackgroundColor()设置
               .show();//显示引导层

setLabel(String label)方法需要传入一个当前引导层的标示,用于sp缓存当前引导层是否已经显示过,因此是一个必须的参数,忘了设置的话会抛出异常!
addHighLight()方法有三个重载,第一个参数是需要高亮的view(通常是通过findViewById找到的view),第二个参数是高亮的类型,目前有四种:矩形,圆形,椭圆,圆角矩形(如果选择圆角矩形的话,调用重载3个参数的方法,第三个参数是圆角的dp值),默认的话是矩形即只有一个参数的重载。
setLayoutRes(int resId,int... id)该方法第一个参数传入xml的布局,可以任意编辑,如提示的文字,颜色,位置,图片等,皆有你来自定义。第二个参数是可变参数,传入该布局内需要点击消失引导层的view的id。之所以用这种方式是因为通过代码来调整视图非常麻烦,无法直接看到效果,我在使用中经常要部署好多次才会确定最终位置,远没有layout.xml来实现布局方便。

效果

效果

当然,这只是最简单的实现效果,具体要如何的界面都可以由你来自定义layout,并通过setLayoutRes()方法传入即可。

更多配置

Controller controller = NewbieGuide.with(this)
                .setOnGuideChangedListener(new OnGuideChangedListener() {//设置监听
                    @Override
                    public void onShowed(Controller controller) {
                        //引导层显示
                    }

                    @Override
                    public void onRemoved(Controller controller) {
                        //引导层消失
                    }
                })
                .setBackgroundColor(Color.BLACK)//设置引导层背景色,建议有透明度,默认背景色为:0xb2000000
                .setEveryWhereCancelable(false)//设置点击任何区域消失,默认为true
                .setLayoutRes(R.layout.view_guide, R.id.textView)//自定义的提示layout,第二个可变参数为点击隐藏引导层view的id
                .alwaysShow(true)//是否每次都显示引导层,默认false
                .build();//构建引导层的控制器
        controller.resetLabel("guide1");
        controller.remove();//移除引导层
        controller.show();//显示引导层

实现原理

这个库非常的小,总共只有5个类,一个接口,一个工具类。
高亮的实现是通过画笔的setXfermode。即当两个画布上都绘制了图片是,可以控制最终显示的样式,有取重叠部分,有去除重叠部分的等等,总共有16中规则,具体下图:

setXfermode属性
我所使用的是clear属性,即先通过canvas.drawColor(mBackgroundColor);绘制背景色,然后通过设置了clear属性的画笔
PorterDuffXfermode xfermode = new PorterDuffXfermode(PorterDuff.Mode.CLEAR);
        mPaint.setXfermode(xfermode);

镂空出需要高亮的view。
view在屏幕的位置可以通过下述方法获取:

public RectF getRectF() {
        RectF rectF = new RectF();
        if (mHole != null) {
            int[] location = new int[2];
            mHole.getLocationOnScreen(location);
            rectF.left = location[0];
            rectF.top = location[1];
            rectF.right = location[0] + mHole.getWidth();
            rectF.bottom = location[1] + mHole.getHeight();
        }
        return rectF;
    }

mHole就是之前传入的需要高亮的view。
余下就是调用的封装了,使用了建造者模式来保证链式调用,有兴趣的可以clone代码看下,也用不了多少时间。

后记

这个库也是我第一个公开的库,目前仅仅实现了基本的功能,后续也会不断维护和升级。有什么疑问或者建议,或者需要补充的需求都可以回复,留言,感觉各位阅读我的文章~

相关文章

网友评论

  • 人称老黄:适配起来好像有个问题! 我现在大屏幕手机上面(锤子手机) FloatingActionButton 是一个变成一个黑色的了! 其他小手机好像都是正常的!
    胡奚冰:@c46d1adc0dd4 Q&A里有说
  • 吃掉你了喔:父布局为ScrollVIew ,需要滚动连续出现指引
    吃掉你了喔:@胡奚冰 这个不是问题,问题在滑动后出现的蒙版,是以最开始高度计算的蒙层,尽管是在滑动后再添加的
    胡奚冰:@吃掉你了喔 百度一下如何判断滚动位置,获取子view,在合适的时机显示引导层
    吃掉你了喔:如何实现
  • 吃掉你了喔:如果是ScrollView 如何使用呢
  • d3163db4c2ea:大神我这有个问题 ,就是引导页出来之后状态栏还是显示出来了
    d3163db4c2ea:该怎么监听呢
    d3163db4c2ea:谢谢 大神 我再问下 我想在引导页点击一个btn然后取消引导 怎么弄呢
    胡奚冰:这篇文章写得还是1.0版本的,最新是2.1.0,你看github上的readme
  • d3163db4c2ea:大神请问下现在最新版本是多少呢
  • 0ba43f5af2a7:大神 我项目使用的fragment是V7包中的 用了您的这个第三方库里面是V4包的 ,我现在运行报错 需要V7 但是您的库是Gradle导入的 没办法修改,想请问下 有没有好的解决办法呢?
    0ba43f5af2a7:@胡奚冰 大神 这个依赖加上了还是报这个错误 可以帮忙看下么
    java.lang.NoClassDefFoundError: Failed resolution of: Landroid/support/v4/animation/AnimatorCompatHelper;
    at android.support.v7.widget.DefaultItemAnimator.resetAnimation(DefaultItemAnimator.java:515)
    0ba43f5af2a7:@胡奚冰 您能看下我下面的这个错误么 是V4要替换V7 还是V7要替换V4 大神 抱歉..
    胡奚冰:@DDDhy 项目的依赖示例不是提供了排除v4引用的方式嘛
  • 瑾瑾瑾i:为何在一个fragment中添加引导,然后其他界面再添加就不好用了?求大神解答
    胡奚冰:@瑾瑾瑾i 是不是用了相同的label?label用于区分不同的引导层
  • 瑾瑾瑾i:为何在一个fragment中添加引导,然后其他界面再添加就不好用了?求大神解答
  • 瑾瑾瑾i:为何在一个fragment中添加引导,然后其他界面再添加就不好用了?求大神解答
  • 韩伊君:大佬,设置外部不可点击之后,如果在指定区域点击消失。
    胡奚冰:@韩伊君 setLayout方法有多个重构,可以传入自定义xml中点击消失的控件id
  • 655a1751b9cc:你好,我在使用中遇到了一个问题,比如华为手机底部有虚拟导航栏,如果是对底部tab做引导,底部tab是在虚拟导航栏上部,而透明蒙层因为是全屏的那么透明蒙层上的布局在手机上展示的位置就会有偏差,请问透明蒙层的全屏展示是在哪里设置的哇?
    胡奚冰:@娥子娥子娥子 高亮的蒙版以及引导层布局都是去掉虚拟导航栏的高度的,应该不会有你说的问题啊,保证引导层布局的参数与要显示的view所在的布局参数一致,比如你的tab是alignBottom,那引导层中用于说明的控件也是alignBottom
  • 根号仨:你好,使用你的遮罩库,因为设置沉浸式全屏,StatusBarUtil设置透明度后,高亮处显示错位
    胡奚冰:@根号仨 我这边能复现这个问题了,我今天看下原因并修复这个问题,兼容StatusBarUtil,修复好了再答复你(会更新一个引导层的小版本)。
    根号仨:你说的是StatusBarUtil?还是引导?
    胡奚冰:@根号仨 使用的版本是1.2.1嘛?
  • 大尾巴_鱼:请问按返回键的时候引导层不消失怎么办啊。。。
    胡奚冰:@大尾巴_鱼 引导层是跟随页面的,消失的操作默认是点击屏幕,如果你需要实现返回消失引导层,复写activity的onBackPressed方法,调用NewbieGuide....build()方法返回的Controller的remove()方法。
    胡奚冰:@大尾巴_鱼 with()传的activit还是Fragment?
  • 66b17a52b89a:话说使用gradle引用是找不到您这个库的。。
  • 66b17a52b89a:甚是佩服,加油
  • 01e02d83e43b:您好,我用的版本是v1.1.1,我需要显示多个引导页,在第一个引导页的监听setOnGuideChangedListener的onRemoved方法里再次使用NewbieGuide.with(this)... 的方法显示第二个引导页,发现第二个引导页不显示,好像是第二个引导页显示后又走了onRemoved方法关闭了,但是连续调用两次就显示了第二个引导页,什么原因呢
    01e02d83e43b:我在fragment里使用的, NewbieGuide.with(this)传的参数是this,我改成getActivity()后就好使了,不会出不显示第二个引导页,尴尬:joy:
  • 0493a0054a95:minSdkVersion 最低16吗
    瑾瑾瑾i:为何在一个fragment中添加引导,然后其他界面再添加就不好用了?求大神解答
    胡奚冰:@ItApe 你说的时整体的背景色吗?可以通过setBackgroundColor(Color.BLACK)设置颜色
    胡奚冰:@Z先生_41f3 只是新建工程默认的16,用到的api是低版本可以支持的,如果着急使用的可以clone后自己修改minSdkVersion,最低支持14
  • qzuser_a821:大佬 怎么设置全屏显示呢, 我修改主题 去掉标题栏,状态栏 好像都不行
    d3163db4c2ea:@qzuser_a821 状态栏在哪儿设置呢?急急 大哥
    qzuser_a821:@胡奚冰 我查看 源码发现了代码给它设置了topMargin=状态栏的高度, 我把他改为0就可以了 嘿嘿 :relaxed:
    胡奚冰:默认就是全屏,只是不包含状态栏河虚拟操作栏,由于引导层是添加在DecorView中的,而状态栏是在DecorVIew之上(不在同一层布局),因此无法用下层的view盖住上层view。你说的效果只有通过设置状态栏透明来实现,可以参考这个库:https://github.com/laobie/StatusBarUtil
  • 开发小菜鸟:传入控件id后高亮的地方在左上角不能对应起来
    开发小菜鸟:@胡奚冰 加入的是两个提示,只加一个的时候是没有问题的
    开发小菜鸟:@胡奚冰 额,说错了,没有传入id就是view
    胡奚冰:控件id不是代表高亮,只是代表点击消失引导层,高亮的view 通过addHighLight(view)添加
  • Only凹凸曼:放在DecorView中,如果快速点击,是不是会出现引导层出现在另一个Activity的上面,这种情况有处理吗
    胡奚冰:@Only凹凸曼 快速点击只会打开2次同一个activity,如果你说的是点击第二次是在有引导层的activity中是不可能触发点击事件的,引导层优先获得事件,要么拦截事件,要么引导层消失消耗时间,只有第三次点击才能触发activity的事件
  • 土豆啊你个马铃薯:如何实现默认显示第一个引导层,然后点击隐藏第一个引导层 显示下一个引导层???
    iae86:有显示多个引导层的代码吗 ,我的老是报错
    胡奚冰:@土豆啊你个马铃薯 设置引导层的小时监听
    .setOnGuideChangedListener(new OnGuideChangedListener() {//设置监听
    @Override
    public void onShowed(Controller controller) {
    //引导层显示
    }

    @Override
    public void onRemoved(Controller controller) {
    //引导层消失
    }
    })
    在消失时添加第二个引导层
  • ee0bcc7732c5:cannot resolve symbol'with()'
    胡奚冰:@智能诊断 请检查参数是否正确:NewbieGuide.with(this)//this为activity或者fragment
  • 50871e6bd13e:这只是activity吧,fragment怎么搞
    胡奚冰:@袁恩胜YES真是巧 fragment中可以通过getActivity()方法获取activity对象,对fragment的更多支持我正在开发中
  • 夜客小虾:setLayoutRes(int resId,int... id)这个方法里的布局是写死的吗,我怎么让第二个参数对应到高亮的view的具体位置,比如在高亮view的上边还是下面
    胡奚冰:这里布局的根布局设置成match_parent,这样就能撑满屏幕,里面的布局自己根据位置调整
  • fbff850cd0ee:真的好奇为什么我引用compile 'com.github.huburt-Hu:NewbieGuide:v1.0.3'会出现错误,
    土豆啊你个马铃薯:@土豆啊你个马铃薯 maven { url "https://jitpack.io" }
    土豆啊你个马铃薯:@胡奚冰 加了也报错了
    胡奚冰:检查一下有没有在
    项目的build.gradle添加

    allprojects {
    repositories {
    ...
    maven { url 'https://jitpack.io' }
    }
    }
  • e7fa46c17820: NewbieGuide.with(this)//传入activity
    .setLabel("guide1")//设置引导层标示,必传!否则报错
    .addHighLight(textView, HighLight.Type.RECTANGLE)//添加需要高亮的view
    .setLayoutRes(R.layout.view_guide)//自定义的提示layout,不要添加背景色,引导层背景色通过setBackgroundColor()设置
    .alwaysShow(true)
    .setBackgroundColor(Color.YELLOW)
    .show();//直接显示引导层

    为何设置背景色,没有什么反应
    e7fa46c17820:@胡奚冰 好的,感谢:clap:
    胡奚冰:对是否设置颜色的判空出现问题,现在已经修复了,可以引用1.0.3版本,就可以正常设置背景色了
    0272f875d802:你自己debug看一下不就行了么:sweat: ,if (backgroundColor > 0) 你把这个代码注释掉就好了

本文标题:推荐一个好用小巧的Android引导蒙版(浮层)库

本文链接:https://www.haomeiwen.com/subject/yrihlxtx.html