de.dreier.mytargets.views.MaterialTapTargetPrompt.java Source code

Java tutorial

Introduction

Here is the source code for de.dreier.mytargets.views.MaterialTapTargetPrompt.java

Source

/*
 * Copyright (C) 2016 Samuel Wall
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package de.dreier.mytargets.views;

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.ColorInt;
import android.support.annotation.ColorRes;
import android.support.annotation.DimenRes;
import android.support.annotation.DrawableRes;
import android.support.annotation.IdRes;
import android.support.annotation.StringRes;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Interpolator;

import de.dreier.mytargets.R;

/**
 * A Material Design tap target onboarding implementation.
 * <p>
 * <div class="special reference">
 * <h3>Onboarding</h3>
 * <p>For more information about onboarding and tap targets, read the
 * <a href="https://www.google.com/design/spec/growth-communications/onboarding.html">Onboarding</a>
 * Material Design guidelines.</p>
 * </div>
 */
public class MaterialTapTargetPrompt {
    private final float mStatusBarHeight;
    private final ViewTreeObserver.OnGlobalLayoutListener mGlobalLayoutListener;
    private Activity mActivity;
    private PromptView mView;
    private View mTargetView;
    private View mDrawView;
    private float mBaseLeft, mBaseTop;
    private float mBaseFocalRadius, mBaseBackgroundRadius;
    private float mFocalRadius10Percent;
    private float mRevealedAmount;
    private String mPrimaryText, mSecondaryText;
    private float mMaxTextWidth;
    private float mTextPadding;
    private boolean mTextPositionRight, mTextPositionAbove;
    private float mFocalToTextPadding;
    private int mPrimaryTextColourAlpha, mSecondaryTextColourAlpha;
    private ValueAnimator mAnimationCurrent, mAnimationFocalRipple;
    private Interpolator mAnimationInterpolator;
    private float mFocalRippleProgress;
    private int mBaseFocalRippleAlpha;
    private TextPaint mPaintPrimaryText, mPaintSecondaryText;
    private OnHidePromptListener mOnHidePromptListener;
    private boolean mDismissing;
    private ViewGroup mParentView;
    private boolean mParentViewIsDecor;
    private ViewGroup mClipToView;
    private boolean mAutoDismiss, mAutoFinish;

    MaterialTapTargetPrompt(final Activity activity) {
        mActivity = activity;
        mView = new PromptView(activity);
        mView.mOnPromptTouchedListener = new PromptView.OnPromptTouchedListener() {
            @Override
            public void onPromptTouched(MotionEvent event, boolean tappedTarget) {
                if (!mDismissing) {
                    MaterialTapTargetPrompt.this.onHidePrompt(event, tappedTarget);
                    if (tappedTarget) {
                        if (mAutoFinish) {
                            finish();
                        }
                    } else {
                        if (mAutoDismiss) {
                            dismiss();
                        }
                    }
                }
            }
        };

        int resourceId = mView.getResources().getIdentifier("status_bar_height", "dimen", "android");
        if (resourceId > 0) {
            mStatusBarHeight = mView.getResources().getDimensionPixelSize(resourceId);
        } else {
            mStatusBarHeight = 0;
        }

        mGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                updateFocalCentrePosition();
            }
        };
    }

    /**
     * Returns {@link #mParentView}.
     * <p>
     * If the {@link #mParentView} is {@link null} it determines what view it should be.
     *
     * @return The view to add the prompt view to.
     */
    private ViewGroup getParentView() {
        if (mParentView == null) {
            final ViewGroup decorView = (ViewGroup) mActivity.getWindow().getDecorView();
            final ViewGroup contentView = (ViewGroup) ((ViewGroup) decorView.findViewById(android.R.id.content))
                    .getChildAt(0);
            // If the content view is a drawer layout then that is the parent so
            // that the prompt can be added behind the navigation drawer
            if (contentView.getClass().getName().equals("android.support.v4.widget.DrawerLayout")) {
                mParentView = contentView;
                mParentViewIsDecor = false;
            } else {
                mParentView = decorView;
                mParentViewIsDecor = true;
            }
            mView.mClipBounds = mParentViewIsDecor;
        }

        return mParentView;
    }

    /**
     * Displays the prompt.
     */
    public void show() {
        final ViewGroup parent = getParentView();
        // If the content view is a drawer layout then that is the parent so
        // that the prompt can be added behind the navigation drawer
        if (parent.getClass().getName().equals("android.support.v4.widget.DrawerLayout")) {
            parent.addView(mView, 1);
        } else {
            parent.addView(mView);
        }

        addGlobalLayoutListener();

        updateFocalCentrePosition();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            startRevealAnimation();
        } else {
            mView.mBackgroundRadius = mBaseBackgroundRadius;
            mView.mFocalRadius = mBaseFocalRadius;
            mView.mPaintFocal.setAlpha(255);
            mView.mPaintBackground.setAlpha(244);
            mPaintSecondaryText.setAlpha(mSecondaryTextColourAlpha);
            mPaintPrimaryText.setAlpha(mPrimaryTextColourAlpha);
        }
    }

    /**
     * Adds layout listener to view parent to capture layout changes.
     */
    private void addGlobalLayoutListener() {
        final ViewTreeObserver viewTreeObserver = getParentView().getViewTreeObserver();
        if (viewTreeObserver.isAlive()) {
            viewTreeObserver.addOnGlobalLayoutListener(mGlobalLayoutListener);
        }
    }

    /**
     * Removes global layout listener added in {@link #addGlobalLayoutListener()}.
     */
    private void removeGlobalLayoutListener() {
        final ViewTreeObserver viewTreeObserver = getParentView().getViewTreeObserver();
        if (viewTreeObserver.isAlive()) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                viewTreeObserver.removeOnGlobalLayoutListener(mGlobalLayoutListener);
            } else {
                //noinspection deprecation
                viewTreeObserver.removeGlobalOnLayoutListener(mGlobalLayoutListener);
            }
        }
    }

    /**
     * Removes the prompt from view, using a expand and fade animation.
     * <p>
     * This is treated as if the user has touched the target focal point.
     */
    public void finish() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            if (mDismissing) {
                return;
            }
            mDismissing = true;
            if (mAnimationCurrent != null) {
                mAnimationCurrent.removeAllListeners();
                mAnimationCurrent.cancel();
                mAnimationCurrent = null;
            }
            mAnimationCurrent = ValueAnimator.ofFloat(1f, 0f);
            mAnimationCurrent.setDuration(225);
            mAnimationCurrent.setInterpolator(mAnimationInterpolator);
            mAnimationCurrent.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @TargetApi(Build.VERSION_CODES.HONEYCOMB)
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    final float value = (float) animation.getAnimatedValue();
                    mRevealedAmount = 1f + ((1f - value) / 4);
                    mView.mBackgroundRadius = mBaseBackgroundRadius * mRevealedAmount;
                    mView.mFocalRadius = mBaseFocalRadius * mRevealedAmount;
                    mView.mPaintFocal.setAlpha((int) (255 * value));
                    mView.mPaintBackground.setAlpha((int) (244 * value));
                    mPaintSecondaryText.setAlpha((int) (mSecondaryTextColourAlpha * value));
                    mPaintPrimaryText.setAlpha((int) (mPrimaryTextColourAlpha * value));
                    if (mView.mIconDrawable != null) {
                        mView.mIconDrawable.setAlpha(mView.mPaintBackground.getAlpha());
                    }
                    mView.invalidate();
                }
            });
            mAnimationCurrent.addListener(new AnimatorListener() {
                @TargetApi(Build.VERSION_CODES.HONEYCOMB)
                @Override
                public void onAnimationEnd(Animator animation) {
                    removeGlobalLayoutListener();
                    getParentView().removeView(mView);
                    mAnimationCurrent.removeAllListeners();
                    mAnimationCurrent = null;
                    mDismissing = false;
                    onHidePromptComplete();
                    mParentView = null;
                }

                @TargetApi(Build.VERSION_CODES.HONEYCOMB)
                @Override
                public void onAnimationCancel(Animator animation) {
                    removeGlobalLayoutListener();
                    getParentView().removeView(mView);
                    mAnimationCurrent.removeAllListeners();
                    mAnimationCurrent = null;
                    mDismissing = false;
                    onHidePromptComplete();
                    mParentView = null;
                }
            });
            mAnimationCurrent.start();
        } else {
            removeGlobalLayoutListener();
            getParentView().removeView(mView);
            onHidePromptComplete();
            mParentView = null;
        }
    }

    /**
     * Removes the prompt from view, using a contract and fade animation.
     * <p>
     * This is treated as if the user has touched outside the target focal point.
     */
    public void dismiss() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            if (mDismissing) {
                return;
            }
            mDismissing = true;
            if (mAnimationCurrent != null) {
                mAnimationCurrent.removeAllListeners();
                mAnimationCurrent.cancel();
                mAnimationCurrent = null;
            }
            mAnimationCurrent = ValueAnimator.ofFloat(1f, 0f);
            mAnimationCurrent.setDuration(225);
            mAnimationCurrent.setInterpolator(mAnimationInterpolator);
            mAnimationCurrent.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @TargetApi(Build.VERSION_CODES.HONEYCOMB)
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    mRevealedAmount = (float) animation.getAnimatedValue();
                    mView.mBackgroundRadius = mBaseBackgroundRadius * mRevealedAmount;
                    mView.mFocalRadius = mBaseFocalRadius * mRevealedAmount;
                    mView.mPaintBackground.setAlpha((int) (244 * mRevealedAmount));
                    mPaintSecondaryText.setAlpha((int) (mSecondaryTextColourAlpha * mRevealedAmount));
                    mPaintPrimaryText.setAlpha((int) (mPrimaryTextColourAlpha * mRevealedAmount));
                    if (mView.mIconDrawable != null) {
                        mView.mIconDrawable.setAlpha(mView.mPaintBackground.getAlpha());
                    }
                    mView.invalidate();
                }
            });
            mAnimationCurrent.addListener(new AnimatorListener() {
                @TargetApi(Build.VERSION_CODES.HONEYCOMB)
                @Override
                public void onAnimationEnd(Animator animation) {
                    removeGlobalLayoutListener();
                    getParentView().removeView(mView);
                    mAnimationCurrent.removeAllListeners();
                    mAnimationCurrent = null;
                    mDismissing = false;
                    onHidePromptComplete();
                    mParentView = null;
                }

                @TargetApi(Build.VERSION_CODES.HONEYCOMB)
                @Override
                public void onAnimationCancel(Animator animation) {
                    removeGlobalLayoutListener();
                    getParentView().removeView(mView);
                    mAnimationCurrent.removeAllListeners();
                    mAnimationCurrent = null;
                    mDismissing = false;
                    onHidePromptComplete();
                    mParentView = null;
                }
            });
            mAnimationCurrent.start();
        } else {
            removeGlobalLayoutListener();
            getParentView().removeView(mView);
            onHidePromptComplete();
            mParentView = null;
        }
    }

    @TargetApi(11)
    private void startRevealAnimation() {
        mPaintSecondaryText.setAlpha(0);
        mPaintPrimaryText.setAlpha(0);
        mView.mPaintBackground.setAlpha(0);
        mView.mPaintFocal.setAlpha(0);
        mView.mFocalRadius = 0;
        mView.mBackgroundRadius = 0;
        if (mView.mIconDrawable != null) {
            mView.mIconDrawable.setAlpha(0);
        }
        mRevealedAmount = 0f;
        mAnimationCurrent = ValueAnimator.ofFloat(0f, 1f);
        mAnimationCurrent.setInterpolator(mAnimationInterpolator);
        mAnimationCurrent.setDuration(225);
        mAnimationCurrent.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @TargetApi(Build.VERSION_CODES.HONEYCOMB)
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mRevealedAmount = (float) animation.getAnimatedValue();
                mView.mBackgroundRadius = mBaseBackgroundRadius * mRevealedAmount;
                mView.mFocalRadius = mBaseFocalRadius * mRevealedAmount;
                mView.mPaintFocal.setAlpha((int) (255 * mRevealedAmount));
                mView.mPaintBackground.setAlpha((int) (244 * mRevealedAmount));
                mPaintSecondaryText.setAlpha((int) (mSecondaryTextColourAlpha * mRevealedAmount));
                mPaintPrimaryText.setAlpha((int) (mPrimaryTextColourAlpha * mRevealedAmount));
                if (mView.mIconDrawable != null) {
                    mView.mIconDrawable.setAlpha(mView.mPaintBackground.getAlpha());
                }
                mView.invalidate();
            }
        });
        mAnimationCurrent.addListener(new AnimatorListener() {
            @TargetApi(Build.VERSION_CODES.HONEYCOMB)
            @Override
            public void onAnimationEnd(Animator animation) {
                animation.removeAllListeners();
                mAnimationCurrent = null;
                mRevealedAmount = 1;
                startIdleAnimations();
            }

            @TargetApi(Build.VERSION_CODES.HONEYCOMB)
            @Override
            public void onAnimationCancel(Animator animation) {
                animation.removeAllListeners();
                mRevealedAmount = 1;
                mAnimationCurrent = null;
            }
        });
        mAnimationCurrent.start();
    }

    @TargetApi(11)
    private void startIdleAnimations() {
        if (mAnimationCurrent != null) {
            mAnimationCurrent.removeAllUpdateListeners();
            mAnimationCurrent.cancel();
            mAnimationCurrent = null;
        }
        mAnimationCurrent = ValueAnimator.ofFloat(0, mFocalRadius10Percent, 0);
        mAnimationCurrent.setInterpolator(mAnimationInterpolator);
        mAnimationCurrent.setDuration(1000);
        mAnimationCurrent.setStartDelay(225);
        mAnimationCurrent.setRepeatCount(ValueAnimator.INFINITE);
        mAnimationCurrent.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            boolean direction = true;

            @TargetApi(Build.VERSION_CODES.HONEYCOMB)
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                final float newFocalFraction = (Float) animation.getAnimatedValue();
                boolean newDirection = direction;
                if (newFocalFraction < mFocalRippleProgress && direction) {
                    newDirection = false;
                } else if (newFocalFraction > mFocalRippleProgress && !direction) {
                    newDirection = true;
                }
                if (newDirection != direction && !newDirection) {
                    mAnimationFocalRipple.start();
                }
                direction = newDirection;
                mFocalRippleProgress = newFocalFraction;
                mView.mFocalRadius = mBaseFocalRadius + mFocalRippleProgress;
                mView.invalidate();
            }
        });
        mAnimationCurrent.start();
        if (mAnimationFocalRipple != null) {
            mAnimationFocalRipple.removeAllUpdateListeners();
            mAnimationFocalRipple.cancel();
            mAnimationFocalRipple = null;
        }
        final float baseRadius = mBaseFocalRadius + mFocalRadius10Percent;
        mAnimationFocalRipple = ValueAnimator.ofFloat(baseRadius, baseRadius + (mFocalRadius10Percent * 6));
        mAnimationFocalRipple.setInterpolator(mAnimationInterpolator);
        mAnimationFocalRipple.setDuration(500);
        mAnimationFocalRipple.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @TargetApi(Build.VERSION_CODES.HONEYCOMB)
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mView.mFocalRippleSize = (float) animation.getAnimatedValue();
                final float fraction;
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
                    fraction = animation.getAnimatedFraction();
                } else {
                    fraction = (mFocalRadius10Percent * 6)
                            / (mView.mFocalRippleSize - mBaseFocalRadius - mFocalRadius10Percent);
                }
                mView.mFocalRippleAlpha = (int) (mBaseFocalRippleAlpha * (1f - fraction));
            }
        });
    }

    private void updateFocalCentrePosition() {
        updateClipBounds();
        if (mTargetView != null) {
            final int[] viewPosition = new int[2];
            mView.getLocationInWindow(viewPosition);
            final int[] targetPosition = new int[2];
            mTargetView.getLocationInWindow(targetPosition);

            mView.mCentreLeft = mBaseLeft + targetPosition[0] - viewPosition[0] + (mTargetView.getWidth() / 2);
            mView.mCentreTop = mBaseTop + targetPosition[1] - viewPosition[1] + (mTargetView.getHeight() / 2);
        } else {
            mView.mCentreLeft = mBaseLeft;
            mView.mCentreTop = mBaseTop;
        }

        final ViewGroup parent = getParentView();
        mTextPositionAbove = mView.mCentreTop > parent.getHeight() / 2;
        mTextPositionRight = mView.mCentreLeft > parent.getWidth() / 2;

        updateTextPositioning();
    }

    private void updateTextPositioning() {
        final float primaryTextWidth = mPaintPrimaryText.measureText(mPrimaryText);
        final float secondaryTextWidth = mSecondaryText != null ? mPaintSecondaryText.measureText(mSecondaryText)
                : 0;
        final float textWidth;
        final float maxWidth = Math.max(80,
                (mView.mClipBounds ? mView.mClipBoundsRight - mView.mClipBoundsLeft : getParentView().getWidth())
                        - (mTextPadding * 2));
        final float textWidthCalculation = Math.min(mMaxTextWidth, Math.max(primaryTextWidth, secondaryTextWidth));
        if (textWidthCalculation > maxWidth) {
            mView.mTextLeft = (mView.mClipBounds ? mView.mClipBoundsLeft : 0) + mTextPadding;
            textWidth = maxWidth;
        } else {
            if (mTextPositionRight) {
                mView.mTextLeft = (mView.mClipBounds ? mView.mClipBoundsRight : getParentView().getRight())
                        - mTextPadding - textWidthCalculation;
            } else {
                mView.mTextLeft = mTextPadding + (mView.mClipBounds ? mView.mClipBoundsLeft : 0);
            }
            textWidth = textWidthCalculation;
        }

        mView.mPrimaryTextLayout = new StaticLayout(mPrimaryText, mPaintPrimaryText, (int) textWidth,
                Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false);

        mView.mPrimaryTextTop = mView.mCentreTop;
        if (mTextPositionAbove) {
            mView.mPrimaryTextTop = mView.mPrimaryTextTop - mBaseFocalRadius - mFocalToTextPadding
                    - mView.mPrimaryTextLayout.getHeight();
        } else {
            mView.mPrimaryTextTop += mBaseFocalRadius + mFocalToTextPadding;
        }

        if (mSecondaryText != null) {
            mView.mSecondaryTextLayout = new StaticLayout(mSecondaryText, mPaintSecondaryText, (int) textWidth,
                    Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false);
            if (mTextPositionAbove) {
                mView.mPrimaryTextTop = mView.mPrimaryTextTop - mView.mTextSeparation
                        - mView.mSecondaryTextLayout.getHeight();
            }

            mView.mSecondaryTextOffsetTop = mView.mPrimaryTextLayout.getHeight() + mView.mTextSeparation;
        } else {
            mView.mSecondaryTextLayout = null;
        }

        updateBackgroundRadius();
        updateIconPosition();
    }

    private void updateBackgroundRadius() {
        final float height;
        if (mTextPositionAbove) {
            height = mView.mCentreTop - mView.mPrimaryTextTop;
        } else {
            height = mView.mPrimaryTextTop + mView.mPrimaryTextLayout.getHeight()
                    + (mView.mSecondaryTextLayout != null ? mView.mSecondaryTextLayout.getHeight() : 0)
                    - mView.mCentreTop + mView.mTextSeparation;
        }

        final float length;
        if (mTextPositionRight) {
            length = mView.mCentreLeft - mView.mTextLeft + mTextPadding;
        } else {
            length = mView.mTextLeft
                    + Math.max(mView.mPrimaryTextLayout.getWidth(),
                            mView.mSecondaryTextLayout != null ? mView.mSecondaryTextLayout.getWidth() : 0)
                    + mTextPadding - mView.mCentreLeft;
        }
        //noinspection SuspiciousNameCombination
        mBaseBackgroundRadius = Double.valueOf(Math.sqrt(Math.pow(length, 2) + Math.pow(height, 2))).floatValue();
    }

    private void updateIconPosition() {
        if (mView.mIconDrawable != null) {
            mView.mIconDrawableLeft = mView.mCentreLeft - (mView.mIconDrawable.getIntrinsicWidth() / 2);
            mView.mIconDrawableTop = mView.mCentreTop - (mView.mIconDrawable.getIntrinsicHeight() / 2);
        } else if (mView.mTargetView != null) {
            int[] viewLocation = new int[2];
            mTargetView.getLocationInWindow(viewLocation);
            int[] rootLocation = new int[2];
            mDrawView.getLocationInWindow(rootLocation);
            int relativeLeft = viewLocation[0] - rootLocation[0];
            int relativeTop = viewLocation[1] - rootLocation[1];
            mView.mDrawView = mDrawView;
            mView.mIconDrawableLeft = mView.mCentreLeft - (mView.mTargetView.getWidth() / 2) - relativeLeft;
            mView.mIconDrawableTop = mView.mCentreTop - (mView.mTargetView.getHeight() / 2) - relativeTop;
        }
    }

    private void updateClipBounds() {
        if (mClipToView != null) {
            mView.mClipBounds = true;
            mView.mClipBoundsLeft = mClipToView.getLeft();
            mView.mClipBoundsBottom = mClipToView.getBottom();
            mView.mClipBoundsTop = mClipToView.getTop();
            mView.mClipBoundsRight = mClipToView.getRight();
            if (mParentViewIsDecor) {
                mView.mClipBoundsTop += mStatusBarHeight;
                mView.mClipBoundsBottom += mStatusBarHeight;
            }
        } else if (mParentViewIsDecor) {
            mView.mClipBounds = true;
            //Stop the canvas drawing over the status bar
            mView.mClipBoundsTop = mStatusBarHeight;
            mView.mClipBoundsLeft = 0f;
            mView.mClipBoundsBottom = mActivity.getResources().getDisplayMetrics().heightPixels - mStatusBarHeight;
            mView.mClipBoundsRight = mActivity.getResources().getDisplayMetrics().widthPixels;
        } else {
            mView.mClipBounds = false;
        }
    }

    protected void onHidePrompt(final MotionEvent event, final boolean targetTapped) {
        if (mOnHidePromptListener != null) {
            mOnHidePromptListener.onHidePrompt(event, targetTapped);
        }
    }

    protected void onHidePromptComplete() {
        if (mOnHidePromptListener != null) {
            mOnHidePromptListener.onHidePromptComplete();
        }
    }

    /**
     * Interface definition for a callback to be invoked when a {@link MaterialTapTargetPrompt} is removed from view.
     */
    public interface OnHidePromptListener {
        /**
         * Called when the use touches the prompt view,
         * but before the prompt is removed from view.
         *
         * @param event        The touch event that triggered the dismiss or finish.
         * @param tappedTarget True if the prompt focal point was touched.
         */
        void onHidePrompt(final MotionEvent event, final boolean tappedTarget);

        /**
         * Called after the prompt has been removed from view.
         */
        void onHidePromptComplete();
    }

    /**
     * View used to render the tap target.
     */
    static class PromptView extends View {
        private float mCentreLeft, mCentreTop;
        private Paint mPaintBackground, mPaintFocal;
        private float mFocalRadius, mBackgroundRadius;
        private float mFocalRippleSize;
        private int mFocalRippleAlpha;
        private Drawable mIconDrawable;
        private float mIconDrawableLeft;
        private float mIconDrawableTop;
        private float mTextLeft;
        private float mPrimaryTextTop;
        private float mSecondaryTextOffsetTop;
        private Layout mPrimaryTextLayout;
        private Layout mSecondaryTextLayout;
        private boolean mDrawRipple = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
        private OnPromptTouchedListener mOnPromptTouchedListener;
        private boolean mCaptureTouchEventOnFocal;
        private float mClipBoundsTop, mClipBoundsLeft, mClipBoundsBottom, mClipBoundsRight;
        private View mTargetView;
        private float mTextSeparation;
        private boolean mClipBounds;
        private boolean mCaptureTouchEventOutsidePrompt;
        private View mDrawView;

        public PromptView(final Context context) {
            super(context);
        }

        @Override
        public void onDraw(final Canvas canvas) {
            if (mClipBounds) {
                canvas.clipRect(mClipBoundsLeft, mClipBoundsTop, mClipBoundsRight, mClipBoundsBottom);
            }

            //Draw the backgrounds
            canvas.drawCircle(mCentreLeft, mCentreTop, mBackgroundRadius, mPaintBackground);
            //Draw the ripple
            if (mDrawRipple) {
                final int oldAlpha = mPaintFocal.getAlpha();
                mPaintFocal.setAlpha(mFocalRippleAlpha);
                canvas.drawCircle(mCentreLeft, mCentreTop, mFocalRippleSize, mPaintFocal);
                mPaintFocal.setAlpha(oldAlpha);
            }
            //Draw the focal
            canvas.drawCircle(mCentreLeft, mCentreTop, mFocalRadius, mPaintFocal);

            //Draw the icon
            if (mIconDrawable != null) {
                canvas.translate(mIconDrawableLeft, mIconDrawableTop);
                mIconDrawable.draw(canvas);
                canvas.translate(-mIconDrawableLeft, -mIconDrawableTop);
            } else if (mDrawView != null) {
                canvas.translate(mIconDrawableLeft, mIconDrawableTop);
                mDrawView.draw(canvas);
                canvas.translate(-mIconDrawableLeft, -mIconDrawableTop);
            }

            //Draw the text
            canvas.translate(mTextLeft, mPrimaryTextTop);
            mPrimaryTextLayout.draw(canvas);
            if (mSecondaryTextLayout != null) {
                canvas.translate(0f, mSecondaryTextOffsetTop);
                mSecondaryTextLayout.draw(canvas);
            }
        }

        @Override
        public boolean onTouchEvent(MotionEvent event) {
            final float x = event.getX();
            final float y = event.getY();
            //If the touch point is within the prompt background stop the event from passing through it
            boolean captureEvent = pointInCircle(x, y, mBackgroundRadius);
            //If the touch event was at least in the background and in the focal
            if (captureEvent && pointInCircle(x, y, mFocalRadius)) {
                //Override allowing the touch event to pass through the view with the user defined value
                captureEvent = mCaptureTouchEventOnFocal;
                onPromptTouched(event, true);
            } else {
                // If the prompt background was not touched
                if (!captureEvent) {
                    captureEvent = mCaptureTouchEventOutsidePrompt;
                }
                onPromptTouched(event, false);
            }
            return captureEvent;
        }

        /**
         * Determines if a point is in the centre of a circle with a radius from the point ({@link #mCentreLeft, {@link #mCentreTop}}.
         *
         * @param x      The x position in the view.
         * @param y      The y position in the view.
         * @param radius The radius of the circle.
         * @return True if the point (x, y) is in the circle.
         */
        private boolean pointInCircle(final float x, final float y, final float radius) {
            return Math.pow(x - mCentreLeft, 2) + Math.pow(y - mCentreTop, 2) < Math.pow(radius, 2);
        }

        protected void onPromptTouched(final MotionEvent event, final boolean targetTapped) {
            if (mOnPromptTouchedListener != null) {
                mOnPromptTouchedListener.onPromptTouched(event, targetTapped);
            }
        }

        /**
         * Interface definition for a callback to be invoked when a {@link PromptView} is touched.
         */
        public interface OnPromptTouchedListener {
            /**
             * Called when a touch event occurs in the prompt view.
             *
             * @param event        The touch event that triggered the dismiss or finish.
             * @param tappedTarget True if the prompt focal point was touched.
             */
            void onPromptTouched(final MotionEvent event, final boolean tappedTarget);
        }
    }

    /**
     * A builder to create a {@link MaterialTapTargetPrompt} instance.
     */
    public static class Builder {
        /**
         * The containing activity.
         */
        private Activity mActivity;

        private boolean mTargetSet;

        /**
         * The view to place the prompt around.
         */
        private View mTargetView;

        /**
         * The left and top positioning for the focal centre point.
         */
        private float mCentreLeft, mCentreTop;

        /**
         * The text to display.
         */
        private String mPrimaryText, mSecondaryText;
        private int mPrimaryTextColour, mSecondaryTextColour, mBackgroundColour, mFocalColour;
        private float mFocalRadius;
        private float mPrimaryTextSize, mSecondaryTextSize;
        private float mMaxTextWidth;
        private float mTextPadding;
        private float mFocalToTextPadding;
        private Interpolator mAnimationInterpolator;
        private Drawable mIconDrawable;
        private OnHidePromptListener mOnHidePromptListener;
        private boolean mCaptureTouchEventOnFocal;
        private float mTextSeparation;
        private boolean mAutoDismiss, mAutoFinish;
        private boolean mCaptureTouchEventOutsidePrompt;
        private View mDrawView;

        /**
         * Creates a builder for a tap target prompt that uses the default
         * tap target prompt theme.
         *
         * @param activity the activity to show the prompt within.
         */
        public Builder(final Activity activity) {
            this(activity, 0);
        }

        /**
         * Creates a builder for a material tap target prompt that uses an explicit theme
         * resource.
         * <p>
         * The {@code themeResId} may be specified as {@code 0}
         * to use the parent {@code context}'s resolved value for
         * {@link R.attr#MaterialTapTargetPromptTheme}.
         *
         * @param activity   the activity to show the prompt within.
         * @param themeResId the resource ID of the theme against which to inflate
         *                   this dialog, or {@code 0} to use the parent
         *                   {@code context}'s default material tap target prompt theme
         */
        public Builder(final Activity activity, int themeResId) {
            mActivity = activity;
            //Attempt to load the theme from the activity theme
            if (themeResId == 0) {
                final TypedValue outValue = new TypedValue();
                activity.getTheme().resolveAttribute(R.attr.MaterialTapTargetPromptTheme, outValue, true);
                themeResId = outValue.resourceId;
            }

            final float density = activity.getResources().getDisplayMetrics().density;
            final TypedArray a = mActivity.obtainStyledAttributes(themeResId, R.styleable.PromptView);
            mPrimaryTextColour = a.getColor(R.styleable.PromptView_primaryTextColour, Color.WHITE);
            mSecondaryTextColour = a.getColor(R.styleable.PromptView_secondaryTextColour,
                    Color.argb(179, 255, 255, 255));
            mPrimaryText = a.getString(R.styleable.PromptView_primaryText);
            mSecondaryText = a.getString(R.styleable.PromptView_secondaryText);
            mBackgroundColour = a.getColor(R.styleable.PromptView_backgroundColour, Color.argb(244, 63, 81, 181));
            mFocalColour = a.getColor(R.styleable.PromptView_focalColour, Color.WHITE);
            mFocalRadius = a.getDimension(R.styleable.PromptView_focalRadius, density * 44);
            mPrimaryTextSize = a.getDimension(R.styleable.PromptView_primaryTextSize, 22 * density);
            mSecondaryTextSize = a.getDimension(R.styleable.PromptView_secondaryTextSize, 18 * density);
            mMaxTextWidth = a.getDimension(R.styleable.PromptView_maxTextWidth, 400 * density);
            mTextPadding = a.getDimension(R.styleable.PromptView_textPadding, 40 * density);
            mFocalToTextPadding = a.getDimension(R.styleable.PromptView_focalToTextPadding, 20 * density);
            mTextSeparation = a.getDimension(R.styleable.PromptView_textSeparation, 16 * density);
            mAutoDismiss = a.getBoolean(R.styleable.PromptView_autoDismiss, true);
            mAutoFinish = a.getBoolean(R.styleable.PromptView_autoFinish, true);
            mCaptureTouchEventOutsidePrompt = a.getBoolean(R.styleable.PromptView_captureTouchEventOutsidePrompt,
                    false);
            mCaptureTouchEventOnFocal = a.getBoolean(R.styleable.PromptView_captureTouchEventOnFocal, false);
            final int targetId = a.getResourceId(R.styleable.PromptView_target, 0);
            a.recycle();

            if (targetId != 0) {
                mTargetView = mActivity.findViewById(targetId);
                if (mTargetView != null) {
                    mTargetSet = true;
                }
            }
        }

        /**
         * Set the view for the prompt to focus on.
         *
         * @param target The view that the prompt will highlight.
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setTarget(final View target) {
            mTargetView = target;
            mTargetSet = true;
            return this;
        }

        public Builder setDrawView(View drawView) {
            mDrawView = drawView;
            return this;
        }

        /**
         * Set the view for the prompt to focus on using the given resource id.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setTarget(@IdRes final int target) {
            mTargetView = mActivity.findViewById(target);
            mTargetSet = mTargetView != null;
            return this;
        }

        /**
         * Set the centre point as a screen position
         *
         * @param left Centre point from screen left
         * @param top  Centre point from screen top
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setTarget(final float left, final float top) {
            mTargetView = null;
            mCentreLeft = left;
            mCentreTop = top;
            mTargetSet = true;
            return this;
        }

        /**
         * Has the target been set successfully?
         *
         * @return True if set successfully.
         */
        public boolean isTargetSet() {
            return mTargetSet;
        }

        /**
         * Set the primary text using the given resource id.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setPrimaryText(@StringRes final int resId) {
            mPrimaryText = mActivity.getString(resId);
            return this;
        }

        /**
         * Set the primary text to the given string
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setPrimaryText(final String text) {
            mPrimaryText = text;
            return this;
        }

        /**
         * Set the primary text font size using the given resource id.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setPrimaryTextSize(@DimenRes final int resId) {
            mPrimaryTextSize = mActivity.getResources().getDimension(resId);
            return this;
        }

        /**
         * Set the primary text font size.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setPrimaryTextSize(final float size) {
            mPrimaryTextSize = size;
            return this;
        }

        /**
         * Set the primary text colour.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setPrimaryTextColour(@ColorInt final int colour) {
            mPrimaryTextColour = colour;
            return this;
        }

        /**
         * Set the primary text colour using the given resource id.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setPrimaryTextColourFromRes(@ColorRes final int resId) {
            mPrimaryTextColour = getColour(resId);
            return this;
        }

        /**
         * Set the secondary text using the given resource id.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setSecondaryText(@StringRes final int resId) {
            mSecondaryText = mActivity.getString(resId);
            return this;
        }

        /**
         * Set the secondary text.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setSecondaryText(final String text) {
            mSecondaryText = text;
            return this;
        }

        /**
         * Set the secondary text font size using the give resource id.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setSecondaryTextSize(@DimenRes final int resId) {
            mSecondaryTextSize = mActivity.getResources().getDimension(resId);
            return this;
        }

        /**
         * Set the secondary text font size.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setSecondaryTextSize(final float size) {
            mSecondaryTextSize = size;
            return this;
        }

        /**
         * Set the secondary text colour.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setSecondaryTextColour(@ColorInt final int colour) {
            mSecondaryTextColour = colour;
            return this;
        }

        /**
         * Set the secondary text colour using the give resource id.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setSecondaryTextColourFromRes(@ColorRes final int resId) {
            mSecondaryTextColour = getColour(resId);
            return this;
        }

        /**
         * Set the text left and right padding.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setTextPadding(final float padding) {
            mTextPadding = padding;
            return this;
        }

        /**
         * Set the text left and right padding using the given resource id.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setTextPadding(@DimenRes final int resId) {
            mTextPadding = mActivity.getResources().getDimension(resId);
            return this;
        }

        /**
         * Set the distance between the primary and secondary text.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setTextSeparation(final float separation) {
            mTextSeparation = separation;
            return this;
        }

        /**
         * Set the distance between the primary and secondary text using the given
         * resource id.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setTextSeparation(@DimenRes final int resId) {
            mTextSeparation = mActivity.getResources().getDimension(resId);
            return this;
        }

        /**
         * Set the padding between the text and the focal point.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setFocalToTextPadding(final float padding) {
            mFocalToTextPadding = padding;
            return this;
        }

        /**
         * Set the padding between the text and the focal point using the given
         * resource id.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setFocalToTextPadding(@DimenRes final int resId) {
            mFocalToTextPadding = mActivity.getResources().getDimension(resId);
            return this;
        }

        /**
         * Set the interpolator to use in animations.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setAnimationInterpolator(final Interpolator interpolator) {
            mAnimationInterpolator = interpolator;
            return this;
        }

        /**
         * Set the icon to draw in the focal point using the given resource id.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setIcon(@DrawableRes final int resId) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                mIconDrawable = mActivity.getDrawable(resId);
            } else {
                //noinspection deprecation
                mIconDrawable = mActivity.getResources().getDrawable(resId);
            }
            if (mIconDrawable != null) {
                mIconDrawable.setBounds(0, 0, mIconDrawable.getIntrinsicWidth(),
                        mIconDrawable.getIntrinsicHeight());
                mIconDrawable.setColorFilter(mBackgroundColour, PorterDuff.Mode.MULTIPLY);
                mIconDrawable.setAlpha(Color.alpha(mBackgroundColour));
            }
            return this;
        }

        /**
         * Set the icon to draw in the focal point.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setIconDrawable(final Drawable drawable) {
            mIconDrawable = drawable;
            return this;
        }

        /**
         * Set the listener to listen for when the prompt is touched.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setOnHidePromptListener(final OnHidePromptListener listener) {
            mOnHidePromptListener = listener;
            return this;
        }

        /**
         * Set if the prompt should stop touch events on the focal point from passing
         * to underlying views. Default is false.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setCaptureTouchEventOnFocal(final boolean captureTouchEvent) {
            mCaptureTouchEventOnFocal = captureTouchEvent;
            return this;
        }

        /**
         * Set the max width that the primary and secondary text can be.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setMaxTextWidth(final float width) {
            mMaxTextWidth = width;
            return this;
        }

        /**
         * Set the max width that the primary and secondary text can be using the given
         * resource id.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setMaxTextWidth(@DimenRes final int resId) {
            mMaxTextWidth = mActivity.getResources().getDimension(resId);
            return this;
        }

        /**
         * Set the background colour.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setBackgroundColour(@ColorInt final int colour) {
            mBackgroundColour = colour;
            return this;
        }

        /**
         * Set the background colour using the given resource id.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setBackgroundColourFromRes(@ColorRes final int resId) {
            mBackgroundColour = getColour(resId);
            return this;
        }

        /**
         * Set the focal point colour.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setFocalColour(@ColorInt final int colour) {
            mFocalColour = colour;
            return this;
        }

        /**
         * Set the focal point colour using the given resource id.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setFocalColourFromRes(@ColorRes final int resId) {
            mFocalColour = getColour(resId);
            return this;
        }

        /**
         * Set the focal point radius.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setFocalRadius(final float radius) {
            mFocalRadius = radius;
            return this;
        }

        /**
         * Set the focal point radius using the given resource id.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setFocalRadius(@DimenRes final int resId) {
            mFocalRadius = mActivity.getResources().getDimension(resId);
            return this;
        }

        /**
         * Set whether the prompt should dismiss itself when a touch event occurs outside the focal.
         * Default is true.
         *
         * @param autoDismiss True - prompt will dismiss when touched outside the focal, false - no action taken.
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setAutoDismiss(final boolean autoDismiss) {
            mAutoDismiss = autoDismiss;
            return this;
        }

        /**
         * Set whether the prompt should finish itself when a touch event occurs inside the focal.
         * Default is true.
         *
         * @param autoFinish True - prompt will finish when touched inside the focal, false - no action taken.
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setAutoFinish(final boolean autoFinish) {
            mAutoFinish = autoFinish;
            return this;
        }

        /**
         * Set if the prompt should stop touch events outside the prompt from passing
         * to underlying views. Default is false.
         *
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setCaptureTouchEventOutsidePrompt(final boolean captureTouchEventOutsidePrompt) {
            mCaptureTouchEventOutsidePrompt = captureTouchEventOutsidePrompt;
            return this;
        }

        /**
         * Creates an {@link MaterialTapTargetPrompt} with the arguments supplied to this
         * builder.
         * <p>
         * Calling this method does not display the prompt. If no additional
         * processing is needed, {@link #show()} may be called instead to both
         * create and display the prompt.
         * </p>
         * <p>
         * Will return {@link null} if a valid target has not been set or the primary text is {@link null}.
         * To check that a valid target has been set call {@link #isTargetSet()}.
         * </p>
         */
        public MaterialTapTargetPrompt create() {
            if (!mTargetSet || mPrimaryText == null) {
                return null;
            }
            final MaterialTapTargetPrompt mPrompt = new MaterialTapTargetPrompt(mActivity);
            if (mTargetView != null) {
                mPrompt.mTargetView = mTargetView;
                mPrompt.mView.mTargetView = mTargetView;
                if (mDrawView == null) {
                    mDrawView = mTargetView;
                }
                mPrompt.mDrawView = mDrawView;
            } else {
                mPrompt.mBaseLeft = mCentreLeft;
                mPrompt.mBaseTop = mCentreTop;
            }
            mPrompt.mClipToView = (ViewGroup) ((ViewGroup) mActivity.findViewById(android.R.id.content))
                    .getChildAt(0);

            mPrompt.mPrimaryText = mPrimaryText;
            mPrompt.mPrimaryTextColourAlpha = Color.alpha(mPrimaryTextColour);
            mPrompt.mSecondaryText = mSecondaryText;
            mPrompt.mSecondaryTextColourAlpha = Color.alpha(mSecondaryTextColour);
            mPrompt.mMaxTextWidth = mMaxTextWidth;
            mPrompt.mTextPadding = mTextPadding;
            mPrompt.mFocalToTextPadding = mFocalToTextPadding;
            mPrompt.mBaseFocalRippleAlpha = 150;

            mPrompt.mView.mTextSeparation = mTextSeparation;

            mPrompt.mOnHidePromptListener = mOnHidePromptListener;
            mPrompt.mView.mCaptureTouchEventOnFocal = mCaptureTouchEventOnFocal;

            if (mAnimationInterpolator != null) {
                mPrompt.mAnimationInterpolator = mAnimationInterpolator;
            } else {
                mPrompt.mAnimationInterpolator = new AccelerateDecelerateInterpolator();
            }

            mPrompt.mBaseFocalRadius = mFocalRadius;
            //Calculate 10% of the focal radius
            mPrompt.mFocalRadius10Percent = mFocalRadius / 100 * 10;

            mPrompt.mView.mIconDrawable = mIconDrawable;

            mPrompt.mView.mPaintFocal = new Paint();
            mPrompt.mView.mPaintFocal.setColor(mFocalColour);
            mPrompt.mView.mPaintFocal.setAlpha(Color.alpha(mFocalColour));
            mPrompt.mView.mPaintFocal.setAntiAlias(true);

            mPrompt.mView.mPaintBackground = new Paint();
            mPrompt.mView.mPaintBackground.setColor(mBackgroundColour);
            mPrompt.mView.mPaintBackground.setAlpha(Color.alpha(mBackgroundColour));
            mPrompt.mView.mPaintBackground.setAntiAlias(true);

            mPrompt.mPaintPrimaryText = new TextPaint();
            mPrompt.mPaintPrimaryText.setColor(mPrimaryTextColour);
            mPrompt.mPaintPrimaryText.setAlpha(Color.alpha(mPrimaryTextColour));
            mPrompt.mPaintPrimaryText.setAntiAlias(true);
            mPrompt.mPaintPrimaryText.setTextSize(mPrimaryTextSize);

            mPrompt.mPaintSecondaryText = new TextPaint();
            mPrompt.mPaintSecondaryText.setColor(mSecondaryTextColour);
            mPrompt.mPaintSecondaryText.setAlpha(Color.alpha(mSecondaryTextColour));
            mPrompt.mPaintSecondaryText.setAntiAlias(true);
            mPrompt.mPaintSecondaryText.setTextSize(mSecondaryTextSize);

            mPrompt.mAutoDismiss = mAutoDismiss;
            mPrompt.mAutoFinish = mAutoFinish;

            mPrompt.mView.mCaptureTouchEventOutsidePrompt = mCaptureTouchEventOutsidePrompt;

            return mPrompt;
        }

        /**
         * Creates an {@link MaterialTapTargetPrompt} with the arguments supplied to this
         * builder and immediately displays the prompt.
         * <p>
         * Calling this method is functionally identical to:
         * </p>
         * <pre>
         *     MaterialTapTargetPrompt prompt = builder.create();
         *     prompt.show();
         * </pre>
         * <p>
         * Will return {@link null} if a valid target has not been set or the primary text is {@link null}.
         * To check that a valid target has been set call {@link #isTargetSet()}.
         * </p>
         */
        public MaterialTapTargetPrompt show() {
            final MaterialTapTargetPrompt mPrompt = create();
            if (mPrompt != null) {
                mPrompt.show();
            }
            return mPrompt;
        }

        private int getColour(final int resId) {
            final int colour;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                colour = mActivity.getColor(resId);
            } else {
                //noinspection deprecation
                colour = mActivity.getResources().getColor(resId);
            }
            return colour;
        }
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    private static class AnimatorListener implements Animator.AnimatorListener {

        @Override
        public void onAnimationStart(Animator animation) {

        }

        @Override
        public void onAnimationEnd(Animator animation) {

        }

        @Override
        public void onAnimationCancel(Animator animation) {

        }

        @Override
        public void onAnimationRepeat(Animator animation) {

        }
    }
}