自定义控件android练习有意思的东西

原来新手引导ShowCase这么简单就可以实现了

2017-07-05  本文已影响721人  皮球二二

最近有一个需求,要求在app首页添加新手引导功能。如此机智的我一下子就想到了showcase这关键字,于是从github上一搜就找到一个700+star的FancyShowCaseView。那么我们今天就以它为基础,去实现自己的ShowCaseView
我自己写的精简版也同步放出ShowCaseView

效果展示

效果1 效果2

原理说明

简单的看一下,showcase是悬浮在最上层同时还包含镂空高亮的区域的一个View。我们来拆解一下关键点:

  1. 悬浮在最高层:DecorView为整个Window界面的最顶层View,我们的自定义视图无疑要添加到DevorView上
  2. 镂空高亮:就是画一个新图层,然后两图层交集部分变成全透明,这个无疑要用PorterDuffXfermode的CLEAR实现

如果你说我早就想到了,那么OK,后面的文章你就可以不用看了,因为这玩意就是这么简单

实现镂空的ImageView

其实不一定非要是ImageView,任意View在onDraw方法里面都能达到相应的效果
该ImageView只具备镂空效果,所以这里只涉及到普通绘制部分。首先声明三个变量,这三个变量代表我们ImageView上的任意元素所使用到的Paint

// 设置背景Paint
Paint mBackgroundPaint;
// 设置高亮点清除中心Paint
Paint mErasePaint;
// 设置高亮点Paint
Paint mCircleBorderPaint;

随后在构造方法中进行初始化。mBackgroundPaint就是我们的背景绘制Paint,mErasePaint是镂空绘制Paint,mCircleBorderPaint就是我们上图在镂空分部添加虚线绘制Paint

mBackgroundPaint=new Paint();
mBackgroundPaint.setAntiAlias(true);

mErasePaint=new Paint();
mErasePaint.setAntiAlias(true);
mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
mErasePaint.setAlpha(0xFF);

mCircleBorderPaint=new Paint();
mCircleBorderPaint.setAntiAlias(true);
mCircleBorderPaint.setColor(mFocusBorderColor);
mCircleBorderPaint.setStrokeWidth(mFocusBorderSize);
mCircleBorderPaint.setStyle(Paint.Style.STROKE);
mCircleBorderPaint.setStrokeJoin(Paint.Join.ROUND);
mCircleBorderPaint.setStrokeCap(Paint.Cap.ROUND);
mCircleBorderPaint.setPathEffect(new DashPathEffect(new float[] {10, 20}, 0));

配置完成之后,就是开始画了。在画之前我们先明确一下,镂空图形可以是任意的图形,比如圆形、圆角矩形等,所以这里先用一个枚举来给用户提供绘制选择。这里为了演示,仅提供圆形与圆角矩形2种

public enum FocusShape {
    CIRCLE,
    ROUNDED_RECTANGLE
}

有了图形之后,我们就要考虑使用者如何将他所希望的图形以对象的形式传递到onDraw()方法里面。这里我们就需要传递一个bean进来。这个bean就包括绘制时所需的各种参数

public class CalculatorBean {
    FocusShape mFocusShape;
    int mCircleCenterX;
    int mCircleCenterY;
    int mCircleRadius;
    // 圆角矩形专用
    int mFocusWidth;
    int mFocusHeight;
}

通过set方法传进所有镂空部分的View

public void setmCalculatorBeen(ArrayList<CalculatorBean> mCalculatorBeen) {
    this.mCalculatorBeen = mCalculatorBeen;
}

剩下就开始绘制了。先绘制整体的背景色,再完成镂空,并添加一定的点缀

@Override
protected void onDraw(Canvas canvas) {
      super.onDraw(canvas);
      if (mBitmap == null) {
          mBitmap= Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
          // 把图片设置成半透明
          mBitmap.eraseColor(0xb2000000);
      }
      canvas.drawBitmap(mBitmap, 0, 0, mBackgroundPaint);
      if (mCalculatorBeen!=null) {
            for (CalculatorBean calculatorBean : mCalculatorBeen) {
                if (calculatorBean.getmFocusShape()==FocusShape.CIRCLE) {
                    drawCircle(canvas, calculatorBean);
                }
                else if (calculatorBean.getmFocusShape()==FocusShape.ROUNDED_RECTANGLE) {
                    drawRoundedRectangle(canvas, calculatorBean);
                }
            }
      }
}

画圆的方法

private void drawCircle(Canvas canvas, CalculatorBean calculatorBean) {
    // 绘制高亮
    canvas.drawCircle(calculatorBean.getmCircleCenterX(), calculatorBean.getmCircleCenterY(), calculatorBean.getmCircleRadius()+ mAnimCounter*animMoveFactor, mErasePaint);
    // 绘制其余部分
    mPath.reset();
    mPath.moveTo(calculatorBean.getmCircleCenterX(), calculatorBean.getmCircleCenterY());
    mPath.addCircle(calculatorBean.getmCircleCenterX(), calculatorBean.getmCircleCenterY(), calculatorBean.getmCircleRadius()+ mAnimCounter*animMoveFactor, Path.Direction.CW);
    canvas.drawPath(mPath, mCircleBorderPaint);
}

画圆角矩形的方法

private void drawRoundedRectangle(Canvas canvas, CalculatorBean calculatorBean) {
    // 绘制高亮
    int centerX=calculatorBean.getmCircleCenterX();
    int centerY=calculatorBean.getmCircleCenterY();
    float left=centerX-calculatorBean.getmFocusWidth()/2- mAnimCounter*animMoveFactor;
    float top=centerY-calculatorBean.getmFocusHeight()/2- mAnimCounter*animMoveFactor;
    float right=centerX+calculatorBean.getmFocusWidth()/2+ mAnimCounter*animMoveFactor;
    float bottom=centerY+calculatorBean.getmFocusHeight()/2+ mAnimCounter*animMoveFactor;
    canvas.drawRoundRect(new RectF(left, top, right, bottom), calculatorBean.getmCircleRadius(), calculatorBean.getmCircleRadius(), mErasePaint);
    // 绘制其余部分
    mPath.reset();
    mPath.moveTo(calculatorBean.getmCircleCenterX(), calculatorBean.getmCircleCenterY());
    mPath.addRoundRect(new RectF(left, top, right, bottom), calculatorBean.getmCircleRadius(), calculatorBean.getmCircleRadius(), Path.Direction.CW);
    canvas.drawPath(mPath, mCircleBorderPaint);
}

添加动画效果,这里是通过改变圆形半径或者圆角矩形的长宽来达到动画效果

if (mAnimationEnabled) {
    if (mAnimCounter==ANIM_COUNTER_MAX) {
        mStep=-1;
    }
    else if (mAnimCounter==0) {
        mStep=1;
    }
    mAnimCounter+=mStep;
    postInvalidate();
}

绘制到DecerView上

我们用队列进行多个引导层的管理,一次性将所需要显示并切换的图层都添加进来

Queue<View> mQueue;
View currentView;

Activity context;

public ShowCaseView(Activity context) {
    this.context = context;
}

public void addViews(ArrayList<View> views) {
    mQueue=new LinkedList<>();
    mQueue.addAll(views);
}

然后就是添加跟移除,这里每次移除完之后都会判断队列中如果还有未展示的,会接着继续展示出来

public void show() {
    if (!mQueue.isEmpty()) {
        currentView=mQueue.poll();
        ((ViewGroup) context.getWindow().getDecorView()).addView(currentView);
    }
}

public void dismiss() {
    if (currentView!=null) {
        ((ViewGroup) context.getWindow().getDecorView()).removeView(currentView);
    }
    show();
}

还有一种情况就是直接全部跳过

public void cancel() {
    if (!mQueue.isEmpty()) {
        mQueue.clear();
    }
    if (currentView!=null) {
        ((ViewGroup) context.getWindow().getDecorView()).removeView(currentView);
    }
}

正式使用

只要获取到高亮指示的控件的坐标,即可对其进行高亮处理

public View a() {
    ArrayList<CalculatorBean> beanArrayList=new ArrayList<>();

    int[] location=new int[2];
    btn_showcase.getLocationOnScreen(location);
    CalculatorBean bean=new CalculatorBean();
    bean.setmCircleCenterX(location[0]+btn_showcase.getMeasuredWidth()/2);
    bean.setmCircleCenterY(location[1]+btn_showcase.getMeasuredHeight()/2);
    bean.setmCircleRadius(150);
    bean.setmFocusShape(FocusShape.CIRCLE);
    beanArrayList.add(bean);

    View view= LayoutInflater.from(ShowcaseActivity.this).inflate(R.layout.view_showcase, null, false);
    ShowCaseImageView image_showcase= view.findViewById(R.id.image_showcase);
    image_showcase.setmAnimationEnabled(true);
    image_showcase.setmCalculatorBeen(beanArrayList);
    image_showcase.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            showCaseView.dismiss();
        }
    });
    return view;
}
上一篇下一篇

猜你喜欢

热点阅读