Android高亮新手引导蒙层实现

在这里插入图片描述
在这里插入图片描述
市面上的 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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值