An Android library that adds floating bubbles to your home screen π¨, supports both XML and π Jetpack Compose
Β
- If you are looking for a Flutter version of this library, check dash_bubble, a Flutter plugin that allows you to create a floating bubble on the screen. by Moaz El-sawaf.
Ensure your appβs minimum SDK version is 21+ and `mavenCentral()` included
1. Ensure your appβs minimum SDK version is 21+. This is declared in the module-level `build.gradle` file
android {
defaultConfig {
...
minSdk 21
}
-
Ensure the
mavenCentral()
repository is declared in the project-levelbuild.gradle
orsetting.gradle
file:build.gradle (project-level)
allprojects { repositories { mavenCentral() ... } ... }
settings.gradle (alternative step If "allprojects" not found)
pluginManagement { repositories { ... mavenCentral() } } dependencyResolutionManagement { ... repositories { ... mavenCentral() } }
Declare the dependencies in the module-level build.gradle
file π
dependencies {
implementation "io.github.torrydo:floating-bubble-view:<LATEST_VERSION>"
}
Java version
public class MyService extends FloatingBubbleService {
@NonNull
@Override
public FloatingBubble.Builder setupBubble(@NonNull FloatingBubble.Action action) {
return ...;
}
// optional, only required if you want to navigate to the expandable-view
@Nullable
@Override
public ExpandableView.Builder setupExpandableView(@NonNull ExpandableView.Action action) {
return ...;
}
}
Kotlin version
class MyService: FloatingBubbleService() {
override fun setupBubble(action: FloatingBubble.Action): FloatingBubble.Builder {
return ...
}
// optional, only required if you want to navigate to the expandable-view
override fun setupExpandableView(action: ExpandableView.Action): ExpandableView.Builder? {
return ...
}
}
<!-- these two permissions are added by default -->
<!-- <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> -->
<!-- <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> -->
<application>
...
<service android:name="<YOUR_PACKAGE>.MyService" />
</application>
Make sure "display over other apps" permission is granted, otherwise the app will crash β βπ₯
Java version
Intent intent = new Intent(context, MyService.class);
ContextCompat.startForegroundService(this, intent);
// or
// startService(intent); // for android version lower than 8.0 (android O)
// startForegroundService(intent); // for android 8.0 and higher
Kotlin version
val intent = Intent(context, MyService::class.java)
ContextCompat.startForegroundService(this, intent)
// or
// startService(intent) // for android version lower than 8.0 (android O)
// startForegroundService(intent) // for android 8.0 and higher
Java
public class MyService extends FloatingBubbleService {
@NonNull
@Override
public FloatingBubble.Builder setupBubble(@NonNull FloatingBubble.Action action) {
// You can create your own view manually, or using this helper class that I specially designed for you π
View v = ViewHelper.fromDrawable(this, R.drawable.ic_rounded_blue_diamond, 60, 60);
v.setOnClickListener{ // sorry I don't remember the syntax :(
action.navigateToExpandableView();
}
return new FloatingBubble.Builder(this)
// pass view
.bubble(v)
// set style for bubble, fade animation by default
.bubbleStyle(null)
// 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)
// set close-bubble icon attributes, currently only drawable and bitmap are supported
.closeBubble(R.drawable.ic_close_bubble, 60, 60)
// set style for close-bubble, null by default
.closeBubbleStyle(null)
// show close-bubble, true by default
.enableCloseBubble(true)
// 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
// FIXED_CLOSE_BUBBLE: bubble will automatically move to the close-bubble when it reaches the closable-area
.behavior(BubbleBehavior.DYNAMIC_CLOSE_BUBBLE)
// enable bottom background, false by default
.bottomBackground(false)
// add listener for the bubble
.addFloatingBubbleListener(new FloatingBubble.Listener() {
@Override
public void onMove(float x, float y) {} // The location of the finger on the screen which triggers the movement of the bubble.
@Override
public void onUp(float x, float y) {} // ..., when finger release from bubble
@Override
public void onDown(float x, float y) {} // ..., when finger tap the bubble
})
// set bubble's opacity
.opacity(1f);
}
@Nullable
@Override
public ExpandableView.Builder setupExpandableView(@NonNull ExpandableView.Action action) {
LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
View layout = inflater.inflate(R.layout.layout_view_test, null);
layout.findViewById(R.id.card_view).setOnClickListener(v -> {
Toast.makeText(this, "hello from card view from java", Toast.LENGTH_SHORT).show();
action.popToBubble();
});
return new ExpandableView.Builder(this)
// set view to expandable-view. Jetpack Compose is not available in Java
.view(layout)
// set the amount of dimming below the view.
.dimAmount(0.8f)
// apply style for the expandable-view
.expandableViewStyle(null)
// add listener for the expandable-view
.addExpandableViewListener(new ExpandableView.Listener() {
@Override
public void onOpenExpandableView() {}
@Override
public void onCloseExpandableView() {}
});
}
}
Kotlin
class MyService : FloatingBubbleService() {
override fun setupBubble(action: FloatingBubble.Action): FloatingBubble.Builder {
// You can create your own view manually, or using this helper class that I specially designed for you π
val v = ViewHelper.fromDrawable(this, R.drawable.ic_rounded_blue_diamond, 60, 60);
v.setOnClickListener{
action.navigateToExpandableView()
}
return FloatingBubble.Builder(this)
// pass view
.bubble(v)
// or our sweetie, Jetpack Compose
.bubble{
BubbleCompose()
}
// set style for the bubble, fade animation by default
.bubbleStyle(null)
// 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)
// set close-bubble icon attributes, currently only drawable and bitmap are supported
.closeBubble(R.drawable.ic_close_bubble, 60, 60)
// set style for close-bubble, null by default
.closeBubbleStyle(null)
// show close-bubble, true by default
.enableCloseBubble(true)
// the more value (dp), the larger closeable-area
.distanceToClose(100)
// choose behavior of the bubbles
// DYNAMIC_CLOSE_BUBBLE: close-bubble moving based on the bubble's location
// FIXED_CLOSE_BUBBLE: bubble will automatically move to the close-bubble when it reaches the closable-area
.behavior(BubbleBehavior.DYNAMIC_CLOSE_BUBBLE)
// enable bottom background, false by default
.bottomBackground(false)
// add listener for the bubble
.addFloatingBubbleListener(object : FloatingBubble.Listener {
override fun onMove(x: Float, y: Float) {} // 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
})
// set bubble's opacity
.opacity(1f)
}
override fun setupExpandableView(action: ExpandableView.Action): ExpandableView.Builder? {
val inflater = getSystemService(LAYOUT_INFLATER_SERVICE) as LayoutInflater
val layout = inflater.inflate(R.layout.layout_view_test, null)
layout.findViewById<View>(R.id.card_view).setOnClickListener { v: View? ->
Toast.makeText(this, "hello from kotlin", Toast.LENGTH_SHORT).show();
action.popToBubble()
}
return ExpandableView.Builder(this)
// You should not pass both view and compose. Passing both will cause crashing. βπ₯
// set view to expandable-view
.view(layout)
// You should not pass both view and compose. Passing both will cause crashing. βπ₯
// set composable to expandable-view
.compose {
TestComposeView(
popBack = {
action.popToBubble()
}
)
}
// set the amount of dimming below the view.
.dimAmount(0.8f)
// apply style for the expandable-view
.expandableViewStyle(null)
// ddd listener for the expandable-view
.addExpandableViewListener(object : ExpandableView.Listener {
override fun onOpenExpandableView() {}
override fun onCloseExpandableView() {}
})
}
}
Java
public class MyService extends FloatingBubbleService {
...
// config the initial notification for Bubble on Android 8 and up.
// return null if you want to show the notification later.
@Override
public Notification initialNotification() {
return new NotificationCompat.Builder(this, channelId())
.setOngoing(true)
.setSmallIcon(R.drawable.ic_rounded_blue_diamond)
.setContentTitle("bubble is running")
.setContentText("click to do nothing")
.setPriority(NotificationCompat.PRIORITY_MIN)
.setCategory(Notification.CATEGORY_SERVICE)
.build();
}
@NonNull
@Override
public String channelId() {
return "you_channel_id";
}
@NonNull
@Override
public String channelName() {
return "your channel name";
}
@Override
public int notificationId() {
return 69;
}
...
}
Kotlin
class MyService : FloatingBubbleService() {
// config the initial notification for Bubble on Android 8 and up.
// return null if you want to show the notification later.
open fun initialNotification(): Notification? {
return NotificationCompat.Builder(this, channelId())
.setOngoing(true)
.setSmallIcon(R.drawable.ic_rounded_blue_diamond)
.setContentTitle("bubble is running")
.setPriority(PRIORITY_MIN)
.setCategory(Notification.CATEGORY_SERVICE)
.setSilent(true)
.build()
}
override fun channelId() = "your_channel_id"
override fun channelName() = "your_channel_name"
override fun notificationId() = 69
}
Notice since Android 13 β
Starting in Android 13 (API level 33), notifications are only visible if the "POST_NOTIFICATIONS" permission is granted.
The service will run normally even if the notification is not visible. π
P/s: You still need to initialize the notification before showing any view.
Boolean b = FloatingBubbleService.isRunning();
public class MyService extends FloatingBubbleService {
...
@NonNull
@Override
public Route initialRoute() { // "Route.Bubble" by default
return Route.Empty;
// Route.Empty: create the service without any view
// Route.Bubble: create the service with the bubble
// Route.ExpandableView: create the service with the expandable-view
}
...
}
XML version
override fun setupExpandableView(action: ExpandableView.Action): ExpandableView.Builder? {
...
val wrapper: ViewGroup = object : FrameLayout(this) {
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
if (event.keyCode == KeyEvent.KEYCODE_BACK) {
action.popToBubble()
return true
}
return super.dispatchKeyEvent(event)
}
}
val layout = inflater.inflate(R.layout.layout_view_test, wrapper) // pass "wrapper" here
...
}
Jetpack Compose version
@Composable
fun SomeComposable(){
...
OverrideDispatchKeyEvent { event ->
if (event?.keyCode == KeyEvent.KEYCODE_BACK) {
// your code here
}
null
}
}
Name | Description |
---|---|
currentRoute() |
Returns the current route of the service (Empty, Bubble, ExpandableView) |
showBubbles() |
Displays the the bubbles |
removeBubbles() |
Removes the floating bubble |
showExpandableView() |
Displays the expandable-view |
removeExpandableView() |
Removes the expandable-view |
removeAllViews() |
Removes all views |
notify(Notification) |
Displays or updates notification |
Contributions are welcome! π
- If you come across a bug or have an idea for a new feature, please let us know by creating an Issue ππ‘
- If you've already fixed a bug or implemented a feature, feel free to submit a Pull request π
- Having questions, ideas, or feedback? Don't worry, I gotchu. Simply open a Discussion π
- Find this project useful? Don't forget to show some love by giving a star β
Thank you! π
This library is still under heavy development. There is still a lot of code cleanup to do, so expect breaking API changes over time.
Please refer to the following page to check out the change-log: Releases
Everything's gonna be ok! π
Copyright 2022 TorryDo
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.