smilevchy
3/10/2016 - 7:55 AM

IntroView.java

/**
 * 蒙层组件
 ** /
public class IntroView extends RelativeLayout {
    private static final String TAG = "IntroView";

    /** 区域镂空 */
    public static final int ERASER_TYPE_HOLLOW = 1;
    /** 区域浮雕 */
    public static final int ERASER_TYPE_CAMEO = 2;

    private static final int DEFAULT_MASK_COLOR = 0x70000000;

    private static final int DEFAULT_INFO_VIEW_MARGIN = 50;    // in pixels

    private Handler mHandler;

    private boolean mIsReady;
    private boolean mIsLayoutCompleted;

    private int mWidth;
    private int mHeight;

    private Target mTarget;
    private Target[] mServants;

    private Paint mEraser;

    private Bitmap mMaskBitmap;
    private Canvas mMaskBitmapCanvas;
    private int mMaskColor = DEFAULT_MASK_COLOR;

    private View mInfoView;

    private OnDismissListener mOnDismissListener;
    private OnSkippedListener mOnSkippedListener;
    private OnShowListener mOnShowListener;

    private boolean mInterceptEvent = true;


    public IntroView(Context context) {
        super(context);

        init(context);
    }

    public IntroView(Context context, AttributeSet attrs) {
        super(context, attrs);

        init(context);
    }

    public IntroView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        init(context);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public IntroView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);

        init(context);
    }

    private void init(final Context context) {
        setWillNotDraw(false);

        setVisibility(INVISIBLE);

        mHandler = new Handler();

        mEraser = new Paint();
        mEraser.setFlags(Paint.ANTI_ALIAS_FLAG);

        getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

            @Override
            public void onGlobalLayout() {
                if (!mIsLayoutCompleted) {
                    setInfoLayout();
                    setSkipLayout();

                    mIsLayoutCompleted = true;

                    removeOnGlobalLayoutListener(IntroView.this, this);
                }
            }
        });
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    public static void removeOnGlobalLayoutListener(View v, ViewTreeObserver.OnGlobalLayoutListener listener) {
        if (Build.VERSION.SDK_INT < 16) {
            v.getViewTreeObserver().removeGlobalOnLayoutListener(listener);
        } else {
            v.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        mWidth = getMeasuredWidth();
        mHeight = getMeasuredHeight();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (!mIsReady) return;

        if (mMaskBitmap == null || canvas == null) {
            if (mMaskBitmap != null) {
                mMaskBitmap.recycle();
                mMaskBitmap = null;
            }

            mMaskBitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
            mMaskBitmapCanvas = new Canvas(mMaskBitmap);
        }

        mMaskBitmapCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
        mMaskBitmapCanvas.drawColor(mMaskColor);

        drawTarget(mTarget);

        if (mServants != null) {
            for (Target servant : mServants) {
                drawTarget(servant);
            }
        }

        if (canvas != null) canvas.drawBitmap(mMaskBitmap, 0, 0, null);
    }

    private void drawTarget(Target target) {
        if (target == null || mMaskBitmapCanvas == null) return;

        if (target.getEraserType() == ERASER_TYPE_CAMEO) {
            mEraser.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));

            target.getView().setDrawingCacheEnabled(true);
            Bitmap drawingCache = target.getView().getDrawingCache();
            if (drawingCache != null) {
                mMaskBitmapCanvas.setDensity(drawingCache.getDensity());
                mMaskBitmapCanvas.drawBitmap(drawingCache, target.getRect().left, target.getRect().top, mEraser);
            }
            target.getView().setDrawingCacheEnabled(false);
        } else if (target.getEraserType() == ERASER_TYPE_HOLLOW) {
            mEraser.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));

            mMaskBitmapCanvas.drawRect(target.getRect().left, target.getRect().top, target.getRect().right, target.getRect().bottom, mEraser);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (!mInterceptEvent) return super.onTouchEvent(event);

        float curX = event.getX();
        float curY = event.getY();

        boolean isTouchOnFocus = false;
        Target target = null;

        if (mTarget.getRect().contains((int) curX, (int) curY)) {
            isTouchOnFocus = true;
            target = mTarget;
        } else if (mServants != null) {
            for (Target t : mServants) {
                if (t.getRect().contains((int) curX, (int) curY)) {
                    isTouchOnFocus = true;
                    target = t;

                    break;
                }
            }
        }

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (isTouchOnFocus) {
                    if (target != null) {
                        target.getView().performClick();

                        if (target.isDismissOnClicked()) {
                            mHandler.post(new Runnable() {

                                @Override
                                public void run() {
                                    dismiss();
                                }
                            });
                        }
                    }

                    return false;
                }

                return true;

            default:
                break;
        }

        return super.onTouchEvent(event);
    }

    private void show(Activity activity) {
        ((ViewGroup) activity.getWindow().getDecorView()).addView(this);

        setReady(true);

        mHandler.post(new Runnable() {

            @Override
            public void run() {
                setVisibility(View.VISIBLE);

                if (mOnShowListener != null) mOnShowListener.onShowed();
            }
        });
    }

    public void dismiss() {
        setVisibility(GONE);
        removeView();

        if (mOnDismissListener != null) mOnDismissListener.onDismiss();
    }

    public boolean isDismissed() {
        return !(getVisibility() == View.VISIBLE);
    }

    private void removeView(){
        if (getParent() != null ) {
            ((ViewGroup) getParent()).removeView(this);
        }
    }

    private void setInfoLayout() {
        mHandler.post(new Runnable() {

            @Override
            public void run() {
                if (mInfoView == null) return;

                if (mInfoView.getParent() != null) {
                    ((ViewGroup) mInfoView.getParent()).removeView(mInfoView);
                }

                RelativeLayout infoViewContainer = new RelativeLayout(getContext());
                infoViewContainer.addView(mInfoView);

                LayoutParams infoViewContainerParams = new LayoutParams(
                        ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);

                if (mTarget.getPoint().y <= mHeight / 2) {
                    infoViewContainer.setGravity(Gravity.TOP|Gravity.CENTER_HORIZONTAL);
                    infoViewContainerParams.setMargins(0, mTarget.getRect().bottom + DEFAULT_INFO_VIEW_MARGIN, 0, 0);
                } else {
                    infoViewContainer.setGravity(Gravity.BOTTOM|Gravity.CENTER_HORIZONTAL);
                    infoViewContainerParams.setMargins(0, 0, 0, mHeight - mTarget.getRect().top + DEFAULT_INFO_VIEW_MARGIN);
                }

                infoViewContainer.setLayoutParams(infoViewContainerParams);

                addView(infoViewContainer);
            }
        });
    }

    private void setSkipLayout() {
        mHandler.post(new Runnable() {

            @Override
            public void run() {
                ImageView skipView = new ImageView(getContext());
                skipView.setImageResource(R.drawable.intro_skip_icon);

                LayoutParams lp = new LayoutParams(
                        ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
                lp.addRule(ALIGN_PARENT_TOP|ALIGN_PARENT_RIGHT);
                lp.setMargins(0, 100, 50, 0);

                skipView.setLayoutParams(lp);

                addView(skipView);

                skipView.setOnClickListener(new OnClickListener() {

                    @Override
                    public void onClick(View v) {
                        if (mOnSkippedListener != null) mOnSkippedListener.onSkipped();
                        dismiss();
                    }
                });
            }
        });
    }

    private void setMaskColor(int maskColor) {
        mMaskColor = maskColor;
    }

    private void setReady(boolean ready) {
        mIsReady = ready;
    }

    private void setTarget(Target target) {
        mTarget = target;
    }

    private void setServants(Target... servants) {
        mServants = servants;
    }

    private void setInfoView(View infoView) {
        mInfoView = infoView;
    }

    private void setOnDismissListener(OnDismissListener onDismissListener) {
        mOnDismissListener = onDismissListener;
    }

    private void setOnSkippedListener(OnSkippedListener onSkippedListener) {
        mOnSkippedListener = onSkippedListener;
    }

    private void setOnShowListener(OnShowListener onShowListener) {
        mOnShowListener = onShowListener;
    }

    public void setInterceptEvent(boolean interceptEvent) {
        mInterceptEvent = interceptEvent;
    }

    public interface OnDismissListener {
        void onDismiss();
    }

    public interface OnSkippedListener {
        void onSkipped();
    }

    public interface OnShowListener {
        void onShowed();
    }

    private interface Target {
        /**
         * 获取目标组件的中心点
         *
         * @return
         */
        Point getPoint();

        /**
         * 获取目标组件的矩形表示
         *
         * @return
         */
        Rect getRect();

        /**
         * 获取目标组件
         *
         * @return
         */
        View getView();

        int getEraserType();

        boolean isDismissOnClicked();
    }

    public static final class ViewTarget implements Target {
        private View mView;
        private int mEraserType = ERASER_TYPE_HOLLOW;
        private boolean mDismissOnClicked = true;


        public ViewTarget(View view) {
            mView = view;
        }

        @Override
        public Point getPoint() {
            int[] location = new int[2];
            mView.getLocationInWindow(location);

            return new Point(location[0] + (mView.getWidth() / 2), location[1] + (mView.getHeight() / 2));
        }

        @Override
        public Rect getRect() {
            int[] location = new int[2];
            mView.getLocationInWindow(location);

            return new Rect(location[0], location[1], location[0] + mView.getWidth(), location[1] + mView.getHeight());
        }

        @Override
        public View getView() {
            return mView;
        }

        @Override
        public int getEraserType() {
            return mEraserType;
        }

        public void setEraserType(int eraserType) {
            mEraserType = eraserType;
        }

        @Override
        public boolean isDismissOnClicked() {
            return mDismissOnClicked;
        }

        public void setDismissOnClicked(boolean dismissOnClicked) {
            mDismissOnClicked = dismissOnClicked;
        }
    }

    public static final class Builder {
        private Activity mActivity;
        private IntroView mIntroView;


        public Builder(Activity activity) {
            mActivity = activity;
            mIntroView = new IntroView(activity);
        }

        public Builder setMaskColor(int maskColor) {
            mIntroView.setMaskColor(maskColor);

            return this;
        }

        public Builder setTarget(View view) {
            mIntroView.setTarget(new ViewTarget(view));

            return this;
        }

        public Builder setTarget(View view, int eraserType) {
            ViewTarget target = new ViewTarget(view);
            target.mEraserType = eraserType;

            mIntroView.setTarget(target);

            return this;
        }

        public Builder setTarget(ViewTarget target) {
            mIntroView.setTarget(target);

            return this;
        }

        public Builder setServants(View... views) {
            if (views != null) {
                Target[] servants = new Target[views.length];

                for (int i = 0, l = views.length; i < l; i++) {
                    servants[i] = new ViewTarget(views[i]);
                }

                mIntroView.setServants(servants);
            }

            return this;
        }

        public Builder setServants(ViewTarget... targets) {
            mIntroView.setServants(targets);

            return this;
        }

        public Builder setInfoView(View view) {
            mIntroView.setInfoView(view);

            return this;
        }

        public Builder setOnDismissListener(OnDismissListener onDismissListener) {
            mIntroView.setOnDismissListener(onDismissListener);

            return this;
        }

        public Builder setOnSkippedListener(OnSkippedListener onSkippedListener) {
            mIntroView.setOnSkippedListener(onSkippedListener);

            return this;
        }

        public Builder setOnShowListener(OnShowListener onShowListener) {
            mIntroView.setOnShowListener(onShowListener);

            return this;
        }

        public Builder setInterceptEvent(boolean interceptEvent) {
            mIntroView.setInterceptEvent(interceptEvent);

            return this;
        }

        public IntroView build() {
            return mIntroView;
        }

        public IntroView show() {
            build().show(mActivity);

            return mIntroView;
        }
    }
}