BubbleShowCase-Android icon indicating copy to clipboard operation
BubbleShowCase-Android copied to clipboard

double tapping on backgrounds is showing 2 bubbles

Open fatimasiddique opened this issue 7 years ago • 1 comments

Hi,

I want to show next sequence bubble when background is clicked. Issue is when the user taps on background twice then 2 bubbles starts appearing.

image

fatimasiddique avatar Jan 31 '19 07:01 fatimasiddique

I resolved this issue by updating this class

package com.elconfidencial.bubbleshowcase

class BubbleShowCase(builder: BubbleShowCaseBuilder) {
 private val SHARED_PREFS_NAME = "BubbleShowCasePrefs"
 private val TAG = "BubbleShowCase
```"

 private val DOUBLE_CLICK_DURATION = 1000
 private val FOREGROUND_LAYOUT_ID = 731

 private val DURATION_SHOW_CASE_ANIMATION = 200 //ms
 private val DURATION_BACKGROUND_ANIMATION = 400 //ms
 private val DURATION_BEATING_ANIMATION = 800 //ms

 private val MAX_WIDTH_MESSAGE_VIEW_TABLET = 420 //dp

 /**
  * Enum class which corresponds to each valid position for the BubbleMessageView arrow
  */
 enum class ArrowPosition {
     TOP, BOTTOM, LEFT, RIGHT
 }

 /**
  * Highlight mode. It represents the way that the target view will be highlighted
  * - VIEW_LAYOUT: Default value. All the view box is highlighted (the rectangle where the view is contained). Example: For a TextView, all the element is highlighted (characters and background)
  * - VIEW_SURFACE: Only the view surface is highlighted, but not the background. Example: For a TextView, only the characters will be highlighted
  */
 enum class HighlightMode {
     VIEW_LAYOUT, VIEW_SURFACE
 }


 private val mActivity: WeakReference<Activity> = builder.mActivity!!

 //BubbleMessageView params
 private val mImage: Drawable? = builder.mImage
 private val mTitle: String? = builder.mTitle
 private val mSubtitle: String? = builder.mSubtitle
 private val mTextNextBtn: String? = builder.mTextNextBtn
 private val mCloseAction: Drawable? = builder.mCloseAction
 private val mBackgroundColor: Int? = builder.mBackgroundColor
 private val mTextColor: Int? = builder.mTextColor
 private val mTitleTextSize: Int? = builder.mTitleTextSize
 private val mSubtitleTextSize: Int? = builder.mSubtitleTextSize
 private val mShowOnce: String? = builder.mShowOnce
 private val mDisableTargetClick: Boolean = builder.mDisableTargetClick
 private val mDisableCloseAction: Boolean = builder.mDisableCloseAction
 private val mHighlightMode: BubbleShowCase.HighlightMode? = builder.mHighlightMode
 private val mArrowPositionList: MutableList<ArrowPosition> = builder.mArrowPositionList
 private val mTargetView: WeakReference<View>? = builder.mTargetView
 private val mBubbleShowCaseListener: BubbleShowCaseListener? = builder.mBubbleShowCaseListener


 //Sequence params
 private val mSequenceListener: SequenceShowCaseListener? = builder.mSequenceShowCaseListener
 private val isFirstOfSequence: Boolean = builder.mIsFirstOfSequence!!
 private val isLastOfSequence: Boolean = builder.mIsLastOfSequence!!

 //References
 private var backgroundDimLayout: RelativeLayout? = null
 private var bubbleMessageViewBuilder: BubbleMessageView.Builder? = null

 fun show() {
     if (mShowOnce != null) {
         if (isBubbleShowCaseHasBeenShowedPreviously(mShowOnce)) {
             notifyDismissToSequenceListener()
             return
         } else {
             registerBubbleShowCaseInPreferences(mShowOnce)
         }
     }

     val rootView = getViewRoot(mActivity.get()!!)
     backgroundDimLayout = getBackgroundDimLayout()
     setBackgroundDimListener(backgroundDimLayout)
     bubbleMessageViewBuilder = getBubbleMessageViewBuilder()

     if (mTargetView != null && mArrowPositionList.size <= 1) {
         //Wait until the end of the layout animation, to avoid problems with pending scrolls or view movements
         val handler = Handler()
         handler.postDelayed({
             val target = mTargetView.get()!!
             //If the arrow list is empty, the arrow position is set by default depending on the targetView position on the screen
             if (mArrowPositionList.isEmpty()) {
                 if (ScreenUtils.isViewLocatedAtHalfTopOfTheScreen(
                         mActivity.get()!!,
                         target
                     )
                 ) mArrowPositionList.add(ArrowPosition.TOP) else mArrowPositionList.add(
                     ArrowPosition.BOTTOM
                 )
                 bubbleMessageViewBuilder = getBubbleMessageViewBuilder()
             }

             if (isVisibleOnScreen(target)) {
                 addTargetViewAtBackgroundDimLayout(target, backgroundDimLayout)
                 addBubbleMessageViewDependingOnTargetView(
                     target,
                     bubbleMessageViewBuilder!!,
                     backgroundDimLayout
                 )
             } else {
                 dismiss()
             }
         }, DURATION_BACKGROUND_ANIMATION.toLong())
     } else {
         addBubbleMessageViewOnScreenCenter(bubbleMessageViewBuilder!!, backgroundDimLayout)
     }
     if (isFirstOfSequence) {
         //Add the background dim layout above the root view
         val animation = AnimationUtils.getFadeInAnimation(0, DURATION_BACKGROUND_ANIMATION)
         backgroundDimLayout?.let {
             rootView.addView(
                 AnimationUtils.setAnimationToView(
                     backgroundDimLayout!!,
                     animation
                 )
             )
         }
     }
 }

 fun dismiss() {
     if (backgroundDimLayout != null && isLastOfSequence) {
         //Remove background dim layout if the BubbleShowCase is the last of the sequence
         finishSequence()
     } else {
         //Remove all the views created over the background dim layout waiting for the next BubbleShowCsse in the sequence
         backgroundDimLayout?.removeAllViews()
     }
     notifyDismissToSequenceListener()
 }

 private fun finishSequence() {
     val rootView = getViewRoot(mActivity.get()!!)
     rootView.removeView(backgroundDimLayout)
     backgroundDimLayout = null
 }

 private fun notifyDismissToSequenceListener() {
     mSequenceListener?.let {
         mSequenceListener.onDismiss()
     }
 }

 private fun getViewRoot(activity: Activity): ViewGroup {
     val androidContent = activity.findViewById<ViewGroup>(android.R.id.content)
     return androidContent.parent.parent as ViewGroup
 }

 private fun getBackgroundDimLayout(): RelativeLayout {
     if (mActivity.get()!!.findViewById<RelativeLayout>(FOREGROUND_LAYOUT_ID) != null)
         return mActivity.get()!!.findViewById(FOREGROUND_LAYOUT_ID)
     val backgroundLayout = RelativeLayout(mActivity.get()!!)
     backgroundLayout.id = FOREGROUND_LAYOUT_ID
     backgroundLayout.layoutParams = RelativeLayout.LayoutParams(
         ViewGroup.LayoutParams.MATCH_PARENT,
         ViewGroup.LayoutParams.MATCH_PARENT
     )
     backgroundLayout.setBackgroundColor(
         ContextCompat.getColor(
             mActivity.get()!!,
             R.color.transparent_grey
         )
     )
     backgroundLayout.isClickable = true
     return backgroundLayout
 }

 private fun setBackgroundDimListener(backgroundDimLayout: RelativeLayout?) {
     backgroundDimLayout?.setOnClickListener {
         try {
             if (SystemClock.elapsedRealtime() - mLastClickTime < DOUBLE_CLICK_DURATION) {//this code disable double tap
                 return@setOnClickListener
             }
             mLastClickTime = SystemClock.elapsedRealtime()

             mBubbleShowCaseListener?.onBackgroundDimClick(this)
             dismiss()
             mBubbleShowCaseListener?.onCloseActionImageClick(this)
         } catch (e: Exception) {
             Log.e(TAG, e.toString())
         }
     }
 }

 private fun getBubbleMessageViewBuilder(): BubbleMessageView.Builder {
     return BubbleMessageView.Builder()
         .from(mActivity.get()!!)
         .arrowPosition(mArrowPositionList)
         .backgroundColor(mBackgroundColor)
         .textColor(mTextColor)
         .titleTextSize(mTitleTextSize)
         .subtitleTextSize(mSubtitleTextSize)
         .textNextBtn(mTextNextBtn)
         .title(mTitle)
         .subtitle(mSubtitle)
         .image(mImage)
         .closeActionImage(mCloseAction)
         .disableCloseAction(mDisableCloseAction)
         .listener(object : OnBubbleMessageViewListener {
             override fun onBubbleClick() {
                 mBubbleShowCaseListener?.onBubbleClick(this@BubbleShowCase)
             }

             override fun onCloseActionImageClick() {
                 try {
                     if (SystemClock.elapsedRealtime() - mLastClickTime < DOUBLE_CLICK_DURATION) {//this code disable double tap
                         return
                     }
                     mLastClickTime = SystemClock.elapsedRealtime()

                     dismiss()
                     mBubbleShowCaseListener?.onCloseActionImageClick(this@BubbleShowCase)

                 } catch (e: Exception) {
                     Log.e(TAG, e.toString())
                 }
             }
         })
 }

 private fun isBubbleShowCaseHasBeenShowedPreviously(id: String): Boolean {
     val mPrefs = mActivity.get()!!.getSharedPreferences(SHARED_PREFS_NAME, MODE_PRIVATE)
     return getString(mPrefs, id) != null
 }

 private fun registerBubbleShowCaseInPreferences(id: String) {
     val mPrefs = mActivity.get()!!.getSharedPreferences(SHARED_PREFS_NAME, MODE_PRIVATE)
     setString(mPrefs, id, id)
 }

 private fun getString(mPrefs: SharedPreferences, key: String): String? {
     return mPrefs.getString(key, null)
 }

 private fun setString(mPrefs: SharedPreferences, key: String, value: String) {
     val editor = mPrefs.edit()
     editor.putString(key, value)
     editor.apply()
 }


 /**
  * This function takes a screenshot of the targetView, creating an ImageView from it. This new ImageView is also set on the layout passed by param
  */
 private fun addTargetViewAtBackgroundDimLayout(
     targetView: View?,
     backgroundDimLayout: RelativeLayout?
 ) {
     if (targetView == null) return

     val targetScreenshot = takeScreenshot(targetView, mHighlightMode)
     val targetScreenshotView = ImageView(mActivity.get()!!)
     targetScreenshotView.setImageBitmap(targetScreenshot)
     targetScreenshotView.setOnClickListener {
         try {
             if (SystemClock.elapsedRealtime() - mLastClickTime < DOUBLE_CLICK_DURATION) {//this code disable double tap
                 return@setOnClickListener
             }
             mLastClickTime = SystemClock.elapsedRealtime()

             if (!mDisableTargetClick)
                 dismiss()
             mBubbleShowCaseListener?.onTargetClick(this)

         } catch (e: Exception) {
             Log.e(TAG, e.toString())
         }
     }

     val targetViewParams = RelativeLayout.LayoutParams(
         RelativeLayout.LayoutParams.WRAP_CONTENT,
         RelativeLayout.LayoutParams.WRAP_CONTENT
     )
     targetViewParams.setMargins(
         getXposition(targetView),
         getYposition(targetView),
         getScreenWidth(mActivity.get()!!) - (getXposition(targetView) + targetView.width),
         0
     )
     backgroundDimLayout?.addView(
         AnimationUtils.setBouncingAnimation(
             targetScreenshotView,
             0,
             DURATION_BEATING_ANIMATION
         ), targetViewParams
     )
 }

 /**
  * This function creates the BubbleMessageView depending the position of the target and the desired arrow position. This new view is also set on the layout passed by param
  */
 private fun addBubbleMessageViewDependingOnTargetView(
     targetView: View?,
     bubbleMessageViewBuilder: BubbleMessageView.Builder,
     backgroundDimLayout: RelativeLayout?
 ) {
     if (targetView == null) return
     val showCaseParams = RelativeLayout.LayoutParams(
         RelativeLayout.LayoutParams.MATCH_PARENT,
         RelativeLayout.LayoutParams.WRAP_CONTENT
     )
     if (bubbleMessageViewBuilder.mArrowPosition.size > 0)
         when (bubbleMessageViewBuilder.mArrowPosition[0]) {
             ArrowPosition.LEFT -> {
                 showCaseParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT)
                 if (ScreenUtils.isViewLocatedAtHalfTopOfTheScreen(
                         mActivity.get()!!,
                         targetView
                     )
                 ) {
                     showCaseParams.setMargins(
                         getXposition(targetView) + targetView.width,
                         getYposition(targetView),
                         if (isTablet()) getScreenWidth(mActivity.get()!!) - (getXposition(
                             targetView
                         ) + targetView.width) - getMessageViewWidthOnTablet(
                             getScreenWidth(mActivity.get()!!) - (getXposition(targetView) + targetView.width)
                         ) else 0,
                         0
                     )
                     showCaseParams.addRule(RelativeLayout.ALIGN_PARENT_TOP)
                 } else {
                     showCaseParams.setMargins(
                         getXposition(targetView) + targetView.width,
                         0,
                         if (isTablet()) getScreenWidth(mActivity.get()!!) - (getXposition(
                             targetView
                         ) + targetView.width) - getMessageViewWidthOnTablet(
                             getScreenWidth(mActivity.get()!!) - (getXposition(targetView) + targetView.width)
                         ) else 0,
                         getScreenHeight(mActivity.get()!!) - getYposition(targetView) - targetView.height
                     )
                     showCaseParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM)
                 }
             }
             ArrowPosition.RIGHT -> {
                 showCaseParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT)
                 if (ScreenUtils.isViewLocatedAtHalfTopOfTheScreen(
                         mActivity.get()!!,
                         targetView
                     )
                 ) {
                     showCaseParams.setMargins(
                         if (isTablet()) getXposition(targetView) - getMessageViewWidthOnTablet(
                             getXposition(targetView)
                         ) else 0,
                         getYposition(targetView),
                         getScreenWidth(mActivity.get()!!) - getXposition(targetView),
                         0
                     )
                     showCaseParams.addRule(RelativeLayout.ALIGN_PARENT_TOP)
                 } else {
                     showCaseParams.setMargins(
                         if (isTablet()) getXposition(targetView) - getMessageViewWidthOnTablet(
                             getXposition(targetView)
                         ) else 0,
                         0,
                         getScreenWidth(mActivity.get()!!) - getXposition(targetView),
                         getScreenHeight(mActivity.get()!!) - getYposition(targetView) - targetView.height
                     )
                     showCaseParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM)
                 }
             }
             ArrowPosition.TOP -> {
                 showCaseParams.addRule(RelativeLayout.ALIGN_PARENT_TOP)
                 if (ScreenUtils.isViewLocatedAtHalfLeftOfTheScreen(
                         mActivity.get()!!,
                         targetView
                     )
                 ) {
                     showCaseParams.setMargins(
                         if (isTablet()) getXposition(targetView) else 0,
                         getYposition(targetView) + targetView.height,
                         if (isTablet()) getScreenWidth(mActivity.get()!!) - getXposition(
                             targetView
                         ) - getMessageViewWidthOnTablet(
                             getScreenWidth(mActivity.get()!!) - getXposition(targetView)
                         ) else 0,
                         0
                     )
                 } else {
                     showCaseParams.setMargins(
                         if (isTablet()) getXposition(targetView) + targetView.width - getMessageViewWidthOnTablet(
                             getXposition(targetView)
                         ) else 0,
                         getYposition(targetView) + targetView.height,
                         if (isTablet()) getScreenWidth(mActivity.get()!!) - getXposition(
                             targetView
                         ) - targetView.width else 0,
                         0
                     )
                 }
             }
             ArrowPosition.BOTTOM -> {
                 showCaseParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM)
                 if (ScreenUtils.isViewLocatedAtHalfLeftOfTheScreen(
                         mActivity.get()!!,
                         targetView
                     )
                 ) {
                     showCaseParams.setMargins(
                         if (isTablet()) getXposition(targetView) else 0,
                         0,
                         if (isTablet()) getScreenWidth(mActivity.get()!!) - getXposition(
                             targetView
                         ) - getMessageViewWidthOnTablet(
                             getScreenWidth(mActivity.get()!!) - getXposition(targetView)
                         ) else 0,
                         (getScreenHeight(mActivity.get()!!) - getYposition(targetView)) + 50//this value add to set margin on bottom side of arrow
                     )
                 } else {
                     showCaseParams.setMargins(
                         if (isTablet()) getXposition(targetView) + targetView.width - getMessageViewWidthOnTablet(
                             getXposition(targetView)
                         ) else 0,
                         0,
                         if (isTablet()) getScreenWidth(mActivity.get()!!) - getXposition(
                             targetView
                         ) - targetView.width else 0,
                         (getScreenHeight(mActivity.get()!!) - getYposition(targetView)) + 50//this value add to set margin on bottom side of arrow
                     )
                 }
             }
         }

     val bubbleMessageView = bubbleMessageViewBuilder.targetViewScreenLocation(
         RectF(
             getXposition(targetView).toFloat(),
             getYposition(targetView).toFloat(),
             getXposition(targetView).toFloat() + targetView.width,
             getYposition(targetView).toFloat() + targetView.height
         )
     )
         .build()

     bubbleMessageView.id = createViewId()
     val animation = AnimationUtils.getScaleAnimation(0, DURATION_SHOW_CASE_ANIMATION)
     backgroundDimLayout?.addView(
         AnimationUtils.setAnimationToView(
             bubbleMessageView,
             animation
         ), showCaseParams
     )
 }

 /**
  * This function creates a BubbleMessageView and it is set on the center of the layout passed by param
  */
 private fun addBubbleMessageViewOnScreenCenter(
     bubbleMessageViewBuilder: BubbleMessageView.Builder,
     backgroundDimLayout: RelativeLayout?
 ) {
     val showCaseParams = RelativeLayout.LayoutParams(
         RelativeLayout.LayoutParams.MATCH_PARENT,
         RelativeLayout.LayoutParams.WRAP_CONTENT
     )
     showCaseParams.addRule(RelativeLayout.CENTER_VERTICAL)
     val bubbleMessageView: BubbleMessageView = bubbleMessageViewBuilder.build()
     bubbleMessageView.id = createViewId()
     if (isTablet()) showCaseParams.setMargins(
         if (isTablet()) getScreenWidth(mActivity.get()!!) / 2 - ScreenUtils.dpToPx(
             MAX_WIDTH_MESSAGE_VIEW_TABLET
         ) / 2 else 0,
         0,
         if (isTablet()) getScreenWidth(mActivity.get()!!) / 2 - ScreenUtils.dpToPx(
             MAX_WIDTH_MESSAGE_VIEW_TABLET
         ) / 2 else 0,
         0
     )
     val animation = AnimationUtils.getScaleAnimation(0, DURATION_SHOW_CASE_ANIMATION)
     backgroundDimLayout?.addView(
         AnimationUtils.setAnimationToView(
             bubbleMessageView,
             animation
         ), showCaseParams
     )
 }

 private fun createViewId(): Int {
     return View.generateViewId()
 }

 private fun takeScreenshot(targetView: View, highlightMode: HighlightMode?): Bitmap? {
     if (highlightMode == null || highlightMode == HighlightMode.VIEW_LAYOUT)
         return takeScreenshotOfLayoutView(targetView)
     return takeScreenshotOfSurfaceView(targetView)
 }

 private fun takeScreenshotOfLayoutView(targetView: View): Bitmap? {
     if (targetView.width == 0 || targetView.height == 0) {
         return null
     }

     val rootView = getViewRoot(mActivity.get()!!)
     val currentScreenView = rootView.getChildAt(0)
     /*currentScreenView.buildDrawingCache()
     val bitmap: Bitmap
     bitmap = Bitmap.createBitmap(currentScreenView.drawingCache, getXposition(targetView), getYposition(targetView), targetView.width, targetView.height)
     currentScreenView.isDrawingCacheEnabled = false
     currentScreenView.destroyDrawingCache()*/

     val bitmapp = Bitmap.createBitmap(
         currentScreenView.width,
         currentScreenView.height,
         Bitmap.Config.ARGB_8888
     )
     val canvas = Canvas(bitmapp)
     val bgDrawable = currentScreenView.background
     if (bgDrawable != null) {
         bgDrawable.draw(canvas)
     } else {
         canvas.drawColor(Color.WHITE)
     }
     targetView.draw(canvas)


     val bitmappp = Bitmap.createBitmap(
         bitmapp,
         getXposition(targetView),
         getYposition(targetView),
         targetView.width,
         targetView.height
     )
     val canvass = Canvas(bitmappp)
     val bgDrawablee = targetView.background
     if (bgDrawablee != null) {
         bgDrawablee.draw(canvass)
     } else {
         canvass.drawColor(Color.WHITE)
     }
     targetView.draw(canvass)

     return bitmappp
 }

 private fun takeScreenshotOfSurfaceView(targetView: View): Bitmap? {
     if (targetView.width == 0 || targetView.height == 0) {
         return null
     }

     targetView.isDrawingCacheEnabled = true
     val bitmap: Bitmap = Bitmap.createBitmap(targetView.drawingCache)
     targetView.isDrawingCacheEnabled = false
     return bitmap
 }

 private fun isVisibleOnScreen(targetView: View?): Boolean {
     if (targetView != null) {
         if (getXposition(targetView) >= 0 && getYposition(targetView) >= 0) {
             return getXposition(targetView) != 0 || getYposition(targetView) != 0
         }
     }
     return false
 }

 private fun getXposition(targetView: View): Int {
     return ScreenUtils.getAxisXpositionOfViewOnScreen(targetView) - getScreenHorizontalOffset()
 }

 private fun getYposition(targetView: View): Int {
     return ScreenUtils.getAxisYpositionOfViewOnScreen(targetView) - getScreenVerticalOffset()
 }

 private fun getScreenHeight(context: Context): Int {
     return ScreenUtils.getScreenHeight(context) - getScreenVerticalOffset()
 }

 private fun getScreenWidth(context: Context): Int {
     return ScreenUtils.getScreenWidth(context) - getScreenHorizontalOffset()
 }

 private fun getScreenVerticalOffset(): Int {
     return if (backgroundDimLayout != null) ScreenUtils.getAxisYpositionOfViewOnScreen(
         backgroundDimLayout!!
     ) else 0
 }

 private fun getScreenHorizontalOffset(): Int {
     return if (backgroundDimLayout != null) ScreenUtils.getAxisXpositionOfViewOnScreen(
         backgroundDimLayout!!
     ) else 0
 }

 private fun getMessageViewWidthOnTablet(availableSpace: Int): Int {
     return if (availableSpace > ScreenUtils.dpToPx(MAX_WIDTH_MESSAGE_VIEW_TABLET)) ScreenUtils.dpToPx(
         MAX_WIDTH_MESSAGE_VIEW_TABLET
     ) else availableSpace
 }

 private fun isTablet(): Boolean = mActivity.get()!!.resources.getBoolean(R.bool.isTablet)


}`

FitApps7 avatar Aug 27 '20 12:08 FitApps7