diff --git a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/Extensions.kt b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/Extensions.kt index 2e5d1eb..55501d6 100644 --- a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/Extensions.kt +++ b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/Extensions.kt @@ -27,8 +27,8 @@ internal fun View.getXYPointOnScreen(): Point { return Point(arr[0], arr[1]) } -internal val Int.toDp: Int get() = (this / getSystem().displayMetrics.density).toInt() -internal val Int.toPx: Int get() = (this * getSystem().displayMetrics.density).toInt() +internal fun Int.toDp(): Int = (this / getSystem().displayMetrics.density).toInt() +internal fun Int.toPx(): Int = (this * getSystem().displayMetrics.density).toInt() inline fun View.afterMeasured(crossinline afterMeasuredWork: () -> Unit) { viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { diff --git a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubble.kt b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubble.kt index 09f1619..881e46f 100644 --- a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubble.kt +++ b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubble.kt @@ -5,7 +5,6 @@ import android.graphics.Bitmap import android.graphics.Point import android.util.Size import android.view.View -import androidx.annotation.Discouraged import androidx.annotation.DrawableRes import androidx.annotation.StyleRes import androidx.core.content.ContextCompat @@ -90,8 +89,8 @@ internal constructor( } internal fun tryShowCloseBubbleAndBackground() { - bottomBackground?.show() closeBubbleView?.show() + bottomBackground?.show() } internal fun tryRemoveCloseBubbleAndBackground() { @@ -201,7 +200,6 @@ internal constructor( // config internal var startPoint = Point(0, 0) - internal var elevation = 0 internal var opacity = 1f internal var isCloseBubbleEnabled = true internal var isAnimateToEdgeEnabled = true @@ -228,7 +226,7 @@ internal constructor( * * @param dp distance between bubble and close-bubble * */ - fun closablePerimeter(dp: Int): Builder { + fun distanceToClose(dp: Int): Builder { this.closablePerimeterDp = dp return this } @@ -242,7 +240,7 @@ internal constructor( } /** - * @param enabled animate bubble to the left/right side of the screen + * @param enabled animate the bubble to the left/right side of the screen when finger is released, true by default * */ fun enableAnimateToEdge(enabled: Boolean): Builder { isAnimateToEdgeEnabled = enabled @@ -275,7 +273,7 @@ internal constructor( * set drawable to bubble width given width and height in dp * */ fun bubble(@DrawableRes drawable: Int, widthDp: Int, heightDp: Int): Builder { - bubbleSizePx = Size(widthDp.toPx, heightDp.toPx) + bubbleSizePx = Size(widthDp.toPx(), heightDp.toPx()) return bubble(drawable) } @@ -291,7 +289,7 @@ internal constructor( * set bitmap to bubble width given width and height in dp * */ fun bubble(bitmap: Bitmap, widthDp: Int, heightDp: Int): Builder { - bubbleSizePx = Size(widthDp.toPx, heightDp.toPx) + bubbleSizePx = Size(widthDp.toPx(), heightDp.toPx()) return bubble(bitmap) } @@ -321,7 +319,7 @@ internal constructor( * set drawable to close-bubble with given width and height in dp * */ fun closeBubble(@DrawableRes drawable: Int, widthDp: Int, heightDp: Int): Builder { - this.closeBubbleSizePx = Size(widthDp.toPx, heightDp.toPx) + this.closeBubbleSizePx = Size(widthDp.toPx(), heightDp.toPx()) return closeBubble(drawable) } @@ -380,29 +378,37 @@ internal constructor( } /** - * examples: x=0, y=0 show bubble on the top-left corner of the screen. + * examples: x=0, y=0 show the bubble on the top-left corner of the screen. * - * you can set x/y as negative value, but the bubble will be outside the screen. + * you can set x/y as a negative values, but the bubble will be outside the screen. * - * @param x 0 ... screenWidth (px). - * @param y 0 ... screenHeight (px). + * @param x 0 ... screenWidth (dp). + * @param y 0 ... screenHeight (dp). * */ fun startLocation(x: Int, y: Int): Builder { - startPoint.x = x - startPoint.y = y + startPoint.x = x.toPx() + startPoint.y = y.toPx() return this } - // not exposed to the outside packages because of being developed - internal fun elevation(dp: Int): Builder { - elevation = dp + /** + * examples: x=0, y=0 show the bubble on the top-left corner of the screen. + * + * you can set x/y as negative values, but the bubble will be outside the screen. + * + * @param x 0 ... screenWidth (px). + * @param y 0 ... screenHeight (px). + * */ + fun startLocationPx(x: Int, y: Int): Builder { + startPoint.x = x + startPoint.y = y return this } /** * - 0.0f: invisible * - 0.0f < x < 1.0f: view with opacity - * - 1.0f: completely visible + * - 1.0f: fully visible * */ fun opacity(opacity: Float): Builder { this.opacity = opacity diff --git a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleService.kt b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleService.kt index 45dace1..eb52dac 100644 --- a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleService.kt +++ b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleService.kt @@ -8,19 +8,12 @@ import android.content.Intent import android.graphics.Color import android.os.Build import android.os.IBinder -import android.util.Log import androidx.annotation.ChecksSdkIntAtLeast import androidx.annotation.Discouraged import androidx.annotation.RequiresApi import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat.PRIORITY_MIN import androidx.core.app.NotificationManagerCompat -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.launch abstract class FloatingBubbleService : Service() { @@ -61,14 +54,14 @@ abstract class FloatingBubbleService : Service() { when (currentRoute) { Route.Empty -> { - initViewsAndNotification() + startWithNotification() } Route.Bubble -> { - initViewsAndNotification() + startWithNotification() showBubbles() } Route.ExpandableView -> { - initViewsAndNotification() + startWithNotification() showExpandableView() } } @@ -84,29 +77,18 @@ abstract class FloatingBubbleService : Service() { * init view instances and notification * */ @Throws(PermissionDeniedException::class) - private fun initViewsAndNotification() { + private fun startWithNotification() { if (!isDrawOverlaysPermissionGranted()) { throw PermissionDeniedException() } - initViewInstances() - if (isHigherThanAndroid8()) { showForegroundNotification() } } - private fun initViewInstances() { - floatingBubble = setupBubble(customFloatingBubbleAction) - .addServiceInteractor(customFloatingBubbleServiceInteractor) - .build() - - expandableView = setupExpandableView(customExpandableViewListener) - ?.build() - } - // region Public Methods ----------------------------------------------------------------------- /** @@ -115,6 +97,11 @@ abstract class FloatingBubbleService : Service() { fun currentRoute() = currentRoute fun showBubbles() { + if (floatingBubble == null) { + floatingBubble = setupBubble(customFloatingBubbleAction) + .addServiceInteractor(customFloatingBubbleServiceInteractor) + .build() + } floatingBubble!!.showIcon() currentRoute = Route.Bubble } @@ -133,13 +120,19 @@ abstract class FloatingBubbleService : Service() { @Throws(NotImplementedError::class) fun showExpandableView(): Boolean { if (expandableView == null) { - throw NotImplementedError("you DID NOT override expandable view") + + expandableView = setupExpandableView(customExpandableViewListener) + ?.build() + + if (expandableView == null) { + throw NotImplementedError("you DID NOT override expandable view") + } } try { expandableView!!.show() currentRoute = Route.ExpandableView } catch (e: Exception) { - Log.e("<>", "showExpandableView: ", e) +// Log.e("<>", "showExpandableView: ", e) return false } @@ -151,16 +144,10 @@ abstract class FloatingBubbleService : Service() { } /** - * remove all views or init notification if first-time call + * remove all views * */ fun removeAllViews() { - if (isNotificationInitialized.not()) { - initViewsAndNotification() - isNotificationInitialized = true - return - } - removeExpandableView() removeBubbles() diff --git a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleView.kt b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleView.kt index b43f27c..66de034 100644 --- a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleView.kt +++ b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleView.kt @@ -3,12 +3,8 @@ package com.torrydo.floatingbubbleview import android.annotation.SuppressLint import android.graphics.Point import android.graphics.PointF -import android.util.Log -import android.view.GestureDetector +import android.view.* import android.view.GestureDetector.SimpleOnGestureListener -import android.view.LayoutInflater -import android.view.MotionEvent -import android.view.WindowManager import com.torrydo.floatingbubbleview.databinding.BubbleBinding internal class FloatingBubbleView( @@ -41,7 +37,7 @@ internal class FloatingBubbleView( } halfIconWidthPx = width / 2 - halfIconHeightPx = height /2 + halfIconHeightPx = height / 2 setupLayoutParams() setupBubbleProperties() @@ -181,7 +177,7 @@ internal class FloatingBubbleView( builder.listener?.onDown(motionEvent.rawX, motionEvent.rawY) } - fun onActionMove(motionEvent: MotionEvent){ + fun onActionMove(motionEvent: MotionEvent) { builder.listener?.onMove(motionEvent.rawX, motionEvent.rawY) } @@ -191,7 +187,19 @@ internal class FloatingBubbleView( // listen actions -------------------------------------------------------------------------- - val gestureDetector = GestureDetector(builder.context, SingleTapConfirm()) + val gestureDetector = GestureDetector(builder.context, object : SimpleOnGestureListener() { + +// override fun onSingleTapConfirmed(e: MotionEvent): Boolean { +// builder.listener?.onClick() +// return super.onSingleTapConfirmed(e) +// } + + override fun onSingleTapUp(e: MotionEvent): Boolean { + builder.listener?.onClick() + return super.onSingleTapUp(e) + } + + }) binding.bubbleView.apply { @@ -199,11 +207,7 @@ internal class FloatingBubbleView( setOnTouchListener { _, motionEvent -> - // detect onTouch event first. If event is consumed, return@setOnTouch... - if (gestureDetector.onTouchEvent(motionEvent)) { - builder.listener?.onClick() - return@setOnTouchListener true - } + gestureDetector.onTouchEvent(motionEvent) when (motionEvent.action) { MotionEvent.ACTION_DOWN -> onActionDown(motionEvent) @@ -213,12 +217,7 @@ internal class FloatingBubbleView( return@setOnTouchListener true } - } - } - private class SingleTapConfirm : SimpleOnGestureListener() { - override fun onSingleTapUp(event: MotionEvent): Boolean { - return true } } diff --git a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingCloseBubbleView.kt b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingCloseBubbleView.kt index a07f9fa..204492e 100644 --- a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingCloseBubbleView.kt +++ b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingCloseBubbleView.kt @@ -56,7 +56,7 @@ internal class FloatingCloseBubbleView( centerCloseBubbleX = baseX + halfWidthPx centerCloseBubbleY = baseY + halfHeightPx - closablePerimeterPx = builder.closablePerimeterDp.toPx + closablePerimeterPx = builder.closablePerimeterDp.toPx() setupLayoutParams() setupCloseBubbleProperties() @@ -121,7 +121,7 @@ internal class FloatingCloseBubbleView( return distanceRatio } - fun distanceRatioFromLocationToClosableArea(x: Float, y: Float): Float{ + fun distanceRatioFromLocationToClosableArea(x: Float, y: Float): Float { val distanceToLocation = MathHelper.distance( x1 = centerCloseBubbleX.toDouble(), y1 = centerCloseBubbleY.toDouble(), @@ -136,6 +136,7 @@ internal class FloatingCloseBubbleView( return distanceRatio } + fun animateCloseIconByBubble(x: Int, y: Int) { val distanceRatio = distanceRatioFromBubbleToClosableArea(x, y) diff --git a/README.md b/README.md index 25e06ce..96efa1f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # πŸ€Floating Bubble View -An Android library that adds floating bubbles to your home screen 🎨, supporting both XML and πŸ’˜ Jetpack Compose +An Android library that adds floating bubbles to your home screen 🎨, supports both XML and πŸ’˜ Jetpack Compose   @@ -158,7 +158,7 @@ Declare the dependencies in the module-level `build.gradle` file πŸ€ Make sure "display over other apps" permission is granted, otherwise the app will crash βš β—πŸ’₯
Java version @@ -209,8 +209,9 @@ public class MyService extends FloatingBubbleService { // set style for bubble, fade animation by default .bubbleStyle(null) - // set start location of bubble, (x=0, y=0) is the top-left - .startLocation(0, 0) + // set start location for the bubble, (x=0, y=0) is the top-left + .startLocation(100, 100) // in dp + .startLocationPx(100, 100) // in px // enable auto animate bubble to the left/right side when release, true by default .enableAnimateToEdge(true) @@ -224,8 +225,8 @@ public class MyService extends FloatingBubbleService { // show close-bubble, true by default .enableCloseBubble(true) - // the more value (dp), the larger closeable-area - .closablePerimeter(100) + // the more value (dp), the larger closable-area + .distanceToClose(100) // choose behavior of the bubbles // DYNAMIC_CLOSE_BUBBLE: close-bubble moving based on the bubble's location @@ -307,8 +308,9 @@ class MyService : FloatingBubbleService() { // set style for bubble, fade animation by default .bubbleStyle(null) - // set start location of bubble, (x=0, y=0) is the top-left - .startLocation(0, 0) + // set start location for the bubble, (x=0, y=0) is the top-left + .startLocation(100, 100) // in dp + .startLocationPx(100, 100) // in px // enable auto animate bubble to the left/right side when release, true by default .enableAnimateToEdge(true) @@ -323,7 +325,7 @@ class MyService : FloatingBubbleService() { .enableCloseBubble(true) // the more value (dp), the larger closeable-area - .closablePerimeter(100) + .distanceToClose(100) // choose behavior of the bubbles // DYNAMIC_CLOSE_BUBBLE: close-bubble moving based on the bubble's location @@ -353,7 +355,7 @@ class MyService : FloatingBubbleService() { val layout = inflater.inflate(R.layout.layout_view_test, null) layout.findViewById(R.id.card_view).setOnClickListener { v: View? -> - Toast.makeText(this, "hello from card view from kotlin", Toast.LENGTH_SHORT).show(); + Toast.makeText(this, "hello from kotlin", Toast.LENGTH_SHORT).show(); action.popToBubble() } diff --git a/app/src/main/java/com/torrydo/testfloatingbubble/MainActivityKt.kt b/app/src/main/java/com/torrydo/testfloatingbubble/MainActivityKt.kt index 5e1e9d4..656ea71 100644 --- a/app/src/main/java/com/torrydo/testfloatingbubble/MainActivityKt.kt +++ b/app/src/main/java/com/torrydo/testfloatingbubble/MainActivityKt.kt @@ -27,6 +27,7 @@ class MainActivityKt : AppCompatActivity() { stopMyService() } else { val intent = Intent(this, MyServiceKt::class.java) + intent.putExtra("size", 60) ContextCompat.startForegroundService(this, intent) isVisible = false } diff --git a/app/src/main/java/com/torrydo/testfloatingbubble/MyServiceKt.kt b/app/src/main/java/com/torrydo/testfloatingbubble/MyServiceKt.kt index 4193f6f..4b2aa34 100644 --- a/app/src/main/java/com/torrydo/testfloatingbubble/MyServiceKt.kt +++ b/app/src/main/java/com/torrydo/testfloatingbubble/MyServiceKt.kt @@ -3,6 +3,7 @@ package com.torrydo.testfloatingbubble import android.app.Notification import android.app.PendingIntent import android.content.Intent +import android.util.Log import android.view.KeyEvent import android.view.LayoutInflater import android.view.View @@ -29,27 +30,31 @@ class MyServiceKt : FloatingBubbleService() { } - } override fun initialRoute(): Route { - return Route.Bubble + return Route.Empty } + private var size = 0 + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - val route = intent?.getStringExtra("route") ?: return START_STICKY + val route = intent?.getStringExtra("route") + + val _size = intent?.getIntExtra("size", 0) + + size = _size ?: 0 - when(route){ + showBubbles() + + when (route) { Route.Bubble.name -> { showBubbles() } Route.ExpandableView.name -> { showExpandableView() } - Route.Empty.name -> { - removeAllViews() - } } return START_STICKY } @@ -113,19 +118,20 @@ class MyServiceKt : FloatingBubbleService() { return FloatingBubble.Builder(this) // set bubble icon attributes, currently only drawable and bitmap are supported - .bubble(R.drawable.ic_rounded_blue_diamond, 60, 60) + .bubble(R.drawable.ic_rounded_blue_diamond, size, size) // set style for bubble, fade animation by default .bubbleStyle(null) - // set start location of bubble, (x=0, y=0) is the top-left - .startLocation(0, 0) + // set start location for the bubble, (x=0, y=0) is the top-left + .startLocation(100, 100) // in dp +// .startLocationPx(0, 0) // in px - // enable auto animate bubble to the left/right side when release, true by default + // animate the bubble to the left/right side of the screen when finger is released, true by default .enableAnimateToEdge(true) // set close-bubble icon attributes, currently only drawable and bitmap are supported - .closeBubble(R.drawable.ic_close_bubble, 60, 60) + .closeBubble(R.drawable.ic_close_bubble, size, size) // set style for close-bubble, null by default .closeBubbleStyle(null) @@ -134,7 +140,7 @@ class MyServiceKt : FloatingBubbleService() { .enableCloseBubble(true) // the more value (dp), the larger closeable-area - .closablePerimeter(100) + .distanceToClose(100) // choose behavior of the bubbles // DYNAMIC_CLOSE_BUBBLE: close-bubble moving based on the bubble's location @@ -156,8 +162,13 @@ class MyServiceKt : FloatingBubbleService() { ) { } // The location of the finger on the screen which triggers the movement of the bubble. - override fun onUp(x: Float, y: Float) {} // ..., when finger release from bubble - override fun onDown(x: Float, y: Float) {} // ..., when finger tap the bubble + override fun onUp(x: Float, y: Float) { + Log.d("<>", "onup: ${x} - ${y}"); + } // ..., when finger release from bubble + + override fun onDown(x: Float, y: Float) { + Log.d("<>", "ondown ${x}-${y}: "); + } // ..., when finger tap the bubble }) // set bubble's opacity .opacity(1f) diff --git a/app/src/main/java/com/torrydo/testfloatingbubble/java_sample/MyService.java b/app/src/main/java/com/torrydo/testfloatingbubble/java_sample/MyService.java index dcee1b0..47d8ffe 100644 --- a/app/src/main/java/com/torrydo/testfloatingbubble/java_sample/MyService.java +++ b/app/src/main/java/com/torrydo/testfloatingbubble/java_sample/MyService.java @@ -10,7 +10,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.app.NotificationCompat; -import androidx.core.content.ContextCompat; import com.torrydo.floatingbubbleview.BubbleBehavior; import com.torrydo.floatingbubbleview.ExpandableView; @@ -117,7 +116,7 @@ public FloatingBubble.Builder setupBubble(@NonNull FloatingBubble.Action action) .enableCloseBubble(true) // the more value (dp), the larger closeable-area - .closablePerimeter(100) + .distanceToClose(100) // choose behavior of the bubbles // DYNAMIC_CLOSE_BUBBLE: close-bubble moving based on the bubble's location diff --git a/gradle.properties b/gradle.properties index 4d9641d..675fbfc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -27,8 +27,8 @@ RELEASE_SIGNING_ENABLED=true GROUP=io.github.torrydo POM_ARTIFACT_ID=floating-bubble-view -VERSION_NAME=0.5.1 -#prev: 0.5.0 +VERSION_NAME=0.5.2 +#prev: 0.5.1 POM_NAME=FloatingBubbleView POM_PACKAGING=aar