Floating Widget In Android

This article illustrates how floating widgets behave and operate in Android. Floating widgets are nothing but simple action buttons that perform some action and always overlay, can float anywhere on the entire screen.

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 buttonUber 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.   
  3. android {  
  4.     compileSdkVersion 28  
  5.     defaultConfig {  
  6.         applicationId "yourdomain.floatingwidgetexample"  
  7.         minSdkVersion 21  
  8.         targetSdkVersion 28  
  9.         versionCode 1  
  10.         versionName "1.0"  
  11.         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"  
  12.     }  
  13.     buildTypes {  
  14.         release {  
  15.             minifyEnabled false  
  16.             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'  
  17.         }  
  18.     }  
  19. }  
  20.   
  21. dependencies {  
  22.     implementation fileTree(dir: 'libs', include: ['*.jar'])  
  23.     implementation 'com.android.support:appcompat-v7:28.0.0'  
  24.     implementation 'com.android.support.constraint:constraint-layout:1.1.3'  
  25.     testImplementation 'junit:junit:4.12'  
  26.     androidTestImplementation 'com.android.support.test:runner:1.0.2'  
  27.     androidTestImplementation 'com.android.support.test.espresso:espresso-   
  28.     core:3.0.2'  
  29.   
  30.     implementation 'com.google.android.gms:play-services-location:15.0.1'  
  31.   
  32. }  
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.   
  6.     <uses-permission android:name="android.permission.INTERNET"/>  
  7.     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>  
  8.     <uses-permission android:name="android.permission.ACTION_MANAGE_OVERLAY_PERMISSION" />  
  9.     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>  
  10.   
  11.     <application  
  12.         android:allowBackup="false"  
  13.         android:icon="@mipmap/ic_launcher"  
  14.         android:label="@string/app_name"  
  15.         android:roundIcon="@mipmap/ic_launcher_round"  
  16.         android:supportsRtl="true"  
  17.         android:theme="@style/AppTheme"  
  18.         tools:ignore="GoogleAppIndexingWarning">  
  19.         <activity android:name=".MainActivity">  
  20.             <intent-filter>  
  21.                 <action android:name="android.intent.action.MAIN" />  
  22.   
  23.                 <category android:name="android.intent.category.LAUNCHER" />  
  24.             </intent-filter>  
  25.         </activity>  
  26.   
  27.         <service android:name=".FloatWidgetService" />  
  28.     </application>  
  29.   
  30. </manifest>  
Step 4 
 
Create a service named FloatWidgetService.java. See the code below.
  1. public class FloatWidgetService extends Service {  
  2.   
  3.     private WindowManager mWindowManager;  
  4.     private View mFloatingWidget;  
  5.     public static final String BROADCAST_ACTION = "magicbox";  
  6.     private static final int MAX_CLICK_DURATION = 200;  
  7.     private long startClickTime;  
  8.   
  9.     public FloatWidgetService() {  
  10.     }  
  11.   
  12.     @Override  
  13.     public IBinder onBind(Intent intent) {  
  14.         return null;  
  15.     }  
  16.   
  17.     @Override  
  18.     public int onStartCommand(Intent intent, int flags, int startId) {  
  19.         return START_STICKY;  
  20.     }  
  21.   
  22.     @Override  
  23.     public void onCreate() {  
  24.         super.onCreate();  
  25.         mFloatingWidget = LayoutInflater.from(this).inflate(R.layout.layout_floating_widget, null);  
  26.   
  27.         final WindowManager.LayoutParams params = new WindowManager.LayoutParams(  
  28.                 WindowManager.LayoutParams.WRAP_CONTENT,  
  29.                 WindowManager.LayoutParams.WRAP_CONTENT,  
  30.                 Build.VERSION.SDK_INT >= Build.VERSION_CODES.O  
  31.                         ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY  
  32.                         : WindowManager.LayoutParams.TYPE_PHONE,  
  33.                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,  
  34.                 PixelFormat.TRANSLUCENT);  
  35.   
  36.         params.gravity = Gravity.TOP | Gravity.LEFT;  
  37.         params.x = 0;  
  38.         params.y = 100;  
  39.   
  40.         mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);  
  41.         mWindowManager.addView(mFloatingWidget, params);  
  42.   
  43.       /*  ImageView closeButtonCollapsed = (ImageView) mFloatingWidget.findViewById(R.id.floating_image); 
  44.         closeButtonCollapsed.setOnClickListener(new View.OnClickListener() { 
  45.             @Override 
  46.             public void onClick(View view) { 
  47.  
  48.            *//*     Intent intent = new Intent(BROADCAST_ACTION); 
  49.                 sendBroadcast(intent); 
  50.  
  51.                 stopSelf();*//* 
  52.             } 
  53.         });*/  
  54.   
  55.         mFloatingWidget.findViewById(R.id.root_container).setOnTouchListener(new View.OnTouchListener() {  
  56.             private int initialX;  
  57.             private int initialY;  
  58.             private float initialTouchX;  
  59.             private float initialTouchY;  
  60.   
  61.             @Override  
  62.             public boolean onTouch(View v, MotionEvent event) {  
  63.                 switch (event.getAction()) {  
  64.                     case MotionEvent.ACTION_DOWN:  
  65.                         initialX = params.x;  
  66.                         initialY = params.y;  
  67.                         initialTouchX = event.getRawX();  
  68.                         initialTouchY = event.getRawY();  
  69.                         startClickTime = Calendar.getInstance().getTimeInMillis();  
  70.                         return false;  
  71.                     case MotionEvent.ACTION_UP:  
  72.                         int Xdiff = (int) (event.getRawX() - initialTouchX);  
  73.                         int Ydiff = (int) (event.getRawY() - initialTouchY);  
  74.   
  75.                         long clickDuration = Calendar.getInstance().getTimeInMillis() - startClickTime;  
  76.   
  77.                         if (clickDuration < MAX_CLICK_DURATION) {  
  78.   
  79.                             Intent intent = new Intent(BROADCAST_ACTION);  
  80.                             sendBroadcast(intent);  
  81.                             stopSelf();  
  82.   
  83.                         }  
  84.   
  85.                         return false;  
  86.                     case MotionEvent.ACTION_MOVE:  
  87.                         params.x = initialX + (int) (event.getRawX() - initialTouchX);  
  88.                         params.y = initialY + (int) (event.getRawY() - initialTouchY);  
  89.                         mWindowManager.updateViewLayout(mFloatingWidget, params);  
  90.                         return false;  
  91.                 }  
  92.                 return false;  
  93.             }  
  94.   
  95.         });  
  96.   
  97.   
  98.     }  
  99.   
  100.   
  101.     @Override  
  102.     public void onDestroy() {  
  103.   
  104.         if (mFloatingWidget != null) mWindowManager.removeView(mFloatingWidget);  
  105.         super.onDestroy();  
  106.     }  
  107.   
  108.   
  109. }  
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.   
  3. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  4.     xmlns:tools="http://schemas.android.com/tools"  
  5.     android:id="@+id/root_container"  
  6.     android:layout_width="wrap_content"  
  7.     android:layout_height="wrap_content"  
  8.     tools:ignore="UselessParent">  
  9.   
  10.     <RelativeLayout  
  11.         android:id="@+id/collapse_view"  
  12.         android:layout_width="wrap_content"  
  13.         android:layout_height="wrap_content"  
  14.         android:orientation="vertical"  
  15.         android:visibility="visible">  
  16.   
  17.         <ImageView  
  18.             android:id="@+id/floating_image"  
  19.             android:layout_width="60dp"  
  20.             android:layout_height="60dp"  
  21.             android:adjustViewBounds="true"  
  22.             android:scaleType="fitXY"  
  23.             android:src="@mipmap/ic_launcher_round" />  
  24.           
  25.     </RelativeLayout>  
  26.   
  27.   
  28. </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.   
  3. import android.Manifest;  
  4. import android.content.ActivityNotFoundException;  
  5. import android.content.BroadcastReceiver;  
  6. import android.content.Context;  
  7. import android.content.Intent;  
  8. import android.content.IntentFilter;  
  9. import android.content.IntentSender;  
  10. import android.content.pm.PackageManager;  
  11. import android.location.Location;  
  12. import android.net.Uri;  
  13. import android.os.Build;  
  14. import android.os.Bundle;  
  15. import android.os.Looper;  
  16. import android.provider.Settings;  
  17. import android.support.annotation.NonNull;  
  18. import android.support.v4.app.ActivityCompat;  
  19. import android.support.v7.app.AppCompatActivity;  
  20. import android.util.Log;  
  21. import android.view.View;  
  22. import android.widget.Button;  
  23. import android.widget.TextView;  
  24. import com.google.android.gms.common.api.ResolvableApiException;  
  25. import com.google.android.gms.location.FusedLocationProviderClient;  
  26. import com.google.android.gms.location.LocationCallback;  
  27. import com.google.android.gms.location.LocationRequest;  
  28. import com.google.android.gms.location.LocationResult;  
  29. import com.google.android.gms.location.LocationServices;  
  30. import com.google.android.gms.location.LocationSettingsRequest;  
  31. import com.google.android.gms.location.LocationSettingsResponse;  
  32. import com.google.android.gms.location.SettingsClient;  
  33. import com.google.android.gms.tasks.OnFailureListener;  
  34. import com.google.android.gms.tasks.OnSuccessListener;  
  35. import com.google.android.gms.tasks.Task;  
  36.   
  37. public class MainActivity extends AppCompatActivity {  
  38.   
  39.     private final static int REQUEST_CODE_LOCATION = 102;  
  40.     private static final int REQUEST_CODE_FOR_OVERLAY_SCREEN = 106;  
  41.     Button mButton;  
  42.     private LocationCallback mLocationCallback;  
  43.     Intent startIntent;  
  44.     String[] permission = {android.Manifest.permission.ACCESS_FINE_LOCATION};  
  45.     private FusedLocationProviderClient mFusedLocationClient;  
  46.     private Location mCurrentLocation;  
  47.   
  48.     String destinationLat = "28.6367764";  
  49.     String destinationLng = "77.4023482";  
  50.   
  51.     TextView latitudeTextView, longitudeTextView;  
  52.   
  53.     GetFloatingIconClick receiver;  
  54.     IntentFilter filter = new IntentFilter();  
  55.   
  56.     private double currentLatitude, currentLongitude;  
  57.   
  58.     @Override  
  59.     protected void onCreate(Bundle savedInstanceState) {  
  60.         super.onCreate(savedInstanceState);  
  61.         setContentView(R.layout.activity_main);  
  62.   
  63.         mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);  
  64.   
  65.         createLocationCallback();  
  66.   
  67.         if (ActivityCompat.checkSelfPermission(MainActivity.this, permission[0]) != PackageManager.PERMISSION_GRANTED) {  
  68.             ActivityCompat.requestPermissions(thisnew String[]{permission[0]}, REQUEST_CODE_LOCATION);  
  69.         } else {  
  70.             getMyLocation();  
  71.         }  
  72.   
  73.         mButton = (Button) findViewById(R.id.button);  
  74.         latitudeTextView = (TextView) findViewById(R.id.latitude_textview);  
  75.         longitudeTextView = (TextView) findViewById(R.id.longitude_textview);  
  76.   
  77.         latitudeTextView.setText("Destination latitude = " + destinationLat);  
  78.         longitudeTextView.setText("Destination longitude = " + destinationLng);  
  79.   
  80.         mButton.setOnClickListener(new View.OnClickListener() {  
  81.             @Override  
  82.             public void onClick(View view) {  
  83.                 try {  
  84.                     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(MainActivity.this)) {  
  85.                         Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,  
  86.                                 Uri.parse("package:" + getPackageName()));  
  87.                         startActivityForResult(intent, REQUEST_CODE_FOR_OVERLAY_SCREEN);  
  88.                     } else {  
  89.                         initializeView();  
  90.                     }  
  91.                 } catch (ActivityNotFoundException e) {  
  92.   
  93.                     Uri gmmIntentUri = Uri.parse("google.navigation:q=" + destinationLat + "," + destinationLng + "&mode=d");  
  94.                     Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri);  
  95.                     mapIntent.setPackage("com.google.android.apps.maps");  
  96.                     startActivity(mapIntent);  
  97.                 }  
  98.   
  99.             }  
  100.         });  
  101.   
  102.     }  
  103.   
  104.     @Override  
  105.     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {  
  106.         switch (requestCode) {  
  107.   
  108.             case REQUEST_CODE_LOCATION:  
  109.   
  110.                 if (grantResults.length > 0  
  111.                         && grantResults[0] == PackageManager.PERMISSION_GRANTED) {  
  112.   
  113.                     if (ActivityCompat.checkSelfPermission(MainActivity.this,  
  114.                             Manifest.permission.ACCESS_FINE_LOCATION)  
  115.                             == PackageManager.PERMISSION_GRANTED) {  
  116.                         getMyLocation();  
  117.                     }  
  118.                 }  
  119.   
  120.   
  121.             default:  
  122.                 break;  
  123.         }  
  124.     }  
  125.   
  126.     private void createLocationCallback() {  
  127.         mLocationCallback = new LocationCallback() {  
  128.             @Override  
  129.             public void onLocationResult(LocationResult locationResult) {  
  130.                 super.onLocationResult(locationResult);  
  131.   
  132.                 mCurrentLocation = locationResult.getLastLocation();  
  133.                 mFusedLocationClient.removeLocationUpdates(mLocationCallback);  
  134.   
  135.                 updateLocationUI(mCurrentLocation.getLatitude(), mCurrentLocation.getLongitude());  
  136.   
  137.             }  
  138.         };  
  139.     }  
  140.   
  141.   
  142.     public void getMyLocation() {  
  143.   
  144.         if (ActivityCompat.checkSelfPermission(MainActivity.this,  
  145.                 Manifest.permission.ACCESS_FINE_LOCATION)  
  146.                 == PackageManager.PERMISSION_GRANTED) {  
  147.   
  148.             final LocationRequest mLocationRequest = new LocationRequest();  
  149.             mLocationRequest.setInterval(10000);  
  150.             mLocationRequest.setFastestInterval(5000);  
  151.             mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);  
  152.   
  153.             LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder()  
  154.                     .addLocationRequest(mLocationRequest);  
  155.             SettingsClient client = LocationServices.getSettingsClient(this);  
  156.             Task<LocationSettingsResponse> task = client.checkLocationSettings(builder.build());  
  157.   
  158.             task.addOnSuccessListener(thisnew OnSuccessListener<LocationSettingsResponse>() {  
  159.                 @Override  
  160.                 public void onSuccess(LocationSettingsResponse locationSettingsResponse) {  
  161.                     // All location settings are satisfied. The client can initialize  
  162.                     // location requests here.  
  163.                     // ...  
  164.                     Log.e("location response", locationSettingsResponse.toString());  
  165.   
  166.                     if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED  
  167.                             && ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_COARSE_LOCATION)  
  168.                             != PackageManager.PERMISSION_GRANTED) {  
  169.                         // TODO: Consider calling  
  170.                         //    ActivityCompat#requestPermissions  
  171.                         // here to request the missing permissions, and then overriding  
  172.                         //   public void onRequestPermissionsResult(int requestCode, String[] permissions,  
  173.                         //                                          int[] grantResults)  
  174.                         // to handle the case where the user grants the permission. See the documentation  
  175.                         // for ActivityCompat#requestPermissions for more details.  
  176.                         return;  
  177.                     }  
  178.                     mFusedLocationClient.requestLocationUpdates(mLocationRequest,  
  179.                             mLocationCallback, Looper.myLooper());  
  180.                 }  
  181.             });  
  182.   
  183.             task.addOnFailureListener(thisnew OnFailureListener() {  
  184.                 @Override  
  185.                 public void onFailure(@NonNull Exception e) {  
  186.                     if (e instanceof ResolvableApiException) {  
  187.                         // Location settings are not satisfied, but this can be fixed  
  188.                         // by showing the user a dialog.  
  189.                         try {  
  190.                             // Show the dialog by calling startResolutionForResult(),  
  191.                             // and check the result in onActivityResult().  
  192.                             ResolvableApiException resolvable = (ResolvableApiException) e;  
  193.                             resolvable.startResolutionForResult(MainActivity.this,  
  194.                                     REQUEST_CODE_LOCATION);  
  195.                         } catch (IntentSender.SendIntentException sendEx) {  
  196.                             // Ignore the error.  
  197.                         }  
  198.                     }  
  199.                 }  
  200.             });  
  201.         }  
  202.   
  203.     }  
  204.   
  205.     private void updateLocationUI(Double lat, Double lng) {  
  206.   
  207.         currentLatitude = lat;  
  208.         currentLongitude = lng;  
  209.   
  210.     }  
  211.   
  212.     private void initializeView() {  
  213.   
  214.         Uri gmmIntentUri = Uri.parse("google.navigation:q=" + destinationLat + "," + destinationLng + "&mode=d");  
  215.         Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri);  
  216.         mapIntent.setPackage("com.google.android.apps.maps");  
  217.         startActivity(mapIntent);  
  218.   
  219.         startIntent = new Intent(MainActivity.this, FloatWidgetService.class);  
  220.         startService(startIntent);  
  221.   
  222.     }  
  223.   
  224.     @Override  
  225.     public void onResume() {  
  226.         super.onResume();  
  227.         receiver = new GetFloatingIconClick();  
  228.         filter.addAction(FloatWidgetService.BROADCAST_ACTION);  
  229.         registerReceiver(receiver, filter);  
  230.   
  231.     }  
  232.   
  233.     private class GetFloatingIconClick extends BroadcastReceiver {  
  234.   
  235.         @Override  
  236.         public void onReceive(Context context, Intent intent) {  
  237.             Intent selfIntent = new Intent(MainActivity.this, MainActivity.class);  
  238.             selfIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP  
  239.                     | Intent.FLAG_ACTIVITY_CLEAR_TOP);  
  240.             startActivity(selfIntent);  
  241.         }  
  242.     }  
  243.   
  244. }  
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.   
  8.     <TextView  
  9.         android:id="@+id/latitude_textview"  
  10.         android:layout_width="wrap_content"  
  11.         android:layout_height="wrap_content"  
  12.         tools:text="Latitude  : "  
  13.         android:layout_alignParentStart="true"  
  14.         android:padding="12dp"/>  
  15.   
  16.     <TextView  
  17.         android:id="@+id/longitude_textview"  
  18.         android:layout_width="wrap_content"  
  19.         android:layout_height="wrap_content"  
  20.         tools:text="Longitude  : "  
  21.         android:padding="12dp"  
  22.         android:layout_below="@id/latitude_textview"/>  
  23.   
  24.   
  25.     <Button  
  26.         android:id="@+id/button"  
  27.         android:layout_width="wrap_content"  
  28.         android:layout_height="wrap_content"  
  29.         android:text="Go to map"  
  30.         android:layout_centerInParent="true" />  
  31.   
  32. </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 the 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.