Android高亮引導實現(xiàn) HiGuide

前言

Github上有不少高亮引導的庫,比如下面的。但是覺得他們的功能太多,不夠簡單,跟項目需求不太符合。原本打算直接使用HightLight(效果比較符合需求),但是發(fā)現(xiàn)issue上有不少bitmap的oom異常,以及停止更新維護了。所以自己嘗試實現(xiàn)一下。

思路

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

  1. 首先兩個最關鍵的參數(shù),Activity,需要高亮的View。Activity用來獲取DecorView,高亮View用來計算高亮位置及區(qū)域。
  2. 獲取Activity的DecorView,并把遮蓋層布局GuideView添加進去,就可以實現(xiàn)遮擋效果。
  3. 在遮蓋層GuideView(FrameLayout)內,根據(jù)高亮View在屏幕的位置(View.getLocationOnSrceen()),及高亮View的寬高,及配置的形狀等參數(shù),使用使用Path進行計算(Path.op()可以進行多個path進行層疊區(qū)域計算,跟Paint的Xfermode一樣),得到鏤空的遮蓋區(qū)域,在畫布上繪制該Path就可以得到有高亮區(qū)域的遮蓋層。
  4. 想要添加其他的提示布局,Textview或Imageview,或者其他Layout容器,在GuideView內add就可以了,注意配置邊距參數(shù)以定位。
  5. 點擊效果通常分兩塊,一塊是背景層,也就是整個遮蓋層,另外一個就是高亮區(qū)域。高亮區(qū)域,可以在使用View在屏幕的位置,及寬高,就可以創(chuàng)建一個RectF矩形區(qū)域,把所有的高亮View的RectF用List保存起來。在GuideView的onTouch中,計算,如果點擊的坐標xy在某個高亮RectF矩形內,即為點擊了高亮區(qū)域。

關鍵代碼

  • 添加遮蓋層
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();
    }
  }
  • 高亮區(qū)域繪制
 @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;
  }
  • 高亮區(qū)域點擊
  @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,這個功能并不復雜,按照思路實現(xiàn)就ok。

源碼

項目Github地址

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容