مستندات جامع اندروید
نسخه 1.0.3
هدف از این بخش از پروژه، نمایش یک نشانگر بر روی موقعیت فعلی کاربر و بهروزرسانی مکان نمایشگر با تغییر موقعیت جغرافیایی کاربر است.
فهرست مطالب این صفحه
خطوط زیر را به وابستگیهای فایل build.gradle (Module.app)
اضافه کنید:
//Play Services
implementation 'com.google.android.gms:play-services-gcm:17.0.0'
implementation 'com.google.android.gms:play-services-location:21.0.1'
AndroidManifest.xml
دسترسیهای زیر را برای به دست آوردن موقعیت جغرافیایی به این فایل اضافه کنید:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
activity_user_location.xml
:
در این صفحه علاوه بر المان نقشه نشان، یک FloatingActionButton
اضافه شدهاست که با هر بار کلیک بر روی آن، متد focusOnUserLocation
صدا زده میشود. این متد در ادامه توضیح داده خواهد شد.
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".UserLocation">
<org.neshan.mapsdk.MapView
android:id="@+id/map"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<android.support.design.widget.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:fabSize="mini"
android:src="@drawable/ic_my_location"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:onClick="focusOnUserLocation"/>
</android.support.constraint.ConstraintLayout>
UserLocation.java
:
ثابتهای جدیدی که در طول برنامه استفاده خواهد شد در این بخش تعریف شدهاست.
ثابت REQUEST_CODE
برای دنبال کردن درخواست مجوز (permission) است و مقدار آن به دلخواه برابر با ۱۲۳ در نظر گرفته شدهاست.
ثابت UPDATE_INTERVAL_IN_MILLISECONDS
فاصله دورههای زمانی جهت درخواست موقعیت جدید را مشخص میکند. بسته به نیاز خود میتوانید مقدار این ثابت را کم یا زیاد کنید.
// used to track request permissions
final int REQUEST_CODE = 123;
// location updates interval - 1 sec
private static final long UPDATE_INTERVAL_IN_MILLISECONDS = 1000;
موقعیت فعلی کاربر در متغیر userLocation
ذخیره میشود.
برای به دست آوردن موقعیت مکانی، از یک شی از نوع FusedLocationProviderClient
استفاده میشود که توضیحات مربوط به این کلاس را میتوانید از اینجا بخوانید:
سایر متغیرهایی که برای دریافت موقعیت کاربر استفاده میشوند، متغیرهایی از کلاسهای SettingsClient
، LocationRequest
، LocationSettingsRequest
و LocationCallback
هستند.
آخرین زمان بهروزرسانی موقعیت فعلی کاربر در متغیر lastUpdateTime
– که از نوع String
است ذخیره میشود. و در آخر متغیر marker
که نشانگر اضافه شده بر روی نقشه در آن نگهداری میشود تا در صورت نیاز امکان حذف یا ویرایش آن وجود داشته باشد.
// User's current location
private Location userLocation;
private FusedLocationProviderClient fusedLocationClient;
private SettingsClient settingsClient;
private LocationRequest locationRequest;
private LocationSettingsRequest locationSettingsRequest;
private LocationCallback locationCallback;
private String lastUpdateTime;
// boolean flag to toggle the ui
private Boolean mRequestingLocationUpdates;
private Marker marker;
متد initLayoutRefrences
جهت مقداردهی اولیه کردن به تمامی المانهای مربوط به رابط کاربری نوشته شدهاست. به دلیل این که لازم است تا المان اندرویدی نقشه نشان ابتدا به طور کامل ایجاد شود و سپس با آن تعامل برقرار شود (متدهای مختلف بر روی المان نقشه اجرا شود)، تمامی متدهای مربوط به رابط کاربری باید در متد onStart
انجام شوند.
در متد onStart
و پس از مقداردهی اولیه المانهای مربوط به رابط کاربری، دو متد initLocation
و startReceivingLocationUpdates
صدا زده میشوند که در ادامه کد، نقش هر یک توضیح داده خواهد شد.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// starting app in full screen
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_user_location);
}
@Override
protected void onStart() {
super.onStart();
// everything related to ui is initialized here
initLayoutReferences();
initLocation();
startReceivingLocationUpdates();
}
در متد onPause
، متد stopLocationUpdates
صدا زده میشود و دریافت موقعیت کاربر متوقف میشود.
@Override
protected void onPause() {
super.onPause();
stopLocationUpdates();
}
در این متد پس از مقداردهی اولیه Viewها و نقشه نشان، موقعیت مکانی مقداردهی اولیه می شود.
// Initializing layout references (views, map and map events)
private void initLayoutReferences() {
// Initializing views
initViews();
// Initializing mapView element
initMap();
}
برای مقداردهی اولیه کردن موقعیت مکانی، متد getFusedLocationProviderClient
از کلاس LocationServices
صدا زده میشود. متغیر settingsClient
با استفاده از متد getSettingsClient
از کلاس LocationServices
مقداردهی میشود. کاربرد این متغیر در متدهای بعدی توضیح داده خواهد شد.
متغیر locationCallback
با یک شی جدید ایجاد شده از کلاس LocationCallback
مقداردهی میشود. متد onLocationResult
این کلاس Override شده است و در این متد آخرین موقعیت کاربر در userLocation
و زمان فعلی (به عنوان آخرین زمان دریافت موقعیت کاربر) در متغیر lastUpdateTime
ذخیره میشود. در نهایت متد onLocationChange
– که مجموعه اتفاقاتی است که با هر بار تغییر موقعیت کاربر باید انجام شود – صدا زده میشود.
در ادامه دوره زمانی دریافت مجدد موقعیت کاربر، حداقل زمان بهروزرسانی و اولویت این کاربر با استفاده از صدا زدن متدهای مربوطه بر روی locationRequest
تنظیم میشود و شی locationSettingsRequest
از روی آن ساخته میشود.
private void initLocation() {
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
settingsClient = LocationServices.getSettingsClient(this);
locationCallback = new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
super.onLocationResult(locationResult);
// location is received
userLocation = locationResult.getLastLocation();
lastUpdateTime = DateFormat.getTimeInstance().format(new Date());
onLocationChange();
}
};
mRequestingLocationUpdates = false;
if (locationRequest == null) {
locationRequest = new LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, UPDATE_INTERVAL_IN_MILLISECONDS).build();
LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder();
builder.addLocationRequest(locationRequest);
locationSettingsRequest = builder.build();
}
}
متد startReceivingLocationUpdates
ابتدا چک میکند که ورژن اندروید دستگاه کاربر 6 و بالاتر از آن است یا خیر، در صورتی که بود، چک میکند که آیا دسترسی به 2 مجوز ACCESS_FINE_LOCATION و ACCESS_COARSE_LOCATION وجود دارد یا خیر، در صورتی که وجود داشت، متد startLocationUpdates
صدا زده میشود. در غیر اینصورت درخواست دسترسی مجوز این 2 مجوز ارسال میشود.
public void startReceivingLocationUpdates() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission(UserLocation.this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(UserLocation.this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
mRequestingLocationUpdates = true;
startLocationUpdates();
} else {
requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION}, REQUEST_CODE);
}
} else {
mRequestingLocationUpdates = true;
startLocationUpdates();
}
}
نتیجه درخواست مجوز در این متد بررسی شده و در صورتی که مجوز توسط کاربر داده شود، مقدار mRequestingLocationUpdates
برابر با true
میشود و متد startLocationUpdates
صدا زده میشود شد.
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CODE) {
if (ContextCompat.checkSelfPermission(UserLocation.this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(UserLocation.this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
mRequestingLocationUpdates = true;
startLocationUpdates();
}
}
}
در متد startLocationUpdates
بررسی میشود که اگر تنظیمات تعریف شده در locationSettingsRequest
برقرار باشد، متد requestLocationUpdates
بر روی fusedLocationClient
صدا زده میشود و سپس متد onLocationChange
صدا زده میشود. در غیر این صورت با بررسی کد خطا، رفتار مناسب انجام میشود (کدهای داخل متد onFailure
و کامنتهای نوشته شده را دنبال کنید)
/**
* Starting location updates
* Check whether location settings are satisfied and then
* location updates will be requested
*/
private void startLocationUpdates() {
settingsClient
.checkLocationSettings(locationSettingsRequest)
.addOnSuccessListener(this, new OnSuccessListener<LocationSettingsResponse>() {
@SuppressLint("MissingPermission")
@Override
public void onSuccess(LocationSettingsResponse locationSettingsResponse) {
Log.i(TAG, "All location settings are satisfied.");
if (ContextCompat.checkSelfPermission(UserLocation.this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(UserLocation.this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
Log.d("UserLocationUpdater", " required permissions are not granted ");
return;
}
fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.myLooper());
}
})
.addOnFailureListener(this, new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
int statusCode = ((ApiException) e).getStatusCode();
switch (statusCode) {
case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
try {
Log.i(TAG, "Location settings are not satisfied. Attempting to upgrade location settings");
// Show the dialog by calling startResolutionForResult(), and check the
// result in onActivityResult().
ResolvableApiException rae = (ResolvableApiException) e;
rae.startResolutionForResult(UserLocation.this, REQUEST_CODE);
} catch (IntentSender.SendIntentException sie) {
Log.i(TAG, "PendingIntent unable to execute request.");
}
break;
case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
String errorMessage = "Location settings are inadequate, and cannot be fixed here. Fix in Settings.";
Log.e(TAG, errorMessage);
Toast.makeText(UserLocation.this, errorMessage, Toast.LENGTH_LONG).show();
}
}
});
}
onActivityResult
صدا زده میشود و در این متد با توجه به نتیجه درخواست که RESULT_OK است، متد startLocationUpdates
صدا زده میشود. در غیر اینصورت متغیر mRequestingLocationUpdates
برابر مقدار false
خواهد شد.
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
// Check for the integer request code originally supplied to startResolutionForResult().
case REQUEST_CODE:
switch (resultCode) {
case Activity.RESULT_OK:
Log.e(TAG, "User agreed to make required location settings changes.");
mRequestingLocationUpdates = true;
startLocationUpdates();
break;
case Activity.RESULT_CANCELED:
Log.e(TAG, "User choose not to make required location settings changes.");
mRequestingLocationUpdates = false;
break;
}
break;
}
}
در این متد، متد removeLocationUpdates
بر روی fusedLocationClient
صدا زده شده و بهروزرسانی موقعیت کاربر متوقف میشود.
public void stopLocationUpdates() {
// Removing location updates
fusedLocationClient
.removeLocationUpdates(locationCallback)
.addOnCompleteListener(this, new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
Toast.makeText(getApplicationContext(), "Location updates stopped!", Toast.LENGTH_SHORT).show();
}
});
}
در صورتی که این متد صدا زده شود و userLocation
به درستی مقدار گرفته باشد، متد addUserMarker
با موقعیت جدید کاربر صدا زده میشود. سپس دوربین روی موقعیت کاربر قرار میگیرد.
private void onLocationChange() {
if (userLocation != null) {
addUserMarker(new LatLng(userLocation.getLatitude(), userLocation.getLongitude()));
map.moveCamera(new LatLng(userLocation.getLatitude(), userLocation.getLongitude()), .5f);
}
}
متد addUserMarker
یک نشانگر در موقعیت فعلی کاربر نمایش میدهد. این متد دقیقا مشابه با متد addMarker
که در اضافه کردن نشانگر توضیح دادهشده است میباشد.
// This method gets a LngLat as input and adds a marker on that position
private void addUserMarker(LatLng loc){
//remove existing marker from map
if(marker!=null){
map.removeMarker(marker);
}
// Creating marker style. We should use an object of type MarkerStyleCreator, set all features on it
// and then call buildStyle method on it. This method returns an object of type MarkerStyle
MarkerStyleBuilder markStCr = new MarkerStyleBuilder();
markStCr.setSize(30f);
markStCr.setBitmap(BitmapUtils.createBitmapFromAndroidBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.ic_marker)));
MarkerStyle markSt = markStCr.buildStyle();
// Creating user marker
marker = new Marker(loc, markSt);
// Adding user marker to map!
map.addMarker(marker);
}
با هر بار لمس دکمه موجود در رابط کاربری، در صورتی که متغیر userLocation
مقداردهی شده باشد (موقعیت کاربر دریافت شده باشد) دوربین بر روی موقعیت کاربر متمرکز میشود. در غیر اینصورت متد startReceivingLocationUpdates
صدا زده میشود.
public void focusOnUserLocation(View view) {
if (userLocation != null) {
map.moveCamera(new LatLng(userLocation.getLatitude(), userLocation.getLongitude()), 0.25f);
map.setZoom(15, 0.25f);
} else {
startReceivingLocationUpdates();
}
}
نمایش محدوده تقریبی کاربر
زمانی که از موقعیت تقریبی کاربر (ACCESS_COARSE_LOCATION) برای نمایش موقعیت کاربر استفاده میکنید، همانطور که از نامش پیداست، موقعیتی که روی نقشه نمایش داده میشود تقریبی است. برای نمایش محدوده تقریبی کاربر میتوانید از متد زیر استفاده نمایید:
map.showAccuracyCircle(userLocation);
پارامتر ورودی این متد از نوع Location است که طبق توضیحات بالا باید متغیر userLocation به آن پاس داده شود. طبق مثال بالا این متد باید در متد onLocationChange صدا زده شود.
نمایش جهت کاربر
برای نمایش جهت ایستادن کاربر روی نقشه میتوانید از متد زیر استفاده نمایید:
map.enableUserMarkerRotation(marker);
نکته: متد بالا باید پس از افزودن مارکر کاربر به نقشه صدا زده شود.
نسخه 1.0.2
هدف از این بخش از پروژه، نمایش یک نشانگر بر روی موقعیت فعلی کاربر و بهروزرسانی مکان نمایشگر با تغییر موقعیت جغرافیایی کاربر است.
خطوط زیر را به وابستگیهای فایل build.gradle (Module.app)
اضافه کنید:
//Play Services
implementation 'com.google.android.gms:play-services-gcm:17.0.0'
implementation 'com.google.android.gms:play-services-location:21.0.1'
AndroidManifest.xml
دسترسیهای زیر را برای به دست آوردن موقعیت جغرافیایی به این فایل اضافه کنید:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
activity_user_location.xml
:
در این صفحه علاوه بر المان نقشه نشان، یک FloatingActionButton
اضافه شدهاست که با هر بار کلیک بر روی آن، متد focusOnUserLocation
صدا زده میشود. این متد در ادامه توضیح داده خواهد شد.
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".UserLocation">
<org.neshan.mapsdk.MapView
android:id="@+id/map"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<android.support.design.widget.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:fabSize="mini"
android:src="@drawable/ic_my_location"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:onClick="focusOnUserLocation"/>
</android.support.constraint.ConstraintLayout>
UserLocation.java
:
ثابتهای جدیدی که در طول برنامه استفاده خواهد شد در این بخش تعریف شدهاست.
ثابت REQUEST_CODE
برای دنبال کردن درخواست مجوز (permission) است و مقدار آن به دلخواه برابر با ۱۲۳ در نظر گرفته شدهاست.
ثابت UPDATE_INTERVAL_IN_MILLISECONDS
فاصله دورههای زمانی جهت درخواست موقعیت جدید را مشخص میکند. بسته به نیاز خود میتوانید مقدار این ثابت را کم یا زیاد کنید.
// used to track request permissions
final int REQUEST_CODE = 123;
// location updates interval - 1 sec
private static final long UPDATE_INTERVAL_IN_MILLISECONDS = 1000;
موقعیت فعلی کاربر در متغیر userLocation
ذخیره میشود.
برای به دست آوردن موقعیت مکانی، از یک شی از نوع FusedLocationProviderClient
استفاده میشود که توضیحات مربوط به این کلاس را میتوانید از اینجا بخوانید:
سایر متغیرهایی که برای دریافت موقعیت کاربر استفاده میشوند، متغیرهایی از کلاسهای SettingsClient
، LocationRequest
، LocationSettingsRequest
و LocationCallback
هستند.
آخرین زمان بهروزرسانی موقعیت فعلی کاربر در متغیر lastUpdateTime
– که از نوع String
است ذخیره میشود. و در آخر متغیر marker
که نشانگر اضافه شده بر روی نقشه در آن نگهداری میشود تا در صورت نیاز امکان حذف یا ویرایش آن وجود داشته باشد.
// User's current location
private Location userLocation;
private FusedLocationProviderClient fusedLocationClient;
private SettingsClient settingsClient;
private LocationRequest locationRequest;
private LocationSettingsRequest locationSettingsRequest;
private LocationCallback locationCallback;
private String lastUpdateTime;
// boolean flag to toggle the ui
private Boolean mRequestingLocationUpdates;
private Marker marker;
متد initLayoutRefrences
جهت مقداردهی اولیه کردن به تمامی المانهای مربوط به رابط کاربری نوشته شدهاست. به دلیل این که لازم است تا المان اندرویدی نقشه نشان ابتدا به طور کامل ایجاد شود و سپس با آن تعامل برقرار شود (متدهای مختلف بر روی المان نقشه اجرا شود)، تمامی متدهای مربوط به رابط کاربری باید در متد onStart
انجام شوند.
در متد onStart
و پس از مقداردهی اولیه المانهای مربوط به رابط کاربری، دو متد initLocation
و startReceivingLocationUpdates
صدا زده میشوند که در ادامه کد، نقش هر یک توضیح داده خواهد شد.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// starting app in full screen
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_user_location);
}
@Override
protected void onStart() {
super.onStart();
// everything related to ui is initialized here
initLayoutReferences();
initLocation();
startReceivingLocationUpdates();
}
در متد onPause
، متد stopLocationUpdates
صدا زده میشود و دریافت موقعیت کاربر متوقف میشود.
@Override
protected void onPause() {
super.onPause();
stopLocationUpdates();
}
در این متد پس از مقداردهی اولیه Viewها و نقشه نشان، موقعیت مکانی مقداردهی اولیه می شود.
// Initializing layout references (views, map and map events)
private void initLayoutReferences() {
// Initializing views
initViews();
// Initializing mapView element
initMap();
}
برای مقداردهی اولیه کردن موقعیت مکانی، متد getFusedLocationProviderClient
از کلاس LocationServices
صدا زده میشود. متغیر settingsClient
با استفاده از متد getSettingsClient
از کلاس LocationServices
مقداردهی میشود. کاربرد این متغیر در متدهای بعدی توضیح داده خواهد شد.
متغیر locationCallback
با یک شی جدید ایجاد شده از کلاس LocationCallback
مقداردهی میشود. متد onLocationResult
این کلاس Override شده است و در این متد آخرین موقعیت کاربر در userLocation
و زمان فعلی (به عنوان آخرین زمان دریافت موقعیت کاربر) در متغیر lastUpdateTime
ذخیره میشود. در نهایت متد onLocationChange
– که مجموعه اتفاقاتی است که با هر بار تغییر موقعیت کاربر باید انجام شود – صدا زده میشود.
در ادامه دوره زمانی دریافت مجدد موقعیت کاربر، حداقل زمان بهروزرسانی و اولویت این کاربر با استفاده از صدا زدن متدهای مربوطه بر روی locationRequest
تنظیم میشود و شی locationSettingsRequest
از روی آن ساخته میشود.
private void initLocation() {
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
settingsClient = LocationServices.getSettingsClient(this);
locationCallback = new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
super.onLocationResult(locationResult);
// location is received
userLocation = locationResult.getLastLocation();
lastUpdateTime = DateFormat.getTimeInstance().format(new Date());
onLocationChange();
}
};
mRequestingLocationUpdates = false;
if (locationRequest == null) {
locationRequest = new LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, UPDATE_INTERVAL_IN_MILLISECONDS).build();
LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder();
builder.addLocationRequest(locationRequest);
locationSettingsRequest = builder.build();
}
}
متد startReceivingLocationUpdates
ابتدا چک میکند که ورژن اندروید دستگاه کاربر 6 و بالاتر از آن است یا خیر، در صورتی که بود، چک میکند که آیا دسترسی به 2 مجوز ACCESS_FINE_LOCATION و ACCESS_COARSE_LOCATION وجود دارد یا خیر، در صورتی که وجود داشت، متد startLocationUpdates
صدا زده میشود. در غیر اینصورت درخواست دسترسی مجوز این 2 مجوز ارسال میشود.
public void startReceivingLocationUpdates() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission(UserLocation.this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(UserLocation.this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
mRequestingLocationUpdates = true;
startLocationUpdates();
} else {
requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION}, REQUEST_CODE);
}
} else {
mRequestingLocationUpdates = true;
startLocationUpdates();
}
}
نتیجه درخواست مجوز در این متد بررسی شده و در صورتی که مجوز توسط کاربر داده شود، مقدار mRequestingLocationUpdates
برابر با true
میشود و متد startLocationUpdates
صدا زده میشود شد.
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CODE) {
if (ContextCompat.checkSelfPermission(UserLocation.this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(UserLocation.this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
mRequestingLocationUpdates = true;
startLocationUpdates();
}
}
}
در متد startLocationUpdates
بررسی میشود که اگر تنظیمات تعریف شده در locationSettingsRequest
برقرار باشد، متد requestLocationUpdates
بر روی fusedLocationClient
صدا زده میشود و سپس متد onLocationChange
صدا زده میشود. در غیر این صورت با بررسی کد خطا، رفتار مناسب انجام میشود (کدهای داخل متد onFailure
و کامنتهای نوشته شده را دنبال کنید)
/**
* Starting location updates
* Check whether location settings are satisfied and then
* location updates will be requested
*/
private void startLocationUpdates() {
settingsClient
.checkLocationSettings(locationSettingsRequest)
.addOnSuccessListener(this, new OnSuccessListener<LocationSettingsResponse>() {
@SuppressLint("MissingPermission")
@Override
public void onSuccess(LocationSettingsResponse locationSettingsResponse) {
Log.i(TAG, "All location settings are satisfied.");
if (ContextCompat.checkSelfPermission(UserLocation.this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(UserLocation.this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
Log.d("UserLocationUpdater", " required permissions are not granted ");
return;
}
fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.myLooper());
}
})
.addOnFailureListener(this, new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
int statusCode = ((ApiException) e).getStatusCode();
switch (statusCode) {
case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
try {
Log.i(TAG, "Location settings are not satisfied. Attempting to upgrade location settings");
// Show the dialog by calling startResolutionForResult(), and check the
// result in onActivityResult().
ResolvableApiException rae = (ResolvableApiException) e;
rae.startResolutionForResult(UserLocation.this, REQUEST_CODE);
} catch (IntentSender.SendIntentException sie) {
Log.i(TAG, "PendingIntent unable to execute request.");
}
break;
case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
String errorMessage = "Location settings are inadequate, and cannot be fixed here. Fix in Settings.";
Log.e(TAG, errorMessage);
Toast.makeText(UserLocation.this, errorMessage, Toast.LENGTH_LONG).show();
}
}
});
}
onActivityResult
صدا زده میشود و در این متد با توجه به نتیجه درخواست که RESULT_OK است، متد startLocationUpdates
صدا زده میشود. در غیر اینصورت متغیر mRequestingLocationUpdates
برابر مقدار false
خواهد شد.
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
// Check for the integer request code originally supplied to startResolutionForResult().
case REQUEST_CODE:
switch (resultCode) {
case Activity.RESULT_OK:
Log.e(TAG, "User agreed to make required location settings changes.");
mRequestingLocationUpdates = true;
startLocationUpdates();
break;
case Activity.RESULT_CANCELED:
Log.e(TAG, "User choose not to make required location settings changes.");
mRequestingLocationUpdates = false;
break;
}
break;
}
}
در این متد، متد removeLocationUpdates
بر روی fusedLocationClient
صدا زده شده و بهروزرسانی موقعیت کاربر متوقف میشود.
public void stopLocationUpdates() {
// Removing location updates
fusedLocationClient
.removeLocationUpdates(locationCallback)
.addOnCompleteListener(this, new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
Toast.makeText(getApplicationContext(), "Location updates stopped!", Toast.LENGTH_SHORT).show();
}
});
}
در صورتی که این متد صدا زده شود و userLocation
به درستی مقدار گرفته باشد، متد addUserMarker
با موقعیت جدید کاربر صدا زده میشود. سپس دوربین روی موقعیت کاربر قرار میگیرد.
private void onLocationChange() {
if (userLocation != null) {
addUserMarker(new LatLng(userLocation.getLatitude(), userLocation.getLongitude()));
map.moveCamera(new LatLng(userLocation.getLatitude(), userLocation.getLongitude()), .5f);
}
}
متد addUserMarker
یک نشانگر در موقعیت فعلی کاربر نمایش میدهد. این متد دقیقا مشابه با متد addMarker
که در اضافه کردن نشانگر توضیح دادهشده است میباشد.
// This method gets a LngLat as input and adds a marker on that position
private void addUserMarker(LatLng loc){
//remove existing marker from map
if(marker!=null){
map.removeMarker(marker);
}
// Creating marker style. We should use an object of type MarkerStyleCreator, set all features on it
// and then call buildStyle method on it. This method returns an object of type MarkerStyle
MarkerStyleBuilder markStCr = new MarkerStyleBuilder();
markStCr.setSize(30f);
markStCr.setBitmap(BitmapUtils.createBitmapFromAndroidBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.ic_marker)));
MarkerStyle markSt = markStCr.buildStyle();
// Creating user marker
marker = new Marker(loc, markSt);
// Adding user marker to map!
map.addMarker(marker);
}
با هر بار لمس دکمه موجود در رابط کاربری، در صورتی که متغیر userLocation
مقداردهی شده باشد (موقعیت کاربر دریافت شده باشد) دوربین بر روی موقعیت کاربر متمرکز میشود. در غیر اینصورت متد startReceivingLocationUpdates
صدا زده میشود.
public void focusOnUserLocation(View view) {
if (userLocation != null) {
map.moveCamera(new LatLng(userLocation.getLatitude(), userLocation.getLongitude()), 0.25f);
map.setZoom(15, 0.25f);
} else {
startReceivingLocationUpdates();
}
}
نسخه 1.0.1
هدف از این بخش از پروژه، نمایش یک نشانگر بر روی موقعیت فعلی کاربر و بهروزرسانی مکان نمایشگر با تغییر موقعیت جغرافیایی کاربر است.
خطوط زیر را به وابستگیهای فایل build.gradle (Module.app)
اضافه کنید:
//Play Services
implementation 'com.google.android.gms:play-services-gcm:15.0.1'
implementation 'com.google.android.gms:play-services-location:15.0.1'
// dexter runtime permissions
implementation 'com.karumi:dexter:4.2.0'
AndroidManifest.xml
دسترسیهای زیر را برای به دست آوردن موقعیت جغرافیایی به این فایل اضافه کنید:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
activity_user_location.xml
:
در این صفحه علاوه بر المان نقشه نشان، یک FloatingActionButton
اضافه شدهاست که با هر بار کلیک بر روی آن، متد focusOnUserLocation
صدا زده میشود. این متد در ادامه توضیح داده خواهد شد.
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".UserLocation">
<org.neshan.mapsdk.MapView
android:id="@+id/map"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<android.support.design.widget.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:fabSize="mini"
android:src="@drawable/ic_my_location"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:onClick="focusOnUserLocation"/>
</android.support.constraint.ConstraintLayout>
UserLocation.java
:
ثابتهای جدیدی که در طول برنامه استفاده خواهد شد در این بخش تعریف شدهاست.
ثابت REQUEST_CODE
برای دنبال کردن درخواست مجوز (permission) است و مقدار آن به دلخواه برابر با ۱۲۳ در نظر گرفته شدهاست.
ثابت UPDATE_INTERVAL_IN_MILLISECONDS
فاصله دورههای زمانی جهت درخواست موقعیت جدید را مشخص میکند. بسته به نیاز خود میتوانید مقدار این ثابت را کم یا زیاد کنید. همچنین ثابت FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS
سریعترین زمان برای دریافت موقعیت جدید کاربر را مشخص میکند. در صورتی که نرمافزارهای دیگر درخواست موقعیت کاربر را داشته باشند، در حداقل زمانهای ۱۰۰۰ میلیثانیهای – در اینجا – موقعیت جدید دریافت میشود.
// used to track request permissions
final int REQUEST_CODE = 123;
// location updates interval - 1 sec
private static final long UPDATE_INTERVAL_IN_MILLISECONDS = 1000;
// fastest updates interval - 1 sec
// location updates will be received if another app is requesting the locations
// than your app can handle
private static final long FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS = 1000;
موقعیت فعلی کاربر در متغیر userLocation
ذخیره میشود.
برای به دست آوردن موقعیت مکانی، از یک شی از نوع FusedLocationProviderClient
استفاده میشود که توضیحات مربوط به این کلاس را میتوانید از اینجا بخوانید:
سایر متغیرهایی که برای دریافت موقعیت کاربر استفاده میشوند، متغیرهایی از کلاسهای SettingsClient
، LocationRequest
، LocationSettingsRequest
و LocationCallback
هستند.
آخرین زمان بهروزرسانی موقعیت فعلی کاربر در متغیر lastUpdateTime
– که از نوع String
است ذخیره میشود. و در آخر متغیر marker
که نشانه اضافه شده بر روی نقشه در آن نگهداری میشود تا در صورت نیاز امکان حذف یا ویرایش آن وجود داشته باشد.
// User's current location
private Location userLocation;
private FusedLocationProviderClient fusedLocationClient;
private SettingsClient settingsClient;
private LocationRequest locationRequest;
private LocationSettingsRequest locationSettingsRequest;
private LocationCallback locationCallback;
private String lastUpdateTime;
// boolean flag to toggle the ui
private Boolean mRequestingLocationUpdates;
private Marker marker;
متد initLayoutRefrences
جهت مقداردهی اولیه کردن به تمامی المانهای مربوط به رابط کاربری نوشته شدهاست. به دلیل این که لازم است تا المان اندرویدی نقشه نشان ابتدا به طور کامل ایجاد شود و سپس با آن تعامل برقرار شود (متدهای مختلف بر روی المان نقشه اجرا شود)، تمامی متدهای مربوط به رابط کاربری باید در متد onStart
انجام شوند.
در متد onStart
و پس از مقداردهی اولیه المانهای مربوط به رابط کاربری، دو متد initLocation
و startReceivingLocationUpdates
صدا زده میشوند که در ادامه کد، نقش هر یک توضیح داده خواهد شد.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// starting app in full screen
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_user_location);
}
@Override
protected void onStart() {
super.onStart();
// everything related to ui is initialized here
initLayoutReferences();
initLocation();
startReceivingLocationUpdates();
}
در متد onResume
متد startLocationUpdates
صدا زده می شود و دریافت موقعیت مکانی کاربر در بازههای زمانی مشخص شده در ثابت UPDATE_INTERVAL_IN_MILLISECONDS
شروع میشود.
در متد onPause
، متد stopLocationUpdates
صدا زده میشود و دریافت موقعیت کاربر متوقف میشود.
@Override
protected void onResume(){
super.onResume();
startLocationUpdates();
}
@Override
protected void onPause() {
super.onPause();
stopLocationUpdates();
}
در این متد پس از مقداردهی اولیه Viewها و نقشه نشان، موقعیت مکانی مقداردهی اولیه می شود.
// Initializing layout references (views, map and map events)
private void initLayoutReferences() {
// Initializing views
initViews();
// Initializing mapView element
initMap();
}
برای مقداردهی اولیه کردن موقعیت مکانی، متد getFusedLocationProviderClient
از کلاس LocationServices
صدا زده میشود. متغیر settingsClient
با استفاده از متد getSettingsClient
از کلاس LocationServices
مقداردهی میشود. کاربرد این متغیر در متدهای بعدی توضیح داده خواهد شد.
متغیر locationCallback
با یک شی جدید ایجاد شده از کلاس LocationCallback
مقداردهی میشود. متد onLocationResult
این کلاس Override شده است و در این متد آخرین موقعیت کاربر در userLocation
و زمان فعلی (به عنوان آخرین زمان دریافت موقعیت کاربر) در متغیر lastUpdateTime
ذخیره میشود. در نهایت متد onLocationChange
– که مجموعه اتفاقاتی است که با هر بار تغییر موقعیت کاربر باید انجام شود – صدا زده میشود.
در ادامه دوره زمانی دریافت مجدد موقعیت کاربر، حداقل زمان بهروزرسانی و اولویت این کار بر با استفاده از صدا زدن متدهای مربوطه بر روی locationRequest
تنظیم میشود و شی locationSettingsRequest
از روی آن ساخته میشود.
private void initLocation() {
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
settingsClient = LocationServices.getSettingsClient(this);
locationCallback = new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
super.onLocationResult(locationResult);
// location is received
userLocation = locationResult.getLastLocation();
lastUpdateTime = DateFormat.getTimeInstance().format(new Date());
onLocationChange();
}
};
mRequestingLocationUpdates = false;
locationRequest = new LocationRequest();
locationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS);
locationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS);
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder();
builder.addLocationRequest(locationRequest);
locationSettingsRequest = builder.build();
}
در این متد بررسی میشود که اگر تنظیمات تعریف شده در locationSettingsRequest
برقرار باشد، متد requestLocationUpdates
بر روی fusedLocationClient
صدا زده میشود و سپس متد onLocationChange
صدا زده میشود. در غیر این صورت با بررسی کد خطا، رفتار مناسب انجام میشود (کدهای داخل متد onFailure
و کامنتهای نوشته شده را دنبال کنید)
/**
* Starting location updates
* Check whether location settings are satisfied and then
* location updates will be requested
*/
private void startLocationUpdates() {
settingsClient
.checkLocationSettings(locationSettingsRequest)
.addOnSuccessListener(this, new OnSuccessListener<LocationSettingsResponse>() {
@SuppressLint("MissingPermission")
@Override
public void onSuccess(LocationSettingsResponse locationSettingsResponse) {
Log.i(TAG, "All location settings are satisfied.");
//noinspection MissingPermission
fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.myLooper());
onLocationChange();
}
})
.addOnFailureListener(this, new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
int statusCode = ((ApiException) e).getStatusCode();
switch (statusCode) {
case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
Log.i(TAG, "Location settings are not satisfied. Attempting to upgrade " +
"location settings ");
if (mRequestingLocationUpdates) {
try {
// Show the dialog by calling startResolutionForResult(), and check the
// result in onActivityResult().
ResolvableApiException rae = (ResolvableApiException) e;
rae.startResolutionForResult(UserLocation.this, REQUEST_CODE);
} catch (IntentSender.SendIntentException sie) {
Log.i(TAG, "PendingIntent unable to execute request.");
}
}
break;
case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
String errorMessage = "Location settings are inadequate, and cannot be " +
"fixed here. Fix in Settings.";
Log.e(TAG, errorMessage);
Toast.makeText(UserLocation.this, errorMessage, Toast.LENGTH_LONG).show();
}
onLocationChange();
}
});
}
در این متد، متد removeLocationUpdates
بر روی fusedLocationClient
صدا زده شده و بهروزرسانی موقعیت کاربر متوقف میشود.
public void stopLocationUpdates() {
// Removing location updates
fusedLocationClient
.removeLocationUpdates(locationCallback)
.addOnCompleteListener(this, new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
Toast.makeText(getApplicationContext(), "Location updates stopped!", Toast.LENGTH_SHORT).show();
}
});
}
در این متد پس از دریافت دسترسی ACCESS_FINE_LOCATION
در زمان اجرا، متد startLocationUpdates
صدا زده میشود و در صورتی که دسترسی دریافت نشود، متد openSettings
صدا زده میشود.
public void startReceivingLocationUpdates() {
// Requesting ACCESS_FINE_LOCATION using Dexter library
Dexter.withActivity(this)
.withPermission(Manifest.permission.ACCESS_FINE_LOCATION)
.withListener(new PermissionListener() {
@Override
public void onPermissionGranted(PermissionGrantedResponse response) {
mRequestingLocationUpdates = true;
startLocationUpdates();
}
@Override
public void onPermissionDenied(PermissionDeniedResponse response) {
if (response.isPermanentlyDenied()) {
// open device settings when the permission is
// denied permanently
openSettings();
}
}
@Override
public void onPermissionRationaleShouldBeShown(com.karumi.dexter.listener.PermissionRequest permission, PermissionToken token) {
token.continuePermissionRequest();
}
}).check();
}
نتیجه درخواست مجوز در این متد بررسی شده و در صورتی که مجوز توسط کاربر داده نشود، مقدار mRequestingLocationUpdates
برابر با false
میشود و درخواست بهروزرسانی موقعیت کاربر انجام نخواهد شد.
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
// Check for the integer request code originally supplied to startResolutionForResult().
case REQUEST_CODE:
switch (resultCode) {
case Activity.RESULT_OK:
Log.e(TAG, "User agreed to make required location settings changes.");
// Nothing to do. startLocationupdates() gets called in onResume again.
break;
case Activity.RESULT_CANCELED:
Log.e(TAG, "User chose not to make required location settings changes.");
mRequestingLocationUpdates = false;
break;
}
break;
}
}
این متد جهت باز کردن پنجره دریافت مجوز در زمان اجرا نوشته شده است.
private void openSettings() {
Intent intent = new Intent();
intent.setAction(
Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package",
BuildConfig.APPLICATION_ID, null);
intent.setData(uri);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
در صورتی که این متد صدا زده شود و userLocation
به درستی مقدار گرفته باشد، متد addUserMarker
با موقعیت جدید کاربر صدا زده میشود.
private void onLocationChange() {
if(userLocation != null) {
addUserMarker(new LatLng(userLocation.getLatitude(), userLocation.getLongitude()));
}
}
متد addUserMarker
یک نشانگر در موقعیت فعلی کاربر نمایش میدهد. این متد دقیقا مشابه با متد addMarker
که در اضافه کردن نشانگر توضیح دادهشده است میباشد.
// This method gets a LngLat as input and adds a marker on that position
private void addUserMarker(LatLng loc){
//remove existing marker from map
if(marker!=null){
map.removeMarker(marker);
}
// Creating marker style. We should use an object of type MarkerStyleCreator, set all features on it
// and then call buildStyle method on it. This method returns an object of type MarkerStyle
MarkerStyleBuilder markStCr = new MarkerStyleBuilder();
markStCr.setSize(30f);
markStCr.setBitmap(BitmapUtils.createBitmapFromAndroidBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.ic_marker)));
MarkerStyle markSt = markStCr.buildStyle();
// Creating user marker
marker = new Marker(loc, markSt);
// Adding user marker to map!
map.addMarker(marker);
}
با هر بار لمس دکمه موجود در رابط کاربری، دوربین بر روی موقعیت کاربر متمرکز میشود.
public void focusOnUserLocation(View view) {
if(userLocation != null) {
LatLng LatLng = new LatLng(userLocation.getLatitude(), userLocation.getLongitude());
map.moveCamera(LatLng, 0);
map.setZoom(15, 0.25f);
}
}