最近在项目开发中,产品设计出了新功能的用户引导,非启动页面左右滑动类型的引导,而是在APP功能界面上,直接上层弹出蒙层形式展示,其实在过往的开发中,也有过相同的功能,但是都比较少,就基本上都采用简单粗暴的直接在XML中布局,然后控制Visible,然后添加点击事件就行。最近公司APP中,产品提出来的有点多,而且部分引导区域需要做到事件的穿透,部分不穿透,甚至出现了异形的镂空,再简单粗暴的去布局,对于后期XML布局的维护存在一定的成本。于是自己决心花费周末的一个时间,写一个公共的库。这个库一定要简单,不能再在XML中布局,并且容易理解。先来几张样例图(网图,和实现功能一样)
废话不多说,先下载Demo 看效果,非常小
网图:
上面是网络上的图片,下面是我的实现图片(图大,就用三张)
自己在线PS做的图,有点丑,将就看。
话不多说,源码:SmartGuideView 到Github去下载吧。
引用方式:
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'aiven.guide.view:library:1.0.1'
//或者使用api
// api 'aiven.guide.view:library:1.0.1'
}
还是说一下代码把,这里只是说一下怎么用,API的含义,具体有兴趣的朋友可以直接看源码。
类入口:SmartGuide
创建蒙层。
SmartGuide.newGuide(this) 这里的this 需要传入一个Activity或者fragment。需要注意的是,fragment一定要在attach执行完毕之后才能去调用,因为归根接地用的还是activity。
第一个初始化方法:initBaseColor(颜色值) 初始化引导蒙层的默认背景颜色,一般都是半透明的黑色,所以默认我采用了50%透明度的黑色,可以之定义,如果不知道,就是默认值。
SmartGuide.newGuide(this) .initBaseColor(颜色值) 这样就构建完成了一个没有引导的蒙层。接下来在蒙层中创建一个用户引导。这里我将引导命名为层Layer。一个引导(Layer)包含一个蒙层中的镂空区域和图片介绍信息,具体例子就是上面蚂蚁花呗那张图中,白色蚂蚁花呗的横条就是镂空区域,下面一行白色文字和手就是图片介绍信息。
所以我将这两个分别命名为:Clip 和Panel。
newLayer(Tag) //创建一个引导,并给这个引导指定一个Tag,相当于Id。
所以一个Layer = Clip + Panel 。当然,都不是必须的。这就是接下来的API
buildViewRectClip、buildCustomClip、buildIntroPanel
不是说两个嘛?怎么冒出了三个函数?
这里ViewRectClip和CustomeClip 都是Clip,两种镂空区域。
ViewRectClip 是一个根据界面上的一个View 在屏幕上的位置,尺寸计算出镂空区域和大小,CustomeClip 是一个绝对位置自定义的裁剪区域,以根布局左上角为坐标定点,自己设置区域和偏移,当然这里我增加了Align,这个就不解释了,程序员做布局基本上都懂。通过buildViewRectClip和buildCustomeClip来设置,参数都是一个Build内部接口,然后返回响应的Clip接口,具体Clip的构建参数见下面的例子中说明。这里不再详细说,或者直接到github上看。clip 有个API需要特殊说明:asIrregularShape(Bitmap) 这个是传入一个异形图片,以图片形状作为镂空形状,上图中有个实例,一个老鼠的造型。
Panel:也通过build方式内部接口回调,Panel也有一个Align,这里的Align和CustomClip的Align 有点区别,这里的Align是Panel 现对于Clip而言。也就是Pannel 局clip的,上、下、做、右。然后设置offsetX,offsetY设置偏移即可。
点击事件:OnGuidClickListener 接口,有三个回调函数,
emptyErrorClicked 点击了非Panel和clip区域,返回值:true 蒙层直接退出,false不退出
clipClicked 点击了clip镂空区域,这里要说明的是,Clip有个接口是API是setEventPassThrough(boolean),这个是标明是否镂空区域点击事件要穿透到APP自身功能布局中的UI响应。
introClicked 点击了Panel 图片介绍区域。
创建完成Layer后可以直接show() 显示,如果一个蒙层同时要显示多个Layer,则要先调用over()方法结束上一个layer的参数设置功能,直接newLayer即可,具体见github的多Layer展示方案。
代码之前,再附带一下传送门:SmartView
https://github.com/aiven163/SmartGuideView
代码实例一:
SmartGuide.newGuide(this)
.initBaseColor(0X80000000)//设置引蒙层背景颜色
//新建一个引导
.newLayer(TAG_USER_HEADER)
//创建一个镂空区域
.buildCustomClip(new SmartGuide.ClipPositionBuilder() {
@Override
public CustomClipbuildTarget() {
//构建镂空区域图形,支持CustomClip 和ViewRectClip
return CustomClip.newClipPos()
.setAlignX(SmartGuide.AlignX.ALIGN_RIGHT)//设置定位水平定位偏向
.setAlignY(SmartGuide.AlignY.ALIGN_TOP)//设置定位垂直定位偏向
.setOffsetX(SmartUtils.dip2px(getApplicationContext(),14))//根据水平定位偏向设置偏移,如果未ALIGN_LEFT,则是距离屏幕左侧偏移量,如果是ALIGN_RIGHT 则是距离屏幕右侧偏移量
.setOffsetY(SmartUtils.getStatusBarHeight(getApplicationContext())+SmartUtils.dip2px(getApplicationContext(),4))
//设置镂空裁剪区域尺寸
.setClipSize(SmartUtils.dip2px(getApplicationContext(),48),SmartUtils.dip2px(getApplicationContext(),48))
.clipRadius(SmartUtils.dip2px(getApplicationContext(),24));
}
})
.buildIntroPanel(new SmartGuide.IntroPanelBuilder() {
@Override
public IntroPanelbuildFacePanel() {
return IntroPanel.newIntroPanel(getApplicationContext())
//设置介绍图片与clipInfo的对齐信息
.setIntroBmp(R.mipmap.test_face)
.setAlign(SmartGuide.AlignX.ALIGN_LEFT,SmartGuide.AlignY.ALIGN_BOTTOM)
.setSize(SmartUtils.dip2px(getApplicationContext(),151),SmartUtils.dip2px(getApplicationContext(),97))
.setOffset(SmartUtils.dip2px(getApplicationContext(),-20),0);
}
})
.over()//多个newLayer必须用over作为上一个newLayer的结束连接
.newLayer(TAG_MUSIC_IMG)
//创建一个镂空区域
.buildViewRectClip(new SmartGuide.ClipPositionBuilder() {
@Override
public ViewRectClipbuildTarget() {
return ViewRectClip.newClipPos()
.setDstView(R.id.text_pos)
.setPadding(SmartUtils.dip2px(getApplicationContext(),5))
.clipRadius(SmartUtils.dip2px(getApplicationContext(),51));
}
})
.buildIntroPanel(new SmartGuide.IntroPanelBuilder() {
@Override
public IntroPanelbuildFacePanel() {
return IntroPanel.newIntroPanel(getApplicationContext())
//设置介绍图片与clipInfo的对齐信息
.setIntroBmp(R.mipmap.test_face_music)
.setAlign(SmartGuide.AlignX.ALIGN_LEFT,SmartGuide.AlignY.ALIGN_TOP)
.setSize(SmartUtils.dip2px(getApplicationContext(),120),SmartUtils.dip2px(getApplicationContext(),120))
.setOffset(SmartUtils.dip2px(getApplicationContext(),-100),0);
}
})
.setOnGuidClickListener(new SmartGuide.OnGuidClickListener() {
@Override
public boolean emptyErrorClicked() {
return true;
}
@Override
public void clipClicked(SmartGuide guide, GuidView view, String tag) {
if (TAG_USER_HEADER.equals(tag)) {
Toast.makeText(getApplicationContext(), "点击了左上角头像裁剪区域", Toast.LENGTH_SHORT).show();
}else if(TAG_MUSIC_IMG.equals(tag)){
Toast.makeText(getApplicationContext(), "点击了紫色音乐图标裁剪区域", Toast.LENGTH_SHORT).show();
}
}
@Override
public void introClicked(SmartGuide guide, GuidView view, String tag) {
if (TAG_USER_HEADER.equals(tag)) {
Toast.makeText(getApplicationContext(), "点击了左上角头像图片介绍区域", Toast.LENGTH_SHORT).show();
}else if(TAG_MUSIC_IMG.equals(tag)){
Toast.makeText(getApplicationContext(), "点击了紫色音乐图片介绍区域", Toast.LENGTH_SHORT).show();
}
}
})
.show();
}
代码实例二:
/**
* 根据View 自身位置定位
* @param view
*/
public void showViewPosLayer(View view){
//构建引导
SmartGuide.newGuide(this)
.initBaseColor(0X80000000)//设置引蒙层背景颜色
//新建一个引导
.newLayer(TAG_MUSIC_IMG)
//创建一个镂空区域
.buildViewRectClip(new SmartGuide.ClipPositionBuilder() {
@Override
public ViewRectClipbuildTarget() {
return ViewRectClip.newClipPos()
.setDstView(R.id.text_pos)
.setPadding(SmartUtils.dip2px(getApplicationContext(),5))
.clipRadius(SmartUtils.dip2px(getApplicationContext(),51));
}
})
.buildIntroPanel(new SmartGuide.IntroPanelBuilder() {
@Override
public IntroPanelbuildFacePanel() {
return IntroPanel.newIntroPanel(getApplicationContext())
//设置介绍图片与clipInfo的对齐信息
.setIntroBmp(R.mipmap.test_face_music)
.setAlign(SmartGuide.AlignX.ALIGN_LEFT,SmartGuide.AlignY.ALIGN_TOP)
.setSize(SmartUtils.dip2px(getApplicationContext(),120),SmartUtils.dip2px(getApplicationContext(),120))
.setOffset(SmartUtils.dip2px(getApplicationContext(),-100),0);
}
})
.setOnGuidClickListener(new SmartGuide.OnGuidClickListener() {
@Override
public boolean emptyErrorClicked() {
return true;
}
@Override
public void clipClicked(SmartGuide guide, GuidView view, String tag) {
Toast.makeText(getApplicationContext(), "点击了紫色音乐图标裁剪区域", Toast.LENGTH_SHORT).show();
}
@Override
public void introClicked(SmartGuide guide, GuidView view, String tag) {
Toast.makeText(getApplicationContext(), "点击了紫色音乐图标介绍图片区域", Toast.LENGTH_SHORT).show();
}
})
.show();
}
最后多个Layer代码实例:
/**
* 单屏显示多个layer
* @param view
*/
public void showMultLayer(View view){
SmartGuide.newGuide(this)
.initBaseColor(0X80000000)//设置引蒙层背景颜色
//新建一个引导
.newLayer(TAG_USER_HEADER)
//创建一个镂空区域
.buildCustomClip(new SmartGuide.ClipPositionBuilder() {
@Override
public CustomClipbuildTarget() {
//构建镂空区域图形,支持CustomClip 和ViewRectClip
return CustomClip.newClipPos()
.setAlignX(SmartGuide.AlignX.ALIGN_RIGHT)//设置定位水平定位偏向
.setAlignY(SmartGuide.AlignY.ALIGN_TOP)//设置定位垂直定位偏向
.setOffsetX(SmartUtils.dip2px(getApplicationContext(),14))//根据水平定位偏向设置偏移,如果未ALIGN_LEFT,则是距离屏幕左侧偏移量,如果是ALIGN_RIGHT 则是距离屏幕右侧偏移量
.setOffsetY(SmartUtils.getStatusBarHeight(getApplicationContext())+SmartUtils.dip2px(getApplicationContext(),4))
//设置镂空裁剪区域尺寸
.setClipSize(SmartUtils.dip2px(getApplicationContext(),48),SmartUtils.dip2px(getApplicationContext(),48))
.clipRadius(SmartUtils.dip2px(getApplicationContext(),24));
}
})
.buildIntroPanel(new SmartGuide.IntroPanelBuilder() {
@Override
public IntroPanelbuildFacePanel() {
return IntroPanel.newIntroPanel(getApplicationContext())
//设置介绍图片与clipInfo的对齐信息
.setIntroBmp(R.mipmap.test_face)
.setAlign(SmartGuide.AlignX.ALIGN_LEFT,SmartGuide.AlignY.ALIGN_BOTTOM)
.setSize(SmartUtils.dip2px(getApplicationContext(),151),SmartUtils.dip2px(getApplicationContext(),97))
.setOffset(SmartUtils.dip2px(getApplicationContext(),-20),0);
}
})
.over()//多个newLayer必须用over作为上一个newLayer的结束连接
.newLayer(TAG_MUSIC_IMG)
//创建一个镂空区域
.buildViewRectClip(new SmartGuide.ClipPositionBuilder() {
@Override
public ViewRectClipbuildTarget() {
return ViewRectClip.newClipPos()
.setDstView(R.id.text_pos)
.setPadding(SmartUtils.dip2px(getApplicationContext(),5))
.clipRadius(SmartUtils.dip2px(getApplicationContext(),51));
}
})
.buildIntroPanel(new SmartGuide.IntroPanelBuilder() {
@Override
public IntroPanelbuildFacePanel() {
return IntroPanel.newIntroPanel(getApplicationContext())
//设置介绍图片与clipInfo的对齐信息
.setIntroBmp(R.mipmap.test_face_music)
.setAlign(SmartGuide.AlignX.ALIGN_LEFT,SmartGuide.AlignY.ALIGN_TOP)
.setSize(SmartUtils.dip2px(getApplicationContext(),120),SmartUtils.dip2px(getApplicationContext(),120))
.setOffset(SmartUtils.dip2px(getApplicationContext(),-100),0);
}
})
.setOnGuidClickListener(new SmartGuide.OnGuidClickListener() {
@Override
public boolean emptyErrorClicked() {
return true;
}
@Override
public void clipClicked(SmartGuide guide, GuidView view, String tag) {
if (TAG_USER_HEADER.equals(tag)) {
Toast.makeText(getApplicationContext(), "点击了左上角头像裁剪区域", Toast.LENGTH_SHORT).show();
}else if(TAG_MUSIC_IMG.equals(tag)){
Toast.makeText(getApplicationContext(), "点击了紫色音乐图标裁剪区域", Toast.LENGTH_SHORT).show();
}
}
@Override
public void introClicked(SmartGuide guide, GuidView view, String tag) {
if (TAG_USER_HEADER.equals(tag)) {
Toast.makeText(getApplicationContext(), "点击了左上角头像图片介绍区域", Toast.LENGTH_SHORT).show();
}else if(TAG_MUSIC_IMG.equals(tag)){
Toast.makeText(getApplicationContext(), "点击了紫色音乐图片介绍区域", Toast.LENGTH_SHORT).show();
}
}
})
.show();
}