How to Implement Draggable Map Like Uber Android, Update with Change Location

How to Implement draggable map like uber android, Update with change location

Updated with latest code also included change location with autocomplete

Complete project can be found here

Logic is Simple we need a framelayout and add a map and marker layout inside that framelayout then make maker layout gravity to center and get latitude and longitude of the centre point of the map on camera change
refer this answer
here is my code
create activity_maps.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:orientation="vertical">

<LinearLayout
android:id="@+id/container_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">

<include
android:id="@+id/toolbar"
layout="@layout/toolbar" />

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_marginTop="5dp"
android:orientation="vertical">

<TextView android:gravity="center_vertical"
android:id="@+id/Locality"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawableLeft="@drawable/ic_btn_current_location"
android:drawablePadding="@dimen/list_row_padding"
android:ellipsize="end"
android:padding="10dp"
android:singleLine="true"
android:text="Click to change location"
android:textSize="@dimen/font_22" />

<EditText
android:id="@+id/Address"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:ellipsize="end"
android:enabled="false"
android:hint="Address"
android:singleLine="true" />
</LinearLayout>
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/container_toolbar">

<fragment
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/map"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.sample.sishin.maplocation.MapsActivity" />
<LinearLayout
android:id="@+id/locationMarker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginBottom="30dp"
android:gravity="center"
android:orientation="vertical" >

<TextView
android:id="@+id/locationMarkertext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/rounded_corner_map"
android:gravity="center"
android:minWidth="250dp"
android:paddingLeft="2dp"
android:paddingRight="2dp"
android:text=" Set your Location "
android:textColor="@android:color/white" />

<ImageView
android:id="@+id/imageMarker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/add_marker" />
</LinearLayout>

</FrameLayout>

create TextView backgound xml here rounded_corner_map.xml

<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >

<solid android:color="#000000" />

<stroke
android:width="2dp"
android:color="#FFFFFF" />

<padding
android:bottom="8dp"
android:left="1dp"
android:right="1dp"
android:top="8dp" />

<corners
android:bottomLeftRadius="15dp"
android:bottomRightRadius="15dp"
android:topLeftRadius="15dp"
android:topRightRadius="15dp" />

</shape>

and here is my activity class MainActivity.java

    package com.sample.sishin.maplocation;

import android.Manifest;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Handler;
import android.os.ResultReceiver;
import android.provider.Settings;
import android.support.v4.app.ActivityCompat;
import android.os.Bundle;

import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.android.gms.common.GooglePlayServicesNotAvailableException;
import com.google.android.gms.common.GooglePlayServicesRepairableException;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.location.places.Place;
import com.google.android.gms.location.places.ui.PlaceAutocomplete;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;

public class MapsActivity extends AppCompatActivity implements OnMapReadyCallback, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, com.google.android.gms.location.LocationListener {
private GoogleMap mMap;
private GoogleApiClient mGoogleApiClient;
private final static int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;
private static String TAG = "MAP LOCATION";
Context mContext;
TextView mLocationMarkerText;
private LatLng mCenterLatLong;

/**
* Receiver registered with this activity to get the response from FetchAddressIntentService.
*/
private AddressResultReceiver mResultReceiver;
/**
* The formatted location address.
*/
protected String mAddressOutput;
protected String mAreaOutput;
protected String mCityOutput;
protected String mStateOutput;
EditText mLocationAddress;
TextView mLocationText;
private static final int REQUEST_CODE_AUTOCOMPLETE = 1;
Toolbar mToolbar;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_maps);
mContext = this;
// Obtain the SupportMapFragment and get notified when the map is ready to be used.
SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
.findFragmentById(R.id.map);

mLocationMarkerText = (TextView) findViewById(R.id.locationMarkertext);
mLocationAddress = (EditText) findViewById(R.id.Address);
mLocationText = (TextView) findViewById(R.id.Locality);
mToolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(mToolbar);
getSupportActionBar().setDisplayShowHomeEnabled(true);

getSupportActionBar().setTitle(getResources().getString(R.string.app_name));

mLocationText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {

openAutocompleteActivity();

}

});
mapFragment.getMapAsync(this);
mResultReceiver = new AddressResultReceiver(new Handler());

if (checkPlayServices()) {
// If this check succeeds, proceed with normal processing.
// Otherwise, prompt user to get valid Play Services APK.
if (!AppUtils.isLocationEnabled(mContext)) {
// notify user
AlertDialog.Builder dialog = new AlertDialog.Builder(mContext);
dialog.setMessage("Location not enabled!");
dialog.setPositiveButton("Open location settings", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface paramDialogInterface, int paramInt) {
Intent myIntent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
startActivity(myIntent);
}
});
dialog.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {

@Override
public void onClick(DialogInterface paramDialogInterface, int paramInt) {
// TODO Auto-generated method stub

}
});
dialog.show();
}
buildGoogleApiClient();
} else {
Toast.makeText(mContext, "Location not supported in this device", Toast.LENGTH_SHORT).show();
}

}

/**
* Manipulates the map once available.
* This callback is triggered when the map is ready to be used.
* This is where we can add markers or lines, add listeners or move the camera. In this case,
* we just add a marker near Sydney, Australia.
* If Google Play services is not installed on the device, the user will be prompted to install
* it inside the SupportMapFragment. This method will only be triggered once the user has
* installed Google Play services and returned to the app.
*/
@Override
public void onMapReady(GoogleMap googleMap) {
Log.d(TAG, "OnMapReady");
mMap = googleMap;

mMap.setOnCameraChangeListener(new GoogleMap.OnCameraChangeListener() {
@Override
public void onCameraChange(CameraPosition cameraPosition) {
Log.d("Camera postion change" + "", cameraPosition + "");
mCenterLatLong = cameraPosition.target;

mMap.clear();

try {

Location mLocation = new Location("");
mLocation.setLatitude(mCenterLatLong.latitude);
mLocation.setLongitude(mCenterLatLong.longitude);

startIntentService(mLocation);
mLocationMarkerText.setText("Lat : " + mCenterLatLong.latitude + "," + "Long : " + mCenterLatLong.longitude);

} catch (Exception e) {
e.printStackTrace();
}
}
});
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return;
}
// mMap.setMyLocationEnabled(true);
// mMap.getUiSettings().setMyLocationButtonEnabled(true);
//
// // Add a marker in Sydney and move the camera
// LatLng sydney = new LatLng(-34, 151);
// mMap.addMarker(new MarkerOptions().position(sydney).title("Marker in Sydney"));
// mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney));
}

@Override
public void onConnected(Bundle bundle) {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return;
}
Location mLastLocation = LocationServices.FusedLocationApi.getLastLocation(
mGoogleApiClient);
if (mLastLocation != null) {
changeMap(mLastLocation);
Log.d(TAG, "ON connected");

} else
try {
LocationServices.FusedLocationApi.removeLocationUpdates(
mGoogleApiClient, this);

} catch (Exception e) {
e.printStackTrace();
}
try {
LocationRequest mLocationRequest = new LocationRequest();
mLocationRequest.setInterval(10000);
mLocationRequest.setFastestInterval(5000);
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
LocationServices.FusedLocationApi.requestLocationUpdates(
mGoogleApiClient, mLocationRequest, this);

} catch (Exception e) {
e.printStackTrace();
}

}

@Override
public void onConnectionSuspended(int i) {
Log.i(TAG, "Connection suspended");
mGoogleApiClient.connect();
}

@Override
public void onLocationChanged(Location location) {
try {
if (location != null)
changeMap(location);
LocationServices.FusedLocationApi.removeLocationUpdates(
mGoogleApiClient, this);

} catch (Exception e) {
e.printStackTrace();
}
}

@Override
public void onConnectionFailed(ConnectionResult connectionResult) {

}

protected synchronized void buildGoogleApiClient() {
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
}

@Override
protected void onStart() {
super.onStart();
try {
mGoogleApiClient.connect();

} catch (Exception e) {
e.printStackTrace();
}
}

@Override
protected void onStop() {
super.onStop();
try {

} catch (RuntimeException e) {
e.printStackTrace();
}
if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
mGoogleApiClient.disconnect();
}
}

private boolean checkPlayServices() {
int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
if (resultCode != ConnectionResult.SUCCESS) {
if (GooglePlayServicesUtil.isUserRecoverableError(resultCode)) {
GooglePlayServicesUtil.getErrorDialog(resultCode, this,
PLAY_SERVICES_RESOLUTION_REQUEST).show();
} else {
//finish();
}
return false;
}
return true;
}

private void changeMap(Location location) {

Log.d(TAG, "Reaching map" + mMap);

if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return;
}

// check if map is created successfully or not
if (mMap != null) {
mMap.getUiSettings().setZoomControlsEnabled(false);
LatLng latLong;

latLong = new LatLng(location.getLatitude(), location.getLongitude());

CameraPosition cameraPosition = new CameraPosition.Builder()
.target(latLong).zoom(19f).tilt(70).build();

mMap.setMyLocationEnabled(true);
mMap.getUiSettings().setMyLocationButtonEnabled(true);
mMap.animateCamera(CameraUpdateFactory
.newCameraPosition(cameraPosition));

mLocationMarkerText.setText("Lat : " + location.getLatitude() + "," + "Long : " + location.getLongitude());
startIntentService(location);

} else {
Toast.makeText(getApplicationContext(),
"Sorry! unable to create maps", Toast.LENGTH_SHORT)
.show();
}

}

/**
* Receiver for data sent from FetchAddressIntentService.
*/
class AddressResultReceiver extends ResultReceiver {
public AddressResultReceiver(Handler handler) {
super(handler);
}

/**
* Receives data sent from FetchAddressIntentService and updates the UI in MainActivity.
*/
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {

// Display the address string or an error message sent from the intent service.
mAddressOutput = resultData.getString(AppUtils.LocationConstants.RESULT_DATA_KEY);

mAreaOutput = resultData.getString(AppUtils.LocationConstants.LOCATION_DATA_AREA);

mCityOutput = resultData.getString(AppUtils.LocationConstants.LOCATION_DATA_CITY);
mStateOutput = resultData.getString(AppUtils.LocationConstants.LOCATION_DATA_STREET);

displayAddressOutput();

// Show a toast message if an address was found.
if (resultCode == AppUtils.LocationConstants.SUCCESS_RESULT) {
// showToast(getString(R.string.address_found));

}

}

}

/**
* Updates the address in the UI.
*/
protected void displayAddressOutput() {
// mLocationAddressTextView.setText(mAddressOutput);
try {
if (mAreaOutput != null)
// mLocationText.setText(mAreaOutput+ "");

mLocationAddress.setText(mAddressOutput);
//mLocationText.setText(mAreaOutput);
} catch (Exception e) {
e.printStackTrace();
}
}

/**
* Creates an intent, adds location data to it as an extra, and starts the intent service for
* fetching an address.
*/
protected void startIntentService(Location mLocation) {
// Create an intent for passing to the intent service responsible for fetching the address.
Intent intent = new Intent(this, FetchAddressIntentService.class);

// Pass the result receiver as an extra to the service.
intent.putExtra(AppUtils.LocationConstants.RECEIVER, mResultReceiver);

// Pass the location data as an extra to the service.
intent.putExtra(AppUtils.LocationConstants.LOCATION_DATA_EXTRA, mLocation);

// Start the service. If the service isn't already running, it is instantiated and started
// (creating a process for it if needed); if it is running then it remains running. The
// service kills itself automatically once all intents are processed.
startService(intent);
}

private void openAutocompleteActivity() {
try {
// The autocomplete activity requires Google Play Services to be available. The intent
// builder checks this and throws an exception if it is not the case.
Intent intent = new PlaceAutocomplete.IntentBuilder(PlaceAutocomplete.MODE_FULLSCREEN)
.build(this);
startActivityForResult(intent, REQUEST_CODE_AUTOCOMPLETE);
} catch (GooglePlayServicesRepairableException e) {
// Indicates that Google Play Services is either not installed or not up to date. Prompt
// the user to correct the issue.
GoogleApiAvailability.getInstance().getErrorDialog(this, e.getConnectionStatusCode(),
0 /* requestCode */).show();
} catch (GooglePlayServicesNotAvailableException e) {
// Indicates that Google Play Services is not available and the problem is not easily
// resolvable.
String message = "Google Play Services is not available: " +
GoogleApiAvailability.getInstance().getErrorString(e.errorCode);

Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show();
}
}

/**
* Called after the autocomplete activity has finished to return its result.
*/
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);

// Check that the result was from the autocomplete widget.
if (requestCode == REQUEST_CODE_AUTOCOMPLETE) {
if (resultCode == RESULT_OK) {
// Get the user's selected place from the Intent.
Place place = PlaceAutocomplete.getPlace(mContext, data);

// TODO call location based filter

LatLng latLong;

latLong = place.getLatLng();

//mLocationText.setText(place.getName() + "");

CameraPosition cameraPosition = new CameraPosition.Builder()
.target(latLong).zoom(19f).tilt(70).build();

if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return;
}
mMap.setMyLocationEnabled(true);
mMap.animateCamera(CameraUpdateFactory
.newCameraPosition(cameraPosition));

}

} else if (resultCode == PlaceAutocomplete.RESULT_ERROR) {
Status status = PlaceAutocomplete.getStatus(mContext, data);
} else if (resultCode == RESULT_CANCELED) {
// Indicates that the activity closed before a selection was made. For example if
// the user pressed the back button.
}
}

}

And manifest file is

    <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.sample.sishin.maplocation">

<!--
The ACCESS_COARSE/FINE_LOCATION permissions are not required to use
Google Maps Android API v2, but you must specify either coarse or fine
location permissions for the 'MyLocation' functionality.
-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">

<!--
The API key for Google Maps-based APIs is defined as a string resource.
(See the file "res/values/google_maps_api.xml").
Note that the API key is linked to the encryption key used to sign the APK.
You need a different API key for each encryption key, including the release key that is used to
sign the APK for publishing.
You can define the keys for the debug and release targets in src/debug/ and src/release/.
-->

<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="your key here" />

<activity
android:name="com.sample.sishin.maplocation.MapsActivity"
android:label="@string/title_activity_maps">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name="com.sample.sishin.maplocation.FetchAddressIntentService"
android:exported="false" />

</application>

</manifest>

Sample Image

How to listen to custom marker location change in Android?

I think you can use FrameLayout to have the overlap effect for your map and markers and then call the onCameraChange as well as Geocoder function.

Sample code:

<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="${relativePackage}.${activityClass}" >

<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >

<fragment
android:id="@+id/map"
android:name="com.google.android.gms.maps.MapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />

<LinearLayout
android:id="@+id/locationMarker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginBottom="30dp"
android:gravity="center"
android:orientation="vertical" >

<TextView
android:id="@+id/locationMarkertext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/rounded_corner_map"
android:gravity="center"
android:minWidth="180dp"
android:paddingLeft="2dp"
android:paddingRight="2dp"
android:text=" Set your Location "
android:textColor="@android:color/white" />

<ImageView
android:id="@+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/add_marker" />
</LinearLayout>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:background="@android:color/white"
android:orientation="vertical" >

<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="Selected Location"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="#28b54c" />

<TextView
android:id="@+id/adressText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:ellipsize="end"
android:singleLine="true"
android:text="Getting location"
android:textSize="16sp" />
</LinearLayout>
</FrameLayout>

</RelativeLayout>

For more details, please refer here.

Creating google maps v2 in a fragment using Navigation Drawer

You have did small mistake i think, Check out OnResume Method:

@Override
public void onResume() {
super.onResume();
initilizeMap();
if (googleMap != null) {

}
}

Change this to,

@Override
public void onResume() {
super.onResume();

if (googleMap != null) {
initilizeMap();
}
}


Related Topics



Leave a reply



Submit