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.
- apply plugin: 'com.android.application'
-
- android {
- compileSdkVersion 28
- defaultConfig {
- applicationId "yourdomain.floatingwidgetexample"
- minSdkVersion 21
- targetSdkVersion 28
- versionCode 1
- versionName "1.0"
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
- }
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
- }
- }
- }
-
- dependencies {
- implementation fileTree(dir: 'libs', include: ['*.jar'])
- implementation 'com.android.support:appcompat-v7:28.0.0'
- implementation 'com.android.support.constraint:constraint-layout:1.1.3'
- testImplementation 'junit:junit:4.12'
- androidTestImplementation 'com.android.support.test:runner:1.0.2'
- androidTestImplementation 'com.android.support.test.espresso:espresso-
- core:3.0.2'
-
- implementation 'com.google.android.gms:play-services-location:15.0.1'
-
- }
Step 3
Add permissions in the manifest file. Let us see the manifest.xml file.
- <?xml version="1.0" encoding="utf-8"?>
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- package="yourdomain.floatingwidgetexample">
-
- <uses-permission android:name="android.permission.INTERNET"/>
- <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
- <uses-permission android:name="android.permission.ACTION_MANAGE_OVERLAY_PERMISSION" />
- <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
-
- <application
- android:allowBackup="false"
- android:icon="@mipmap/ic_launcher"
- android:label="@string/app_name"
- android:roundIcon="@mipmap/ic_launcher_round"
- android:supportsRtl="true"
- android:theme="@style/AppTheme"
- tools:ignore="GoogleAppIndexingWarning">
- <activity android:name=".MainActivity">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
-
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
-
- <service android:name=".FloatWidgetService" />
- </application>
-
- </manifest>
Step 4
Create a service named FloatWidgetService.java. See the code below.
- public class FloatWidgetService extends Service {
-
- private WindowManager mWindowManager;
- private View mFloatingWidget;
- public static final String BROADCAST_ACTION = "magicbox";
- private static final int MAX_CLICK_DURATION = 200;
- private long startClickTime;
-
- public FloatWidgetService() {
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- return START_STICKY;
- }
-
- @Override
- public void onCreate() {
- super.onCreate();
- mFloatingWidget = LayoutInflater.from(this).inflate(R.layout.layout_floating_widget, null);
-
- final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
- WindowManager.LayoutParams.WRAP_CONTENT,
- WindowManager.LayoutParams.WRAP_CONTENT,
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
- ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
- : WindowManager.LayoutParams.TYPE_PHONE,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
- PixelFormat.TRANSLUCENT);
-
- params.gravity = Gravity.TOP | Gravity.LEFT;
- params.x = 0;
- params.y = 100;
-
- mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
- mWindowManager.addView(mFloatingWidget, params);
-
-
-
-
-
-
-
-
-
-
-
-
-
- mFloatingWidget.findViewById(R.id.root_container).setOnTouchListener(new View.OnTouchListener() {
- private int initialX;
- private int initialY;
- private float initialTouchX;
- private float initialTouchY;
-
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- initialX = params.x;
- initialY = params.y;
- initialTouchX = event.getRawX();
- initialTouchY = event.getRawY();
- startClickTime = Calendar.getInstance().getTimeInMillis();
- return false;
- case MotionEvent.ACTION_UP:
- int Xdiff = (int) (event.getRawX() - initialTouchX);
- int Ydiff = (int) (event.getRawY() - initialTouchY);
-
- long clickDuration = Calendar.getInstance().getTimeInMillis() - startClickTime;
-
- if (clickDuration < MAX_CLICK_DURATION) {
-
- Intent intent = new Intent(BROADCAST_ACTION);
- sendBroadcast(intent);
- stopSelf();
-
- }
-
- return false;
- case MotionEvent.ACTION_MOVE:
- params.x = initialX + (int) (event.getRawX() - initialTouchX);
- params.y = initialY + (int) (event.getRawY() - initialTouchY);
- mWindowManager.updateViewLayout(mFloatingWidget, params);
- return false;
- }
- return false;
- }
-
- });
-
-
- }
-
-
- @Override
- public void onDestroy() {
-
- if (mFloatingWidget != null) mWindowManager.removeView(mFloatingWidget);
- super.onDestroy();
- }
-
-
- }
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
- <?xml version="1.0" encoding="utf-8"?>
-
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:id="@+id/root_container"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- tools:ignore="UselessParent">
-
- <RelativeLayout
- android:id="@+id/collapse_view"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:visibility="visible">
-
- <ImageView
- android:id="@+id/floating_image"
- android:layout_width="60dp"
- android:layout_height="60dp"
- android:adjustViewBounds="true"
- android:scaleType="fitXY"
- android:src="@mipmap/ic_launcher_round" />
-
- </RelativeLayout>
-
-
- </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
- package yourdomain.floatingwidgetexample;
-
- import android.Manifest;
- import android.content.ActivityNotFoundException;
- import android.content.BroadcastReceiver;
- import android.content.Context;
- import android.content.Intent;
- import android.content.IntentFilter;
- import android.content.IntentSender;
- import android.content.pm.PackageManager;
- import android.location.Location;
- import android.net.Uri;
- import android.os.Build;
- import android.os.Bundle;
- import android.os.Looper;
- import android.provider.Settings;
- import android.support.annotation.NonNull;
- import android.support.v4.app.ActivityCompat;
- import android.support.v7.app.AppCompatActivity;
- import android.util.Log;
- import android.view.View;
- import android.widget.Button;
- import android.widget.TextView;
- import com.google.android.gms.common.api.ResolvableApiException;
- import com.google.android.gms.location.FusedLocationProviderClient;
- import com.google.android.gms.location.LocationCallback;
- import com.google.android.gms.location.LocationRequest;
- import com.google.android.gms.location.LocationResult;
- import com.google.android.gms.location.LocationServices;
- import com.google.android.gms.location.LocationSettingsRequest;
- import com.google.android.gms.location.LocationSettingsResponse;
- import com.google.android.gms.location.SettingsClient;
- import com.google.android.gms.tasks.OnFailureListener;
- import com.google.android.gms.tasks.OnSuccessListener;
- import com.google.android.gms.tasks.Task;
-
- public class MainActivity extends AppCompatActivity {
-
- private final static int REQUEST_CODE_LOCATION = 102;
- private static final int REQUEST_CODE_FOR_OVERLAY_SCREEN = 106;
- Button mButton;
- private LocationCallback mLocationCallback;
- Intent startIntent;
- String[] permission = {android.Manifest.permission.ACCESS_FINE_LOCATION};
- private FusedLocationProviderClient mFusedLocationClient;
- private Location mCurrentLocation;
-
- String destinationLat = "28.6367764";
- String destinationLng = "77.4023482";
-
- TextView latitudeTextView, longitudeTextView;
-
- GetFloatingIconClick receiver;
- IntentFilter filter = new IntentFilter();
-
- private double currentLatitude, currentLongitude;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
-
- mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
-
- createLocationCallback();
-
- if (ActivityCompat.checkSelfPermission(MainActivity.this, permission[0]) != PackageManager.PERMISSION_GRANTED) {
- ActivityCompat.requestPermissions(this, new String[]{permission[0]}, REQUEST_CODE_LOCATION);
- } else {
- getMyLocation();
- }
-
- mButton = (Button) findViewById(R.id.button);
- latitudeTextView = (TextView) findViewById(R.id.latitude_textview);
- longitudeTextView = (TextView) findViewById(R.id.longitude_textview);
-
- latitudeTextView.setText("Destination latitude = " + destinationLat);
- longitudeTextView.setText("Destination longitude = " + destinationLng);
-
- mButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- try {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(MainActivity.this)) {
- Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
- Uri.parse("package:" + getPackageName()));
- startActivityForResult(intent, REQUEST_CODE_FOR_OVERLAY_SCREEN);
- } else {
- initializeView();
- }
- } catch (ActivityNotFoundException e) {
-
- Uri gmmIntentUri = Uri.parse("google.navigation:q=" + destinationLat + "," + destinationLng + "&mode=d");
- Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri);
- mapIntent.setPackage("com.google.android.apps.maps");
- startActivity(mapIntent);
- }
-
- }
- });
-
- }
-
- @Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
- switch (requestCode) {
-
- case REQUEST_CODE_LOCATION:
-
- if (grantResults.length > 0
- && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
-
- if (ActivityCompat.checkSelfPermission(MainActivity.this,
- Manifest.permission.ACCESS_FINE_LOCATION)
- == PackageManager.PERMISSION_GRANTED) {
- getMyLocation();
- }
- }
-
-
- default:
- break;
- }
- }
-
- private void createLocationCallback() {
- mLocationCallback = new LocationCallback() {
- @Override
- public void onLocationResult(LocationResult locationResult) {
- super.onLocationResult(locationResult);
-
- mCurrentLocation = locationResult.getLastLocation();
- mFusedLocationClient.removeLocationUpdates(mLocationCallback);
-
- updateLocationUI(mCurrentLocation.getLatitude(), mCurrentLocation.getLongitude());
-
- }
- };
- }
-
-
- public void getMyLocation() {
-
- if (ActivityCompat.checkSelfPermission(MainActivity.this,
- Manifest.permission.ACCESS_FINE_LOCATION)
- == PackageManager.PERMISSION_GRANTED) {
-
- final LocationRequest mLocationRequest = new LocationRequest();
- mLocationRequest.setInterval(10000);
- mLocationRequest.setFastestInterval(5000);
- mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
-
- LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder()
- .addLocationRequest(mLocationRequest);
- SettingsClient client = LocationServices.getSettingsClient(this);
- Task<LocationSettingsResponse> task = client.checkLocationSettings(builder.build());
-
- task.addOnSuccessListener(this, new OnSuccessListener<LocationSettingsResponse>() {
- @Override
- public void onSuccess(LocationSettingsResponse locationSettingsResponse) {
-
-
-
- Log.e("location response", locationSettingsResponse.toString());
-
- if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
- && ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_COARSE_LOCATION)
- != PackageManager.PERMISSION_GRANTED) {
-
-
-
-
-
-
-
- return;
- }
- mFusedLocationClient.requestLocationUpdates(mLocationRequest,
- mLocationCallback, Looper.myLooper());
- }
- });
-
- task.addOnFailureListener(this, new OnFailureListener() {
- @Override
- public void onFailure(@NonNull Exception e) {
- if (e instanceof ResolvableApiException) {
-
-
- try {
-
-
- ResolvableApiException resolvable = (ResolvableApiException) e;
- resolvable.startResolutionForResult(MainActivity.this,
- REQUEST_CODE_LOCATION);
- } catch (IntentSender.SendIntentException sendEx) {
-
- }
- }
- }
- });
- }
-
- }
-
- private void updateLocationUI(Double lat, Double lng) {
-
- currentLatitude = lat;
- currentLongitude = lng;
-
- }
-
- private void initializeView() {
-
- Uri gmmIntentUri = Uri.parse("google.navigation:q=" + destinationLat + "," + destinationLng + "&mode=d");
- Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri);
- mapIntent.setPackage("com.google.android.apps.maps");
- startActivity(mapIntent);
-
- startIntent = new Intent(MainActivity.this, FloatWidgetService.class);
- startService(startIntent);
-
- }
-
- @Override
- public void onResume() {
- super.onResume();
- receiver = new GetFloatingIconClick();
- filter.addAction(FloatWidgetService.BROADCAST_ACTION);
- registerReceiver(receiver, filter);
-
- }
-
- private class GetFloatingIconClick extends BroadcastReceiver {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- Intent selfIntent = new Intent(MainActivity.this, MainActivity.class);
- selfIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP
- | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- startActivity(selfIntent);
- }
- }
-
- }
activity_main.xml
- <?xml version="1.0" encoding="utf-8"?>
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context=".MainActivity">
-
- <TextView
- android:id="@+id/latitude_textview"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- tools:text="Latitude : "
- android:layout_alignParentStart="true"
- android:padding="12dp"/>
-
- <TextView
- android:id="@+id/longitude_textview"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- tools:text="Longitude : "
- android:padding="12dp"
- android:layout_below="@id/latitude_textview"/>
-
-
- <Button
- android:id="@+id/button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Go to map"
- android:layout_centerInParent="true" />
-
- </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.
Now, after 1-2 seconds (or immediately), the permission model will arrive.
Allow it and click on the "Go To MAP" button. You will be redirected to some other permissions and then the map.
Toggle the button you are seeing above to give the permission of overlaying on screen and you will see the next screen as below.
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.
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.
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.