市面上的 APP 大多都有这个功能,我们这次做的新手高亮引导蒙层有什么特殊之处呢?
最基础的蒙层就是盖在页面的上方大概位置,不能精确定位。也不会随着 view 的变化蒙层的高亮区域和 tips 指示区域跟随底部 view 的位置变化而变化,也不能进行点击事件透传。一般都是点击蒙层后蒙层消失然后需要再次点击 view
我们这次开发的蒙层具备哪些功能,和解决了哪些技术难点,和市面上大多数蒙层博客有什么异同呢。
1 : 蒙层的高亮区域跟随底部目标 view ( 以下简称 targetView )
我们的 APP 可能包含多个 ABTest 可能一个页面同一个 view 不同的 AB 版本 view 所在的位置一样(例如底部 tab 5 个按钮可能变成 4 个),不可能每个版本都去开发一个对应的蒙层,这样工作量大而且代码复杂冗余势必我们需要兼容 view 的位置随时可能变化的问题。
Rect targetRect = new Rect();
targetView.getGlobalVisibleRect(targetRect);
通过传入的 targetView 在其 view 测绘完后,我们拿到这个 view 的尺寸以及在全局的坐标,去计算我们覆盖物和 tips 需要覆盖的位置然后 add 到 parent 中。注意这个时机在蒙层 show 的时机去做,过早去计算可能会因为 view 的测量还未完成拿到的是 0 值的 x,y 坐标
2 : 支持贴覆盖物以及自绘制高亮区域
这一期需求的高亮区域为贴的覆盖物,但是实际不管是覆盖物还是自己绘制高亮区域。都支持
@IntDef({
HShape.CIRCLE,
HShape.RECTANGLE,
HShape.OVAL
})
public @interface HShape {
int CIRCLE = 0;
int RECTANGLE = 1;
int OVAL = 2;
}
目前支持的自绘制的形状有三(见上方注解) 1 圆形 2 方形 3 椭圆
3: 点击事件透传
获取到持有的 targetView 计算他的四个边的边界。然后拿到 touch 事件监听的 MotionEvent x,y 判断是够在边界区域内,如果是调用 view 的 performClick
核心代码:
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
···
if (view != null && inRangeOfView(view, event)) {
view.performClick();
}
···
}}
public boolean inRangeOfView(View view, MotionEvent ev) {
int[] location = new int[2];
view.getLocationOnScreen(location);
int x = location[0];
int y = location[1];
if (ev.getX() < x || ev.getX() > (x + view.getWidth()) || ev.getY() < y || ev.getY() > (y + view.getHeight())) {
return false;
}
return true;
}
解决业务上的问题
1 适应埋点需求的 Listener
当用户对蒙层操作或者可见等各种状态时,我们需要会用户的操作做埋点采集。来统计数据进行大数据分析,产品获得更可靠的值。
蒙层有 1 蒙层 show 时候的曝光 、 2 蒙层高亮区域被点击 、 3 蒙层高亮区域之外被点击、4 蒙层的 dissmiss
根据上述四类每个蒙层的 guider 都可以设置 OnStateChangedListener
public interface OnStateChangedListener {
void onShow();
void onDismiss();
void onExcludeHighlightDismiss();
void onHeightLightViewClick(View view);
void onOcclusionListener();
}
2 某 targetView 被顶出屏幕外的兜底策略处理
如上图: 立即找房按钮的高亮可能随着 fragment 找房按钮的上方 view 元素多过被 底 tab 遮盖。更多甚者可能会被顶出屏幕外,处理方式: 获取 targetView Y 轴最底部随即和屏幕的最底部做比较,此处还要减去底 tab 的高度如果立即找房按钮被底 tab 遮挡部分也做兜底策略处理
3 弹出蒙层前被阻止的弹窗用户点击蒙层后继续弹出
当弹出蒙层前原本 APP 存在检查用户登录 和 活动弹窗的 dialog 如果在蒙层前后弹出就会存在视觉和逻辑上的混乱。从业务层面我们需要在蒙层弹出前阻塞,用户点击蒙层后还要把之前阻塞的弹窗再次弹出。
处理方式,检查用户是否是第一次安装且符合蒙层弹出条件,如果这个时候有其他 dialog 需要弹出将其次数和类型记录到容器里,阻止 dialog 弹出,采集到的容器释放时机是在用户点击完最后一个蒙层后的 dismiss
if (GuiderManager.getInstance().isExistOnScreen() || SPUtils.getBoolean(SPUtils.IS_PLAY_GUIDER)) {
Log.e("guider", "add handlePopup");
GuiderManager.getInstance().addPrevent(2);
return;
}
总结:
这版蒙层技术上完成了
跟随 view 移动
点击事件透传
覆盖物和自绘制高亮区域
业务上实现了
多埋点适配 listener
targetView 被顶出兜底处理
采集阻塞弹窗后释放逻辑处理
这版本蒙层较为良好的从技术和业务上实现了产品需求,代码逻辑清晰无太多冗余。
源码:
public class GuideView extends RelativeLayout {
private int mScreenWidth;
private int mScreenHeight;
private int mBgColor = 0x99000000;
private float mStrokeWidth;
private Paint mPaint;
private Bitmap mBitmap;
private RectF mBitmapRect;
private RectF outRect = new RectF();
private Canvas mCanvas;
private List<HighlightArea> mHighlightList;
private Xfermode mode;
public GuideView(Context context) {
this(context, null);
}
public GuideView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public GuideView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
Point point = new Point();
wm.getDefaultDisplay().getSize(point);
mScreenWidth = point.x;
mScreenHeight = point.y;
initView();
}
private void initView() {
initPaint();
mBitmapRect = new RectF();
mode = new PorterDuffXfermode(PorterDuff.Mode.CLEAR);
setWillNotDraw(false);
setClickable(true);
}
private void initPaint() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(mBgColor);
mPaint.setMaskFilter(new BlurMaskFilter(10, BlurMaskFilter.Blur.INNER));
}
private void initCanvas() {
if (mBitmapRect.width() > 0 && mBitmapRect.height() > 0) {
mBitmap = Bitmap.createBitmap((int) mBitmapRect.width(),
(int) mBitmapRect.height(),
Bitmap.Config.ARGB_8888);
} else {
mBitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
}
// 矩形最大边距
mStrokeWidth = Math.max(Math.max(mBitmapRect.left, mBitmapRect.top),
Math.max(mScreenWidth - mBitmapRect.right, mScreenHeight - mBitmapRect.bottom));
outRect.left = mBitmapRect.left - mStrokeWidth / 2;
outRect.top = mBitmapRect.top - mStrokeWidth / 2;
outRect.right = mBitmapRect.right + mStrokeWidth / 2;
outRect.bottom = mBitmapRect.bottom + mStrokeWidth / 2;
mCanvas = new Canvas(mBitmap);
mCanvas.drawColor(mBgColor);
}
/**
* 设置高亮区域
*
* @param list
*/
public void setHightLightAreas(List<HighlightArea> list) {
mHighlightList = list;
if (list != null && !list.isEmpty()) {
for (HighlightArea area : list) {
// 合并矩形框
mBitmapRect.union(area.getRectF());
}
}
initCanvas();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mHighlightList != null && mHighlightList.size() > 0) {
mPaint.setXfermode(mode);
mPaint.setStyle(Paint.Style.FILL);
for (HighlightArea area : mHighlightList) {
RectF rectF = area.getRectF();
rectF.offset(-mBitmapRect.left, -mBitmapRect.top);
switch (area.mShape) {
case HShape.CIRCLE:
mCanvas.drawCircle(rectF.centerX(), rectF.centerY(),
Math.min(area.mHighLightView.getWidth(), area.mHighLightView.getHeight()) / 2,
mPaint);
break;
case HShape.RECTANGLE:
mCanvas.drawRect(rectF, mPaint);
break;
case HShape.OVAL:
mCanvas.drawOval(rectF, mPaint);
break;
}
}
canvas.drawBitmap(mBitmap, mBitmapRect.left, mBitmapRect.top, null);
//绘制剩余空间的矩形
mPaint.setXfermode(null);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mStrokeWidth + 0.1f);
canvas.drawRect(outRect, mPaint);
}
}
public void recyclerBitmap() {
if (mBitmap != null) {
mBitmap.recycle();
mBitmap = null;
}
}
@Override
public boolean performClick() {
return super.performClick();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
recyclerBitmap();
}
}
public class Guider implements Comparable<Guider> {
private Activity mActivity;
private FrameLayout mParentView;
private GuideView mGuideView;
private LinearLayout mTipView;
private List<HighlightArea> mAreas = new ArrayList<>();
private List<TipsView> mIndicators = new ArrayList<>();
private List<Message> mMessages = new ArrayList<>();
private Confirm mConfirm;
private boolean dismissAnyWhere;
private boolean performViewClick;
public int guideType; // 1 搜索 2 情报站 3 金刚 4 智能找房
private View targetView;
private @DrawableRes
int overlay;
private OnStateChangedListener listener;
public Guider(Activity activity) {
this(activity, null, null, null, null, true, false, 0, null, 0);
}
public Guider(Activity activity, List<HighlightArea> areas, List<TipsView> indicators,
List<Message> messages, Confirm confirm, boolean dismissAnyWhere, boolean performViewClick
, int guideType, View targetView, int overlay
) {
this.mActivity = activity;
this.mAreas = areas;
this.mIndicators = indicators;
this.mMessages = messages;
this.mConfirm = confirm;
this.dismissAnyWhere = dismissAnyWhere;
this.performViewClick = performViewClick;
this.guideType = guideType;
this.targetView = targetView;
this.overlay = overlay;
mParentView = (FrameLayout) mActivity.getWindow().getDecorView();
}
/**
* 设置引导提示 状态改变(显示/取消) 监听
*
* @param listener
*/
public void setOnStateChangedListener(OnStateChangedListener listener) {
this.listener = listener;
}
public void showByOverlay() {
mGuideView = new GuideView(mActivity);
mGuideView.setBackgroundColor(mActivity.getResources().getColor(R.color.color_99000000));
ImageView imageView = new ImageView(mActivity);
imageView.setImageResource(overlay);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
params.alignWithParent = true;
imageView.setLayoutParams(params);
imageView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
Rect targetRect = new Rect();
targetView.getGlobalVisibleRect(targetRect);
imageView.setY(targetRect.top);
imageView.setX(targetRect.left + (targetRect.right - targetRect.left) / 2 - imageView.getMeasuredWidth() / 2);
if (guideType == 4 && mParentView.getHeight() > 0 && targetRect.bottom > 0) {
if (targetRect.bottom > mParentView.getHeight()) {
if (listener != null) {
listener.onOcclusionListener();
return;
}
//在屏幕外
} else {
//在屏幕内
if (targetRect.bottom > mParentView.getHeight() - GuiderManager.getInstance().getIntelligenceTabHeight()) {
if (listener != null) {
listener.onOcclusionListener();
return;
}
//在屏幕内但是有且有部分被 底 tab 遮盖
}
}
}
View tipsView = null;
// 1 搜索 2 情报站 3 金刚 4 智能找房
if (guideType == 2) { // 底部情报站
tipsView = LayoutInflater.from(mActivity).inflate(R.layout.guide_tips_intelligence, mGuideView, false);
tipsView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
tipsView.setY(targetRect.top - tipsView.getMeasuredHeight() - dip2px(mActivity, 10));
tipsView.setX(targetRect.left + (targetRect.right - targetRect.left) / 2 - tipsView.getMeasuredWidth() / 2);
} else if (guideType == 1) { //搜索
tipsView = new ImageView(mActivity);
tipsView.setBackground(mActivity.getResources().getDrawable(R.drawable.search_guide_tips));
RelativeLayout.LayoutParams searchParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
searchParams.alignWithParent = true;
searchParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
searchParams.rightMargin = dip2px(mActivity, 15);
tipsView.setLayoutParams(searchParams);
tipsView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
tipsView.setY(targetRect.bottom + dip2px(mActivity, 10));
tipsView.setX(mGuideView.getX());
} else if (guideType == 3) { //金刚
tipsView = new ImageView(mActivity);
tipsView.setBackground(mActivity.getResources().getDrawable(R.drawable.find_house_guide_tips));
RelativeLayout.LayoutParams searchParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
searchParams.alignWithParent = true;
searchParams.addRule(RelativeLayout.ALIGN_LEFT);
searchParams.leftMargin = dip2px(mActivity, 15);
tipsView.setLayoutParams(searchParams);
tipsView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
tipsView.setY(targetRect.bottom + dip2px(mActivity, 10));
} else if (guideType == 4) {//智能卡片找房
tipsView = new ImageView(mActivity);
tipsView.setBackground(mActivity.getResources().getDrawable(R.drawable.find_house_card_guide_tips));
RelativeLayout.LayoutParams searchParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
searchParams.alignWithParent = true;
searchParams.addRule(RelativeLayout.ALIGN_RIGHT);
searchParams.rightMargin = dip2px(mActivity, 15);
tipsView.setLayoutParams(searchParams);
tipsView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
tipsView.setY(targetRect.top - dip2px(mActivity, 10) - tipsView.getMeasuredHeight());
}
if (tipsView != null) {
mGuideView.addView(tipsView);
}
mGuideView.addView(imageView);
mParentView.addView(mGuideView, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mGuideView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (ACTION_UP == event.getAction()) {
if (mAreas.size() > 0) {
for (HighlightArea area : mAreas) {
final View view = area.mHighLightView;
// 如果点击事件作用在该View上
if (view != null && inRangeOfView(view, event)) {
dismiss();
if (listener != null) {
listener.onHeightLightViewClick(view);
}
if (performViewClick) {
view.performClick();
}
} else if (dismissAnyWhere) {
if (listener != null) {
listener.onExcludeHighlightDismiss();
}
dismiss();
}
}
return true;
} else {
dismiss();
return false;
}
}
return false;
}
});
if (listener != null) {
listener.onShow();
}
}
/**
* 显示引导提示
*/
public void show() {
mGuideView = new GuideView(mActivity);
mGuideView.setHightLightAreas(mAreas);
mTipView = new LinearLayout(mActivity);
mTipView.setGravity(Gravity.CENTER_HORIZONTAL);
mTipView.setLayoutParams(new RelativeLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
mTipView.setOrientation(LinearLayout.VERTICAL);
if (mIndicators != null) {
for (TipsView tipsView : mIndicators) {
addView(tipsView.view, tipsView.offsetX, tipsView.offsetY, tipsView.params);
}
}
if (mMessages != null) {
int padding = dip2px(mActivity, 5);
for (Message message : mMessages) {
TextView tvMsg = new TextView(mActivity);
tvMsg.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
tvMsg.setPadding(padding, padding, padding, padding);
tvMsg.setGravity(Gravity.CENTER);
tvMsg.setText(message.message);
tvMsg.setTextColor(Color.WHITE);
tvMsg.setTextSize(message.textSize == -1 ? 12 : message.textSize);
mTipView.addView(tvMsg);
}
}
if (mConfirm != null) {
TextView tvConfirm = new TextView(mActivity);
tvConfirm.setGravity(Gravity.CENTER);
tvConfirm.setText(mConfirm.text);
tvConfirm.setTextColor(Color.WHITE);
tvConfirm.setTextSize(mConfirm.textSize == -1 ? 13 : mConfirm.textSize);
tvConfirm.setBackgroundResource(R.drawable.btn_selector);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
params.topMargin = dip2px(mActivity, 10);
tvConfirm.setLayoutParams(params);
int lr = dip2px(mActivity, 8);
int tb = dip2px(mActivity, 5);
tvConfirm.setPadding(lr, tb, lr, tb);
tvConfirm.setOnClickListener(mConfirm.listener != null ?
mConfirm.listener : new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
}
});
mTipView.addView(tvConfirm);
}
addView(mTipView, Constants.CENTER, Constants.CENTER, new RelativeLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
if (dismissAnyWhere || performViewClick) {
mGuideView.setClickable(true);
mGuideView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case ACTION_UP:
if (mAreas.size() > 0) {
for (HighlightArea area : mAreas) {
final View view = area.mHighLightView;
// 如果点击事件作用在该View上
if (view != null && inRangeOfView(view, event)) {
dismiss();
if (listener != null) {
listener.onHeightLightViewClick(view);
}
if (performViewClick) {
view.performClick();
}
} else if (dismissAnyWhere) {
dismiss();
}
}
return true;
} else {
dismiss();
return false;
}
default:
break;
}
return true;
}
});
}
if (listener != null) {
listener.onShow();
}
}
/**
* 取消引导提示
*/
public void dismiss() {
mGuideView.recyclerBitmap();
if (mParentView.indexOfChild(mGuideView) > 0) {
mParentView.removeView(mGuideView);
if (listener != null) {
listener.onDismiss();
}
}
}
/**
* 添加任意 View 到引导提示的布局上
*
* @param view
* @param offsetX X轴偏移,正数表示从布局的左侧往右偏移量,负数表示从布局的右侧往左偏移量。{@link Constants#CENTER}表示居中
* @param offsetY Y轴偏移,正数表示从上往下,负数表示从下往上。{@link Constants#CENTER}表示居中
* @param params 参数
*/
private void addView(View view, int offsetX, int offsetY, RelativeLayout.LayoutParams params) {
if (params == null)
params = new RelativeLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
if (offsetX == Constants.CENTER) {
params.addRule(RelativeLayout.CENTER_HORIZONTAL, RelativeLayout.TRUE);
} else if (offsetX < 0) {
params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE);
params.rightMargin = -offsetX;
} else {
params.leftMargin = offsetX;
}
if (offsetY == Constants.CENTER) {
params.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE);
} else if (offsetY < 0) {
params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, RelativeLayout.TRUE);
params.bottomMargin = -offsetY;
} else {
params.topMargin = offsetY;
}
mGuideView.addView(view, params);
}
public boolean isShowing() {
return mParentView.indexOfChild(mGuideView) > 0;
}
public boolean inRangeOfView(View view, MotionEvent ev) {
int[] location = new int[2];
view.getLocationOnScreen(location);
int x = location[0];
int y = location[1];
if (ev.getX() < x || ev.getX() > (x + view.getWidth()) || ev.getY() < y || ev.getY() > (y + view.getHeight())) {
return false;
}
return true;
}
@Override
public int compareTo(Guider o) {
return this.guideType - o.guideType;
}
public static class Builder {
Activity activity;
List<HighlightArea> areas = new ArrayList<>();
List<TipsView> views = new ArrayList<>();
List<Message> messages = new ArrayList<>();
Confirm confirm;
boolean dismissAnyWhere = true;
boolean performViewClick;
int guideType; // 1 搜索 2 情报站 3 金刚 4 智能找房
View targetView; //底部高亮目标 view
@DrawableRes
int overlay; //底部高亮上面覆盖资源
public Builder(Activity activity) {
this.activity = activity;
}
/**
* 添加高亮区域
*
* @param view
* @param shape 高亮区域形状
* @return
*/
public Builder addHightArea(View view, @HShape int shape) {
HighlightArea area = new HighlightArea(view, shape);
areas.add(area);
return this;
}
public Builder addHightLightArea(HighlightArea area) {
areas.add(area);
return this;
}
/**
* 添加箭头指示的图片资源
*
* @param resId
* @param offX X轴偏移 正数表示从布局的左侧往右偏移量,负数表示从布局的右侧往左偏移量。{@link Constants#CENTER}表示居中
* @param offY Y轴偏移 正数表示从布局的上侧往下偏移量,负数表示从布局的下侧往上偏移量。{@link Constants#CENTER}表示居中
* @return
*/
public Builder addIndicator(int resId, int offX, int offY) {
ImageView ivIndicator = new ImageView(activity);
ivIndicator.setImageResource(resId);
views.add(new TipsView(ivIndicator, offX, offY));
return this;
}
public Builder addView(View view, int offX, int offY) {
views.add(new TipsView(view, offX, offY));
return this;
}
/**
* 添加任意的View
*
* @param view
* @param offX X轴偏移 正数表示从布局的左侧往右偏移量,负数表示从布局的右侧往左偏移量。{@link Constants#CENTER}表示居中
* @param offY Y轴偏移 正数表示从布局的上侧往下偏移量,负数表示从布局的下侧往上偏移量。{@link Constants#CENTER}表示居中
* @param params 参数
* @return
*/
public Builder addView(View view, int offX, int offY, RelativeLayout.LayoutParams params) {
views.add(new TipsView(view, offX, offY, params));
return this;
}
/**
* 添加提示信息,默认居中显示
*
* @param message
* @param textSize
* @return
*/
public Builder addMessage(String message, int textSize) {
messages.add(new Message(message, textSize));
return this;
}
/**
* 添加确定按钮,默认居中显示在提示信息下方
*
* @param btnText
* @param textSize
* @return
*/
public Builder setPositiveButton(String btnText, int textSize) {
this.confirm = new Confirm(btnText, textSize);
return this;
}
public Builder setPositiveButton(String btnText, int textSize, View.OnClickListener listener) {
this.confirm = new Confirm(btnText, textSize, listener);
return this;
}
/**
* 是否点击任意区域消失。默认true
*
* @param dismissAnyWhere
* @return
*/
public Builder dismissAnyWhere(boolean dismissAnyWhere) {
this.dismissAnyWhere = dismissAnyWhere;
return this;
}
// 1 搜索 2 情报站 3 金刚 4 智能找房
public Builder addType(int guideType) {
this.guideType = guideType;
return this;
}
public Builder addTargetView(View targetView) {
this.targetView = targetView;
addHightArea(targetView, HShape.RECTANGLE);
return this;
}
public Builder addTargetOverlay(@DrawableRes int overlay) {
this.overlay = overlay;
return this;
}
/**
* 若点击作用在高亮区域,是否执行高亮区域的点击事件
*
* @param performViewClick
* @return
*/
public Builder performViewClick(boolean performViewClick) {
this.performViewClick = performViewClick;
return this;
}
public Guider build() {
return new Guider(activity, areas, views, messages, confirm, dismissAnyWhere, performViewClick, guideType, targetView, overlay);
}
}
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
/**
* 获取情报站底部高度
*/
public int getTargetViewHeight() {
if (targetView != null && guideType == 2) {
Log.e("getTargetViewHeight", "getHeight : " + targetView.getHeight());
Log.e("getTargetViewHeight", "getMeasuredHeight" + targetView.getMeasuredHeight());
return targetView.getHeight();
}
return 0;
}
}
public class GuiderManager {
private List<Guider> guiderList = new ArrayList<>();
/**
* 1 登录 2 活动
*/
private List<Integer> preventList = new ArrayList<>();//阻止过的登录弹窗或者活动冲屏弹窗点击蒙层后继续弹
private static GuiderManager mInstance;
private int intelligenceTabHeight;
private GuiderManager() {
}
public static GuiderManager getInstance() {
if (mInstance == null) {
mInstance = new GuiderManager();
}
return mInstance;
}
private Guider firstGuider;
private Guider nextGuider;
/**
* 被阻止过的弹窗和类型 1 登录 2 活动
*/
public void addPrevent(int prevent) {
if (!preventList.contains(prevent)) {
preventList.add(prevent);
}
}
// 金刚 和 智能找房卡片可能依赖网络
public void addGuider(Guider guider) {
//1 满足实验 C
if (!ABIntellgentHelper.getNoviceGuide(NewEventConstants.P_HOME).equals("C")) {
return;
}
//2 满足机器人引导
if (!SPUtils.getBoolean(SPUtils.IS_PLAY_GUIDER)) {
return;
}
if (guider == null) {
return;
}
if (!guiderList.contains(guider)) {
//1 搜索 2 情报站 3 金刚 4 智能找房
guiderList.add(guider);
if (guider.guideType == 2) {
intelligenceTabHeight = guider.getTargetViewHeight();
}
}
if (guiderList.size() == 4) {
Collections.sort(guiderList);
/**
* 1 有意向房源有意向楼盘;
* 引导第一步;搜索
* 引导第二步:智能找房
* 2 有意向房源无意向楼盘;
* 引导第一步:智能找房;
* 引导第二步:楼盘列表;
* 3 无意向房源无意向楼盘
* 引导第一步:智能找房;
* 引导第二步:情报局;
*/
int route = SPUtils.getInt(SPUtils.SMART_ROBOT_ROUTE);
if (route == 0 || route == 3) {
firstGuider = guiderList.get(3);
nextGuider = guiderList.get(1);
} else if (route == 1) {
firstGuider = guiderList.get(0);
nextGuider = guiderList.get(3);
} else if (route == 2) {
firstGuider = guiderList.get(3);
nextGuider = guiderList.get(2);
}
if (firstGuider == null || nextGuider == null) {
return;
}
firstGuider.setOnStateChangedListener(new OnStateChangedListener() {
@Override
public void onOcclusionListener() {
//当 firstGuider 是智能找房卡片被遮挡 走 next 路径
if (nextGuider != null) {
nextGuider.showByOverlay();
}
}
@Override
public void onShow() {
if (firstGuider.guideType == 1) { // 1 搜索 2 情报站 3 金刚 4 智能找房
Track_pHome._8762_eModuleExposure();//搜索曝光
} else if (firstGuider.guideType == 2) {
Track_pHome._8772_eModuleExposure();// 情报局曝光
} else if (firstGuider.guideType == 3) {
Track_pHome._8769_eModuleExposure();//楼盘查询曝光
} else if (firstGuider.guideType == 4) {
Track_pHome._8765_eModuleExposure(String.valueOf(route));//立即找房曝光
}
}
@Override
public void onExcludeHighlightDismiss() {
if (firstGuider.guideType == 1) { // 1 搜索 2 情报站 3 金刚 4 智能找房
Track_pHome._8764_eClickSmectite();//搜索蒙层
} else if (firstGuider.guideType == 2) {
Track_pHome._8774_eClickSmectite();// 情报局蒙层
} else if (firstGuider.guideType == 3) {
Track_pHome._8771_eClickSmectite();//楼盘查询蒙层
} else if (firstGuider.guideType == 4) {
Track_pHome._8767_eClickSmectite(String.valueOf(route));//立即找房蒙层
}
//点击第一个蒙层非高亮区域
nextGuider.showByOverlay();
}
@Override
public void onDismiss() {
}
@Override
public void onHeightLightViewClick(View view) {
//点击第一个蒙层高亮区域
SPUtils.put(SPUtils.IS_PLAY_GUIDER, false); //点击过第二次蒙层后 下次杀死应用不再展示引导
if (firstGuider.guideType == 1) { // 1 搜索 2 情报站 3 金刚 4 智能找房
SPUtils.put(SPUtils.IS_SEARCH_FIRST, true);
Track_pHome._8763_eClickHighlight();//搜索高亮
} else if (firstGuider.guideType == 2) {
Track_pHome._8773_eClickHighlight();// 情报局高亮
} else if (firstGuider.guideType == 3) {
SPUtils.put(SPUtils.IS_HOUSELIST_FIRST, true);
Track_pHome._8770_eClickHighlight();//楼盘查询高亮
} else if (firstGuider.guideType == 4) {
Track_pHome._8766_eClickHighlight(NewEventConstants.P_HOME, String.valueOf(route));//立即找房高亮
}
}
});
firstGuider.showByOverlay();
nextGuider.setOnStateChangedListener(new OnStateChangedListener() {
@Override
public void onOcclusionListener() {
//当 nextGuider 是智能找房卡片被遮挡 路径 1 搜索 + 智能找房
SPUtils.put(SPUtils.IS_PLAY_GUIDER, false); //点击过第二次蒙层后 下次杀死应用不再展示引导
}
@Override
public void onShow() {
if (nextGuider.guideType == 1) { // 1 搜索 2 情报站 3 金刚 4 智能找房
Track_pHome._8762_eModuleExposure();//搜索曝光
} else if (nextGuider.guideType == 2) {
Track_pHome._8772_eModuleExposure();// 情报局曝光
} else if (nextGuider.guideType == 3) {
Track_pHome._8769_eModuleExposure();//楼盘查询曝光
} else if (nextGuider.guideType == 4) {
Track_pHome._8765_eModuleExposure(String.valueOf(route));//立即找房曝光
}
}
@Override
public void onDismiss() {
SPUtils.put(SPUtils.IS_PLAY_GUIDER, false); //点击过第二次蒙层后 下次杀死应用不再展示引导
SPUtils.put(SPUtils.IS_HOUSELIST_FIRST, false);
SPUtils.put(SPUtils.IS_SEARCH_FIRST, false);
}
@Override
public void onExcludeHighlightDismiss() {
//点击第二个蒙层非高亮区域
if (nextGuider.guideType == 1) { // 1 搜索 2 情报站 3 金刚 4 智能找房
Track_pHome._8764_eClickSmectite();//搜索蒙层
} else if (nextGuider.guideType == 2) {
Track_pHome._8774_eClickSmectite();// 情报局蒙层
} else if (nextGuider.guideType == 3) {
Track_pHome._8771_eClickSmectite();//楼盘查询蒙层
} else if (nextGuider.guideType == 4) {
Track_pHome._8767_eClickSmectite(String.valueOf(route));//立即找房蒙层
}
if (mOnGuiderPreventDialogListener != null && preventList.size() > 0) {
mOnGuiderPreventDialogListener.continuePopup(preventList);
}
}
@Override
public void onHeightLightViewClick(View view) {
//点击第二个蒙层高亮区域
if (nextGuider.guideType == 1) { // 1 搜索 2 情报站 3 金刚 4 智能找房
SPUtils.put(SPUtils.IS_SEARCH_FIRST, true);
Track_pHome._8763_eClickHighlight();//搜索高亮
} else if (nextGuider.guideType == 2) {
Track_pHome._8773_eClickHighlight();// 情报局高亮
} else if (nextGuider.guideType == 3) {
SPUtils.put(SPUtils.IS_HOUSELIST_FIRST, true);
Track_pHome._8770_eClickHighlight();//楼盘查询高亮
} else if (nextGuider.guideType == 4) {
Track_pHome._8766_eClickHighlight(NewEventConstants.P_HOME, String.valueOf(route));//立即找房高亮
}
}
});
}
}
public List<Guider> getGuiderList() {
return guiderList;
}
/**
* 当前是否有蒙层存在屏幕上展示
*/
public boolean isExistOnScreen() {
if (guiderList.size() == 0) {
return false;
}
for (Guider guider : guiderList) {
if (guider.isShowing()) {
return true;
}
}
return false;
}
public void destroy() {
guiderList.clear();
guiderList = null;
}
private OnGuiderPreventDialogListener mOnGuiderPreventDialogListener;
/**
* 阻止过的弹窗 继续弹出的监听
*
* @param onGuiderPreventDialogListener
*/
public void setOnGuiderPreventDialogListener(OnGuiderPreventDialogListener onGuiderPreventDialogListener) {
mOnGuiderPreventDialogListener = onGuiderPreventDialogListener;
}
public void clearGuiderPrevent() {
if (mOnGuiderPreventDialogListener != null) {
mOnGuiderPreventDialogListener = null;
}
preventList.clear();
}
public int getIntelligenceTabHeight() {
return intelligenceTabHeight;
}
}