android 功能引导,android 新手引导浮层的实现

本文介绍了作者如何重构一个新手引导浮层,支持自定义样式、高亮、定位和回调,重点讲解了使用建筑者模式、Xfermode技巧、内存优化以及管理浮层显示的策略。通过实例展示了如何在不同场景下正确使用浮层,并提供了关键代码片段。
摘要由CSDN通过智能技术生成

前言

这个模块写了很早了,在实际项目中更新了很多次,但是太懒了,博客里没有跟着更新,看到阅读人数这么多,在回头看看自己写的代码这么烂,都不好意思了,于是决定更新一下。PS:看了评价里有人说放在fragment里显示会乱,我表示不知道为什么他们会这么想,在fragment显示正常的。

更新(2016-11-22):

使用建筑者模式构造引导浮层

支持在onCreate()等方法(View还未加载时)中设置并显示引导浮层

支持高亮洞洞的额外扩大和缩小

本来在我的项目中使用的新手引导浮层是这个TourGuide开源项目,这个新手引导项目功能比较多,一部分功能用不到,用到的地方只能大体上满足视觉的样式,不能一模一样。因此决定重构一遍,满足自己项目中的新手引导,本人项目中新手引导页的样式如下图所示:

5aa96683d0dc

新手引导.png

实现的功能

选中的view高亮可以有任意多个,形状有矩形,圆形,椭圆形

指示箭头或者其他图片可以在任意位置,可以有任意多个

文字和我知道了按钮可以在任意位置(默认我知道了在文字下方,两者水平居中,上下可调)

点击我知道了引导浮层消失

可设置点击任何位置引导浮层消失(默认点击消失)

浮层出现和消失可以有回调接口,可以延迟出现

实现的原理

1. 浮层的位置,放在activity的DecorView里,DecorView为FrameLayout的子类。

DecorView为整个Window界面的最顶层View。

DecorView只有一个子元素为LinearLayout,代表整个Window界面。

LinearLayout里有两个FrameLayout子元素,分别是标题栏和内容。

可通过以下代码获取,不清楚可参考这篇文章Android DecorView浅析

mParentView = (FrameLayout) mActivity.getWindow().getDecorView();

2. 引导浮层布局及上面的元素

浮层为相对布局,除了高亮的地方和半透明的背景其余都是通过addView的方式添加进去,通过设置margin来调整添加子view的位置。

比如箭头元素的添加,offsetX(offsetY)负数则从右边(下边)开始偏移,CENTER为居中,方便设置具体位置

public NewbieGuide addIndicateImg(int id, int offsetX, int offsetY) {

ImageView arrowImg = new ImageView(mActivity);

arrowImg.setImageResource(id);

mGuideView.addView(arrowImg, getLp(offsetX, offsetY));

return this;

}```

```java

private RelativeLayout.LayoutParams getLp(int offsetX, int offsetY) {

RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(ViewGroup

.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);

//水平方向

if (offsetX == CENTER) {

lp.addRule(RelativeLayout.CENTER_HORIZONTAL, RelativeLayout.TRUE);

} else if (offsetX < 0) {

lp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE);

lp.rightMargin = -offsetX;

} else {

lp.leftMargin = offsetX;

}

//垂直方向

if (offsetY == CENTER) {

lp.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE);

} else if (offsetY < 0) {

lp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, RelativeLayout.TRUE);

lp.bottomMargin = -offsetY;

} else {

lp.topMargin = offsetY;

}

return lp;

}

高亮洞洞的绘制

思路一,本人想了一个比较巧妙的方法,拿圆形的高亮洞洞来说,画一个镂空的圆形,你会发现当画笔足够粗到把屏幕都遮起来的时候,刚好中心的小圆没有画到是高亮的,这个方法只需要调整好画笔的粗度和圆形的直径就可以了,矩形也是可垟,调整好边长和画笔的粗度就可以实现了,不过最后发现椭圆是不行的, 所以总结下这个方法只能一个高亮洞洞,而且只能圆形或矩形。

5aa96683d0dc

画笔粗到可以遮挡屏幕时,中间的地方就高亮的洞洞

所以最后还是使用和TourGuide同样的方法,通过画笔的setXfermode来实现,即当两个画布上都绘制了图片是,可以控制最终显示的样式,有取重叠部分,有去除重叠部分的等等,这个有16中规则,具体下图:

5aa96683d0dc

setXfermode属性

具体用法可以参考两篇文章 Android中Xfermode简单用法和详解Paint的setXfermode。简单地说,TourGuide做法就是在一个画布上画了一个屏幕大小背景,在另一个画布上画了一个圆形,因为重叠了,所以去除了重叠的部分,高亮洞洞就显示出来了。

本人针对这个做法做了点优化,即画了一个和洞洞所需要一样大小的图片而不是屏幕一样大小,如果有两个洞洞,则画了一个能同时容下两个洞洞的矩形大小的图片,这样显示的结果最终会变成下面这样:

5aa96683d0dc

只画了满足高亮洞洞大小的图片

高亮洞洞都显示出来了,但是只画了一部分的背景,首先这样做的目的是为了用较少的内存去完成(一个1080p屏幕大小的半透明的背景图bitmap大概需要3M以上)那剩余的空白部分该怎么填充呢,一个方法是用四个view去填充上,这个做法是可行的,但是麻烦,其实一个比较简单的做法我已经在上面提到了,就是思路一中说的画一个矩形,调整好画笔的粗度和长宽即可填充完,关键代码如下。

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

if(mHoleList != null && mHoleList.size() > 0) {

mPaint.setXfermode(pdf);

mPaint.setMaskFilter(bmf);

mPaint.setStyle(Paint.Style.FILL);

for (HoleBean hole : mHoleList) {

switch (hole.getType()) {

case HoleBean.TYPE_CIRCLE:

mCanvas.drawCircle(hole.getCenterX() - mBitmapRect.left, hole

.getCenterY() - mBitmapRect.top, hole.getRadius(),

mPaint);

break;

case HoleBean.TYPE_RECTANGLE:

mCanvas.drawRect(modifyRect(hole.getRectF()), mPaint);

break;

case HoleBean.TYPE_OVAL:

mCanvas.drawOval(modifyRect(hole.getRectF()), mPaint);

break;

}

}

canvas.drawBitmap(mBitmap, mBitmapRect.left, mBitmapRect.top, null);

//绘制剩余空间的矩形

mPaint.setXfermode(null);

mPaint.setMaskFilter(null);

mPaint.setStyle(Paint.Style.STROKE);

mPaint.setStrokeWidth(mStrokeWidth + 0.1f);

canvas.drawRect(fillRect(mBitmapRect), mPaint);

}

}

3. 注意点

在准备调用对某个View高亮的引导层方法时,需要确定这个view是已经加载完成了,即可以获取长和宽,在activity的onCreate和onResume等方法都不是正在加载完view的地方,真正加载完view的是onWindowFocusChanged,所以要注意调用时机,适当延迟。

最后本人写了一个manager来管理不同地方的新手引导浮层,通过SharedPreferences来保存状态。

每次使用的时候都需要判断下是够显示过了,如下:

if(NewbieGuideManager.isNeverShowed(this, NewbieGuideManager.TYPE_COLLECT)) {

new NewbieGuideManager(this, NewbieGuideManager.TYPE_COLLECT).addView

(mCollect, HoleBean.TYPE_CIRCLE).addView(mTitleTv, HoleBean

.TYPE_RECTANGLE).show();

}

有时候在list滚动到某个位置时,会显示出某个引导浮层,这个要注意listview快速滚动的情况,这里提供一种写法,在onScroll当滚动到position为6的item时,显示:

@Override

public void onScroll(final AbsListView view, int firstVisibleItem, int

visibleItemCount, int totalItemCount) {

if(firstVisibleItem >= 6 && NewbieGuideManager.isNeverShowed(this,

NewbieGuideManager.TYPE_LIST) && !isShow) {

isShow = true;

mListView.smoothScrollToPosition(6);

mListView.postDelayed(new Runnable() {

@Override

public void run() {

mListView.setSelection(6);

mListView.post(new Runnable() {

@Override

public void run() {

new NewbieGuideManager(MainActivity.this,

NewbieGuideManager.TYPE_LIST).addView(view

.getChildAt(0).findViewById(R.id.logo), HoleBean

.TYPE_RECTANGLE).show();

}

});

}

}, 200);

}

}

5aa96683d0dc

滚动到item6后显示引导浮层

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值