diff --git a/tourguide/src/main/java/tourguide/tourguide/FrameLayoutWithHole.java b/tourguide/src/main/java/tourguide/tourguide/FrameLayoutWithHole.java index 69eaf48..c96cfd9 100644 --- a/tourguide/src/main/java/tourguide/tourguide/FrameLayoutWithHole.java +++ b/tourguide/src/main/java/tourguide/tourguide/FrameLayoutWithHole.java @@ -119,8 +119,12 @@ private void init(AttributeSet attrs, int defStyle) { size.x = mActivity.getResources().getDisplayMetrics().widthPixels; size.y = mActivity.getResources().getDisplayMetrics().heightPixels; - mEraserBitmap = Bitmap.createBitmap(size.x, size.y, Bitmap.Config.ARGB_8888); - mEraserCanvas = new Canvas(mEraserBitmap); + try { + mEraserBitmap = Bitmap.createBitmap(size.x, size.y, Bitmap.Config.ARGB_8888); + mEraserCanvas = new Canvas(mEraserBitmap); + } catch (OutOfMemoryError e) { + Log.e("tourguide", "OutOfMemoryError", e); + } mPaint = new Paint(); mPaint.setColor(0xcc000000); @@ -230,7 +234,13 @@ public boolean dispatchTouchEvent(MotionEvent ev) { Log.d("tourguide", "[dispatchTouchEvent] Y lower bound: "+ pos[1]); Log.d("tourguide", "[dispatchTouchEvent] Y higher bound: "+(pos[1] +mViewHole.getHeight())); - if(ev.getRawY() >= pos[1] && ev.getRawY() <= (pos[1] + mViewHole.getHeight()) && ev.getRawX() >= pos[0] && ev.getRawX() <= (pos[0] + mViewHole.getWidth())) { //location button event + if( + ev.getRawY() >= pos[1] + && ev.getRawY() <= (pos[1] + mViewHole.getHeight()) + && ev.getRawX() >= pos[0] + && ev.getRawX() <= (pos[0] + mViewHole.getWidth()) + && !mOverlay.mDisableClickThroughHole + ) { //location button event Log.d("tourguide","to the BOTTOM!"); Log.d("tourguide",""+ev.getAction()); @@ -273,7 +283,7 @@ protected void onDraw(Canvas canvas) { int padding = (int) (10 * mDensity); if (mOverlay.mStyle == Overlay.Style.Rectangle) { mEraserCanvas.drawRect(mPos[0] - padding, mPos[1] - padding, mPos[0] + mViewHole.getWidth() + padding, mPos[1] + mViewHole.getHeight() + padding, mEraser); - } else { + } else if (mOverlay.mStyle == Overlay.Style.Circle) { mEraserCanvas.drawCircle(mPos[0] + mViewHole.getWidth() / 2, mPos[1] + mViewHole.getHeight() / 2, mRadius, mEraser); } } diff --git a/tourguide/src/main/java/tourguide/tourguide/Overlay.java b/tourguide/src/main/java/tourguide/tourguide/Overlay.java index e53ebde..7db1546 100644 --- a/tourguide/src/main/java/tourguide/tourguide/Overlay.java +++ b/tourguide/src/main/java/tourguide/tourguide/Overlay.java @@ -10,12 +10,13 @@ public class Overlay { public int mBackgroundColor; public boolean mDisableClick; + public boolean mDisableClickThroughHole; public Style mStyle; public Animation mEnterAnimation, mExitAnimation; public View.OnClickListener mOnClickListener; public enum Style { - Circle, Rectangle + Circle, Rectangle, NoHole } public Overlay() { this(true, Color.parseColor("#55000000"), Style.Circle); @@ -47,6 +48,17 @@ public Overlay disableClick(boolean yes_no){ return this; } + /** + * Set to true if you want to disallow the highlighted view to be clicked through the hole, + * set to false if you want to allow the highlighted view to be clicked through the hole + * @param yes_no + * @return return Overlay instance for chaining purpose + */ + public Overlay disableClickThroughHole(boolean yes_no){ + mDisableClickThroughHole = yes_no; + return this; + } + public Overlay setStyle(Style style){ mStyle = style; return this; diff --git a/tourguide/src/main/java/tourguide/tourguide/ToolTip.java b/tourguide/src/main/java/tourguide/tourguide/ToolTip.java index 85ec0ef..2481fa8 100644 --- a/tourguide/src/main/java/tourguide/tourguide/ToolTip.java +++ b/tourguide/src/main/java/tourguide/tourguide/ToolTip.java @@ -17,6 +17,7 @@ public class ToolTip { public boolean mShadow; public int mGravity; public View.OnClickListener mOnClickListener; + public View mCustomView; public ToolTip(){ /* default values */ @@ -116,4 +117,13 @@ public ToolTip setOnClickListener(View.OnClickListener onClickListener){ mOnClickListener = onClickListener; return this; } + + public View getCustomView() { + return mCustomView; + } + + public ToolTip setCustomView(View view) { + mCustomView = view; + return this; + } } diff --git a/tourguide/src/main/java/tourguide/tourguide/TourGuide.java b/tourguide/src/main/java/tourguide/tourguide/TourGuide.java index 9da1a45..8269dc6 100644 --- a/tourguide/src/main/java/tourguide/tourguide/TourGuide.java +++ b/tourguide/src/main/java/tourguide/tourguide/TourGuide.java @@ -7,8 +7,8 @@ import android.app.Activity; import android.graphics.Color; import android.graphics.Point; +import android.os.Build; import android.util.Log; -import android.view.Display; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; @@ -223,31 +223,48 @@ private int getYBasedOnGravity(int height){ } } + private class BooleanFlag { + public boolean flag; + } + private void setupView(){ // TODO: throw exception if either mActivity, mDuration, mHighlightedView is null checking(); - final ViewTreeObserver viewTreeObserver = mHighlightedView.getViewTreeObserver(); - viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + + final BooleanFlag viewLaidOut = new BooleanFlag(); + viewLaidOut.flag = false; + mHighlightedView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { // make sure this only run once - mHighlightedView.getViewTreeObserver().removeGlobalOnLayoutListener(this); - - /* Initialize a frame layout with a hole */ - mFrameLayout = new FrameLayoutWithHole(mActivity, mHighlightedView, mMotionType, mOverlay); - /* handle click disable */ - handleDisableClicking(mFrameLayout); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { + //noinspection deprecation + mHighlightedView.getViewTreeObserver().removeGlobalOnLayoutListener(this); + } else { + mHighlightedView.getViewTreeObserver().removeOnGlobalLayoutListener(this); + } - /* setup floating action button */ - if (mPointer != null) { - FloatingActionButton fab = setupAndAddFABToFrameLayout(mFrameLayout); - performAnimationOn(fab); + if (!viewLaidOut.flag) { + viewLaidOut.flag = true; + + /* Initialize a frame layout with a hole */ + mFrameLayout = new FrameLayoutWithHole(mActivity, mHighlightedView, mMotionType, mOverlay); + /* handle click disable */ + handleDisableClicking(mFrameLayout); + + /* setup floating action button */ + if (mPointer != null) { + FloatingActionButton fab = setupAndAddFABToFrameLayout(mFrameLayout); + performAnimationOn(fab); + } + setupFrameLayout(); + /* setup tooltip view */ + setupToolTip(); } - setupFrameLayout(); - /* setup tooltip view */ - setupToolTip(); } }); + // ensure that the global layout listener for the highlighted view gets called soon after setting it + mHighlightedView.requestLayout(); } private void checking(){ // There is not check for tooltip because tooltip can be null, it means there no tooltip will be shown @@ -277,15 +294,32 @@ private void setupToolTip(){ /* inflate and get views */ ViewGroup parent = (ViewGroup) mActivity.getWindow().getDecorView(); LayoutInflater layoutInflater = mActivity.getLayoutInflater(); - mToolTipViewGroup = layoutInflater.inflate(R.layout.tooltip, null); - View toolTipContainer = mToolTipViewGroup.findViewById(R.id.toolTip_container); - TextView toolTipTitleTV = (TextView) mToolTipViewGroup.findViewById(R.id.title); - TextView toolTipDescriptionTV = (TextView) mToolTipViewGroup.findViewById(R.id.description); - /* set tooltip attributes */ - toolTipContainer.setBackgroundColor(mToolTip.mBackgroundColor); - toolTipTitleTV.setText(mToolTip.mTitle); - toolTipDescriptionTV.setText(mToolTip.mDescription); + if (mToolTip.getCustomView() == null) { + mToolTipViewGroup = layoutInflater.inflate(R.layout.tooltip, null); + View toolTipContainer = mToolTipViewGroup.findViewById(R.id.toolTip_container); + TextView toolTipTitleTV = (TextView) mToolTipViewGroup.findViewById(R.id.title); + TextView toolTipDescriptionTV = (TextView) mToolTipViewGroup.findViewById(R.id.description); + + /* set tooltip attributes */ + toolTipContainer.setBackgroundColor(mToolTip.mBackgroundColor); + + if (mToolTip.mTitle == null || mToolTip.mTitle.isEmpty()) { + toolTipTitleTV.setVisibility(View.GONE); + } else { + toolTipTitleTV.setVisibility(View.VISIBLE); + } + toolTipTitleTV.setText(mToolTip.mTitle); + + if (mToolTip.mDescription == null || mToolTip.mDescription.isEmpty()) { + toolTipDescriptionTV.setVisibility(View.GONE); + } else { + toolTipDescriptionTV.setVisibility(View.VISIBLE); + } + toolTipDescriptionTV.setText(mToolTip.mDescription); + } else { + mToolTipViewGroup = mToolTip.getCustomView(); + } mToolTipViewGroup.startAnimation(mToolTip.mEnterAnimation); @@ -348,16 +382,21 @@ private void setupToolTip(){ // this needs an viewTreeObserver, that's because TextView measurement of it's vertical height is not accurate (didn't take into account of multiple lines yet) before it's rendered // re-calculate height again once it's rendered - final ViewTreeObserver viewTreeObserver = mToolTipViewGroup.getViewTreeObserver(); - viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + mToolTipViewGroup.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { - mToolTipViewGroup.getViewTreeObserver().removeGlobalOnLayoutListener(this);// make sure this only run once + // make sure this only run once + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { + //noinspection deprecation + mToolTipViewGroup.getViewTreeObserver().removeGlobalOnLayoutListener(this); + } else { + mToolTipViewGroup.getViewTreeObserver().removeOnGlobalLayoutListener(this); + } int fixedY; int toolTipHeightAfterLayouted = mToolTipViewGroup.getHeight(); fixedY = getYForTooTip(mToolTip.mGravity, toolTipHeightAfterLayouted, targetViewY, adjustment); - layoutParams.setMargins((int)mToolTipViewGroup.getX(),fixedY,0,0); + layoutParams.setMargins((int) mToolTipViewGroup.getX(), fixedY, 0, 0); } }); @@ -413,12 +452,16 @@ private FloatingActionButton setupAndAddFABToFrameLayout(final FrameLayoutWithHo fab.setClickable(false); // When invisFab is layouted, it's width and height can be used to calculate the correct position of fab - final ViewTreeObserver viewTreeObserver = invisFab.getViewTreeObserver(); - viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + invisFab.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { // make sure this only run once - invisFab.getViewTreeObserver().removeGlobalOnLayoutListener(this); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { + //noinspection deprecation + invisFab.getViewTreeObserver().removeGlobalOnLayoutListener(this); + } else { + invisFab.getViewTreeObserver().removeOnGlobalLayoutListener(this); + } final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); frameLayoutWithHole.addView(fab, params);