728x90

샘플 코드만 적어둔다.

Android 네이버 Map 라이브러리를 이용하여 검색(Search)으로 위치 이동과 더불어 지도 레벨을 지정해야 할 때 필요한 코드이다.

 

private fun LocationAt(latitude: Double, longitude: Double) {
    mNaverMap?.locationTrackingMode = LocationTrackingMode.None
    val coord = LatLng(latitude, longitude)
    val cameraPosition = CameraPosition(coord, 16.0)
    mNaverMap.cameraPosition = cameraPosition
    val cameraUpdate = CameraUpdate.scrollTo(coord).animate(CameraAnimation.Easing, 300)
    mNaverMap!!.moveCamera(cameraUpdate)
}

 

 

블로그 이미지

Link2Me

,
728x90

박상권님이 GitHub 에 오픈해 주신 https://github.com/ParkSangGwon/TedNaverMapClustering 

 

GitHub - ParkSangGwon/TedNaverMapClustering: 네이버지도용 클러스터 유틸리티 라이브러리

네이버지도용 클러스터 유틸리티 라이브러리. Contribute to ParkSangGwon/TedNaverMapClustering development by creating an account on GitHub.

github.com

Android 용 TedNaverMapClustering 파일을 받아서 구현한 네이버 지도에 Clustering 기능을 구현했다.

 

라이브러리 적용 전 코드와 적용 후 코드를 구분하여 적어둔다.

서버로부터 받아오는 데이터 구조가 아래와 같이 구현되어 있었다. (일부 변수는 삭제)

import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
 
@Parcelize
data class ItemData (
    val code: String,
    val bldNM: String// 빌딩명
    val address: String// 주소
    var latitude: Double, // 위도
    var longitude: Double, // 경도
): Parcelable

 

TedNaverMapClustering 적용 후 dataclass

import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
 
@Parcelize
data class ItemData (
    val code: String,
    val bldNM: String// 빌딩명
    val address: String// 주소
    var latitude: Double, // 위도
    var longitude: Double, // 경도
): Parcelable
 

 

ItemData data class 는 그대로 두고 별도로 ItemData 에서 필수 항목만 뽑아서 아래와 같이 코드를 작성한다.

import ted.gun0912.clustering.clustering.TedClusterItem
import ted.gun0912.clustering.geometry.TedLatLng

data class NaverItem(var position: LatLng) : TedClusterItem {
    override fun getTedLatLng(): TedLatLng {
        return TedLatLng(position.latitude, position.longitude)
    }
 
    var code: String= null
    var bldNM: String= null
    var chtype: Int = 0
    var Cust: Int = 0
 
    constructor(lat: Double, lng: Double) : this(LatLng(lat, lng)) {
        code = null
        bldNM = null
        chtype = 0
        Cust = 0
    }
 
    constructor(lat: Double, lng: Double, code: String?, bldNM: String?, chtype: Int, Cust: Int) : this(
        LatLng(lat, lng)
    ) {
        this.code = code
        this.bldNM = bldNM
        this.chtype = chtype
        this.Cust = Cust
    }
}

 

 

기존 marker 처리 코드

https://navermaps.github.io/android-map-sdk/guide-ko/5-1.html 부분에 "대량의 오버레이를 다룰 경우 객체를 생성하고 초기 옵션을 지정하는 작업은 백그라운드 쓰레드에서 수행하고 지도에 추가하는 작업만을 메인 쓰레드에서 수행하면 메인 쓰레드를 효율적으로 사용할 수 있습니다." 라고 나온다. 많은 marker 를 생성시에는 백그라운드 쓰레드에서 처리하도록 하고 있다.

var activeMarkers: MutableList<Marker>= null
private fun makeAllMarker(itemList: List<ItemData>) {
    val executor = Executors.newSingleThreadExecutor()
    val handler = Handler(Looper.getMainLooper())
    executor.execute {
        // 백그라운드 쓰레드
        activeMarkers = ArrayList()
        for (i in itemList.indices) {
            val itemData = itemList[i]
            val marker = Marker()
            marker.position = LatLng(itemData.latitude, itemData.longitude)
            marker.icon = OverlayImage.fromBitmap(makePinBitmap(itemData.Cust,itemData.chtype)!!)
            //marker.setIconTintColor(Color.RED); // 덧입힐 색상
            marker.width = Marker.SIZE_AUTO
            marker.height = Marker.SIZE_AUTO
            //marker.setCaptionMinZoom(12);
            //marker.setCaptionMaxZoom(16);
            marker.captionText = itemData.bldNM
            marker.isHideCollidedCaptions = true // 겹치는 캡션 자동 숨김 처리
            marker.isHideCollidedSymbols = true //  마커와 겹치는 지도 심벌을 자동으로 숨기도록 지정
            //marker.setHideCollidedMarkers(false);
            marker.tag = itemData
            activeMarkers?.add(marker)
            marker.onClickListener = Overlay.OnClickListener { overlay: Overlay ->
                mInfoWindow.open(marker)
                val position = LatLng(itemData.latitude, itemData.longitude) // 현재 위치
                val cameraUpdate = CameraUpdate.scrollTo(position).animate(CameraAnimation.Easing)
                mNaverMap!!.moveCamera(cameraUpdate)
                onClickMarker(overlay)
                true
            }
        }
        handler.post {
            // 메인 스레드
            for (marker in activeMarkers!!) {
                marker.map = mNaverMap
            }
        }
    }
}
 
private fun freeActiveMarkers() {
    if (activeMarkers == nullreturn
    for (marker in activeMarkers!!) {
        marker.map = null // 마커들 지도에서 삭제
    }
}
 

 

 

위 코드를 TedNaverMapClustering 코드로 변경한 코드이다.

import ted.gun0912.clustering.naver.TedNaverClustering

lateinit var tedNaverClustering: TedNaverClustering<NaverItem>
private fun makeClusterMarker(itemList: List<NaverItem>){
    tedNaverClustering = TedNaverClustering.with<NaverItem>(this, mNaverMap)
        .customMarker{ itemData ->
            Marker(itemData.position).apply {
                icon = OverlayImage.fromBitmap(makePinBitmap(itemData.Cust,itemData.chtype)!!)
                captionText = itemData.bldNM!!
                tag = itemData
            }
        }
        .markerClickListener { itemData ->
            val position = itemData.position
            val cameraUpdate = CameraUpdate.scrollTo(position).animate(CameraAnimation.Easing)
            mNaverMap!!.moveCamera(cameraUpdate)
            onClickClusterMarker(itemData.code)
        }
        .minClusterSize(20)
        .make()
 
    val executor = Executors.newSingleThreadExecutor()
    val handler = Handler(Looper.getMainLooper())
    executor.execute {
        // 백그라운드 스레드
        tedNaverClustering.addItems(itemList)
        Log.e("TAG""Number of tedNaverClustering :" + itemList.size)
    } // end executor.execute
}
 

 

fun onClickClusterMarker(code: String?){
    // 호출해야 할 데이터가 다른 ArrayList 에서 code 값이 서로 일치하는 데이터만 추출해야 한다.
    val itemData: ItemData = itemDataList.filter {it.code.equals(code)}.single()
 
}

 

서버에서 가져온 데이터 List 에 추가하는 코드 중에서 핵심만 발췌

response.body()?.let {
    if (it.status.contains("success")) {
        itemDataList.clear() // 서버에서 가져온 데이터 초기화
        naverItemList.clear()
        val itemData: List<ItemData> = it.iteminfo
        Log.e("TAG""Number of itemData :" + itemData.size)
        for (item in itemData) {
            // 자동으로 타입 변환을 하는 듯함.
            itemDataList.add(item)
            putNaverItem(item.latitude,item.longitude,item.code,item.bldNM,item.chtype,item.Cust)
        }
        showMapMarker(itemDataList)
    } else {
        Utils.showAlert(context, it.status, it.message)
    }
 
fun putNaverItem(lat: Double, lng: Double, code: String, bldNM: String, chtype: Int, Cust: Int){
    // NaverItem 생성자에 데이터 추가 및 naverItemList 에 추가
    var naverItem = NaverItem(lat,lngitude,code,bldNM,chtype,Cust)
    naverItemList.add(naverItem)
}

 

코드 구현 이슈

Thread Pool size 를 초과하는 데이터 크기(서버로부터의 Input 데이터 개수)는 좀 더 신경써야 할 거 같다.

 

 

 

블로그 이미지

Link2Me

,
728x90

현재 위치한 반경 km 범위내 데이터를 가져오는 쿼리문

숫자가 직접 기입된 부분을 userLAT, userLNG 로 바꿔줘야 한다.

 

--쿼리문
SELECT *, (6371 * acos(cos(radians(37.504696)) * cos(radians(latitude)) * cos(radians(longitude) - radians(127.041237)) + sin(radians(37.504696))*sin(radians(latitude)))) AS distance 
FROM blding 
HAVING distance <= 0.5 
ORDER BY distance;
 

 

지도를 움직일 경우 ** 정보를 조회해서 화면에 표시될 수 있도록 한다.

지도를 일정 레벨 이상으로 확대했을 경우, 해당 위치에 있는 정보 List를 서버에 요청한다.

onCameraMove → showList() → getDataFromServer() → addMarker()

 

 

 

블로그 이미지

Link2Me

,
728x90

developers.kakao.com/ 에서 로그인을 한다.

 

 

 

build.gradle(Project)에서 카카오 레파지토리 설정

allprojects {
    repositories {
        google()
        jcenter()
        maven { url 'https://navercorp.bintray.com/maps' }
        maven { url "https://jitpack.io" }
        //mavenCentral()
        maven { url 'https://devrepo.kakao.com/nexus/content/groups/public/' }
    }
}
 

 

 

 

build.gradle (module)에 dependencies 설정

 

dependencies {
 
    implementation "com.kakao.sdk:v2-navi:2.4.2" // 카카오내비
    implementation group: 'com.kakao.sdk', name: 'kakaonavi', version: '1.30.6'
 
}
 

 

SDK 사용해서 키 해시 구하기

카카오 SDK를 통해서도 키 해시 확인이 가능하다.

 

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    context = this@MainActivity
 
    // 카카오 내비 hashKey 등록을 위해 사용
    val keyHash = com.kakao.util.helper.Utility.getKeyHash(this /* context */)
    Log.d("Hash",keyHash)
}

 

 

※ 개발용 hash 키를 위 코드로 쉽게 Log 확인한 다음에 사이트에 등록했을 때는 잘 되었는데....

Release hash 키 등록을 윈도우 PC용으로 구해서 하니까 다른 코드가 나온다.

뭔가 잘못을 한 것인지 카카오내비가 동작하지 않았다.

그래서 아래 코드를 팝업창으로 띄워서 확인했더니 다른 키값을 반환한다.

이 코드로 생성된 hash 를 사이트에 등록하니 카카오내비가 정상적으로 구동된다.

// 카카오 내비 hashKey 등록을 위해 사용
val keyHash = com.kakao.util.helper.Utility.getKeyHash(this /* context */)
val message = keyHash
val builder = AlertDialog.Builder(context)
builder.setTitle("Release Key Hash")
builder.setMessage(message)
    .setCancelable(false)
    .setPositiveButton("OK") { dialog, id ->
        dialog.dismiss()
    }
val alert = builder.create()
alert.show()
 

 

 

블로그 이미지

Link2Me

,
728x90

private val groupList: Unit
    private get() {
        remoteService = RetrofitAPI.getClient().create(RemoteService::class.java)
        val groupResultCall = remoteService?.getGroupList(Value.encrypt(Value.URLkey()))
        groupResultCall?.enqueue(object : Callback<GroupResult> {
            override fun onResponse(call: Call<GroupResult>, response: Response<GroupResult>) {
                if (response.body()!!.status.contains("success")) {
                    groupItem.clear()
                    val groupData = response.body()!!.message
                    for (item in groupData) {
                        groupItem.add(item)
                    }
                    Log.d("TAG", "Number of groupItem :" + groupItem.size)
                }
            }

            override fun onFailure(call: Call<GroupResult>,t: Throwable) {
            }
        })
    }
 


Java로 작성한 코드를 코틀린으로 변환하여 테스트하고 적어둔다.


override fun onClick(view: View) {
    when (view.id) {
        R.id.btn_drawer -> drawer_layout.openDrawer(GravityCompat.START)
        R.id.btn_group -> {
            // 팝업 메뉴 나오게 하는 방법
            Log.d("TAG", "popupMenu of groupItem :" + groupItem.size)
            val popupMenu = PopupMenu(this, view)
            val menu = popupMenu.menu
            var i = 0
            while (i < groupItem.size) {
                menu.add(Menu.NONE,i,Menu.NONE,groupItem[i].name)
                i++
            }
            popupMenu.setOnMenuItemClickListener { item: MenuItem ->
                val i = item.itemId
                freeActiveMarkers()
                getAddrData(item.title.toString())
                LocationAt(groupItem[i].latitude.toDouble(),groupItem[i].longitude.toDouble())
                Toast.makeText(context,item.title.toString() + " 선택했습니다",Toast.LENGTH_SHORT).show()
                true
            }
            popupMenu.show()
        }
    }
}


private fun LocationAt(latitude: Double, longitude: Double) {
    val coord = LatLng(latitude, longitude)
    mNaverMap!!.moveCamera(CameraUpdate.scrollTo(coord).animate(CameraAnimation.Easing, 300))
}


PHP 서버코드

<?php
error_reporting(0);
//error_reporting(E_ALL);
//ini_set("display_errors", 1);
header("Cache-Control: no-cache, must-revalidate");
header("Content-type: application/json; charset=utf-8");

// 파일을 직접 실행하는 비정상적 동작을 방지 하기 위한 목적
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST);
    require_once 'path.php';// root 폴더를 기준으로 상대적인 경로 자동 구하기
    require_once $g['path_config'].'config.php';
    require_once $g['path_class'].'dbconnect.php';
    require_once $g['path_class'].'loginClass.php';
    $c = new LoginClass;

    //키워드 확인
    if(!isset($_POST['keyword'])){
        $result = array(
            'status' => "fail",
            'message' => "no keyword"
        );
        echo json_encode($result);
        exit;
    }

    $keyword=$c->AES_Decode($_POST['keyword']);
    //키워드 일치 확인
    if(strcmp($keyword,$mykey)<>0){
        $result = array(
            'status' => "keyfail",
            'message' => "서버와 단말의 KEY가 일치하지 않습니다."
        );
        echo json_encode($result);
        exit;
    }

    // 배열 저장
    $R = array();
    array_push($R,array("name"=>"전체","latitude"=>"37.358752","longitude"=>"127.1149243"));
    $map_sql = "select name,latitude,longitude from mapTree";
    $map_result = mysqli_query($db,$map_sql);
    while($row = mysqli_fetch_row($map_result)) {
        array_push($R,array("name"=>$row[0],"latitude"=>$row[1],"longitude"=>$row[2]));
    }

    $status = "success";
    $message = $R;
   
    $result = array(
        'status' => $status,
        'message' => $message
    );
    echo json_encode($result);
}
?> 


블로그 이미지

Link2Me

,
728x90

앱을 만들다보면 앱을 실행하면서 현재 위치 좌표값만 획득하고 싶을 경우가 있다.


AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<!-- Android 10 이상 백그라운드 위치 액세스 요청 --> 


앱 build.gradle

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

    implementation 'gun0912.ted:tedpermission:2.0.0'
    implementation 'com.google.android.gms:play-services-location:17.0.0'
    implementation 'com.naver.maps:map-sdk:3.7.1' // 네이버 지도 SDK

}


SplashActivity.java

public class SplashActivity extends AppCompatActivity {
    private final String TAG = this.getClass().getSimpleName();
    Context mContext;

    private final int GPS_ENABLE_REQUEST_CODE = 2001;
    private final long UPDATE_INTERVAL_MS = 1000 * 60 * 15;
    private final long FASTEST_UPDATE_INTERVAL_MS = 1000 * 30 ; // 30초 단위로 화면 갱신

    private FusedLocationProviderClient mFusedLocationProviderClient;
    private LocationRequest locationRequest;
    private double longitude, latitude;

    PermissionListener permissionlistener = new PermissionListener() {
        @Override
        public void onPermissionGranted() {
            initView();
        }

        @Override
        public void onPermissionDenied(ArrayList<String> deniedPermissions) {
            Toast.makeText(SplashActivity.this, "권한 허용을 하지 않으면 서비스를 이용할 수 없습니다.", Toast.LENGTH_SHORT).show();
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_splash);
        mContext = SplashActivity.this;
        PrefsHelper.init(getApplicationContext()); // 한번만 실행하면 된다.

        checkPermissions();
    }

    private void checkPermissions() {

        if(Build.VERSION.SDK_INT >= 26){ // 출처를 알 수 없는 앱 설정 화면 띄우기
            PackageManager pm = mContext.getPackageManager();
            if (!pm.canRequestPackageInstalls()){
                startActivity(new Intent(android.provider.Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES,
                        Uri.parse("package:" + getPackageName())));
            }
        }

        if (Build.VERSION.SDK_INT >= 23) { // 마시멜로(안드로이드 6.0) 이상 권한 체크
            TedPermission.with(mContext)
                    .setPermissionListener(permissionlistener)
                    .setRationaleMessage("앱을 이용하기 위해서는 접근 권한이 필요합니다")
                    .setDeniedMessage("앱에서 요구하는 권한설정이 필요합니다...\n [설정] > [권한] 에서 사용으로 활성화해주세요.")
                    .setPermissions(new String[]{
                            android.Manifest.permission.ACCESS_FINE_LOCATION,
                            android.Manifest.permission.ACCESS_COARSE_LOCATION
                            //android.Manifest.permission.ACCESS_BACKGROUND_LOCATION
                            //앱이 Android 10 이상 앱이 백그라운드에 있는 동안 정기 위치 업데이트를 받을 시
                    })
                    .check();

        } else {
            initView();
        }
    }

    private void initView() {
        checkLocationSetting();
    }

    @SuppressLint("MissingPermission")
    private void checkLocationSetting() {
        // https://developer.android.com/training/location/change-location-settings 읽어라.
        // 위치 요청 설정
        locationRequest = LocationRequest.create();
        locationRequest.setInterval(UPDATE_INTERVAL_MS); // 위치가 Update 되는 주기
        locationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL_MS); // 위치 획득후 업데이트되는 주기
        locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);

        // 현재 위치 설정 받기
        LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder().addLocationRequest(locationRequest);
        SettingsClient settingsClient = LocationServices.getSettingsClient(this);
        settingsClient.checkLocationSettings(builder.build())
                .addOnSuccessListener(this, locationSettingsResponse -> {
                    mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(SplashActivity.this);
                    mFusedLocationProviderClient.requestLocationUpdates(locationRequest, locationCallback, null);
                })
                .addOnFailureListener(SplashActivity.this, e -> {
                    int statusCode = ((ApiException) e).getStatusCode();
                    switch (statusCode) {
                        case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
                            try {
                                ResolvableApiException rae = (ResolvableApiException) e;
                                rae.startResolutionForResult(SplashActivity.this, GPS_ENABLE_REQUEST_CODE);
                            } catch (IntentSender.SendIntentException sie) {
                                Log.e(TAG, "unable to start resolution for result due to " + sie.getLocalizedMessage());
                            }
                            break;
                        case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
                            Log.e(TAG, "Fix in Settings.");
                    }
                });
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == GPS_ENABLE_REQUEST_CODE) {
            if (resultCode == RESULT_OK) {
                checkLocationSetting();
            } else {
                finish();
            }
        }
    }

    private LocationCallback locationCallback = new LocationCallback() {
        @Override
        public void onLocationResult(LocationResult locationResult) {
            super.onLocationResult(locationResult);
            longitude = locationResult.getLastLocation().getLongitude();
            latitude = locationResult.getLastLocation().getLatitude();
            mFusedLocationProviderClient.removeLocationUpdates(locationCallback);

            Log.e(TAG, "latitude:"+latitude+" , longitude:"+longitude);
            PrefsHelper.write("latitude", String.valueOf(latitude));
            PrefsHelper.write("longitude", String.valueOf(longitude));

            Intent intent = new Intent(SplashActivity.this, MainActivity.class);
            startActivity(intent);
            finish();
        }

        @Override
        public void onLocationAvailability(LocationAvailability locationAvailability) {
            super.onLocationAvailability(locationAvailability);
            Log.i(TAG, "onLocationAvailability - " + locationAvailability);
        }
    };

}
 


PrefsHelper.java

public class PrefsHelper {
    public static final String PREFERENCE_NAME="pref";
    private Context mContext;
    private static SharedPreferences prefs;
    private static SharedPreferences.Editor prefsEditor;
    private static PrefsHelper instance;

    public static synchronized PrefsHelper init(Context context){
        if(instance == null)
            instance = new PrefsHelper(context);
        return instance;
    }

    private PrefsHelper(Context context) {
        mContext = context;
        prefs = mContext.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE );
        prefsEditor = prefs.edit();
    }

    public static String read(String key, String defValue) {
        return prefs.getString(key, defValue);
    }

    public static void write(String key, String value) {
        prefsEditor.putString(key, value);
        prefsEditor.commit();
        // double 변수 값은 string으로 감싸서 데이터를 넘기면 된다.
    }

    public static Integer read(String key, int defValue) {
        return prefs.getInt(key, defValue); // 32비트
    }

    public static void write(String key, Integer value) {
        prefsEditor.putInt(key, value).commit();
    }

    public static long read(String key, Long defValue) {
        return prefs.getLong(key, defValue); // 64비트
    }

    public static void write(String key, Long value) {
        prefsEditor.putLong(key, value).commit();
    }

    public static boolean read(String key, boolean defValue) {
        return prefs.getBoolean(key, defValue);
    }

    public static void write(String key, boolean value) {
        prefsEditor.putBoolean(key, value);
        prefsEditor.commit();
    }
}


지도를 띄우지 않은 상태에서 현재 위치 좌표만 얻어지는 걸 확인할 수 있다.


참고하면 좋은 게시글

https://medium.com/@droidbyme/android-turn-on-gps-programmatically-d585cf29c1ef



블로그 이미지

Link2Me

,
728x90

메인 화면 구성을 위해서 구글에서 Github 검색으로 https://github.com/Whhoesj/sliding-content-drawer 를 하나 찾아서 테스트하고 간단하게 적어둔다.

 


화면 구성은 위와 같다.

https://link2me.tistory.com/1810 에 Navigation Drawer 구성에 대한 개념이 설명되어 있는데, SlidingContentDrawer 는 아래 Java 코드만 살펴보면 이해될 것이다.


Project build.gradle

allprojects {
    repositories {
        google()
        jcenter()
        maven { url 'https://navercorp.bintray.com/maps' }
        maven { url "https://jitpack.io" }
    }
}


앱 build.gradle

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

    implementation 'gun0912.ted:tedpermission:2.0.0'
    implementation 'com.google.android.material:material:1.1.0'
    implementation 'com.wouterhabets:slidingcontentdrawer:1.0'
    implementation 'com.naver.maps:map-sdk:3.7.1' // 네이버 지도 SDK
    implementation 'com.google.android.gms:play-services-location:17.0.0'
}


AndroidManifest.xml

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

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
    <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:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="false"
        android:theme="@style/AppTheme.NoActionBar">
        <activity android:name=".SplashActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".MapActivity" />
        <activity android:name=".MainActivity" />

        <meta-data
            android:name="com.naver.maps.map.CLIENT_ID"
            android:value="@string/naver_app_key" />
    </application>

</manifest>


strings.xml

<resources>
    <string name="app_name">SlidingContentDrawer</string>
    <string name="navigation_drawer_open">Open navigation drawer</string>
    <string name="navigation_drawer_close">Close navigation drawer</string>
    <string name="naver_app_key">여기에 키를 적어주세요</string>
</resources>


Drawer Layout 구성

- Layout 구성을 버전에 맞춰 변경 수정했다.

- 파일을 3개의 파일로 분리 구성했다.

<?xml version="1.0" encoding="utf-8"?>
<com.wouterhabets.slidingcontentdrawer.widget.SlidingDrawerLayout
    android:id="@+id/drawer_layout"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="?attr/colorPrimaryDark"
    android:fitsSystemWindows="true">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="centerCrop"
        android:src="@drawable/background"/>

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

        <include layout="@layout/drawer_menu"/>

    </FrameLayout>

    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="?android:colorBackground"
        android:elevation="6dp"
        android:fitsSystemWindows="true"
        android:tag="content">

        <View
            android:layout_width="match_parent"
            android:layout_height="10dp"
            android:background="@android:color/holo_green_light"/>

        <com.google.android.material.appbar.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:theme="@style/AppTheme.AppBarOverlay">

            <androidx.appcompat.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="?attr/colorPrimary"
                app:popupTheme="@style/AppTheme.PopupOverlay"/>

        </com.google.android.material.appbar.AppBarLayout>

        <include
            layout="@layout/drawer_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/fab"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|end"
            android:layout_margin="@dimen/fab_margin"
            android:src="@android:drawable/ic_dialog_email"/>

    </androidx.coordinatorlayout.widget.CoordinatorLayout>

</com.wouterhabets.slidingcontentdrawer.widget.SlidingDrawerLayout>

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="start|center_vertical"
    android:orientation="vertical"
    android:paddingLeft="16dp">

    <TextView
        android:id="@+id/navermap_to"
        android:layout_width="match_parent"
        android:layout_height="42dp"
        android:layout_gravity="start|center_vertical"
        android:text="네이버 지도"
        android:layout_marginBottom="30dp"
        android:textColor="@android:color/white"/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="42dp"
        android:layout_gravity="start|center_vertical"
        android:text="Menu item 2"
        android:layout_marginBottom="30dp"
        android:textColor="@android:color/white"/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="42dp"
        android:layout_gravity="start|center_vertical"
        android:text="Menu item 3"
        android:layout_marginBottom="30dp"
        android:textColor="@android:color/white"/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="42dp"
        android:layout_gravity="start|center_vertical"
        android:text="Menu item 4"
        android:layout_marginBottom="30dp"
        android:textColor="@android:color/white"/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="42dp"
        android:layout_gravity="start|center_vertical"
        android:text="Menu item 5"
        android:textColor="@android:color/white"/>
</LinearLayout>

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="@string/app_name"/>
    </RelativeLayout>

</androidx.constraintlayout.widget.ConstraintLayout>



MainActivity.java 구현

package com.link2me.android.slidingcontentdrawer;

import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import com.link2me.android.common.BackPressHandler;
import com.link2me.android.common.PrefsHelper;
import com.wouterhabets.slidingcontentdrawer.widget.SlidingDrawerLayout;
import com.wouterhabets.slidingcontentdrawer.widget.SlidingDrawerToggle;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private final String TAG = this.getClass().getSimpleName();
    Context mContext;
    private BackPressHandler backPressHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mContext = MainActivity.this;
        backPressHandler = new BackPressHandler(this); // 뒤로 가기 버튼 이벤트

        initView();
    }

    private void initView() {
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });

        SlidingDrawerLayout drawer = (SlidingDrawerLayout) findViewById(R.id.drawer_layout);
        SlidingDrawerToggle toggle = new SlidingDrawerToggle(
                this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
        drawer.setDrawerListener(toggle);
        toggle.syncState();

        // 메뉴 버튼 클릭 리스너
        findViewById(R.id.navermap_to).setOnClickListener(this);
    }

    @Override
    public void onBackPressed() {
        SlidingDrawerLayout drawer = (SlidingDrawerLayout) findViewById(R.id.drawer_layout);
        if (drawer.isDrawerOpen()) {
            drawer.closeDrawer();
        } else {
            backPressHandler.onBackPressed();
        }
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.navermap_to:
                Intent intent = new Intent(mContext,MapActivity.class);
                startActivity(intent);
                break;
        }
    }
}


MapActivity.java 구현

package com.link2me.android.slidingcontentdrawer;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.cardview.widget.CardView;
import androidx.drawerlayout.widget.DrawerLayout;

import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;

import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import com.link2me.android.common.PrefsHelper;
import com.naver.maps.geometry.LatLng;
import com.naver.maps.map.CameraAnimation;
import com.naver.maps.map.CameraUpdate;
import com.naver.maps.map.LocationTrackingMode;
import com.naver.maps.map.MapView;
import com.naver.maps.map.NaverMap;
import com.naver.maps.map.OnMapReadyCallback;
import com.naver.maps.map.UiSettings;
import com.naver.maps.map.overlay.InfoWindow;
import com.naver.maps.map.util.FusedLocationSource;
import com.naver.maps.map.widget.LocationButtonView;

public class MapActivity extends AppCompatActivity implements OnMapReadyCallback {
    private final String TAG = this.getClass().getSimpleName();
    Context mContext;

    // NaverMap API 3.0
    private MapView mMapView;
    private LocationButtonView locBtnView;
    DrawerLayout drawer_layout;
    ImageView btn_drawer;
    CardView cardView;

    // FusedLocationSource (Google)
    private static final int LOCATION_PERMISSION_REQUEST_CODE = 1000;
    private FusedLocationSource locationSource;

    private boolean  isFirstLocation = true;
    private NaverMap mNaverMap;
    private InfoWindow mInfoWindow; // 지도 위에 올리는 정보 창을 정의
    private boolean showMarket = false;

    String latitude;
    String longitude;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_map);
        mContext = MapActivity.this;

        // https://console.ncloud.com/mc/solution/naverService/application 에서 패키지명 등록 확인부터하라.
        mMapView = findViewById(R.id.map_view);
        mMapView.onCreate(savedInstanceState);
        mMapView.getMapAsync(this);

        // 내 현재 위치 버튼
        locBtnView = findViewById(R.id.btn_curr_location);
        // implementation "com.google.android.gms:play-services-location:17.0.0" 안해주면 에러 발생
        // 내 위치 찾기 위한 소스
        locationSource = new FusedLocationSource(this,LOCATION_PERMISSION_REQUEST_CODE);

        FloatingActionButton fab_message = findViewById(R.id.fab_message);
        fab_message.setOnClickListener(view -> {
            Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                    .setAction("Action", null).show();
        });
    }

    @Override
    public void onMapReady(@NonNull NaverMap naverMap) {
        UiSettings uiSettings = naverMap.getUiSettings();
        uiSettings.setLocationButtonEnabled(false);
        uiSettings.setCompassEnabled(true);

        // 카메라 초기 위치 설정
        LatLng initialPosition = new LatLng(37.506855, 127.066242);

        mNaverMap = naverMap;

        locBtnView.setMap(naverMap);

        // Location Change Listener를 사용하기 위한 FusedLocationSource 설정
        naverMap.setLocationSource(locationSource);
        naverMap.setLocationTrackingMode(LocationTrackingMode.NoFollow); // LocationTrackingMode.Face

        naverMap.addOnLocationChangeListener(location -> {
            if (isFirstLocation) { // 현재 위치로 초기 화면 이동
                naverMap.setLocationTrackingMode(LocationTrackingMode.None);
                LatLng initialPosition1 = new LatLng(location.getLatitude(),location.getLongitude()); // 현재 위치
                latitude = String.valueOf(location.getLatitude());
                longitude = String.valueOf(location.getLongitude());
                PrefsHelper.write("latitude",latitude);
                PrefsHelper.write("longitude",longitude);
                //Log.e(TAG,"latitude:"+latitude+" , longitude:"+longitude);
                CameraUpdate cameraUpdate = CameraUpdate.scrollTo(initialPosition1).animate(CameraAnimation.Easing);
                naverMap.moveCamera(cameraUpdate);
            }
            isFirstLocation = false;
        });

    }
}


지도 처리를 위해서는 Naver Cloud 에 앱패키지명을 등록해야 한다.


테스트에 사용한 파일을 첨부한다.

slidingcontentdrawer.zip


블로그 이미지

Link2Me

,
728x90

네이버 지도를 이용하기 위해서는 네이버 클라우드 플랫폼에 환경 설정 정보를 등록해야 한다.

 

https://www.ncloud.com/ 에 로그인 하고 나서 결제 연동 정보 등을 등록하고 이용하고자 하는 API를 선택한다.

 

기본 초기 설정 정보는 https://imweb.me/faq?mode=view&category=29&category2=37&idx=48381 참조하시라.

 

 

 

 

 

 

Web URL 또는 Android 앱 패키지 명을 등록하고 저장하면 앱 또는 Android에서 naver map aip를 이용할 수 있다.

 

 

 <meta-data
   android:name="com.naver.maps.map.CLIENT_ID"
   android:value="클라이언트값" />

 

Client ID 값을 복사하여 클라이언트값에 붙여넣기한다.

 

네이버 지도 소개

https://navermaps.github.io/android-map-sdk/guide-ko/0.html

 

네이버지도 데모파일

https://github.com/navermaps/android-map-sdk

 

오픈 소스를 활용한 지도 구글 서비스 개발 (서버 : Node.js)

https://github.com/kairo96/

이 자료는 책을 구입해서 같이 봐야 이해하기 쉽고 도움된다.

http://www.kyobobook.co.kr/product/detailViewKor.laf?ejkGb=KOR&mallGb=KOR&barcode=9791185890968&orderClick=LAG&Kc=

 

블로그 이미지

Link2Me

,