Introduction

The very first question you might be thinking is, what are floating widgets in Android. Well, here is the answer - "Floating widgets are simple action buttons that perform some action and always overlay, can float everywhere on the entire screen and are simply draggable -you can leave it anywhere on the screen.
One example of floating widgets is the Facebook chat head bubble button. Uber and Ola driver applications also have these floating widget buttons that can switch from the app to Maps and vice versa.
So here, we are going to create an application in which a floating button widget will be used to switch between Maps and the app itself.
Step 1
First of all, let us create a simple activity and name it MainActivity. You need a service that helps in back navigation from the Maps application to our application. We will see this later in this article.
Step 2
Add the Location Services Gradle in build.gradle.
  1. apply plugin: 'com.android.application'
  2. android {
  3. compileSdkVersion 28
  4. defaultConfig {
  5. applicationId "yourdomain.floatingwidgetexample"
  6. minSdkVersion 21
  7. targetSdkVersion 28
  8. versionCode 1
  9. versionName "1.0"
  10. testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
  11. }
  12. buildTypes {
  13. release {
  14. minifyEnabled false
  15. proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
  16. }
  17. }
  18. }
  19. dependencies {
  20. implementation fileTree(dir: 'libs', include: ['*.jar'])
  21. implementation 'com.android.support:appcompat-v7:28.0.0'
  22. implementation 'com.android.support.constraint:constraint-layout:1.1.3'
  23. testImplementation 'junit:junit:4.12'
  24. androidTestImplementation 'com.android.support.test:runner:1.0.2'
  25. androidTestImplementation 'com.android.support.test.espresso:espresso-
  26. core:3.0.2'
  27. implementation 'com.google.android.gms:play-services-location:15.0.1'
  28. }
Step 3
Add permissions in the manifest file. Let us see the manifest.xml file.
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:tools="http://schemas.android.com/tools"
  4. package="yourdomain.floatingwidgetexample">
  5. <uses-permission android:name="android.permission.INTERNET"/>
  6. <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
  7. <uses-permission android:name="android.permission.ACTION_MANAGE_OVERLAY_PERMISSION" />
  8. <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
  9. <application
  10. android:allowBackup="false"
  11. android:icon="@mipmap/ic_launcher"
  12. android:label="@string/app_name"
  13. android:roundIcon="@mipmap/ic_launcher_round"
  14. android:supportsRtl="true"
  15. android:theme="@style/AppTheme"
  16. tools:ignore="GoogleAppIndexingWarning">
  17. <activity android:name=".MainActivity">
  18. <intent-filter>
  19. <action android:name="android.intent.action.MAIN" />
  20. <category android:name="android.intent.category.LAUNCHER" />
  21. </intent-filter>
  22. </activity>
  23. <service android:name=".FloatWidgetService" />
  24. </application>
  25. </manifest>
Step 4
Create a service named FloatWidgetService.java. See the code below.
  1. public class FloatWidgetService extends Service {
  2. private WindowManager mWindowManager;
  3. private View mFloatingWidget;
  4. public static final String BROADCAST_ACTION = "magicbox";
  5. private static final int MAX_CLICK_DURATION = 200;
  6. private long startClickTime;
  7. public FloatWidgetService() {
  8. }
  9. @Override
  10. public IBinder onBind(Intent intent) {
  11. return null;
  12. }
  13. @Override
  14. public int onStartCommand(Intent intent, int flags, int startId) {
  15. return START_STICKY;
  16. }
  17. @Override
  18. public void onCreate() {
  19. super.onCreate();
  20. mFloatingWidget = LayoutInflater.from(this).inflate(R.layout.layout_floating_widget, null);
  21. final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
  22. WindowManager.LayoutParams.WRAP_CONTENT,
  23. WindowManager.LayoutParams.WRAP_CONTENT,
  24. Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
  25. ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
  26. : WindowManager.LayoutParams.TYPE_PHONE,
  27. WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
  28. PixelFormat.TRANSLUCENT);
  29. params.gravity = Gravity.TOP | Gravity.LEFT;
  30. params.x = 0;
  31. params.y = 100;
  32. mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
  33. mWindowManager.addView(mFloatingWidget, params);
  34. /* ImageView closeButtonCollapsed = (ImageView) mFloatingWidget.findViewById(R.id.floating_image);
  35. closeButtonCollapsed.setOnClickListener(new View.OnClickListener() {
  36. @Override
  37. public void onClick(View view) {
  38. *//* Intent intent = new Intent(BROADCAST_ACTION);
  39. sendBroadcast(intent);
  40. stopSelf();*//*
  41. }
  42. });*/
  43. mFloatingWidget.findViewById(R.id.root_container).setOnTouchListener(new View.OnTouchListener() {
  44. private int initialX;
  45. private int initialY;
  46. private float initialTouchX;
  47. private float initialTouchY;
  48. @Override
  49. public boolean onTouch(View v, MotionEvent event) {
  50. switch (event.getAction()) {
  51. case MotionEvent.ACTION_DOWN:
  52. initialX = params.x;
  53. initialY = params.y;
  54. initialTouchX = event.getRawX();
  55. initialTouchY = event.getRawY();
  56. startClickTime = Calendar.getInstance().getTimeInMillis();
  57. return false;
  58. case MotionEvent.ACTION_UP:
  59. int Xdiff = (int) (event.getRawX() - initialTouchX);
  60. int Ydiff = (int) (event.getRawY() - initialTouchY);
  61. long clickDuration = Calendar.getInstance().getTimeInMillis() - startClickTime;
  62. if (clickDuration < MAX_CLICK_DURATION) {
  63. Intent intent = new Intent(BROADCAST_ACTION);
  64. sendBroadcast(intent);
  65. stopSelf();
  66. }
  67. return false;
  68. case MotionEvent.ACTION_MOVE:
  69. params.x = initialX + (int) (event.getRawX() - initialTouchX);
  70. params.y = initialY + (int) (event.getRawY() - initialTouchY);
  71. mWindowManager.updateViewLayout(mFloatingWidget, params);
  72. return false;
  73. }
  74. return false;
  75. }
  76. });
  77. }
  78. @Override
  79. public void onDestroy() {
  80. if (mFloatingWidget != null) mWindowManager.removeView(mFloatingWidget);
  81. super.onDestroy();
  82. }
  83. }
Now, we can see that we have inflated a layout in the service whose button will be floating. Let us look into layout_floating_widget.xml
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:tools="http://schemas.android.com/tools"
  4. android:id="@+id/root_container"
  5. android:layout_width="wrap_content"
  6. android:layout_height="wrap_content"
  7. tools:ignore="UselessParent">
  8. <RelativeLayout
  9. android:id="@+id/collapse_view"
  10. android:layout_width="wrap_content"
  11. android:layout_height="wrap_content"
  12. android:orientation="vertical"
  13. android:visibility="visible">
  14. <ImageView
  15. android:id="@+id/floating_image"
  16. android:layout_width="60dp"
  17. android:layout_height="60dp"
  18. android:adjustViewBounds="true"
  19. android:scaleType="fitXY"
  20. android:src="@mipmap/ic_launcher_round" />
  21. </RelativeLayout>
  22. </RelativeLayout>
Now, it is time to write some location gathering code to our MainActivity.java since we are gathering the location. We are going to open the Maps app and revert back to our application by simply touching the Service button as we have seen above.
MainActivity.java
  1. package yourdomain.floatingwidgetexample;
  2. import android.Manifest;
  3. import android.content.ActivityNotFoundException;
  4. import android.content.BroadcastReceiver;
  5. import android.content.Context;
  6. import android.content.Intent;
  7. import android.content.IntentFilter;
  8. import android.content.IntentSender;
  9. import android.content.pm.PackageManager;
  10. import android.location.Location;
  11. import android.net.Uri;
  12. import android.os.Build;
  13. import android.os.Bundle;
  14. import android.os.Looper;
  15. import android.provider.Settings;
  16. import android.support.annotation.NonNull;
  17. import android.support.v4.app.ActivityCompat;
  18. import android.support.v7.app.AppCompatActivity;
  19. import android.util.Log;
  20. import android.view.View;
  21. import android.widget.Button;
  22. import android.widget.TextView;
  23. import com.google.android.gms.common.api.ResolvableApiException;
  24. import com.google.android.gms.location.FusedLocationProviderClient;
  25. import com.google.android.gms.location.LocationCallback;
  26. import com.google.android.gms.location.LocationRequest;
  27. import com.google.android.gms.location.LocationResult;
  28. import com.google.android.gms.location.LocationServices;
  29. import com.google.android.gms.location.LocationSettingsRequest;
  30. import com.google.android.gms.location.LocationSettingsResponse;
  31. import com.google.android.gms.location.SettingsClient;
  32. import com.google.android.gms.tasks.OnFailureListener;
  33. import com.google.android.gms.tasks.OnSuccessListener;
  34. import com.google.android.gms.tasks.Task;
  35. public class MainActivity extends AppCompatActivity {
  36. private final static int REQUEST_CODE_LOCATION = 102;
  37. private static final int REQUEST_CODE_FOR_OVERLAY_SCREEN = 106;
  38. Button mButton;
  39. private LocationCallback mLocationCallback;
  40. Intent startIntent;
  41. String[] permission = {android.Manifest.permission.ACCESS_FINE_LOCATION};
  42. private FusedLocationProviderClient mFusedLocationClient;
  43. private Location mCurrentLocation;
  44. String destinationLat = "28.6367764";
  45. String destinationLng = "77.4023482";
  46. TextView latitudeTextView, longitudeTextView;
  47. GetFloatingIconClick receiver;
  48. IntentFilter filter = new IntentFilter();
  49. private double currentLatitude, currentLongitude;
  50. @Override
  51. protected void onCreate(Bundle savedInstanceState) {
  52. super.onCreate(savedInstanceState);
  53. setContentView(R.layout.activity_main);
  54. mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
  55. createLocationCallback();
  56. if (ActivityCompat.checkSelfPermission(MainActivity.this, permission[0]) != PackageManager.PERMISSION_GRANTED) {
  57. ActivityCompat.requestPermissions(this, new String[]{permission[0]}, REQUEST_CODE_LOCATION);
  58. } else {
  59. getMyLocation();
  60. }
  61. mButton = (Button) findViewById(R.id.button);
  62. latitudeTextView = (TextView) findViewById(R.id.latitude_textview);
  63. longitudeTextView = (TextView) findViewById(R.id.longitude_textview);
  64. latitudeTextView.setText("Destination latitude = " + destinationLat);
  65. longitudeTextView.setText("Destination longitude = " + destinationLng);
  66. mButton.setOnClickListener(new View.OnClickListener() {
  67. @Override
  68. public void onClick(View view) {
  69. try {
  70. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(MainActivity.this)) {
  71. Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
  72. Uri.parse("package:" + getPackageName()));
  73. startActivityForResult(intent, REQUEST_CODE_FOR_OVERLAY_SCREEN);
  74. } else {
  75. initializeView();
  76. }
  77. } catch (ActivityNotFoundException e) {
  78. Uri gmmIntentUri = Uri.parse("google.navigation:q=" + destinationLat + "," + destinationLng + "&mode=d");
  79. Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri);
  80. mapIntent.setPackage("com.google.android.apps.maps");
  81. startActivity(mapIntent);
  82. }
  83. }
  84. });
  85. }
  86. @Override
  87. public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
  88. switch (requestCode) {
  89. case REQUEST_CODE_LOCATION:
  90. if (grantResults.length > 0
  91. && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
  92. if (ActivityCompat.checkSelfPermission(MainActivity.this,
  93. Manifest.permission.ACCESS_FINE_LOCATION)
  94. == PackageManager.PERMISSION_GRANTED) {
  95. getMyLocation();
  96. }
  97. }
  98. default:
  99. break;
  100. }
  101. }
  102. private void createLocationCallback() {
  103. mLocationCallback = new LocationCallback() {
  104. @Override
  105. public void onLocationResult(LocationResult locationResult) {
  106. super.onLocationResult(locationResult);
  107. mCurrentLocation = locationResult.getLastLocation();
  108. mFusedLocationClient.removeLocationUpdates(mLocationCallback);
  109. updateLocationUI(mCurrentLocation.getLatitude(), mCurrentLocation.getLongitude());
  110. }
  111. };
  112. }
  113. public void getMyLocation() {
  114. if (ActivityCompat.checkSelfPermission(MainActivity.this,
  115. Manifest.permission.ACCESS_FINE_LOCATION)
  116. == PackageManager.PERMISSION_GRANTED) {
  117. final LocationRequest mLocationRequest = new LocationRequest();
  118. mLocationRequest.setInterval(10000);
  119. mLocationRequest.setFastestInterval(5000);
  120. mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
  121. LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder()
  122. .addLocationRequest(mLocationRequest);
  123. SettingsClient client = LocationServices.getSettingsClient(this);
  124. Task<LocationSettingsResponse> task = client.checkLocationSettings(builder.build());
  125. task.addOnSuccessListener(this, new OnSuccessListener<LocationSettingsResponse>() {
  126. @Override
  127. public void onSuccess(LocationSettingsResponse locationSettingsResponse) {
  128. // All location settings are satisfied. The client can initialize
  129. // location requests here.
  130. // ...
  131. Log.e("location response", locationSettingsResponse.toString());
  132. if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
  133. && ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_COARSE_LOCATION)
  134. != PackageManager.PERMISSION_GRANTED) {
  135. // TODO: Consider calling
  136. // ActivityCompat#requestPermissions
  137. // here to request the missing permissions, and then overriding
  138. // public void onRequestPermissionsResult(int requestCode, String[] permissions,
  139. // int[] grantResults)
  140. // to handle the case where the user grants the permission. See the documentation
  141. // for ActivityCompat#requestPermissions for more details.
  142. return;
  143. }
  144. mFusedLocationClient.requestLocationUpdates(mLocationRequest,
  145. mLocationCallback, Looper.myLooper());
  146. }
  147. });
  148. task.addOnFailureListener(this, new OnFailureListener() {
  149. @Override
  150. public void onFailure(@NonNull Exception e) {
  151. if (e instanceof ResolvableApiException) {
  152. // Location settings are not satisfied, but this can be fixed
  153. // by showing the user a dialog.
  154. try {
  155. // Show the dialog by calling startResolutionForResult(),
  156. // and check the result in onActivityResult().
  157. ResolvableApiException resolvable = (ResolvableApiException) e;
  158. resolvable.startResolutionForResult(MainActivity.this,
  159. REQUEST_CODE_LOCATION);
  160. } catch (IntentSender.SendIntentException sendEx) {
  161. // Ignore the error.
  162. }
  163. }
  164. }
  165. });
  166. }
  167. }
  168. private void updateLocationUI(Double lat, Double lng) {
  169. currentLatitude = lat;
  170. currentLongitude = lng;
  171. }
  172. private void initializeView() {
  173. Uri gmmIntentUri = Uri.parse("google.navigation:q=" + destinationLat + "," + destinationLng + "&mode=d");
  174. Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri);
  175. mapIntent.setPackage("com.google.android.apps.maps");
  176. startActivity(mapIntent);
  177. startIntent = new Intent(MainActivity.this, FloatWidgetService.class);
  178. startService(startIntent);
  179. }
  180. @Override
  181. public void onResume() {
  182. super.onResume();
  183. receiver = new GetFloatingIconClick();
  184. filter.addAction(FloatWidgetService.BROADCAST_ACTION);
  185. registerReceiver(receiver, filter);
  186. }
  187. private class GetFloatingIconClick extends BroadcastReceiver {
  188. @Override
  189. public void onReceive(Context context, Intent intent) {
  190. Intent selfIntent = new Intent(MainActivity.this, MainActivity.class);
  191. selfIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP
  192. | Intent.FLAG_ACTIVITY_CLEAR_TOP);
  193. startActivity(selfIntent);
  194. }
  195. }
  196. }
activity_main.xml
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:tools="http://schemas.android.com/tools"
  4. android:layout_width="match_parent"
  5. android:layout_height="match_parent"
  6. tools:context=".MainActivity">
  7. <TextView
  8. android:id="@+id/latitude_textview"
  9. android:layout_width="wrap_content"
  10. android:layout_height="wrap_content"
  11. tools:text="Latitude : "
  12. android:layout_alignParentStart="true"
  13. android:padding="12dp"/>
  14. <TextView
  15. android:id="@+id/longitude_textview"
  16. android:layout_width="wrap_content"
  17. android:layout_height="wrap_content"
  18. tools:text="Longitude : "
  19. android:padding="12dp"
  20. android:layout_below="@id/latitude_textview"/>
  21. <Button
  22. android:id="@+id/button"
  23. android:layout_width="wrap_content"
  24. android:layout_height="wrap_content"
  25. android:text="Go to map"
  26. android:layout_centerInParent="true" />
  27. </RelativeLayout>
Now, we can see that the MainActivity consists of a method named getMyLocation(). This method is gathering the location information once we need a location because we want to show the navigation from one place to another place. Destination's latitude, longitude are been taken statically. For the sake of simplicity, you can put your desired latitude, longitude.
Simply, run the application. We get the output screen as below.
Output
The first screen will look like the following.
Floating Widget In Android
Now, after 1-2 seconds (or immediately), the permission model will arrive.
Floating Widget In Android
Allow it and click on the "Go To MAP" button. You will be redirected to some other permissions and then the map.
Floating Widget In Android
Toggle the button you are seeing above to give the permission of overlaying on screen and you will see the next screen as below.
Floating Widget In Android
Again, after enabling the toggle button, it automatically grants permission. Press the back button to go to the application we just created. Now again, click on the "Go to MAP" button.
Now, it will navigate to the Maps and show the path to the destination place and our current location.
Floating Widget In Android
See the left side round button with an Android symbol on it. It is called the launcher icon. You can set your icon's image as well. And this button is called a floating widget. Now, when you tap on it, you will be redirected to your application and shown the very first screen.
Floating Widget In Android
Finally, the first screen shows up when you tap on the Android icon floating on the map screen.

Conclusion

From this article, we learned about the creation of floating widgets and their behavior and need. This article shows the beauty of floating widgets and how simple they are to create.