Skip to content

نمایش موقعیت کاربر

amir-ba-2020 edited this page Mar 27, 2022 · 3 revisions

نمایش موقعیت کاربر

هدف از این بخش از پروژه، نمایش یک نشانگر بر روی موقعیت فعلی کاربر و به‌روزرسانی مکان نمایشگر با تغییر موقعیت جغرافیایی کاربر است.

خطوط زیر را به وابستگی‌های فایل 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) است و مقدار آن به دلخواه برابر با ۱۲۳ در نظر گرفته شده‌است.

    // used to track request permissions
    final int REQUEST_CODE = 123;

ثابت UPDATE_INTERVAL_IN_MILLISECONDS فاصله دوره‌های زمانی جهت درخواست موقعیت جدید را مشخص می‌کند. بسته به نیاز خود می‌توانید مقدار این ثابت را کم یا زیاد کنید. همچنین ثابت FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS سریع‌ترین زمان برای دریافت موقعیت جدید کاربر را مشخص می‌کند. در صورتی که نرم‌افزارهای دیگر درخواست موقعیت کاربر را داشته باشند، در حداقل زمان‌های ۱۰۰۰ میلی‌ثانیه‌ای - در این‌جا - موقعیت جدید دریافت می‌شود.

    // 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();
        getLocationPermission();
        // 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();
        // Initializing user location
    }

برای مقداردهی اولیه کردن موقعیت مکانی، متد 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 ");
                                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);
        }
    }


صفحه قبل (تغییر استایل نقشه)

صفحه بعد (لایه ترافیک)

Clone this wiki locally