Android高亮引导实现 HiGuide

前言

Github上有不少高亮引导的库,比如下面的。但是觉得他们的功能太多,不够简单,跟项目需求不太符合。原本打算直接使用HightLight(效果比较符合需求),但是发现issue上有不少bitmap的oom异常,以及停止更新维护了。所以自己尝试实现一下。

  • ShowcaseView
  • TourGuide
  • Highlight
  • ShowTipsView

思路

看了好几篇文章,大体思路都是在DecorView上添加布局,在遮盖Activity的布局。HiGuide也不例外。

  1. 首先两个最关键的参数,Activity,需要高亮的View。Activity用来获取DecorView,高亮View用来计算高亮位置及区域。
  2. 获取Activity的DecorView,并把遮盖层布局GuideView添加进去,就可以实现遮挡效果。
  3. 在遮盖层GuideView(FrameLayout)内,根据高亮View在屏幕的位置(View.getLocationOnSrceen()),及高亮View的宽高,及配置的形状等参数,使用使用Path进行计算(Path.op()可以进行多个path进行层叠区域计算,跟Paint的Xfermode一样),得到镂空的遮盖区域,在画布上绘制该Path就可以得到有高亮区域的遮盖层。
  4. 想要添加其他的提示布局,Textview或Imageview,或者其他Layout容器,在GuideView内add就可以了,注意配置边距参数以定位。
  5. 点击效果通常分两块,一块是背景层,也就是整个遮盖层,另外一个就是高亮区域。高亮区域,可以在使用View在屏幕的位置,及宽高,就可以创建一个RectF矩形区域,把所有的高亮View的RectF用List保存起来。在GuideView的onTouch中,计算,如果点击的坐标xy在某个高亮RectF矩形内,即为点击了高亮区域。

关键代码

  • 添加遮盖层
mRootView = (ViewGroup) ((Activity) mContext).getWindow().getDecorView();
if (mRootView instanceof FrameLayout) {
      ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
          ViewGroup.LayoutParams.MATCH_PARENT);
      mRootView.addView(mGuideView, mRootView.getChildCount(), lp);
    } else {
      FrameLayout frameLayout = new FrameLayout(mContext);
      ViewGroup parent = (ViewGroup) mRootView.getParent();
      parent.removeView(mRootView);
      parent.addView(frameLayout, mRootView.getLayoutParams());
      ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(
          ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
      frameLayout.addView(mRootView, lp);
    } 
  • 移除遮盖层
private void remove() {
    ViewGroup parent = (ViewGroup) getParent();
    if (parent instanceof RelativeLayout || parent instanceof FrameLayout) {
      parent.removeView(this);
    } else {
      parent.removeView(this);
      View origin = parent.getChildAt(0);
      ViewGroup graParent = (ViewGroup) parent.getParent();
      graParent.removeView(parent);
      graParent.addView(origin, parent.getLayoutParams());
    }

    if (mRemoveCallback != null) {
      mRemoveCallback.callback();
    }
  }
  • 高亮区域绘制
 @Override
  protected void onDraw(Canvas canvas) {
    mBgPath.reset();
    mShapePath.reset();

    mBgPath.addRect(0, 0, getWidth(), getHeight(), Path.Direction.CW);

    for (HightLight hightLight : mOverlay.getHightLightList()) {
      Path shapePath = calcHightLightShapePath(hightLight, mShapePath);
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        mBgPath.op(shapePath, Path.Op.XOR);
      }
    }

    canvas.drawPath(mBgPath, mPaint);
  }
  • 高亮形状计算
private Path calcHightLightShapePath(HightLight hightLight, Path shapePath) {
    shapePath.reset();
    switch (hightLight.getShape()) {
      case HiGuide.SHAPE_CIRCLE:
        shapePath.addCircle(hightLight.getRectF().centerX(),
            hightLight.getRectF().centerY(),
            hightLight.getRadius(), Path.Direction.CW);
        break;
      case HiGuide.SHAPE_OVAL:
        shapePath.addOval(hightLight.getRectF(), Path.Direction.CW);
        break;
      case HiGuide.SHAPE_RECT:
        shapePath.addRect(hightLight.getRectF(), Path.Direction.CW);
        break;
      default:
        break;
    }
    return shapePath;
  }
  • 高亮区域点击
  @Override
  public boolean onTouchEvent(MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_UP) {
      //点击高亮
      if (mOverlay.getOnClickHightLightListener() != null) {
        float x = event.getX();
        float y = event.getY();
        for (RectF rectF : mOverlay.getHightLightAreas()) {
          if (rectF.contains(x, y)) {
            mOverlay.getOnClickHightLightListener().onClick(this);
            remove();
            return super.onTouchEvent(event); //点击高亮后不做其他的处理了,直接返回
          }
        }
      }
      //全局点击
      if (mOverlay.getOnClickGuideViewListener() != null) {
        mOverlay.getOnClickGuideViewListener().onClick(this);
      }
      if (mOverlay.isTouchDismiss()) {
        remove();
      }
    }

    return true;
  }
  • 添加提示布局
 public void addTipsView(Tips tips) {
    View tipsView = LayoutInflater.from(getContext()).inflate(tips.layoutRes, this, false);

    LayoutParams lp = (LayoutParams) tipsView.getLayoutParams();

    lp.leftMargin = tips.leftMargin;
    lp.topMargin = tips.topMargin;
    lp.rightMargin = tips.rightMargin;
    lp.bottomMargin = tips.bottomMargin;

    if (lp.rightMargin != 0) {
      lp.gravity = Gravity.END;
    } else {
      lp.gravity = Gravity.START;
    }
    if (lp.bottomMargin != 0) {
      lp.gravity |= Gravity.BOTTOM;
    } else {
      lp.gravity |= Gravity.TOP;
    }

    tipsView.setLayoutParams(lp);

    addView(tipsView, lp);
  }

效果

效果图

后记

因为之前看了一篇文章了解到使用Path进行自定义view绘制,而HightLight项目又是使用Bitmap,被不少issue都提到这个,所以打算使用Path替代Bitmap,这个功能并不复杂,按照思路实现就ok。

源码

项目Github地址

你可能感兴趣的:(Android高亮引导实现 HiGuide)