> 文档中心 > 自定义引导控件

自定义引导控件

自定义引导控件

        • 引导控件
        • attrs文件
        • 使用示例
          • xml
          • java
        • 监听事件

引导控件

1.可在XML文件中直接绑定当页需引导的控件集合
2.可在java文件中手动绑定当页需引导的控件集合,亦可单独绑定/添加
3.可在java文件中手动绑定当页需引导的矩阵位置集合,亦可单独绑定/添加
注:绑定集合则跳转集合首位引导位置,绑定单一引导则跳转至该引导,添加时不跳转
支持矩形/圆角矩形/椭圆形镂空标注引导位置
支持任意View子控件做提示标注(标注位置自动计算),但标注控件需要为GuideView的ChildView

public class GuideView extends FrameLayout {    public static final int TYPE_RECT = 0, TYPE_ROUND_RECT = 1, TYPE_OVAL = 2;    private RectF rectF;    private Region region;    private View hintView;    private Path innerPath;    private Paint boundPaint;    private String resourceIds;    private OnBindListener opListener;    private ArrayList relationRects;    private ArrayList hintResource;    private boolean isDrawBound, isLayoutFinished;    private float offset,//目标内边距     radius,     distanceX, distanceY;//提示视图和目标边距    private int clipType, backgroundColor, hintViewId, stepNum=-1;    private ArrayList views;    @Override    protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if (relationRects != null) {     relationRects.clear(); } if (hintResource != null) {     hintResource.clear(); } if (views != null) {     views.clear(); } relationRects = null; hintResource = null; resourceIds = null; boundPaint = null; opListener = null; innerPath = null; hintView = null; region = null; rectF = null; views = null;    }    @Retention(RetentionPolicy.SOURCE)    @IntDef({TYPE_RECT, TYPE_ROUND_RECT, TYPE_OVAL})    public @interface clipType {    }    public GuideView(Context context) { this(context, null);    }    public GuideView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0);    }    public GuideView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.GuideView); clipType = array.getInt(R.styleable.GuideView_clipType, TYPE_RECT); resourceIds = array.getString(R.styleable.GuideView_relation_ids); hintViewId = array.getResourceId(R.styleable.GuideView_hint_view_id, NO_ID); offset = array.getDimension(R.styleable.GuideView_offset, BaseUtils.dp2px(10)); float distance = array.getDimension(R.styleable.GuideView_distance, BaseUtils.dp2px(20)); distanceX = array.getDimension(R.styleable.GuideView_distanceX, distance); distanceY = array.getDimension(R.styleable.GuideView_distanceY, distance); radius = array.getDimension(R.styleable.GuideView_android_radius, BaseUtils.dp2px(10)); backgroundColor = array.getColor(R.styleable.GuideView_backgroundColor, context.getResources().getColor(R.color.translucent)); float boundWidth = array.getDimension(R.styleable.GuideView_boundWidth, 0); int boundColor = array.getColor(R.styleable.GuideView_boundColor, Color.TRANSPARENT); array.recycle(); relationRects = new ArrayList(); innerPath = new Path(); rectF = new RectF(); region = new Region(); setWillNotDraw(false); boundPaint = new Paint(); boundPaint.setStyle(Paint.Style.STROKE); boundPaint.setAntiAlias(true); setBoundWidth(boundWidth, false); setBoundColor(boundColor, false); try {     getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {  @Override  public void onGlobalLayout() {      getViewTreeObserver().removeOnGlobalLayoutListener(this);      findRelationView();  }     }); } catch (Exception ignored) {     isLayoutFinished = true; }    }    private void findRelationView() { isLayoutFinished = true; if (views != null) {     bindViews(views); } else {     bindRelationViews(); }    }    @Override    protected void onFinishInflate() { super.onFinishInflate(); if (hintViewId != NO_ID) {     hintView = findViewById(hintViewId); }    }    private void bindRelationViews() { try {     if (resourceIds == null || TextUtils.isEmpty(resourceIds))  return;     View rootView = getRootView();     String[] split = resourceIds.split(",");     for (String s : split) {  try {      addRelationView(rootView.findViewById(ResourceUtils.getIdByName(s)));  } catch (Exception ignored) {  }     }     jumpTo(0); } catch (Exception ignored) { }    }    public void setLabelView(View labelView) { this.labelView = labelView; bringChildToFront(labelView);    }    public void setDistanceX(float px) { if (distanceX != px) {     this.distanceX = px;     if (!isInLayout()) {  requestLayout();     } }    }    public void setDistanceY(float px) { if (distanceY != px) {     this.distanceY = px;     if (!isInLayout()) {  requestLayout();     } }    }    public void setDistance(float px) { if (distanceX != px || distanceY != px) {     this.distanceY = px;     this.distanceX = px;     if (!isInLayout()) {  requestLayout();     } }    }    public void setBoundWidth(int dp) { setBoundWidth(BaseUtils.dp2px(dp), true);    }    public void setBoundWidth(float px) { setBoundWidth(px, true);    }    public void setBoundWidth(float px, boolean isRefresh) { if (boundPaint.getStrokeWidth() != px) {     isDrawBound = px > 0;     boundPaint.setStrokeWidth(px);     if (isRefresh) {  invalidate();     } }    }    public void setBoundColor(@ColorInt int color) { setBoundColor(color, true);    }    public void setBoundColor(@ColorInt int color, boolean isRefresh) { if (boundPaint.getColor() != color) {     isDrawBound = isDrawBound && (color != Color.TRANSPARENT);     boundPaint.setColor(color);     if (isRefresh) {  invalidate();     } }    }    @Override    protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); //布局提示控件 if (hintView != null && rectF != null && rectF.width() > 0 && rectF.height() > 0) {     int width = hintView.getWidth();     int height = hintView.getHeight();     float realWidth = width + distanceX;     float realHeight = height + distanceY;     int _left = (int) (rectF.left - (realWidth));     int _top = (int) (rectF.top - realHeight);     int _right = (int) (rectF.right + realWidth);     int _bottom = (int) (rectF.bottom + realHeight);     left += getPaddingLeft();     right -= getPaddingRight();     top += getPaddingTop();     bottom -= getPaddingBottom();     if (_top >= top && _top + height <= rectF.top) {//目标上边  if (_left = left && _left + width <= rectF.left) {//目标左边  if (_top < top) {      _top = top;  }  hintView.layout(_left, _top, _left + width, Math.min(_top + height, bottom));     } else if (_right = rectF.right) {//目标右边  if (_bottom > bottom) {      _bottom = bottom;  }  hintView.layout(_right - width, Math.max(_bottom - height, bottom), _right, _bottom);     } else if (_bottom = rectF.bottom) {//目标下边  if (_right > right) {      _right = right;  }  hintView.layout(Math.max(_right - width, left), _bottom - height, _right, _bottom);     } else {//目标内左上角  int x = (int) (rectF.left + distanceX + offset);  int y = (int) (rectF.top + distanceY + offset);  hintView.layout(x, y, x + width, y + height);     } }    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY),  MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.EXACTLY));    }    public void setClipType(@clipType int clipType) { this.clipType = clipType; createPath(false);    }    public void setRadius(float radius) { this.radius = radius; createPath(false);    }    public void setRect(Rect rect) { rectF.set(rect); createPath();    }    public void setRect(RectF rect) { rectF.set(rect); createPath();    }    public void setOffset(@FloatRange(from = 0) float offset) { this.offset = offset; createPath();    }    /     * 获取当前目标矩阵     */    public RectF getRelationRectF() { return rectF;    }    private void createPath() { createPath(true);    }    /     * 创建路径     */    private void createPath(boolean isRequestLayout) { if (offset > 0) {     rectF.left -= offset;     rectF.top -= offset;     rectF.right += offset;     rectF.bottom += offset; } innerPath.reset(); innerPath.moveTo(rectF.left, rectF.top); if (clipType == TYPE_OVAL) {     innerPath.addOval(rectF, Path.Direction.CW); } else if (clipType == TYPE_ROUND_RECT) {     innerPath.addRoundRect(rectF, radius, radius, Path.Direction.CW); } else {     innerPath.addRect(rectF, Path.Direction.CW); } innerPath.close(); innerPath.computeBounds(rectF, true); region.setEmpty(); region.setPath(innerPath, new Region((int) rectF.left, (int) rectF.top, (int) rectF.right, (int) rectF.bottom)); if (isRequestLayout && !isInLayout()) {     requestLayout(); } postInvalidate();    }    /     * 清空原有目标  绑定所有目标 并 显示对首目标     */    public void bindRectF(ArrayList rectFs) { if (rectFs != null && rectFs.size() > 0) {     if (relationRects == null) {  relationRects = new ArrayList();     } else {  relationRects.clear();     }     if (relationRects.addAll(rectFs)) {  jumpTo(0);     } }    }    /     * 如果存在当前目标 则绑定显示 否则添加值队尾并绑定显示     */    public void bindRectF(RectF rectF) { if (rectF == null)     return; if (relationRects != null) {     int index = relationRects.indexOf(rectF);     if (index > -1) {  jumpTo(index);     } else if (addRelationRectF(rectF)) {  jumpTo(relationRects.size() - 1);     } } else {     relationRects = new ArrayList();     addRelationRectF(rectF);     jumpTo(0); }    }     public void jumpToNext() { jumpTo(stepNum + 1);    } /     * 绑定显示指定位置的目标     */    public void jumpTo(int index) { RectF rect = null; if (relationRects != null && relationRects.size() > index) {     rect = relationRects.get(index); } if (rect == null||stepNum == index)     return; stepNum = index; if (opListener != null) {     opListener.onBind(this, index); } try {     if (hintView instanceof TextView && hintResource != null && hintResource.size() > index) {  ((TextView) hintView).setText(hintResource.get(index));     } } catch (Exception ignored) { } setRect(rect);    }    /     * 绑定显示指定控件位置目标     */    public void bindView(View view) { bindRectF(getRelationViewRectF(view));    }    /     * 清空原有目标  绑定所有目标 并 显示对首目标     */    public void bindViews(ArrayList views) { if (this.views == null) {     this.views = views; } if (isLayoutFinished && views != null && views.size() > 0) {     ArrayList rectFS = new ArrayList();     for (View v : views) {  rectFS.add(getRelationViewRectF(v));     }     bindRectF(rectFS); }    }    public void bindHintText(ArrayList hintResource) { this.hintResource = hintResource;    }    /     * 附加目标     */    public boolean addRelationView(View view) { return addRelationView(view, -1);    }    /     * 附加目标     */    public boolean addRelationView(View view, int index) { return addRelationRectF(getRelationViewRectF(view), index);    }    /     * 附加目标     */    public boolean addRelationRectF(RectF rectF) { return addRelationRectF(rectF, -1);    }    /     * 附加目标     */    public boolean addRelationRectF(RectF rectF, int index) { try {     if (relationRects != null && rectF != null && !relationRects.contains(rectF)) {  if (index > -1) {      relationRects.add(index, rectF);  } else {      relationRects.add(rectF);  }     }     return true; } catch (Exception ignored) { } return false;    }    public RectF getRelationViewRectF(View view) { if (view == null)     return null; int[] size = new int[2]; view.getLocationInWindow(size); float x = size[0]; float y = size[1]; getLocationInWindow(size); float left = x - size[0]; float top = y - size[1]; RectF rectF = new RectF(); rectF.left = left; rectF.top = top; rectF.right = left + view.getWidth(); rectF.bottom = top + view.getHeight(); return rectF;    }    @Override    public boolean dispatchTouchEvent(MotionEvent event) { boolean isContain = event != null && region != null && region.contains((int) event.getX(), (int) event.getY()); if (isContain) {     if (opListener == null || !opListener.onRelationViewClick(this, stepNum + 1)) {  jumpToNext();     } } return !(isContain || isClickLabel(event)) || super.dispatchTouchEvent(event);    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) { return isClickLabel(ev) || super.onInterceptTouchEvent(ev);    }    private boolean isClickLabel(MotionEvent ev) { if (ev == null || hintView== null)     return false; float x = ev.getX(); float y = ev.getY(); return x >= hintView.getLeft() && x = hintView.getTop() && y <= hintView.getBottom();    }    @SuppressLint("ClickableViewAccessibility")    @Override    public boolean onTouchEvent(MotionEvent event) { if (isClickLabel(event)) {     if (!onTouchEvent(hintView, event) && hintViewinstanceof ViewGroup) {  ViewGroup group = (ViewGroup) this.labelView;  for (int i = 0; i = left && x = top && y <= bottom;    }    @Override    public void setBackground(Drawable background) {    }    @Override    public void setBackgroundResource(int resid) {    }    @Override    public void setBackgroundDrawable(Drawable background) {    }    @Override    public void setBackgroundColor(int backgroundColor) { if (this.backgroundColor != backgroundColor) {     this.backgroundColor = backgroundColor;     postInvalidate(); }    }    @Override    public void onDrawForeground(Canvas canvas) { super.onDrawForeground(canvas); canvas.save(); if (innerPath == null || innerPath.isEmpty())     return; if (hintView != null) {     canvas.clipRect(hintView.getLeft(), hintView.getTop(), hintView.getRight(), hintView.getBottom(), Region.Op.DIFFERENCE); } //绘制背景 canvas.clipPath(innerPath, Region.Op.DIFFERENCE); canvas.drawColor(backgroundColor); if (isDrawBound) {     canvas.drawPath(innerPath, boundPaint); } canvas.restore();    }    public void setOnNextListener(OnBindListener nextListener) { this.opListener = nextListener;    }    public interface OnBindListener { /  * 绑定目标视图事件(绘制前)  *  * @param stepNum 当前目标id  */ void onBind(GuideView view, int stepNum); /  * 当前目标视图点击事件  *  * @param nextStepNum 下一个目标id  * @return 是否拦击自动绑定下一个目标视图  */ boolean onRelationViewClick(GuideView view, int nextStepNum);    }}

attrs文件

                               

使用示例

xml
                 

注意 容器必须为FrameLayout 之类的可以让GuideView match_parent的容器

java
//java绑定目标集合方式一  按添加顺序进行引导ArrayList objects = new ArrayList();  objects.add(guideView.getRelationViewRectF(目标控件1));  objects.add(guideView.getRelationViewRectF(目标控件2));  objects.add(guideView.getRelationViewRectF(目标控件3));  objects.add(guideView.getRelationViewRectF(目标控件4));  objects.add(guideView.getRelationViewRectF(目标控件5));  guideView.bindRectF(objects);//java绑定目标集合方式二  按添加顺序进行引导ArrayList objects = new ArrayList(); objects.add(目标控件1); objects.add(目标控件2); objects.add(目标控件3); objects.add(目标控件4); objects.add(目标控件5); guideView.bindViews(objects); //单一目标绑定方式  添加至队尾  并跳转至该引导guideView.bindRectF(RectF rectF);guideView.bindView(View view);//跳转至index步guideView. jumpTo(int index);//单一目标添加方式  添加至队尾 /指定位置guideView.addRelationView(View view) ;guideView.addRelationView(View view, int index); guideView.addRelationRectF(RectF rectF);guideView.addRelationRectF(RectF rectF, int index);//绑定提示文字  提示控件为文本控件时生效  内容顺序需和引导目标集合顺序一致  可在OnBindListener 监听中自定义提示bindHintText(ArrayList hintResource)

监听事件

public interface OnBindListener { /  * 跳转至指定引导目标时(绘制之前)  可修改提示文字和引导目标边框绘制属性  *  * @param stepNum 当前引导顺序指针  */ void onBind(GuideView view, int stepNum); /  * 当前引导目标位置点击事件  可拦截自定义处理跳转  *  * @param nextStepNum 下一个目标顺序指针  * @return 是否拦击自动跳转下一个引导目标  */ boolean onRelationViewClick(GuideView view, int nextStepNum);    }

MSDN工具下载