├── .gitignore ├── README.md ├── build.gradle ├── floating-bubble ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── gen │ └── com │ │ └── bsk │ │ └── floatingbubblelib │ │ ├── BuildConfig.java │ │ ├── Manifest.java │ │ └── R.java │ ├── java │ └── com │ │ └── bsk │ │ └── floatingbubblelib │ │ ├── DefaultFloatingBubbleTouchListener.java │ │ ├── FloatingBubbleAnimator.java │ │ ├── FloatingBubbleConfig.java │ │ ├── FloatingBubbleLogger.java │ │ ├── FloatingBubblePermissions.java │ │ ├── FloatingBubblePhysics.java │ │ ├── FloatingBubbleService.java │ │ ├── FloatingBubbleTouch.java │ │ └── FloatingBubbleTouchListener.java │ └── res │ ├── drawable │ ├── bubble_default_icon.png │ ├── close_default_icon.png │ └── triangle_icon.png │ ├── layout │ ├── floating_bubble_view.xml │ ├── floating_expandable_view.xml │ └── floating_remove_bubble_view.xml │ └── values │ └── strings.xml ├── sampleapp ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── gen │ └── com │ │ └── bsk │ │ └── sampleapp │ │ ├── BuildConfig.java │ │ ├── Manifest.java │ │ └── R.java │ ├── java │ └── com │ │ └── bsk │ │ └── sampleapp │ │ ├── MainActivity.java │ │ └── SimpleService.java │ └── res │ ├── drawable │ ├── close_default_icon.png │ ├── cover.jpg │ └── web_icon.png │ ├── layout │ ├── activity_main.xml │ └── sample_view_1.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── screenshots ├── demo.gif ├── screenshot_1.png └── screenshot_2.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .externalNativeBuild 2 | .gradle 3 | local.properties 4 | .idea/ 5 | .DS_Store 6 | build/ 7 | captures/ 8 | *.iml 9 | gradle.properties 10 | gradle/ 11 | gradlew 12 | gradlew.bat 13 | app/build/ 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Floating Bubble Library 2 | Simple library for adding a floating bubble in your application! 3 | 4 | ## Installation 5 | 6 | ```java 7 | compile 'com.github.bijoysingh.floating-bubble:3.0.0' 8 | 9 | // compile 'com.github.bijoysingh.floating-bubble:2.0.0' for Android Support v27 10 | // compile 'com.github.bijoysingh.floating-bubble:1.2.0' for Android Support v26 11 | ``` 12 | 13 | ## Demos 14 | ![Demo](screenshots/demo.gif) 15 | 16 | ## Usage 17 | 18 | ### Setup 19 | Let's start with a simple setup for the Service 20 | ```java 21 | public class FloatingService extends FloatingBubbleService { 22 | ... 23 | } 24 | ``` 25 | 26 | Adding your library in the manifest 27 | ```java 28 | 29 | ``` 30 | 31 | Start the service 32 | ```java 33 | startService(new Intent(context, FloatingService.class)); 34 | ``` 35 | 36 | ### Customising the Service 37 | ```java 38 | public class FloatingService extends FloatingBubbleService { 39 | 40 | @Override 41 | protected FloatingBubbleConfig getConfig() { 42 | return new FloatingBubbleConfig.Builder() 43 | // Set the drawable for the bubble 44 | .bubbleIcon(bubbleDrawable) 45 | 46 | // Set the drawable for the remove bubble 47 | .removeBubbleIcon(removeIconDrawable) 48 | 49 | // Set the size of the bubble in dp 50 | .bubbleIconDp(64) 51 | 52 | // Set the size of the remove bubble in dp 53 | .removeBubbleIconDp(64) 54 | 55 | // Set the padding of the view from the boundary 56 | .paddingDp(4) 57 | 58 | // Set the radius of the border of the expandable view 59 | .borderRadiusDp(4) 60 | 61 | // Does the bubble attract towards the walls 62 | .physicsEnabled(true) 63 | 64 | // The color of background of the layout 65 | .expandableColor(Color.WHITE) 66 | 67 | // The color of the triangular layout 68 | .triangleColor(Color.WHITE) 69 | 70 | // Horizontal gravity of the bubble when expanded 71 | .gravity(Gravity.END) 72 | 73 | // The view which is visible in the expanded view 74 | .expandableView(yourViewAfterClick) 75 | 76 | // Set the alpha value for the remove bubble icon 77 | .removeBubbleAlpha(0.75f) 78 | 79 | // Building 80 | .build(); 81 | } 82 | } 83 | ``` 84 | 85 | Override the onGetIntent function. It will return true if the intent is valid, else false 86 | ```java 87 | @Override 88 | protected boolean onGetIntent(@NonNull Intent intent) { 89 | // your logic to get information from the intent 90 | return true; 91 | } 92 | ``` 93 | 94 | You can change the state of the expanded view at runtime by 95 | ```java 96 | // To expand 97 | setState(true); 98 | 99 | // To compress 100 | setState(false); 101 | ``` 102 | 103 | ## License 104 | ```java 105 | Copyright 2016 Bijoy Singh Kochar 106 | 107 | Licensed under the Apache License, Version 2.0 (the "License"); 108 | you may not use this file except in compliance with the License. 109 | You may obtain a copy of the License at 110 | 111 | http://www.apache.org/licenses/LICENSE-2.0 112 | 113 | Unless required by applicable law or agreed to in writing, software 114 | distributed under the License is distributed on an "AS IS" BASIS, 115 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 116 | See the License for the specific language governing permissions and 117 | limitations under the License. 118 | ``` -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | google() 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.3.1' 10 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1' 11 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.4' 12 | 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | jcenter() 21 | google() 22 | } 23 | } 24 | 25 | task clean(type: Delete) { 26 | delete rootProject.buildDir 27 | } 28 | -------------------------------------------------------------------------------- /floating-bubble/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /floating-bubble/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.github.dcendents.android-maven' 3 | 4 | android { 5 | compileSdkVersion 28 6 | buildToolsVersion "28.0.3" 7 | 8 | defaultConfig { 9 | minSdkVersion 17 10 | targetSdkVersion 28 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | implementation fileTree(dir: 'libs', include: ['*.jar']) 24 | implementation 'com.android.support:support-core-utils:28.0.0' 25 | implementation 'com.android.support:support-v4:28.0.0' 26 | implementation 'com.android.support:cardview-v7:28.0.0' 27 | } 28 | 29 | ext { 30 | bintrayRepo = 'maven' 31 | bintrayName = 'floating-bubble' 32 | 33 | publishedGroupId = 'com.github.bijoysingh' 34 | libraryName = 'floating-bubble' 35 | artifact = 'floating-bubble' 36 | 37 | libraryDescription = 'Simple library to add Floating Bubble into your app!' 38 | 39 | siteUrl = 'https://github.com/BijoySingh/FloatingBubbleLib' 40 | gitUrl = 'https://github.com/BijoySingh/FloatingBubbleLib.git' 41 | 42 | libraryVersion = '3.0.0' 43 | 44 | developerId = 'bijoysingh693' 45 | developerName = 'Bijoy Singh Kochar' 46 | developerEmail = 'bijoysingh693@gmail.com' 47 | 48 | licenseName = 'The Apache Software License, Version 2.0' 49 | licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 50 | allLicenses = ["Apache-2.0"] 51 | } 52 | 53 | apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle' 54 | apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1.gradle' 55 | -------------------------------------------------------------------------------- /floating-bubble/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/bijoy/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /floating-bubble/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /floating-bubble/src/main/gen/com/bsk/floatingbubblelib/BuildConfig.java: -------------------------------------------------------------------------------- 1 | /*___Generated_by_IDEA___*/ 2 | 3 | package com.bsk.floatingbubblelib; 4 | 5 | /* This stub is only used by the IDE. It is NOT the BuildConfig class actually packed into the APK */ 6 | public final class BuildConfig { 7 | public final static boolean DEBUG = Boolean.parseBoolean(null); 8 | } -------------------------------------------------------------------------------- /floating-bubble/src/main/gen/com/bsk/floatingbubblelib/Manifest.java: -------------------------------------------------------------------------------- 1 | /*___Generated_by_IDEA___*/ 2 | 3 | package com.bsk.floatingbubblelib; 4 | 5 | /* This stub is only used by the IDE. It is NOT the Manifest class actually packed into the APK */ 6 | public final class Manifest { 7 | } -------------------------------------------------------------------------------- /floating-bubble/src/main/gen/com/bsk/floatingbubblelib/R.java: -------------------------------------------------------------------------------- 1 | /*___Generated_by_IDEA___*/ 2 | 3 | package com.bsk.floatingbubblelib; 4 | 5 | /* This stub is only used by the IDE. It is NOT the R class actually packed into the APK */ 6 | public final class R { 7 | } -------------------------------------------------------------------------------- /floating-bubble/src/main/java/com/bsk/floatingbubblelib/DefaultFloatingBubbleTouchListener.java: -------------------------------------------------------------------------------- 1 | package com.bsk.floatingbubblelib; 2 | 3 | /** 4 | * Created by bijoysingh on 2/19/17. 5 | */ 6 | 7 | public class DefaultFloatingBubbleTouchListener implements FloatingBubbleTouchListener { 8 | @Override 9 | public void onDown(float x, float y) { 10 | 11 | } 12 | 13 | @Override 14 | public void onTap(boolean expanded) { 15 | 16 | } 17 | 18 | @Override 19 | public void onRemove() { 20 | 21 | } 22 | 23 | @Override 24 | public void onMove(float x, float y) { 25 | 26 | } 27 | 28 | @Override 29 | public void onUp(float x, float y) { 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /floating-bubble/src/main/java/com/bsk/floatingbubblelib/FloatingBubbleAnimator.java: -------------------------------------------------------------------------------- 1 | package com.bsk.floatingbubblelib; 2 | 3 | import android.animation.ValueAnimator; 4 | import android.util.Log; 5 | import android.view.View; 6 | import android.view.WindowManager; 7 | 8 | /** 9 | * Animator 10 | * Created by bijoysingh on 2/19/17. 11 | */ 12 | 13 | public class FloatingBubbleAnimator { 14 | 15 | private static final int ANIMATION_TIME = 100; 16 | private static final int ANIMATION_STEPS = 5; 17 | 18 | private View bubbleView; 19 | private WindowManager.LayoutParams bubbleParams; 20 | private WindowManager windowManager; 21 | private int sizeX; 22 | private int sizeY; 23 | 24 | private FloatingBubbleAnimator(Builder builder) { 25 | bubbleView = builder.bubbleView; 26 | bubbleParams = builder.bubbleParams; 27 | windowManager = builder.windowManager; 28 | sizeX = builder.sizeX; 29 | sizeY = builder.sizeY; 30 | } 31 | 32 | public void animate( 33 | final float x, 34 | final float y) { 35 | final float startX = bubbleParams.x; 36 | final float startY = bubbleParams.y; 37 | ValueAnimator animator = ValueAnimator.ofInt(0, 5) 38 | .setDuration(ANIMATION_TIME); 39 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 40 | @Override 41 | public void onAnimationUpdate(ValueAnimator valueAnimator) { 42 | try { 43 | float currentX = startX + ((x - startX) * 44 | (Integer) valueAnimator.getAnimatedValue() / ANIMATION_STEPS); 45 | float currentY = startY + ((y - startY) * 46 | (Integer) valueAnimator.getAnimatedValue() / ANIMATION_STEPS); 47 | bubbleParams.x = (int) currentX; 48 | bubbleParams.x = bubbleParams.x < 0 ? 0 : bubbleParams.x; 49 | bubbleParams.x = bubbleParams.x > sizeX - bubbleView.getWidth() ? sizeX - bubbleView.getWidth() : bubbleParams.x; 50 | 51 | bubbleParams.y = (int) currentY; 52 | bubbleParams.y = bubbleParams.y < 0 ? 0 : bubbleParams.y; 53 | bubbleParams.y = bubbleParams.y > sizeY - bubbleView.getWidth() ? sizeY - bubbleView.getWidth() : bubbleParams.y; 54 | 55 | windowManager.updateViewLayout(bubbleView, bubbleParams); 56 | } catch (Exception exception) { 57 | Log.e(FloatingBubbleAnimator.class.getSimpleName(), exception.getMessage()); 58 | } 59 | } 60 | }); 61 | animator.start(); 62 | } 63 | 64 | public static final class Builder { 65 | private View bubbleView; 66 | private WindowManager.LayoutParams bubbleParams; 67 | private WindowManager windowManager; 68 | private int sizeX; 69 | private int sizeY; 70 | 71 | public Builder() { 72 | } 73 | 74 | public Builder bubbleView(View val) { 75 | bubbleView = val; 76 | return this; 77 | } 78 | 79 | public Builder bubbleParams(WindowManager.LayoutParams val) { 80 | bubbleParams = val; 81 | return this; 82 | } 83 | 84 | public Builder windowManager(WindowManager val) { 85 | windowManager = val; 86 | return this; 87 | } 88 | 89 | public Builder sizeX(int val) { 90 | sizeX = val; 91 | return this; 92 | } 93 | 94 | public Builder sizeY(int val) { 95 | sizeY = val; 96 | return this; 97 | } 98 | 99 | public FloatingBubbleAnimator build() { 100 | return new FloatingBubbleAnimator(this); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /floating-bubble/src/main/java/com/bsk/floatingbubblelib/FloatingBubbleConfig.java: -------------------------------------------------------------------------------- 1 | package com.bsk.floatingbubblelib; 2 | 3 | import android.content.Context; 4 | import android.graphics.Color; 5 | import android.graphics.drawable.Drawable; 6 | import android.view.Gravity; 7 | import android.view.View; 8 | import android.support.v4.content.ContextCompat; 9 | import android.widget.TextView; 10 | 11 | /** 12 | * Floating configurations 13 | * Created by bijoysingh on 2/19/17. 14 | */ 15 | 16 | public class FloatingBubbleConfig { 17 | private Drawable bubbleIcon; 18 | private Drawable removeBubbleIcon; 19 | private View expandableView; 20 | private int bubbleIconDp; 21 | private int removeBubbleIconDp; 22 | private float removeBubbleAlpha; 23 | private int expandableColor; 24 | private int triangleColor; 25 | private int gravity; 26 | private int paddingDp; 27 | private int borderRadiusDp; 28 | private boolean physicsEnabled; 29 | 30 | private FloatingBubbleConfig(Builder builder) { 31 | bubbleIcon = builder.bubbleIcon; 32 | removeBubbleIcon = builder.removeBubbleIcon; 33 | expandableView = builder.expandableView; 34 | bubbleIconDp = builder.bubbleIconDp; 35 | removeBubbleIconDp = builder.removeBubbleIconDp; 36 | expandableColor = builder.expandableColor; 37 | triangleColor = builder.triangleColor; 38 | gravity = builder.gravity; 39 | paddingDp = builder.paddingDp; 40 | borderRadiusDp = builder.borderRadiusDp; 41 | physicsEnabled = builder.physicsEnabled; 42 | removeBubbleAlpha = builder.removeBubbleAlpha; 43 | } 44 | 45 | public static Builder getDefaultBuilder(Context context) { 46 | return new Builder() 47 | .bubbleIcon(ContextCompat.getDrawable(context, R.drawable.bubble_default_icon)) 48 | .removeBubbleIcon(ContextCompat.getDrawable(context, R.drawable.close_default_icon)) 49 | .bubbleIconDp(64) 50 | .removeBubbleIconDp(64) 51 | .paddingDp(4) 52 | .removeBubbleAlpha(1.0f) 53 | .physicsEnabled(true) 54 | .expandableColor(Color.WHITE) 55 | .triangleColor(Color.WHITE) 56 | .gravity(Gravity.END); 57 | } 58 | 59 | public static FloatingBubbleConfig getDefault(Context context) { 60 | return getDefaultBuilder(context).build(); 61 | } 62 | 63 | public Drawable getBubbleIcon() { 64 | return bubbleIcon; 65 | } 66 | 67 | public Drawable getRemoveBubbleIcon() { 68 | return removeBubbleIcon; 69 | } 70 | 71 | public View getExpandableView() { 72 | return expandableView; 73 | } 74 | 75 | public int getBubbleIconDp() { 76 | return bubbleIconDp; 77 | } 78 | 79 | public int getRemoveBubbleIconDp() { 80 | return removeBubbleIconDp; 81 | } 82 | 83 | public int getExpandableColor() { 84 | return expandableColor; 85 | } 86 | 87 | public int getTriangleColor() { 88 | return triangleColor; 89 | } 90 | 91 | public int getGravity() { 92 | return gravity; 93 | } 94 | 95 | public int getPaddingDp() { 96 | return paddingDp; 97 | } 98 | 99 | public boolean isPhysicsEnabled() { 100 | return physicsEnabled; 101 | } 102 | 103 | public int getBorderRadiusDp() { 104 | return borderRadiusDp; 105 | } 106 | 107 | public float getRemoveBubbleAlpha() { 108 | return removeBubbleAlpha; 109 | } 110 | 111 | public static final class Builder { 112 | private Drawable bubbleIcon; 113 | private Drawable removeBubbleIcon; 114 | private View expandableView; 115 | private int bubbleIconDp = 64; 116 | private int removeBubbleIconDp = 64; 117 | private int expandableColor = Color.WHITE; 118 | private int triangleColor = Color.WHITE; 119 | private int gravity = Gravity.END; 120 | private int paddingDp = 4; 121 | private int borderRadiusDp = 4; 122 | private float removeBubbleAlpha = 1.0f; 123 | private boolean physicsEnabled = true; 124 | 125 | public Builder() { 126 | } 127 | 128 | public Builder bubbleIcon(Drawable val) { 129 | bubbleIcon = val; 130 | return this; 131 | } 132 | 133 | public Builder removeBubbleIcon(Drawable val) { 134 | removeBubbleIcon = val; 135 | return this; 136 | } 137 | 138 | public Builder expandableView(View val) { 139 | expandableView = val; 140 | return this; 141 | } 142 | 143 | public Builder bubbleIconDp(int val) { 144 | bubbleIconDp = val; 145 | return this; 146 | } 147 | 148 | public Builder removeBubbleIconDp(int val) { 149 | removeBubbleIconDp = val; 150 | return this; 151 | } 152 | 153 | public Builder triangleColor(int val) { 154 | triangleColor = val; 155 | return this; 156 | } 157 | 158 | public Builder expandableColor(int val) { 159 | expandableColor = val; 160 | return this; 161 | } 162 | 163 | public FloatingBubbleConfig build() { 164 | return new FloatingBubbleConfig(this); 165 | } 166 | 167 | public Builder gravity(int val) { 168 | gravity = val; 169 | if (gravity == Gravity.CENTER || 170 | gravity == Gravity.CENTER_VERTICAL || 171 | gravity == Gravity.CENTER_HORIZONTAL) { 172 | gravity = Gravity.CENTER_HORIZONTAL; 173 | } else if (gravity == Gravity.TOP || 174 | gravity == Gravity.BOTTOM) { 175 | gravity = Gravity.END; 176 | } 177 | return this; 178 | } 179 | 180 | public Builder paddingDp(int val) { 181 | paddingDp = val; 182 | return this; 183 | } 184 | 185 | public Builder borderRadiusDp(int val) { 186 | borderRadiusDp = val; 187 | return this; 188 | } 189 | 190 | public Builder physicsEnabled(boolean val) { 191 | physicsEnabled = val; 192 | return this; 193 | } 194 | 195 | public Builder removeBubbleAlpha(float val) { 196 | removeBubbleAlpha = val; 197 | return this; 198 | } 199 | } 200 | 201 | 202 | } 203 | -------------------------------------------------------------------------------- /floating-bubble/src/main/java/com/bsk/floatingbubblelib/FloatingBubbleLogger.java: -------------------------------------------------------------------------------- 1 | package com.bsk.floatingbubblelib; 2 | 3 | import android.util.Log; 4 | 5 | /** 6 | * Floating Bubble Logger 7 | * Created by bijoy on 1/6/17. 8 | */ 9 | 10 | public class FloatingBubbleLogger { 11 | private boolean isDebugEnabled; 12 | private String tag; 13 | 14 | public FloatingBubbleLogger() { 15 | isDebugEnabled = false; 16 | tag = FloatingBubbleLogger.class.getSimpleName(); 17 | } 18 | 19 | public FloatingBubbleLogger setTag(String tag) { 20 | this.tag = tag; 21 | return this; 22 | } 23 | 24 | public FloatingBubbleLogger setDebugEnabled(boolean enabled) { 25 | this.isDebugEnabled = enabled; 26 | return this; 27 | } 28 | 29 | public void log(String message) { 30 | if (isDebugEnabled) { 31 | Log.d(tag, message); 32 | } 33 | } 34 | 35 | public void log(String message, Throwable throwable) { 36 | if (isDebugEnabled) { 37 | Log.e(tag, message, throwable); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /floating-bubble/src/main/java/com/bsk/floatingbubblelib/FloatingBubblePermissions.java: -------------------------------------------------------------------------------- 1 | package com.bsk.floatingbubblelib; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.net.Uri; 7 | import android.os.Build; 8 | import android.provider.Settings; 9 | 10 | /** 11 | * Utilities for handling permissions 12 | * Created by bijoy on 1/6/17. 13 | */ 14 | 15 | public class FloatingBubblePermissions { 16 | 17 | public static final Integer REQUEST_CODE_ASK_PERMISSIONS = 1201; 18 | 19 | /** 20 | * Checks if the permissions is required 21 | * 22 | * @param context the application context 23 | * @return is the permission request needed 24 | */ 25 | public static boolean requiresPermission(Context context) { 26 | return Build.VERSION.SDK_INT >= 23 && !Settings.canDrawOverlays(context); 27 | } 28 | 29 | /** 30 | * Start the permission request 31 | * 32 | * @param activity the activity 33 | */ 34 | public static void startPermissionRequest(Activity activity) { 35 | if (Build.VERSION.SDK_INT >= 23 && requiresPermission(activity)) { 36 | Intent intent = new Intent( 37 | Settings.ACTION_MANAGE_OVERLAY_PERMISSION, 38 | Uri.parse("package:" + activity.getPackageName())); 39 | activity.startActivityForResult( 40 | intent, 41 | REQUEST_CODE_ASK_PERMISSIONS); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /floating-bubble/src/main/java/com/bsk/floatingbubblelib/FloatingBubblePhysics.java: -------------------------------------------------------------------------------- 1 | package com.bsk.floatingbubblelib; 2 | 3 | import android.graphics.Point; 4 | import android.util.Log; 5 | import android.view.View; 6 | import android.view.WindowManager; 7 | 8 | /** 9 | * FLoating Bubble Physics 10 | * Created by bijoysingh on 2/19/17. 11 | */ 12 | 13 | public class FloatingBubblePhysics extends DefaultFloatingBubbleTouchListener { 14 | 15 | private int sizeX; 16 | private int sizeY; 17 | private View bubbleView; 18 | private WindowManager windowManager; 19 | private FloatingBubbleConfig config; 20 | 21 | private WindowManager.LayoutParams bubbleParams; 22 | private FloatingBubbleAnimator animator; 23 | private Point[] previous = {null, null}; 24 | 25 | private FloatingBubblePhysics(Builder builder) { 26 | sizeX = builder.sizeX; 27 | sizeY = builder.sizeY; 28 | bubbleView = builder.bubbleView; 29 | windowManager = builder.windowManager; 30 | config = builder.config; 31 | 32 | bubbleParams = (WindowManager.LayoutParams) bubbleView.getLayoutParams(); 33 | animator = new FloatingBubbleAnimator.Builder() 34 | .bubbleParams(bubbleParams) 35 | .bubbleView(bubbleView) 36 | .sizeX(sizeX) 37 | .sizeY(sizeY) 38 | .windowManager(windowManager) 39 | .build(); 40 | } 41 | 42 | @Override 43 | public void onDown(float x, float y) { 44 | super.onDown(x, y); 45 | previous[0] = null; 46 | previous[1] = new Point((int) x, (int) y); 47 | } 48 | 49 | @Override 50 | public void onMove(float x, float y) { 51 | super.onMove(x, y); 52 | addSelectively(x, y); 53 | } 54 | 55 | @Override 56 | public void onUp(float x, float y) { 57 | addSelectively(x, y); 58 | Log.d(FloatingBubblePhysics.class.getSimpleName(), previous.toString()); 59 | 60 | if (previous[0] == null) { 61 | moveToCorner(); 62 | } else { 63 | moveLinearlyToCorner(); 64 | } 65 | } 66 | 67 | private void moveLinearlyToCorner() { 68 | int x1 = previous[0].x; 69 | int y1 = previous[0].y; 70 | int x2 = previous[1].x; 71 | int y2 = previous[1].y; 72 | 73 | if (x2 == x1) { 74 | moveToCorner(); 75 | return; 76 | } 77 | 78 | int xf = x1 < x2 ? sizeX - bubbleView.getWidth() : 0; 79 | int yf = y1 + (y2 - y1) * (xf - x1) / (x2 - x1); 80 | animator.animate(xf, yf); 81 | } 82 | 83 | private void moveToCorner() { 84 | if (previous[1].x < sizeX / 2) { 85 | animator.animate(0, previous[1].y); 86 | } else { 87 | animator.animate(sizeX - bubbleView.getWidth(), previous[1].y); 88 | } 89 | } 90 | 91 | private void addSelectively(float x, float y) { 92 | if (previous[1] != null && previous[1].x == (int) x && previous[1].y == (int) y) { 93 | return; 94 | } 95 | 96 | previous[0] = previous[1]; 97 | previous[1] = new Point((int) x, (int) y); 98 | } 99 | 100 | public static final class Builder { 101 | private int sizeX; 102 | private int sizeY; 103 | private View bubbleView; 104 | private WindowManager windowManager; 105 | private FloatingBubbleConfig config; 106 | 107 | public Builder() { 108 | } 109 | 110 | public Builder sizeX(int val) { 111 | sizeX = val; 112 | return this; 113 | } 114 | 115 | public Builder sizeY(int val) { 116 | sizeY = val; 117 | return this; 118 | } 119 | 120 | public Builder bubbleView(View val) { 121 | bubbleView = val; 122 | return this; 123 | } 124 | 125 | public Builder windowManager(WindowManager val) { 126 | windowManager = val; 127 | return this; 128 | } 129 | 130 | public Builder config(FloatingBubbleConfig val) { 131 | config = val; 132 | return this; 133 | } 134 | 135 | public FloatingBubblePhysics build() { 136 | return new FloatingBubblePhysics(this); 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /floating-bubble/src/main/java/com/bsk/floatingbubblelib/FloatingBubbleService.java: -------------------------------------------------------------------------------- 1 | package com.bsk.floatingbubblelib; 2 | 3 | import android.app.Service; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.res.Configuration; 7 | import android.content.res.Resources; 8 | import android.graphics.PixelFormat; 9 | import android.graphics.Point; 10 | import android.os.Build; 11 | import android.os.IBinder; 12 | import android.support.annotation.NonNull; 13 | import android.support.v7.widget.CardView; 14 | import android.util.DisplayMetrics; 15 | import android.view.Gravity; 16 | import android.view.LayoutInflater; 17 | import android.view.View; 18 | import android.view.ViewGroup; 19 | import android.view.WindowManager; 20 | import android.widget.ImageView; 21 | import android.widget.LinearLayout; 22 | 23 | /** 24 | * Floating Bubble Service. This file is the actual bubble view. 25 | * Created by bijoy on 1/6/17. 26 | */ 27 | 28 | public class FloatingBubbleService extends Service { 29 | 30 | protected static final String TAG = FloatingBubbleService.class.getSimpleName(); 31 | 32 | // Constructor Variable 33 | protected FloatingBubbleLogger logger; 34 | 35 | // The Window Manager View 36 | protected WindowManager windowManager; 37 | 38 | // The layout inflater 39 | protected LayoutInflater inflater; 40 | 41 | // Window Dimensions 42 | protected Point windowSize = new Point(); 43 | 44 | // The Views 45 | protected View bubbleView; 46 | protected View removeBubbleView; 47 | protected View expandableView; 48 | 49 | protected WindowManager.LayoutParams bubbleParams; 50 | protected WindowManager.LayoutParams removeBubbleParams; 51 | protected WindowManager.LayoutParams expandableParams; 52 | 53 | private FloatingBubbleConfig config; 54 | private FloatingBubblePhysics physics; 55 | private FloatingBubbleTouch touch; 56 | 57 | @Override 58 | public void onCreate() { 59 | super.onCreate(); 60 | logger = new FloatingBubbleLogger().setDebugEnabled(true).setTag(TAG); 61 | } 62 | 63 | @Override 64 | public IBinder onBind(Intent intent) { 65 | return null; 66 | } 67 | 68 | @Override 69 | public int onStartCommand(Intent intent, int flags, int startId) { 70 | if (intent == null || !onGetIntent(intent)) { 71 | return Service.START_NOT_STICKY; 72 | } 73 | 74 | logger.log("Start with START_STICKY"); 75 | 76 | // Remove existing views 77 | removeAllViews(); 78 | 79 | // Load the Window Managers 80 | setupWindowManager(); 81 | setupViews(); 82 | setTouchListener(); 83 | return super.onStartCommand(intent, flags, Service.START_STICKY); 84 | 85 | } 86 | 87 | 88 | @Override 89 | public void onDestroy() { 90 | super.onDestroy(); 91 | logger.log("onDestroy"); 92 | removeAllViews(); 93 | } 94 | 95 | private void removeAllViews() { 96 | if (windowManager == null) { 97 | return; 98 | } 99 | 100 | if (bubbleView != null) { 101 | windowManager.removeView(bubbleView); 102 | bubbleView = null; 103 | } 104 | 105 | if (removeBubbleView != null) { 106 | windowManager.removeView(removeBubbleView); 107 | removeBubbleView = null; 108 | } 109 | 110 | if (expandableView != null) { 111 | windowManager.removeView(expandableView); 112 | expandableView = null; 113 | } 114 | } 115 | 116 | @Override 117 | public void onConfigurationChanged(Configuration newConfig) { 118 | super.onConfigurationChanged(newConfig); 119 | 120 | } 121 | 122 | private void setupWindowManager() { 123 | windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); 124 | setLayoutInflater(); 125 | windowManager.getDefaultDisplay().getSize(windowSize); 126 | } 127 | 128 | protected LayoutInflater setLayoutInflater() { 129 | inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); 130 | return inflater; 131 | } 132 | 133 | /** 134 | * Creates the views 135 | */ 136 | protected void setupViews() { 137 | config = getConfig(); 138 | int padding = dpToPixels(config.getPaddingDp()); 139 | int iconSize = dpToPixels(config.getBubbleIconDp()); 140 | int bottomMargin = getExpandableViewBottomMargin(); 141 | 142 | // Setting up view 143 | bubbleView = inflater.inflate(R.layout.floating_bubble_view, null); 144 | removeBubbleView = inflater.inflate(R.layout.floating_remove_bubble_view, null); 145 | expandableView = inflater.inflate(R.layout.floating_expandable_view, null); 146 | 147 | // Setting up the Remove Bubble View setup 148 | removeBubbleParams = getDefaultWindowParams(); 149 | removeBubbleParams.gravity = Gravity.TOP | Gravity.START; 150 | removeBubbleParams.width = dpToPixels(config.getRemoveBubbleIconDp()); 151 | removeBubbleParams.height = dpToPixels(config.getRemoveBubbleIconDp()); 152 | removeBubbleParams.x = (windowSize.x - removeBubbleParams.width) / 2; 153 | removeBubbleParams.y = windowSize.y - removeBubbleParams.height - bottomMargin; 154 | removeBubbleView.setVisibility(View.GONE); 155 | removeBubbleView.setAlpha(config.getRemoveBubbleAlpha()); 156 | windowManager.addView(removeBubbleView, removeBubbleParams); 157 | 158 | // Setting up the Expandable View setup 159 | expandableParams = getDefaultWindowParams( 160 | WindowManager.LayoutParams.MATCH_PARENT, 161 | WindowManager.LayoutParams.MATCH_PARENT); 162 | expandableParams.height = windowSize.y - iconSize - bottomMargin; 163 | expandableParams.gravity = Gravity.TOP | Gravity.START; 164 | expandableView.setVisibility(View.GONE); 165 | ((LinearLayout) expandableView).setGravity(config.getGravity()); 166 | expandableView.setPadding(padding, padding, padding, padding); 167 | windowManager.addView(expandableView, expandableParams); 168 | 169 | // Setting up the Floating Bubble View 170 | bubbleParams = getDefaultWindowParams(); 171 | bubbleParams.gravity = Gravity.TOP | Gravity.START; 172 | bubbleParams.width = iconSize; 173 | bubbleParams.height = iconSize; 174 | windowManager.addView(bubbleView, bubbleParams); 175 | 176 | // Setting the configuration 177 | if (config.getRemoveBubbleIcon() != null) { 178 | ((ImageView) removeBubbleView).setImageDrawable(config.getRemoveBubbleIcon()); 179 | } 180 | if (config.getBubbleIcon() != null) { 181 | ((ImageView) bubbleView).setImageDrawable(config.getBubbleIcon()); 182 | } 183 | 184 | CardView card = (CardView) expandableView.findViewById(R.id.expandableViewCard); 185 | card.setRadius(dpToPixels(config.getBorderRadiusDp())); 186 | 187 | ImageView triangle = (ImageView) expandableView.findViewById(R.id.expandableViewTriangle); 188 | LinearLayout container = 189 | (LinearLayout) expandableView.findViewById(R.id.expandableViewContainer); 190 | if (config.getExpandableView() != null) { 191 | triangle.setColorFilter(config.getTriangleColor()); 192 | ViewGroup.MarginLayoutParams params = 193 | (ViewGroup.MarginLayoutParams) triangle.getLayoutParams(); 194 | params.leftMargin = dpToPixels((config.getBubbleIconDp() - 16) / 2); 195 | params.rightMargin = dpToPixels((config.getBubbleIconDp() - 16) / 2); 196 | 197 | triangle.setVisibility(View.VISIBLE); 198 | container.setVisibility(View.VISIBLE); 199 | card.setVisibility(View.VISIBLE); 200 | 201 | container.setBackgroundColor(config.getExpandableColor()); 202 | container.removeAllViews(); 203 | container.addView(config.getExpandableView()); 204 | } else { 205 | triangle.setVisibility(View.GONE); 206 | container.setVisibility(View.GONE); 207 | card.setVisibility(View.GONE); 208 | } 209 | } 210 | 211 | /** 212 | * Get the Bubble config 213 | * 214 | * @return the config 215 | */ 216 | protected FloatingBubbleConfig getConfig() { 217 | return FloatingBubbleConfig.getDefault(getContext()); 218 | } 219 | 220 | /** 221 | * Sets the touch listener 222 | */ 223 | protected void setTouchListener() { 224 | physics = new FloatingBubblePhysics.Builder() 225 | .sizeX(windowSize.x) 226 | .sizeY(windowSize.y) 227 | .bubbleView(bubbleView) 228 | .config(config) 229 | .windowManager(windowManager) 230 | .build(); 231 | 232 | touch = new FloatingBubbleTouch.Builder() 233 | .sizeX(windowSize.x) 234 | .sizeY(windowSize.y) 235 | .listener(getTouchListener()) 236 | .physics(physics) 237 | .bubbleView(bubbleView) 238 | .removeBubbleSize(dpToPixels(config.getRemoveBubbleIconDp())) 239 | .windowManager(windowManager) 240 | .expandableView(expandableView) 241 | .removeBubbleView(removeBubbleView) 242 | .config(config) 243 | .marginBottom(getExpandableViewBottomMargin()) 244 | .padding(dpToPixels(config.getPaddingDp())) 245 | .build(); 246 | 247 | bubbleView.setOnTouchListener(touch); 248 | } 249 | 250 | /** 251 | * Gets the touch listener for the bubble 252 | * 253 | * @return the touch listener 254 | */ 255 | public FloatingBubbleTouchListener getTouchListener() { 256 | return new DefaultFloatingBubbleTouchListener() { 257 | @Override 258 | public void onRemove() { 259 | stopSelf(); 260 | } 261 | }; 262 | } 263 | 264 | /** 265 | * Get the default window layout params 266 | * 267 | * @return the layout param 268 | */ 269 | protected WindowManager.LayoutParams getDefaultWindowParams() { 270 | return getDefaultWindowParams( 271 | WindowManager.LayoutParams.WRAP_CONTENT, 272 | WindowManager.LayoutParams.WRAP_CONTENT); 273 | } 274 | 275 | /** 276 | * Get the default window layout params 277 | * 278 | * @return the layout param 279 | */ 280 | protected WindowManager.LayoutParams getDefaultWindowParams(int width, int height) { 281 | return new WindowManager.LayoutParams( 282 | width, 283 | height, 284 | Build.VERSION.SDK_INT >= 26 285 | ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY 286 | : WindowManager.LayoutParams.TYPE_PHONE, 287 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 288 | | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH 289 | | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, 290 | PixelFormat.TRANSLUCENT); 291 | } 292 | 293 | /** 294 | * Handles the intent for the service (only if it is not null) 295 | * 296 | * @param intent the intent 297 | */ 298 | protected boolean onGetIntent(@NonNull Intent intent) { 299 | return true; 300 | } 301 | 302 | /** 303 | * Get the layout inflater for view inflation 304 | * 305 | * @return the layout inflater 306 | */ 307 | protected LayoutInflater getInflater() { 308 | return inflater == null ? setLayoutInflater() : inflater; 309 | } 310 | 311 | /** 312 | * Get the context for the service 313 | * 314 | * @return the context 315 | */ 316 | protected Context getContext() { 317 | return getApplicationContext(); 318 | } 319 | 320 | /** 321 | * Sets the state of the expanded view 322 | * @param expanded the expanded view state 323 | */ 324 | protected void setState(boolean expanded) { 325 | touch.setState(expanded); 326 | } 327 | 328 | /** 329 | * Get the expandable view's bottom margin 330 | * 331 | * @return margin 332 | */ 333 | private int getExpandableViewBottomMargin() { 334 | Resources resources = getContext().getResources(); 335 | int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android"); 336 | int navBarHeight = 0; 337 | if (resourceId > 0) { 338 | navBarHeight = resources.getDimensionPixelSize(resourceId); 339 | } 340 | 341 | return navBarHeight; 342 | } 343 | 344 | /** 345 | * Converts DPs to Pixel values 346 | * 347 | * @return the pixel value 348 | */ 349 | private int dpToPixels(int dpSize) { 350 | DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 351 | return Math.round(dpSize * (displayMetrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT)); 352 | } 353 | } 354 | -------------------------------------------------------------------------------- /floating-bubble/src/main/java/com/bsk/floatingbubblelib/FloatingBubbleTouch.java: -------------------------------------------------------------------------------- 1 | package com.bsk.floatingbubblelib; 2 | 3 | import android.view.Gravity; 4 | import android.view.MotionEvent; 5 | import android.view.View; 6 | import android.view.WindowManager; 7 | 8 | /** 9 | * Touch event for the Floating Bubble Service 10 | * Created by bijoysingh on 2/19/17. 11 | */ 12 | 13 | public class FloatingBubbleTouch implements View.OnTouchListener { 14 | 15 | private static final int TOUCH_CLICK_TIME = 250; 16 | private static final float EXPANSION_FACTOR = 1.25f; 17 | 18 | private int sizeX; 19 | private int sizeY; 20 | 21 | private View bubbleView; 22 | private View removeBubbleView; 23 | private View expandableView; 24 | private WindowManager windowManager; 25 | private FloatingBubbleTouchListener listener; 26 | private FloatingBubbleTouchListener physics; 27 | private int removeBubbleSize; 28 | private FloatingBubbleConfig config; 29 | private int padding; 30 | private int marginBottom; 31 | 32 | private WindowManager.LayoutParams bubbleParams; 33 | private WindowManager.LayoutParams removeBubbleParams; 34 | private WindowManager.LayoutParams expandableParams; 35 | private int removeBubbleStartSize; 36 | private int removeBubbleExpandedSize; 37 | private FloatingBubbleAnimator animator; 38 | 39 | private long touchStartTime = 0; 40 | private long lastTouchTime = 0; 41 | private boolean expanded = false; 42 | 43 | private FloatingBubbleTouch(Builder builder) { 44 | padding = builder.padding; 45 | config = builder.config; 46 | removeBubbleSize = builder.removeBubbleSize; 47 | physics = builder.physics; 48 | listener = builder.listener; 49 | windowManager = builder.windowManager; 50 | expandableView = builder.expandableView; 51 | removeBubbleView = builder.removeBubbleView; 52 | bubbleView = builder.bubbleView; 53 | sizeY = builder.sizeY; 54 | sizeX = builder.sizeX; 55 | marginBottom = builder.marginBottom; 56 | 57 | bubbleParams = (WindowManager.LayoutParams) bubbleView.getLayoutParams(); 58 | removeBubbleParams = (WindowManager.LayoutParams) removeBubbleView.getLayoutParams(); 59 | expandableParams = (WindowManager.LayoutParams) expandableView.getLayoutParams(); 60 | removeBubbleStartSize = removeBubbleSize; 61 | removeBubbleExpandedSize = (int) (EXPANSION_FACTOR * removeBubbleSize); 62 | animator = new FloatingBubbleAnimator.Builder() 63 | .sizeX(sizeX) 64 | .sizeY(sizeY) 65 | .windowManager(windowManager) 66 | .bubbleView(bubbleView) 67 | .bubbleParams(bubbleParams) 68 | .build(); 69 | } 70 | 71 | @Override 72 | public boolean onTouch(View view, MotionEvent motionEvent) { 73 | switch (motionEvent.getActionMasked()) { 74 | case MotionEvent.ACTION_DOWN: 75 | touchStartTime = System.currentTimeMillis(); 76 | if (listener != null) { 77 | listener.onDown(motionEvent.getRawX(), motionEvent.getRawY()); 78 | } 79 | if (sendEventToPhysics()) { 80 | physics.onDown(motionEvent.getRawX(), motionEvent.getRawY()); 81 | } 82 | break; 83 | 84 | case MotionEvent.ACTION_MOVE: 85 | lastTouchTime = System.currentTimeMillis(); 86 | moveBubbleView(motionEvent); 87 | if (lastTouchTime - touchStartTime > TOUCH_CLICK_TIME) { 88 | compressView(); 89 | showRemoveBubble(View.VISIBLE); 90 | } 91 | 92 | if (listener != null) { 93 | listener.onMove(motionEvent.getRawX(), motionEvent.getRawY()); 94 | } 95 | if (sendEventToPhysics()) { 96 | physics.onMove(motionEvent.getRawX(), motionEvent.getRawY()); 97 | } 98 | break; 99 | 100 | case MotionEvent.ACTION_UP: 101 | case MotionEvent.ACTION_CANCEL: 102 | showRemoveBubble(View.GONE); 103 | lastTouchTime = System.currentTimeMillis(); 104 | if (lastTouchTime - touchStartTime < TOUCH_CLICK_TIME) { 105 | toggleView(); 106 | if (listener != null) { 107 | listener.onTap(expanded); 108 | } 109 | if (sendEventToPhysics()) { 110 | physics.onTap(expanded); 111 | } 112 | } else { 113 | boolean isRemoved = checkRemoveBubble(); 114 | if (listener != null) { 115 | listener.onUp(motionEvent.getRawX(), motionEvent.getRawY()); 116 | } 117 | if (!isRemoved && sendEventToPhysics()) { 118 | physics.onUp(motionEvent.getRawX(), motionEvent.getRawY()); 119 | } 120 | } 121 | } 122 | return true; 123 | } 124 | 125 | private void moveBubbleView(MotionEvent motionEvent) { 126 | float halfClipSize = bubbleView.getWidth() / 2; 127 | float clipSize = bubbleView.getWidth(); 128 | 129 | float leftX = motionEvent.getRawX() - halfClipSize; 130 | leftX = (leftX > sizeX - clipSize) ? (sizeX - clipSize) : leftX; 131 | leftX = leftX < 0 ? 0 : leftX; 132 | 133 | float topY = motionEvent.getRawY() - halfClipSize; 134 | topY = (topY > sizeY - clipSize) ? (sizeY - clipSize) : topY; 135 | topY = topY < 0 ? 0 : topY; 136 | 137 | bubbleParams.x = (int) leftX; 138 | bubbleParams.y = (int) topY; 139 | 140 | handleRemove(); 141 | windowManager.updateViewLayout(bubbleView, bubbleParams); 142 | windowManager.updateViewLayout(removeBubbleView, removeBubbleParams); 143 | } 144 | 145 | private void handleRemove() { 146 | if (isInsideRemoveBubble()) { 147 | removeBubbleParams.height = removeBubbleExpandedSize; 148 | removeBubbleParams.width = removeBubbleExpandedSize; 149 | removeBubbleParams.x = (sizeX - removeBubbleParams.width) / 2; 150 | removeBubbleParams.y = sizeY - removeBubbleParams.height - marginBottom; 151 | bubbleParams.x = removeBubbleParams.x + (removeBubbleExpandedSize - bubbleView.getWidth()) / 2; 152 | bubbleParams.y = removeBubbleParams.y + (removeBubbleExpandedSize - bubbleView.getWidth()) / 2; 153 | } else { 154 | removeBubbleParams.height = removeBubbleStartSize; 155 | removeBubbleParams.width = removeBubbleStartSize; 156 | removeBubbleParams.x = (sizeX - removeBubbleParams.width) / 2; 157 | removeBubbleParams.y = sizeY - removeBubbleParams.height - marginBottom; 158 | } 159 | } 160 | 161 | private boolean isInsideRemoveBubble() { 162 | int bubbleSize = removeBubbleView.getWidth() == 0 163 | ? removeBubbleStartSize 164 | : removeBubbleView.getWidth(); 165 | int top = removeBubbleParams.y; 166 | int right = removeBubbleParams.x + bubbleSize; 167 | int bottom = removeBubbleParams.y + bubbleSize; 168 | int left = removeBubbleParams.x; 169 | 170 | int centerX = bubbleParams.x + bubbleView.getWidth() / 2; 171 | int centerY = bubbleParams.y + bubbleView.getWidth() / 2; 172 | 173 | return centerX > left && centerX < right && centerY > top && centerY < bottom; 174 | } 175 | 176 | private boolean checkRemoveBubble() { 177 | if (isInsideRemoveBubble()) { 178 | if (listener != null) { 179 | listener.onRemove(); 180 | } 181 | if (sendEventToPhysics()) { 182 | physics.onRemove(); 183 | } 184 | return true; 185 | } 186 | return false; 187 | } 188 | 189 | private boolean sendEventToPhysics() { 190 | return config.isPhysicsEnabled() && physics != null; 191 | } 192 | 193 | private void showRemoveBubble(int visibility) { 194 | removeBubbleView.setVisibility(visibility); 195 | } 196 | 197 | public void setState(boolean state) { 198 | expanded = state; 199 | if (expanded) { 200 | expandView(); 201 | } else { 202 | compressView(); 203 | } 204 | } 205 | 206 | private void toggleView() { 207 | expanded = !expanded; 208 | setState(expanded); 209 | } 210 | 211 | private void compressView() { 212 | expandableView.setVisibility(View.GONE); 213 | } 214 | 215 | private void expandView() { 216 | int x = 0; 217 | int y = padding; 218 | switch (config.getGravity()) { 219 | case Gravity.CENTER: 220 | case Gravity.CENTER_HORIZONTAL: 221 | case Gravity.CENTER_VERTICAL: 222 | x = (sizeX - bubbleView.getWidth()) / 2; 223 | break; 224 | case Gravity.LEFT: 225 | case Gravity.START: 226 | x = padding; 227 | break; 228 | case Gravity.RIGHT: 229 | case Gravity.END: 230 | x = sizeX - bubbleView.getWidth() - padding; 231 | break; 232 | } 233 | 234 | animator.animate(x, y); 235 | expandableView.setVisibility(View.VISIBLE); 236 | expandableParams.y = y + bubbleView.getWidth(); 237 | windowManager.updateViewLayout(expandableView, expandableParams); 238 | } 239 | 240 | public static final class Builder { 241 | private int sizeX; 242 | private int sizeY; 243 | private View bubbleView; 244 | private View removeBubbleView; 245 | private View expandableView; 246 | private FloatingBubbleLogger logger; 247 | private WindowManager windowManager; 248 | private FloatingBubbleTouchListener listener; 249 | private int removeBubbleSize; 250 | private FloatingBubbleTouchListener physics; 251 | private FloatingBubbleConfig config; 252 | private int padding; 253 | private int marginBottom; 254 | 255 | public Builder() { 256 | } 257 | 258 | public Builder sizeX(int val) { 259 | sizeX = val; 260 | return this; 261 | } 262 | 263 | public Builder sizeY(int val) { 264 | sizeY = val; 265 | return this; 266 | } 267 | 268 | public Builder bubbleView(View val) { 269 | bubbleView = val; 270 | return this; 271 | } 272 | 273 | public Builder removeBubbleView(View val) { 274 | removeBubbleView = val; 275 | return this; 276 | } 277 | 278 | public Builder expandableView(View val) { 279 | expandableView = val; 280 | return this; 281 | } 282 | 283 | public Builder logger(FloatingBubbleLogger val) { 284 | logger = val; 285 | return this; 286 | } 287 | 288 | public Builder windowManager(WindowManager val) { 289 | windowManager = val; 290 | return this; 291 | } 292 | 293 | public FloatingBubbleTouch build() { 294 | return new FloatingBubbleTouch(this); 295 | } 296 | 297 | public Builder removeBubbleSize(int val) { 298 | removeBubbleSize = val; 299 | return this; 300 | } 301 | 302 | public Builder physics(FloatingBubbleTouchListener val) { 303 | physics = val; 304 | return this; 305 | } 306 | 307 | public Builder listener(FloatingBubbleTouchListener val) { 308 | listener = val; 309 | return this; 310 | } 311 | 312 | public Builder config(FloatingBubbleConfig val) { 313 | config = val; 314 | return this; 315 | } 316 | 317 | public Builder padding(int val) { 318 | padding = val; 319 | return this; 320 | } 321 | 322 | public Builder marginBottom(int val) { 323 | marginBottom = val; 324 | return this; 325 | } 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /floating-bubble/src/main/java/com/bsk/floatingbubblelib/FloatingBubbleTouchListener.java: -------------------------------------------------------------------------------- 1 | package com.bsk.floatingbubblelib; 2 | 3 | /** 4 | * Floating bubble remove listener 5 | * Created by bijoysingh on 2/19/17. 6 | */ 7 | 8 | public interface FloatingBubbleTouchListener { 9 | 10 | void onDown(float x, float y); 11 | 12 | void onTap(boolean expanded); 13 | 14 | void onRemove(); 15 | 16 | void onMove(float x, float y); 17 | 18 | void onUp(float x, float y); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /floating-bubble/src/main/res/drawable/bubble_default_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BijoySingh/Floating-Bubble-Library-Android/320f4c976d5192d49aac741589bfd7f98fa97ad7/floating-bubble/src/main/res/drawable/bubble_default_icon.png -------------------------------------------------------------------------------- /floating-bubble/src/main/res/drawable/close_default_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BijoySingh/Floating-Bubble-Library-Android/320f4c976d5192d49aac741589bfd7f98fa97ad7/floating-bubble/src/main/res/drawable/close_default_icon.png -------------------------------------------------------------------------------- /floating-bubble/src/main/res/drawable/triangle_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BijoySingh/Floating-Bubble-Library-Android/320f4c976d5192d49aac741589bfd7f98fa97ad7/floating-bubble/src/main/res/drawable/triangle_icon.png -------------------------------------------------------------------------------- /floating-bubble/src/main/res/layout/floating_bubble_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | -------------------------------------------------------------------------------- /floating-bubble/src/main/res/layout/floating_expandable_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 20 | 21 | 28 | 29 | 35 | 36 | -------------------------------------------------------------------------------- /floating-bubble/src/main/res/layout/floating_remove_bubble_view.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /floating-bubble/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | FloatingBubbleLib 3 | 4 | Remove Floating Bubble 5 | Floating Bubble 6 | 7 | -------------------------------------------------------------------------------- /sampleapp/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /sampleapp/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | buildToolsVersion "28.0.3" 6 | 7 | defaultConfig { 8 | minSdkVersion 17 9 | targetSdkVersion 28 10 | versionCode 1 11 | versionName "1.0" 12 | 13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | } 22 | 23 | dependencies { 24 | implementation fileTree(dir: 'libs', include: ['*.jar']) 25 | androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', { 26 | exclude group: 'com.android.support', module: 'support-annotations' 27 | }) 28 | implementation 'com.android.support:appcompat-v7:28.0.0' 29 | implementation 'com.android.support:support-core-utils:28.0.0' 30 | implementation 'com.android.support:support-v4:28.0.0' 31 | implementation 'com.android.support:cardview-v7:28.0.0' 32 | implementation project(':floating-bubble') 33 | testImplementation 'junit:junit:4.12' 34 | } 35 | -------------------------------------------------------------------------------- /sampleapp/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/bijoy/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /sampleapp/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /sampleapp/src/main/gen/com/bsk/sampleapp/BuildConfig.java: -------------------------------------------------------------------------------- 1 | /*___Generated_by_IDEA___*/ 2 | 3 | package com.bsk.sampleapp; 4 | 5 | /* This stub is only used by the IDE. It is NOT the BuildConfig class actually packed into the APK */ 6 | public final class BuildConfig { 7 | public final static boolean DEBUG = Boolean.parseBoolean(null); 8 | } -------------------------------------------------------------------------------- /sampleapp/src/main/gen/com/bsk/sampleapp/Manifest.java: -------------------------------------------------------------------------------- 1 | /*___Generated_by_IDEA___*/ 2 | 3 | package com.bsk.sampleapp; 4 | 5 | /* This stub is only used by the IDE. It is NOT the Manifest class actually packed into the APK */ 6 | public final class Manifest { 7 | } -------------------------------------------------------------------------------- /sampleapp/src/main/gen/com/bsk/sampleapp/R.java: -------------------------------------------------------------------------------- 1 | /*___Generated_by_IDEA___*/ 2 | 3 | package com.bsk.sampleapp; 4 | 5 | /* This stub is only used by the IDE. It is NOT the R class actually packed into the APK */ 6 | public final class R { 7 | } -------------------------------------------------------------------------------- /sampleapp/src/main/java/com/bsk/sampleapp/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.bsk.sampleapp; 2 | 3 | import android.content.Intent; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.os.Bundle; 6 | import android.view.View; 7 | 8 | import com.bsk.floatingbubblelib.FloatingBubblePermissions; 9 | import com.bsk.floatingbubblelib.FloatingBubbleService; 10 | 11 | public class MainActivity extends AppCompatActivity { 12 | 13 | @Override 14 | protected void onCreate(Bundle savedInstanceState) { 15 | super.onCreate(savedInstanceState); 16 | setContentView(R.layout.activity_main); 17 | 18 | FloatingBubblePermissions.startPermissionRequest(this); 19 | View startBubble = findViewById(R.id.start_bubble); 20 | startBubble.setOnClickListener(new View.OnClickListener() { 21 | @Override 22 | public void onClick(View view) { 23 | startService(new Intent(getApplicationContext(), SimpleService.class)); 24 | } 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /sampleapp/src/main/java/com/bsk/sampleapp/SimpleService.java: -------------------------------------------------------------------------------- 1 | package com.bsk.sampleapp; 2 | 3 | import android.content.Context; 4 | import android.graphics.Color; 5 | import android.support.v4.content.ContextCompat; 6 | import android.view.Gravity; 7 | import android.view.WindowManager; 8 | 9 | import com.bsk.floatingbubblelib.FloatingBubbleConfig; 10 | import com.bsk.floatingbubblelib.FloatingBubbleService; 11 | import com.bsk.floatingbubblelib.FloatingBubbleTouchListener; 12 | 13 | /** 14 | * Created by bijoysingh on 2/19/17. 15 | */ 16 | 17 | public class SimpleService extends FloatingBubbleService { 18 | @Override 19 | protected FloatingBubbleConfig getConfig() { 20 | Context context = getApplicationContext(); 21 | return new FloatingBubbleConfig.Builder() 22 | .bubbleIcon(ContextCompat.getDrawable(context, R.drawable.web_icon)) 23 | .removeBubbleIcon(ContextCompat.getDrawable(context, com.bsk.floatingbubblelib.R.drawable.close_default_icon)) 24 | .bubbleIconDp(54) 25 | .expandableView(getInflater().inflate(R.layout.sample_view_1, null)) 26 | .removeBubbleIconDp(54) 27 | .paddingDp(4) 28 | .borderRadiusDp(0) 29 | .physicsEnabled(true) 30 | .expandableColor(Color.WHITE) 31 | .triangleColor(0xFF215A64) 32 | .gravity(Gravity.LEFT) 33 | .build(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /sampleapp/src/main/res/drawable/close_default_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BijoySingh/Floating-Bubble-Library-Android/320f4c976d5192d49aac741589bfd7f98fa97ad7/sampleapp/src/main/res/drawable/close_default_icon.png -------------------------------------------------------------------------------- /sampleapp/src/main/res/drawable/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BijoySingh/Floating-Bubble-Library-Android/320f4c976d5192d49aac741589bfd7f98fa97ad7/sampleapp/src/main/res/drawable/cover.jpg -------------------------------------------------------------------------------- /sampleapp/src/main/res/drawable/web_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BijoySingh/Floating-Bubble-Library-Android/320f4c976d5192d49aac741589bfd7f98fa97ad7/sampleapp/src/main/res/drawable/web_icon.png -------------------------------------------------------------------------------- /sampleapp/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 |