안드로이드/GoogleMaps


개발 과정

테드

투명액티비티( 레이아웃은x style 배경투명색 -> 매니페스트로 적용)
http://mydevromance.tistory.com/22

액티비티 클릭시 액티비티 끝내기
http://mydevromance.tistory.com/22

핸들러를 통해서 3초 늦게 투명액티비티 띄우기
http://yoo-hyeok.tistory.com/31


addMarket함수에서 마크 찍을 때, snippter으로 busstop 들어가게하고,
place에서는 place로 들어가게 해서, busstop 일때만, 마커바꾸기+액티비티

백버튼 Rxlifecycle은 onPause에서 멈추질 않고 onCreate(구독)->onDestroy에서(구독그만)을 실행해준다.
그래서 계속 갱신토스트가 떳다.
-https://stackoverflow.com/questions/49763034/how-to-pause-rxjava-observable-interval-when-underlying-activity-pauses
Compositedisposable에 subscribe까지 한 내용을 담아  이용해서 중단/재개 시킨다.


마커추가시 모델에 int 추가-> marker의 zindex에 보냄-> rxbus를 int형으로 받아서 보내기(Object)로 하니까.. 잘안된다.
rxbus를 int형으로 개조할듯
--->
마커는 같은 자리 찍으면,,, marker에 달린 snippet이 0으로 먹힌다.. 액티비티를 새로 열어도..
-> 다시 RxBus로 하되, 싱글턴패턴 업그레이드하고...
-> busstop_id =0 일 때, 천천히해달라고 토스트메세지를 띄우자. 그리고 마커추가하는 함수 getsamplelist() 걸어두자..확인은x


투명버튼

광주버스json으로 바꿈
http://bus.gjcity.net/busmap/stationSearch#


리플효과
https://www.android-examples.com/add-ripple-effect-animation-on-android-button/

소개 다이얼로그
http://webnautes.tistory.com/1094
  - 구글맵이 떠있는 상태로 띄우기 때문에, dialog나 액티비티를 띄우면 다시 호출되서 돌아올 때 앱이꺼진다
  -

    private void show() {
         Logger.i("");

        final List<String> ListItems = new ArrayList<>();

        ListItems.add("개발자 : 동신한의 조재성");
         ListItems.add("기획 : 동신한의 양운호");
         ListItems.add("-----------------------");
         ListItems.add("Rxjava2 : React Extenstion");
         ListItems.add("Retrofit2 : 통신 라이브러리");
         ListItems.add("Googlemap : 맵 및 정류장 마커");
         ListItems.add("광주버스 : 통신 json파일 제공");
         ListItems.add("-----------------------");
         final CharSequence[] items =  ListItems.toArray(new String[ ListItems.size()]);

        AlertDialog.Builder builder = new AlertDialog.Builder(this);
         builder.setTitle("AlertDialog Title");
         builder.setItems(items, new DialogInterface.OnClickListener() {
             public void onClick(DialogInterface dialog, int pos) {
                 String selectedText = items[pos].toString();
                 Toast.makeText(IntroActivity.this, selectedText, Toast.LENGTH_SHORT).show();
             }
         });
         builder.setPositiveButton("닫기",new DialogInterface.OnClickListener() {
             public void onClick(DialogInterface dialog, int which) {
                 finish();
             }
         });
         builder.show();
     }


액션바 색상변경
https://medium.com/@mindwing/actionbar-%EB%A5%BC-%EB%8B%A4%EB%A4%84%EB%B4%85%EC%8B%9C%EB%8B%A4-401709e5480d

리스트가 비어서 [] 로 호출된다면,  fromIterable로 못들어 간다면,
isEmpty()로 비교해서 액티비티 닫도록
- 구글에서 java 리스트 비었을 때 비교 로 검색

리스트 1개 더 만들어놔서 예비리스트가 isEmpty()가 아닐 때, equals로 비교해보자.
equals는 리스트의 각 해쉬코드를 비교하기 때문에, 옵져버블출신 datas는 리스트해쉬코드가 바껴서 들어온다.
1번째 아이템을 get(0).getLocation()하여 같은 문자열이 찍혀도, equals는 듣지 않는다.
-> 조건식을 잘못달았따. 앞에는 null이 아닐 때 true && 뒤에도 원하는 조건이 true로 나와야한다.
  list2개가 다를 때, true가 떠야한다.
!=은 비교안되더라...ㅠㅠ;




1. 먼저 지도를 띄울 액티비티에 OnMapClickListener를 달아주어야한다.

* OnMarkerClickListener도 달아주어야 하지만, 나는 이전 강의에서, 그냥 마커 변수에다가 리스너를 달아줬다.


public class MainActivity extends AppCompatActivity implements OnMapReadyCallback, GoogleMap.OnMapClickListener{


2. 마커를 add해줄 때, 입력할 데이터들의  Model 클래스를 만들어주자. 이렇게 모델로 만들어서, 정보창에 한꺼번에 들어가도록 할 것이다.

  이때, 위도, 경도, 제목 정도를 필드(멤버변수)로 넣어준다. 각 객체에서 3가지만 이용하도록 하자.

   3가지 변수를 만들고, 생성자를 만들고, getter/setter도 만들어준다.


public class MarkerModel {

double lat;
double lng;
String title;

public MarkerModel(double lat, double lng, String title) {
this.lat = lat;
this.lng = lng;
this.title = title;
}

public double getLat() {
return lat;
}

public void setLat(double lat) {
this.lat = lat;
}

public double getLng() {
return lng;
}

public void setLng(double lng) {
this.lng = lng;
}

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}
}


3.  addMarker를 커스텀으로 정의해준다. api의 addMarker와 다르게, MarkerModel 변수, 마커가 선택되어있는지 유무를 파라미터로 받는다.

 MarkerModel 객체(위도,경도,제목) 와 클릭된 상태 유무(isSelectedMarker)를 같이 받아서 ->

  (1)  MakerModel.get변수명으로 위도와 경도, 제목을 빼내어서 --> 마커옵션에 정보를 넣는다.

  (2) 클릭된 상태유무를 받아서, 다른 이미지를 보여주도록 if문으로 --> 마커옵션에 icon을 설정한다.

 >  그 마커옵션으로 실제 공식 addMarker하는 mMap변수에 .addMarker(마커옵션);을 통해 mMap에 마커를 찍는다. 

     원래 api상에서, addMarker는 반환형이 Marker이다. 맵변수.addMarker(마커옵션);을 통해 Marker가 반환된다.


  ***아직 입력받은 위도, 경도, title중에 title만 표시한다.

    public Marker addMarker(MarkerModel markerModel, boolean isSelectedMarker) {


LatLng position = new LatLng(markerModel.getLat(), markerModel.getLng());
String title = markerModel.getTitle();

/* tv_marker.setText(title);

if (isSelectedMarker) {
tv_marker.setBackgroundResource(R.drawable.ic_marker_phone_blue);
tv_marker.setTextColor(Color.WHITE);
} else {
tv_marker.setBackgroundResource(R.drawable.ic_marker_phone);
tv_marker.setTextColor(Color.BLACK);
}*/


//작업5. for문을 돌고 있는 addMarker는 , 돌면서 하나하나에 , 제목,위치,아이콘을 넣는다.
MarkerOptions markerOptions = new MarkerOptions();
markerOptions.title(title);
markerOptions.position(position);

//작업6. 마커에 옵션으로 아이콘을 배치하는데, 클릭에 따라서 다른 아이콘 배치
if (isSelectedMarker) {
markerOptions.icon(BitmapDescriptorFactory.
fromResource(R.drawable.ic_directions_bus_blue_24dp));
} else {
markerOptions.icon(BitmapDescriptorFactory.
fromResource(R.drawable.ic_directions_bus_black_24dp));
}

return mMap.addMarker(markerOptions);
}


4. 이제, MarkerModel형식으로 직접 마커를 넣어주는 매쏘드 getSampleMarkerItems()를 만들어준다.

   ArrayList<MarkerModel> 형식의 list에다가 .add로, 찍어줄 마커들의 정보를 담아서 넣는다.

    -> list에 담긴 마커(위,경,좌표)들은 for문을 통해서, <선택되었는지 유무인 isSelectedMarker>를 초기화로서 false로 같이 입력하여

   정의해준 addMarker의 파라미터에 들어간다.


private void getSampleMarkerItems() {

ArrayList<MarkerModel> sampleList = new ArrayList();

//작업4. 원래는 서버에서 받을 정보
sampleList.add(new MarkerModel(35.050917, 126.723083, "나주농협 송현지점(하행)"));
sampleList.add(new MarkerModel(35.050733, 126.723019, "나주농협 송현지점(상행)"));
sampleList.add(new MarkerModel(35.050538, 126.720457, "동신대 종점"));
sampleList.add(new MarkerModel(35.049065, 126.720265, "나주동신대(하행)"));


for (MarkerModel markerModel : sampleList) {
addMarker(markerModel, false);
}
}


5. 클릭시 넘어올 때, 선택된 마커로서, 겹쳐서 색칠된 아이콘을 띄워줄 마커를 전역변수로 추가한다.

private Marker selectedMarker;

6. mMap. 클릭리스너 내부에, 클릭시 <넘어오는 마커 - 위,경,제목의 정보를 가짐>를 이용해서 아이콘을 겹쳐서 생성/삭제 하는 changeSelectedMarker()를 만든다.

 (1) 이전에 클릭되서 기존 색칠된 아이콘이 띄워져 있을 경우, 삭제하는 처리

  1.selectedMarkernull아니라서 띄워져있다면 의 정보를 빼내서

          그자리에 색칠안된 일반 마커가 찍히도록 addMarker(selectedMarker, false)를 통해 찍어준다.

2.selectedMarker의 자리에 색칠 안된 마커가 찍혀지게 됬으므로 삭제해준다.
3.정보창은 클릭되서 넘어온 marker자리에 정보창을 띄워준다.

 (2) 클릭된 자리에, 색칠마커에 정보를 넣어주고, 클릭된 자리의 색칠안된 마커는 없애주는 처리

1.넘어오는 markernull만 아니면 (기본적으로클릭되서 넘어온 Maker에서 위,,제목을 이용해
추가적으로 addMarker모델, true );로  선택됬을 때 나오는 아이콘겹쳐서 하나 더  찍어주고, marker정보를 selectedMarker에 넣는다.
2. 넘어오는 marker는 이제 지워준다(marker.remove();)

3. selectedMarker의 정보창을 띄워준다.
private void changeSelectedMarker(Marker marker) {

//작업 12. 최고난도,

// 선택했던 마커 되돌리기
if (selectedMarker != null) {
addMarker(new MarkerModel(selectedMarker.getPosition().latitude,selectedMarker.getPosition().longitude,selectedMarker.getTitle()), false);
selectedMarker.remove();

marker.showInfoWindow();
}

// 선택한 마커 표시
if (marker != null) {
selectedMarker = addMarker(new MarkerModel(marker.getPosition().latitude,marker.getPosition().longitude,marker.getTitle()), true);
marker.remove();
selectedMarker.showInfoWindow(); //클릭리스너를 달면, 클릭->정보창 안떴는데, 클릭리스너안에 showInfoWindow()를 호출하면 정보창도 같이 뜬다!!
}
}




7. 과제 

아직 입력받은 위도, 경도, title중에 title만 addMarker( , ){}속에서 마커옵션으로 넣어준다.

나는 커스텀xml에, recyclerview를 넣고, 어댑터에다가 파싱한 정보를 뿌려줘야한다.




  1. 오소리 2019.08.06 11:07

    우선 좋은글 감사드립니다
    한의대 공부하면서 프로그램까지 대단 하시네요
    마커모델에 데이타 값을 추가 하고
    가령 가격,거래일시등;
    마커옵션.icon에 커스텀 레이아웃(xml)을 인플레이트해서 보여주는 방법이 있던데
    잘 실행이 안돼네요



[1] 현재 위치를 사용하기 위해서, Google Places API for Android를 써야한다. 


1. api키를 받아오기 위해서 구글 클라우드 플랫폼의 [라이브러리]로 가서 기존 프로젝트에 Google Places API for Android를 사용설정한다.

 - 1번 게시물에서 매니피스트에 넣어준 maps의 key와 같이 사용하므로, 그대로 둔다.

* 튜토리얼을 따라가기 위해서, 그외 1번 게시물에 작성한 자바코드는 다 주석처리하고 공부하였다.



2. 1번 게시물에서 추가한 Google Maps API와 동일한 버전의 , location과 places SDK가 필요하다. gradle에 추가하.


    compile 'com.google.android.gms:play-services-location:11.8.0'

    compile 'com.google.android.gms:play-services-places:11.8.0'




[2] 예제를 참고하여, 튜토리얼을 따라가보자.  파일 세팅 및 변수들 먼저 참고하자.

/android-samples

https://github.com/googlemaps/android-samples/blob/master/tutorials/CurrentPlaceDetailsOnMap/app/src/main/java/com/example/currentplacedetailsonmap/MapsActivityCurrentPlace.java


1. 먼저 예제에 필요한 xml파일 2개를 복사붙혀넣기 하자.

android-samples/tutorials/CurrentPlaceDetailsOnMap/app/src/main/res/

 - res > menu > current_place_menu.xml

https://github.com/googlemaps/android-samples/blob/master/tutorials/CurrentPlaceDetailsOnMap/app/src/main/res/menu/current_place_menu.xml

 - res > layout > custom_info_contents.xml 

https://github.com/googlemaps/android-samples/blob/master/tutorials/CurrentPlaceDetailsOnMap/app/src/main/res/layout/custom_info_contents.xml



2. 툴바에 메뉴를 넣어주기 위해 아래 매쏘드 2개를 오버라이딩 하자.

    //2-1메뉴 생성

    @Override

    public boolean onCreateOptionsMenu(Menu menu) {

        getMenuInflater().inflate(R.menu.current_place_menu, menu);

        return true;

    }

    //2-2 해당메뉴 선택시 작동할 코드

    @Override

    public boolean onOptionsItemSelected(MenuItem item) { 

        if (item.getItemId() == R.id.option_get_place) {

            showCurrentPlace();

        }

        return true;

    }





3. 액티비티에 쓰일 모든 변수들을 복붙하자.


private GoogleMap mMap; //화면이동, 마커달기 등등으로 쓰이는 구글맵 변수.
private CameraPosition mCameraPosition; //1번 게시물에서 사용했었던, 좌표로이동 + 3d 효과까지 줄 수 있는 변수

// The entry points to the Places API.
private GeoDataClient mGeoDataClient; // 구글 Places API에 접근해서 지역정보를 얻는 변수
private PlaceDetectionClient mPlaceDetectionClient; //구글 Places API에 접근해서 <현재 위치>를 얻는 변수

// The entry point to the Fused Location Provider.
private FusedLocationProviderClient mFusedLocationProviderClient; // 구글 Places API에 접근해서, <융합된 주위정보>를 얻는 변수

// A default location (Sydney, Australia) and default zoom to use when location permission is
// not granted.
private final LatLng mDefaultLocation = new LatLng(35.05148245, 126.72306776); //인터넷 연결안됬을 때, 연결된 default 시작장소의 좌표값 <수정해서 사용>
private static final int DEFAULT_ZOOM = 15; // 줌의 정도 상수
private static final int PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION = 1; // 위치정보사용에 대한 동의 상수로, requestCode랑 비교해서 같으면 ok
private boolean mLocationPermissionGranted; // 불린형으로, 위치정보사용 동의시 true대입

// The geographical location where the device is currently located. That is, the last-known
// location retrieved by the Fused Location Provider.
private Location mLastKnownLocation; // mFusedLocationProviderClient에 의해 Places API에 연결되어 현재위치의 주위Location정보를 담는 Location변수

private static final String KEY_CAMERA_POSITION = "camera_position";
private static final String KEY_LOCATION = "location";
// 액티비티가 잠시 중지되어서 다시 켜질 때를 위해, 값을 저장할라고 호출되는 onSaveInstanceState()에 사용되는 키값으로, 파라미터로 넘어오는 번들객체에다가
// 번들의 밸류인 현재좌표, 현재위치정보를 담을 때 사용될 번들의 키값 상수들이다.


// Used for selecting the current place.
private static final int M_MAX_ENTRIES = 5; // [메뉴]버튼을 눌렀을 때, 현재위치에서 좋아할만한 장소를 띄울 <최대>갯수로서, 아래 있는 각 장소에 대한 이름/주소/속성/좌표의 최대 갯수가 되기도 한다. 만약 내가 좋아할만한 장소가 int count랑 비교해서 5개가 안되면 count 만큼 띄워지도록 비교하는 문장이 있다.
private String[] mLikelyPlaceNames;
private String[] mLikelyPlaceAddresses;
private String[] mLikelyPlaceAttributions;
private LatLng[] mLikelyPlaceLatLngs; // 좋아할만한 장소에 대한 값들



[3] 액티비티 자바코드를 따라가면서 분석해보자.


1. 먼저 필요한 콜백함수를 액티비티에 추가하자.

public class MainActivity extends AppCompatActivity implements OnMapReadyCallback

 - 1번 게시물에서 이미 작성한 콜백함수(인터페이스)로서, onMapReady()를 구현화 할 수 있다.


2. 이제 onCretae()에서 할 작업을 살펴보자.


(1) setContentView()로 액티비티xml을 붙히기전에, 만약 액티비티가 잠시중지되었다가 다시 호출되어 붙히는 거면, xml을 앱 화면에 붙히기 전에,

    액티비티가 중지되서 onSaveInstanceState()를 호출해 <위치좌표location 과 카메라 좌표값 cameraposition>을 넣어줬던 것을 불러와서, 

    해당 변수에 다싯 넣어 준다음 xml화면을 띄우게 하자

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);


        // Retrieve location and camera position from saved instance state.

        if (savedInstanceState != null) {

            mLastKnownLocation = savedInstanceState.getParcelable(KEY_LOCATION);

            mCameraPosition = savedInstanceState.getParcelable(KEY_CAMERA_POSITION);

        }

        // Retrieve the content view that renders the map.


        setContentView(R.layout.activity_main);


(2) Places API에 연결하는 각종 클라이언트들( 지역정보+ 현재위치추적 / 위치주변정보)을 각 변수에 받아오고, 지도 프래그먼트를 띄우자.

     

      // Construct a GeoDataClient.

        mGeoDataClient = Places.getGeoDataClient(this, null);

        // Construct a PlaceDetectionClient.

        mPlaceDetectionClient = Places.getPlaceDetectionClient(this, null);

        // Construct a FusedLocationProviderClient.

        mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this);


        // Build the map.

        SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()

                .findFragmentById(R.id.map);

        mapFragment.getMapAsync(this);


    }


[4]. 이제 액티비티 이너매쏘드들을 살펴보자.


1. onSaveInstanceState()는 액티비티 잠시 중지시에, 좌표와 위치주변정보를 담아놓는다.


    //액티비티가 중지되었을 때, 맵 만 살아 있으면, 맵변수에서 좌표정보/위치 주변정보를 번들객체에 담아둔다. -> onCreate()호출시 xml화면 보여주기 전에 이 값들을 회수할 것이다.

    @Override

    protected void onSaveInstanceState(Bundle outState) {

        if (mMap != null) {

            outState.putParcelable(KEY_CAMERA_POSITION, mMap.getCameraPosition());

            outState.putParcelable(KEY_LOCATION, mLastKnownLocation);

            super.onSaveInstanceState(outState);

        }

    }




2. onMapReady()는 맵이 로딩이 되었을 때 호출 되어서, 맵 변수를 가진다. 파라미터로 넘어오는 googleMap을 전역변수 mMap에 넣고 다른데서도 활용할 수 있게 하자.

(참고 : https://developers.google.com/maps/documentation/android-api/infowindows?hl=ko)

 (1) mMap에다가 <마커를 클릭했을 때, 정보창을 띄워주는>setInfoWindowAdapter()를 달아준다.

   그 정보창 어댑터는 2가지 인터페이스를 구현화 하는데,  getInfoWindow()와 getInfoContents()가 있다.

  우리는 layout 폴더에 추가해준 [custom_info_contents.xml]를 화면으로써 띄우는 getInfoContents()를 사용하고, getInfoWindow에는 return null처리 한다.

 - getInfoContents()에서는 [custom_info_contents.xml]를 인플레이트해서 (FrameLayout)으로 캐스팅해 R.id.map(맵 프래그먼트)에 붙힌다.

   인플레이팅이 완료되면, xml내부의 텍스트뷰2개를 id를 참조하고 setText해주는데,

  - 넘어오는 파라미터인 <마커>로부터 타이틀과 지역정보(대호동, 나주시, 서울)를 포함하는 Snippet를 받을 수 있다.


      mMap.setInfoWindowAdapter(new GoogleMap.InfoWindowAdapter() {


            @Override

            // Return null here, so that getInfoContents() is called next.

            public View getInfoWindow(Marker arg0) {

                return null;

            }


            @Override

            public View getInfoContents(Marker marker) {

                // Inflate the layouts for the info window, title and snippet.

                View infoWindow = getLayoutInflater().inflate(R.layout.custom_info_contents,

                        (FrameLayout) findViewById(R.id.map), false);


                TextView title = ((TextView) infoWindow.findViewById(R.id.title));

                title.setText(marker.getTitle());


                TextView snippet = ((TextView) infoWindow.findViewById(R.id.snippet));

                snippet.setText(marker.getSnippet());


                return infoWindow;

            }

        });


(2) 맵이 준비되었을 때, 첫번째로 호출되는 함수는 getLocationPermission()이다. (공식문서에 함수명 오타로 되있음)

  -  앱자체에 미리 권한이 받아져 있는지 물어보고, 받아져있으면 mLocationPermissionGranted에 true를 준다.

if (ContextCompat.checkSelfPermission(this.getApplicationContext(), android.Manifest.permission.ACCESS_FINE_LOCATION)  == PackageManager.PERMISSION_GRANTED) 

  -  만약 앱에 위치사용허가가 미리 안되있으면 else문으로 와서, 위치사용 허가를 물어보는 창을 띄운다.

ActivityCompat.requestPermissions(this, new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);


            private void getLocationPermission() { // 퍼미션을 요청하는 매쏘드

                if (ContextCompat.checkSelfPermission(this.getApplicationContext(),

                        android.Manifest.permission.ACCESS_FINE_LOCATION)

                        == PackageManager.PERMISSION_GRANTED) {

                    mLocationPermissionGranted = true;

                } else {

                    ActivityCompat.requestPermissions(this,

                            new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION},

                            PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);

                }

    }


 (3) 이렇게 띄워진, 퍼미션 요청 창은 클릭을 통해 자동으로 onRequestPermissionsResult()를 호출 시킨다. 

    - 허가가 없는 상태에서 띄워진 창 이므로, mLocationPermissionGranted 에는 변수선언만 하고 초기화 값이 들어가 있으니, false로 첨에 초기화 해놓는다.

   -  요청코드가 만약 로케이션 허용이라면, mLocationPermissionGranted 에 true가 들어가는 구조다.


    @Override  //getLocationPermission()에서 요청한 퍼미션 창에서 결과를 받아, 호출되는 콜백매쏘드. 사용자에 의해 결정된다.

    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

        mLocationPermissionGranted = false;

        switch (requestCode) {

            case PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION: {

                // If request is cancelled, the result arrays are empty.

                if (grantResults.length > 0

                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

                    mLocationPermissionGranted = true;

                }

            }

        }

    }






(4) 2번째로 호출되는 함수는 updateLocationUI()이다. 우측상단의 자신의 위치로 가게하는 버튼을 표시해주는 매쏘드다.


 - 먼저 mMap이 떠있는지 null검사를 한 뒤에, getLocationPermission()에 허가를 받아  mLocationPermissionGranted 가 true인 상태면,

    맵 변수 mMap에다가 2개의 옵션을 준다. 

 - setMyLocationEnabled()가 true이면, 우측상단에 [버튼띄운다]

    setMyLocationButtonEnabled()가 true이면, 우측상단에 [버튼을 띄운것을 허용한다]. 없어도 되지만 false면 사라진다.

    그러므로 2개는 같이 붙어다닌다.


                mMap.setMyLocationEnabled(true);

                mMap.getUiSettings().setMyLocationButtonEnabled(true);



(5) 3번째 호출되는 함수는 getDeviceLocation()이다.  (이것은 Location SDK에서 얻어온 놈으로, 현재위치를 조회할 수 있게 한다.)

*지니모션에서 모든 코드 작성후 NullPoint에러가 나면, 실제 디바이스로 컴파일 해보자. 그래도 안되면 코드수정

 - getLocationPermission()에서 mLocationPermissionGranted에 true값을 받은 상태면,

   mFusedLocationProviderClient를 이용해서 현재(최종)위치의 주변정보의 위도/경도를 뽑아내 --> task에 전달  --> 전달완료시

   mLastKnownLocation에다가 최종적으로 정보를 넣어준다. 

   그리고 난 후,  최종정보에서 위도/경도를 뽑아내, moveCamera()를 이용해서 mMap을 이동시킨다.

 - 만약 task의 작업이 실패했으면, mDefaultLocation로 이동하면서 mMap.getUiSettings().setMyLocationButtonEnabled(false);를 통해 [+] 내위치 버튼을 없앤다.


   @SuppressLint("MissingPermission")

    private void getDeviceLocation() { 


        /*

     * Before getting the device location, you must check location

     * permission, as described earlier in the tutorial. Then:

     * Get the best and most recent location of the device, which may be

     * null in rare cases when a location is not available.

     */

        try {

            if (mLocationPermissionGranted) {

                Task<Location> locationResult = mFusedLocationProviderClient.getLastLocation();

                locationResult.addOnCompleteListener(this, new OnCompleteListener<Location>() {

                    @Override

                    public void onComplete(@NonNull Task<Location> task) {

                        if (task.isSuccessful()) {

                            // Set the map's camera position to the current location of the device.

                            mLastKnownLocation = task.getResult();

                            mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(

                                    new LatLng(mLastKnownLocation.getLatitude(),

                                            mLastKnownLocation.getLongitude()), DEFAULT_ZOOM));




                        } else {

                            Log.d("", "Current location is null. Using defaults.");

                            Log.e("", "Exception: %s", task.getException());

                            mMap.moveCamera(CameraUpdateFactory

                                    .newLatLngZoom(mDefaultLocation, DEFAULT_ZOOM));

                            mMap.getUiSettings().setMyLocationButtonEnabled(false);

                        }

                    }

                });

            }

        } catch (SecurityException e) {

            Log.e("Exception: %s", e.getMessage());

        }


    }



Location SKD를 이용해서

< onCretae()에서 호출 -> 맵로딩 -> onMapReady()호출 -> 맵에다가 마커 및 정보창 달아놓기 / 퍼미션 요청 / 내위치버튼 달기 > 이다.

< 앞으로 메뉴버튼을 눌렀을 때 주변정보를 선택해서 표시할 수 있는 처리를 한다>


[5] 메뉴를 통해 띄워질 주변 정보를 처리해보자.

1. 일단 메뉴xml인 [current_place_menu.xml]를 수정해서 아이콘이 보이게 하자.

(메뉴에 관한 참고 : http://itmir.tistory.com/409)


(1) <item>속성중 android:icon로 수정해서 drawable폴더의 그림을 하나 넣자.

  - https://material.io/icons/ 에서 location을 검색해서 다운받고 프로젝트의 res폴더에 5개의 해상도별 폴더를 넣어주자. (자동으로 drawable에 나옴)

<menu xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item

        android:id="@+id/option_get_place"

        android:title="메뉴다"

        android:icon="@drawable/ic_add_location_black_24dp"

        app:showAsAction="always"/>

</menu>



2. 이제 onOptionsItemSelected()에서 해당 아이템이 눌러졌을 때 호출되는 showCurrentPlace()를 보자. 

    메뉴를 클릭했을때, <현재위치(location)의 주변정보(places)>를 보여준다.

- mLocationPermissionGranted을 먼저 확인한다. 위치사용허가가 난 상태면, mPlaceDetectionClient를 이용해서. getCurrentPlace()로 현재위치의 주변정보를 가져온다.

- 받은 현재장소 정보는 task placeResult에 담고 -> 처리 완료시에 ->[좋아할만한 장소] 최대 5개를 likelyPlaces에 담고 ->openPlacesDialog()를 호출한다.

  * 이때, getCurrentPlace()의 파라미터로, 가져올 수 있는 장소필터링을 넣는다. null로 되있어서, 검색을 해봤지만 필터링이 제대로 안되었다.

(필터로 넣어봤었는데 잘 안되서 주석처리함)


3. openPlacesDialog()에서는 메뉴버튼을 누를 때, 최대 5개가 담긴 [좋아할만한장소]가 뜨는데, 여기에 클릭리스너가 단다.

-메뉴중 하나가 클릭이 되면, 해당정보를 mMap에 마커를 찍고, 타이틀/위치/스니펫(지역정보)까지 입력시킨뒤, 카메라를 이동시킨다.

mMap.addMarker(new MarkerOptions()
.title(mLikelyPlaceNames[which])
.position(markerLatLng)
.snippet(markerSnippet));



[6] 이제 1번 게시물에서 배운 것을응용해보자. (마커를 클릭하면 3d로 가도록)


1. onMapReady()에서 mMap변수에 setInfoWindowAdapter()를 달아서, <마커클릭시>정보창을 띄우도록 했다.

  그러나, mMap에는 마커클릭리스너인 setOnMarkerClickListener()를 달 수 있는데, setInfoWindowAdapter()가 있는 상태에서 같은 수준의 마커클릭리스너를 달면,  마커클릭리스너가 우선순위로 되서 정보창이 안열린다.

이때, 마커클릭리스너의 파라미터인 marker를 .getPosition()이용해서 <1번게시물에서 했던 3D로 확대>코드를 넣고

이어서 마커클릭리스너 밖에서 입력되어 클릭시 정보창이 씹혔던 것을, 열어줄 수 있는 marker.showInfoWindow()을 이용해 확대후 정보창도 열어주자


@Override
public void onMapReady(final GoogleMap googleMap) {
mMap = googleMap; //공통변수를 mMap으로 선언해 받아쓴다.

//mMap위의 마커클릭시 행동을 나타낼 수 있는 마커클릭리스너가 있다.
mMap.setOnMarkerClickListener(new GoogleMap.OnMarkerClickListener() {
@Override
public boolean onMarkerClick(Marker marker) {

CameraPosition cameraPosition = new CameraPosition.Builder()
.target(marker.getPosition()) // Sets the center of the map to Mountain View
.zoom(17) // Sets the zoom
.bearing(90) // Sets the orientation of the camera to east
.tilt(30) // Sets the tilt of the camera to 30 degrees
.build(); // Creates a CameraPosition from the builder
mMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));

marker.showInfoWindow(); //클릭리스너를 달면, 클릭->정보창 안떴는데, 클릭리스너안에 showInfoWindow()를 호출하면 정보창도 같이 뜬다!!

return true;
}
});


2. 기본적인 동신대 정류장을 마크로 찍어놓자.

onMapReady()에서 mMap.에 addMarker로 찍으면 될 것 같다.



3. 더 심화를 공부할 때는 커스텀 마커를 사용하는 사이트를 참고하자

 - http://gun0912.tistory.com/57


***첨에 들어올 때, 한번 현재위치 못잡는다.

***위도경도에 null에러가 나면 && task.Result() != null를 조건에 붙혀주자.

*** 처음 현재위치 못잡는거에 대해서, 검색했더니, 공식문서는 GoogleClientApi를 쓴다... 나중에 이걸로 현재위치를 가져오자.







[코드 저장]


package com.example.cho.googlemap_2018;

import android.annotation.SuppressLint;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.TextView;

import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.location.places.GeoDataClient;
import com.google.android.gms.location.places.PlaceDetectionClient;
import com.google.android.gms.location.places.PlaceLikelihood;
import com.google.android.gms.location.places.PlaceLikelihoodBufferResponse;
import com.google.android.gms.location.places.Places;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;

public class MainActivity extends AppCompatActivity implements OnMapReadyCallback {

private GoogleMap mMap; //화면이동, 마커달기 등등으로 쓰이는 구글맵 변수.
private CameraPosition mCameraPosition; //1번 게시물에서 사용했었던, 좌표로이동 + 3d 효과까지 줄 수 있는 변수

// The entry points to the Places API.
private GeoDataClient mGeoDataClient; // 구글 Places API에 접근해서 지역정보를 얻는 변수(안쓰임)
private PlaceDetectionClient mPlaceDetectionClient; //구글 Places API에 접근해서 <현재 위치>를 얻는 변수

// The entry point to the Fused Location Provider.
private FusedLocationProviderClient mFusedLocationProviderClient; // 구글 Places API에 접근해서, <융합된 주위정보>를 얻는 변수

// A default location (Sydney, Australia) and default zoom to use when location permission is
// not granted.
private final LatLng mDefaultLocation = new LatLng(35.050538, 126.720457); //인터넷 연결안됬을 때, 연결된 default 시작장소의 좌표값 <수정해서 사용>
private static final int DEFAULT_ZOOM = 15; // 줌의 정도 상수
private static final int PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION = 1; // 위치정보사용에 대한 동의 상수로, requestCode랑 비교해서 같으면 ok
private boolean mLocationPermissionGranted; // 불린형으로, 위치정보사용 허가에 대한 유/무

// The geographical location where the device is currently located. That is, the last-known
// location retrieved by the Fused Location Provider.
private Location mLastKnownLocation; // mFusedLocationProviderClient에 의해 Places API에 연결되어 현재위치의 주위Location정보를 담는 Location변수

private static final String KEY_CAMERA_POSITION = "camera_position";
private static final String KEY_LOCATION = "location";
// 액티비티가 잠시 중지되어서 다시 켜질 때를 위해, 값을 저장할라고 호출되는 onSaveInstanceState()에 사용되는 키값으로, 파라미터로 넘어오는 번들객체에다가
// 번들의 밸류인 현재좌표, 현재위치정보를 담을 때 사용될 번들의 키값 상수들이다.


// Used for selecting the current place.
private static final int M_MAX_ENTRIES = 5; // [메뉴]버튼을 눌렀을 때, 현재위치에서 좋아할만한 장소를 띄울 <최대>갯수로서, 아래 있는 각 장소에 대한 이름/주소/속성/좌표의 최대 갯수가 되기도 한다. 만약 내가 좋아할만한 장소가 int count랑 비교해서 5개가 안되면 count 만큼 띄워지도록 비교하는 문장이 있다.
private String[] mLikelyPlaceNames;
private String[] mLikelyPlaceAddresses;
private String[] mLikelyPlaceAttributions;
private LatLng[] mLikelyPlaceLatLngs; // 좋아할만한 장소에 대한 값들

public GoogleApiClient mGoogleApiClient;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Retrieve location and camera position from saved instance state.
if (savedInstanceState != null) {
mLastKnownLocation = savedInstanceState.getParcelable(KEY_LOCATION);
mCameraPosition = savedInstanceState.getParcelable(KEY_CAMERA_POSITION);
}
// Retrieve the content view that renders the map.
setContentView(R.layout.activity_main);

// Construct a GeoDataClient.
mGeoDataClient = Places.getGeoDataClient(this, null);
// Construct a PlaceDetectionClient.
mPlaceDetectionClient = Places.getPlaceDetectionClient(this, null);
// Construct a FusedLocationProviderClient.
mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this);





// Build the map.
SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
.findFragmentById(R.id.map);
mapFragment.getMapAsync(this);

}




//액티비티가 중지되었을 때, 맵 만 살아 있으면, 맵변수에서 좌표정보/위치 주변정보를 번들객체에 담아둔다. -> onCreate()호출시 xml화면 보여주기 전에 이 값들을 회수할 것이다.
@Override
protected void onSaveInstanceState(Bundle outState) {
if (mMap != null) {
outState.putParcelable(KEY_CAMERA_POSITION, mMap.getCameraPosition());
outState.putParcelable(KEY_LOCATION, mLastKnownLocation);
super.onSaveInstanceState(outState);
}
}



@Override
public void onMapReady(final GoogleMap googleMap) {
mMap = googleMap; //공통변수를 mMap으로 선언해 받아쓴다.


//응용2. 정류장 미리 찍어놓자.
mMap.addMarker(new MarkerOptions()
.title("나주농협 송현지점(하행)")
.position(new LatLng(35.050917, 126.723083)));
mMap.addMarker(new MarkerOptions()
.title("나주농협 송현지점(상행)")
.position(new LatLng(35.050733, 126.723019)));
mMap.addMarker(new MarkerOptions()
.title("동신대 종점")
.position(new LatLng(35.050538, 126.720457)));
mMap.addMarker(new MarkerOptions()
.title("나주동신대(하행)")
.position(new LatLng(35.049065, 126.720265)));
mMap.addMarker(new MarkerOptions()
.title("나주동신대(상행)")
.position(new LatLng(35.048784, 126.720469)));
mMap.addMarker(new MarkerOptions()
.title("싸이클경기장(하행)")
.position(new LatLng(35.046148, 126.719017)));
mMap.addMarker(new MarkerOptions()
.title("싸이클경기장(상행)")
.position(new LatLng(35.045753, 126.718888)));
mMap.addMarker(new MarkerOptions()
.title("정렬사(하행)")
.position(new LatLng(35.043807, 126.716275)));
mMap.addMarker(new MarkerOptions()
.title("정렬사(상행)")
.position(new LatLng(35.043596, 126.716698)));
mMap.addMarker(new MarkerOptions()
.title("정렬사사거리")
.position(new LatLng(35.043486, 126.715923)));



//응용1. mMap위의 마커클릭시 행동을 나타낼 수 있는 마커클릭리스너가 있다.
mMap.setOnMarkerClickListener(new GoogleMap.OnMarkerClickListener() {
@Override
public boolean onMarkerClick(Marker marker) {

CameraPosition cameraPosition = new CameraPosition.Builder()
.target(marker.getPosition()) // Sets the center of the map to Mountain View
.zoom(17) // Sets the zoom
.bearing(90) // Sets the orientation of the camera to east
.tilt(30) // Sets the tilt of the camera to 30 degrees
.build(); // Creates a CameraPosition from the builder
mMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));

marker.showInfoWindow(); //클릭리스너를 달면, 클릭->정보창 안떴는데, 클릭리스너안에 showInfoWindow()를 호출하면 정보창도 같이 뜬다!!

return true;
}
});

//Map에다가 setInfoWindowAdapter()를 달아준다. 그 정보창 어댑터는 2가지 인터페이스를 구현화 하는데, getInfoWindow()와 getInfoContents()가 있다.
// 우리는 layout 폴더에 추가해준 [custom_info_contents.xml]를 화면으로써 띄우는 getInfoContents()를 사용하고, getInfoWindow에는 return null처리 한다.
mMap.setInfoWindowAdapter(new GoogleMap.InfoWindowAdapter() {

@Override
// Return null here, so that getInfoContents() is called next.
public View getInfoWindow(Marker arg0) {
return null;
}


@Override
public View getInfoContents(Marker marker) {
// Inflate the layouts for the info window, title and snippet.
View infoWindow = getLayoutInflater().inflate(R.layout.custom_info_contents,
(FrameLayout) findViewById(R.id.map), false);

TextView title = ((TextView) infoWindow.findViewById(R.id.title));
title.setText(marker.getTitle());

TextView snippet = ((TextView) infoWindow.findViewById(R.id.snippet));
snippet.setText(marker.getSnippet());

return infoWindow;
}
});

getLocationPermission();
// Turn on the My Location layer and the related control on the map.
updateLocationUI();

// 8. 핸드폰의 현재위치를 맵의 포지션에 지정하는 매쏘드
getDeviceLocation();


/* final LatLng hoban_bustop = new LatLng(35.050607, 126.722983); //마커를 달 위치를 담는다.
googleMap.addMarker(new MarkerOptions().position(hoban_bustop).title("호반아파트 정류장")); //구글맵에 마커를 다는데, 옵션으로서, LatLng에 담긴 위치와 타이틀을 넣어준다.

//정류장들 중앙좌표 35.048169, 126.720031
LatLng center_bustop = new LatLng(35.048169, 126.720031); //처음 맵을 시작시킬 위치 담기
googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(center_bustop, 15)); //구글맵의 위치를 로딩이 완료된 시점에서, 맵의 보이는 위치를 옮겨 놓는다. 좌표와 함께, 줌의 정도도 같이준다.

//3d효과를 위한 버튼
Button button = (Button) findViewById(R.id.button_3d);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {


//3d효과
CameraPosition cameraPosition = new CameraPosition.Builder()
.target(hoban_bustop) // Sets the center of the map to Mountain View
.zoom(17) // Sets the zoom
.bearing(90) // Sets the orientation of the camera to east
.tilt(30) // Sets the tilt of the camera to 30 degrees
.build(); // Creates a CameraPosition from the builder
googleMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));

}
});*/
}





private void getLocationPermission() { // 퍼미션을 요청하는 매쏘드
if (ContextCompat.checkSelfPermission(this.getApplicationContext(), android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED)
{
mLocationPermissionGranted = true;
} else {
ActivityCompat.requestPermissions(this,
new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION},
PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);
}
}


@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String permissions[],
@NonNull int[] grantResults) {
mLocationPermissionGranted = false;
switch (requestCode) {
case PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
mLocationPermissionGranted = true;
}
}
}
updateLocationUI();
}

//11. 업데이트 ui 매쏘드를 만들어준다. onMapReady로 넘어오는 googlemap변수를 썼었는데, 여기서도 공통으로 쓰기위해, 전역변수로 선언해주고, onMapReady에서도 파라미터를 전역변수에 받아주어 사용한다.

private void updateLocationUI() {
if (mMap == null) {
return;
}
try {
if (mLocationPermissionGranted) {
mMap.setMyLocationEnabled(true);
mMap.getUiSettings().setMyLocationButtonEnabled(true);
} else {
mMap.setMyLocationEnabled(false);
mMap.getUiSettings().setMyLocationButtonEnabled(false);
mLastKnownLocation = null;
getLocationPermission();
}
} catch (SecurityException e) {
Log.e("Exception: %s", e.getMessage());
}
}

@SuppressLint("MissingPermission")
private void getDeviceLocation() { //15 디바이스 위치를 얻는 함수 추가. //17. 안에 LocationServices 이놈이 import 안된다... gradle compile 'com.google.android.gms:play-services-location:11.8.0' 추가해줄 것이다. maps랑 같은 버전으로 이후 rebuild

/*
* Before getting the device location, you must check location
* permission, as described earlier in the tutorial. Then:
* Get the best and most recent location of the device, which may be
* null in rare cases when a location is not available.
*/

try {
if (mLocationPermissionGranted) {
Task<Location> locationResult = mFusedLocationProviderClient.getLastLocation();
locationResult.addOnCompleteListener(this, new OnCompleteListener<Location>() {





@Override
public void onComplete(@NonNull Task<Location> task) {
if (task.isSuccessful() && task.getResult() != null) {
// Set the map's camera position to the current location of the device.
mLastKnownLocation = task.getResult();
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(
new LatLng(mLastKnownLocation.getLatitude(),
mLastKnownLocation.getLongitude()), DEFAULT_ZOOM));



} else {
Log.d("", "Current location is null. Using defaults.");
Log.e("", "Exception: %s", task.getException());
mMap.moveCamera(CameraUpdateFactory
.newLatLngZoom(mDefaultLocation, DEFAULT_ZOOM));
mMap.getUiSettings().setMyLocationButtonEnabled(false);
}
}
});
}
} catch (SecurityException e) {
Log.e("Exception: %s", e.getMessage());
}





}


//2-1메뉴 생성
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.current_place_menu, menu);
return true;
}
//2-2메뉴추가
@Override
public boolean onOptionsItemSelected(MenuItem item) { //21. 메뉴xml을 깃허브에서 복사해온 뒤에, 메뉴달아주는 코드.
if (item.getItemId() == R.id.option_get_place) {
showCurrentPlace();
}
return true;
}






private void showCurrentPlace() {
if (mMap == null) {
return;
}

if (mLocationPermissionGranted) {


//필터 넣으려해봤지만 안됨.
/* ArrayList<String> filters = new ArrayList<>();
filters.add(Place.TYPE_ATM + "");
filters.add(Place.TYPE_BANK + "");
filters.add(Place.TYPE_BUS_STATION + "");
filters.add("restaurant");
filters.add("establishment");
filters.add(Place.TYPE_STORE + "");

PlaceFilter placeFilter = new PlaceFilter(false, filters);*/


// Get the likely places - that is, the businesses and other points of interest that
// are the best match for the device's current location.
@SuppressWarnings("MissingPermission") final
Task<PlaceLikelihoodBufferResponse> placeResult = mPlaceDetectionClient.getCurrentPlace(null);
placeResult.addOnCompleteListener
(new OnCompleteListener<PlaceLikelihoodBufferResponse>() {
@Override
public void onComplete(@NonNull Task<PlaceLikelihoodBufferResponse> task) {
if (task.isSuccessful() && task.getResult() != null) {
PlaceLikelihoodBufferResponse likelyPlaces = task.getResult();

// Set the count, handling cases where less than 5 entries are returned.
int count;
if (likelyPlaces.getCount() < M_MAX_ENTRIES) {
count = likelyPlaces.getCount();
} else {
count = M_MAX_ENTRIES;
}

int i = 0;
mLikelyPlaceNames = new String[count];
mLikelyPlaceAddresses = new String[count];
mLikelyPlaceAttributions = new String[count];
mLikelyPlaceLatLngs = new LatLng[count];

for (PlaceLikelihood placeLikelihood : likelyPlaces) {
// Build a list of likely places to show the user.
mLikelyPlaceNames[i] = (String) placeLikelihood.getPlace().getName();
mLikelyPlaceAddresses[i] = (String) placeLikelihood.getPlace()
.getAddress();
mLikelyPlaceAttributions[i] = (String) placeLikelihood.getPlace()
.getAttributions();
mLikelyPlaceLatLngs[i] = placeLikelihood.getPlace().getLatLng();

i++;
if (i > (count - 1)) {
break;
}
}

// Release the place likelihood buffer, to avoid memory leaks.
likelyPlaces.release();

// Show a dialog offering the user the list of likely places, and add a
// marker at the selected place.
openPlacesDialog();

} else {
}
}
});
} else {
// The user has not granted permission.

// Add a default marker, because the user hasn't selected a place.
mMap.addMarker(new MarkerOptions()
.title(getString(R.string.default_info_title))
.position(mDefaultLocation)
.snippet(getString(R.string.default_info_snippet)));

// Prompt the user for permission.
getLocationPermission();
}
}
private void openPlacesDialog() { //메뉴가 눌러졌을 때, 5개 좋아할만한 장소를 띄우고, 클릭리스너를 처리하는 곳.
// Ask the user to choose the place where they are now.
DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// The "which" argument contains the position of the selected item.
LatLng markerLatLng = mLikelyPlaceLatLngs[which];
String markerSnippet = mLikelyPlaceAddresses[which];
if (mLikelyPlaceAttributions[which] != null) {
markerSnippet = markerSnippet + "\n" + mLikelyPlaceAttributions[which];
}

// Add a marker for the selected place, with an info window
// showing information about that place.
mMap.addMarker(new MarkerOptions()
.title(mLikelyPlaceNames[which])
.position(markerLatLng)
.snippet(markerSnippet));



/* CameraPosition cameraPosition = new CameraPosition.Builder()
.target(markerLatLng) // Sets the center of the map to Mountain View
.zoom(17) // Sets the zoom
.bearing(90) // Sets the orientation of the camera to east
.tilt(30) // Sets the tilt of the camera to 30 degrees
.build(); // Creates a CameraPosition from the builder
mMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));*/

// Position the map's camera at the location of the marker.
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(markerLatLng,
DEFAULT_ZOOM));




}
};

// Display the dialog.
AlertDialog dialog = new AlertDialog.Builder(this)
.setTitle(R.string.pick_place)
.setItems(mLikelyPlaceNames, listener)
.show();
}
}


  1. 박카스 2019.02.13 13:28

    안녕하세요! android로 구글맵 써보려고 하는 학생입니다.

    json으로 정보를 받아서 받은 정보의 좌표를 찍어주려고 하는데 onMapReady에서 찍어줘도 되나요??

    • nittaku 2019.02.15 16:40 신고

      네. 근데 저는 유튜브랑 책 독학이라 현업에서는 어떻게하는지는 몰라요 . 좌표도 몇개안찍었어서요^^
      한번 다른 프로젝트 검색도 해보셔요 온맵래디로 구글에서욥

  2. kghjdo 2020.11.20 00:13

    유용한 내용 정말 잘 보고 가용~

     세팅하기 - 지니모션 설치 > 구글 플레이 설치 > 안드로이드 연동


1. 지니모션 공식홈페이지(https://www.genymotion.com) > 가입후 > [Resourcess] > [Fun Zone] > 개인사용자용 다운


2. 설치시 VirtualBox까지 설치할 것


3. 디바이스를 선택해 실행하였는데, 에러가 난다면

    VirtualBox  실행 > 파일 > 업데이트찾기 > 최신업데이트 설치할 것!


4. 지니모션의 구글플레이 설치를 위한 ARM파일 설치 파일(첨부파일)을 지니모션 화면에 drag&drop


Genymotion_ARM_Translation_v1.1.zip



5. 구글플레이 설치를 위해 지니모션 우측메뉴중 GApps 클릭후 설치


6. 지니모션 우측 메뉴중 GPS를 켜고, 현재위치로 쓸 좌표값도 넣어 놓을 것


7. 한글입력을 위해 구글플레이스토어에 'google korean input' 설치


8. 안드로이드 스튜디오와 연동

(1) File > Settings > Plugins를 선택하고, 하단에 보이는 Browse repositories 버튼을 클릭

(2) 왼쪽 상단에 보이는 입력란에 genymotion을 검색 후 Genymotion 플러그인의 세부 정보에서 Install 초록색 버튼을 클릭

(3) 플러그인 설치완료 후,  Restart Android Studio 버튼을 클릭

(4) 안드로이드 스튜디오가  다시 실행되면  메뉴에서 File > Settings > [ Other Settings ] > Genymotion을 선택 후 Genymotion 설치경로 입력

     cf)나는 c용량이 모자라서 d에서 설치 ==>  D:\Program Files\Genymobile\Genymotion

(5)툴바 오른쪽 끝에 Genymotion 아이콘이 추가된다. Android Studio 3.0의 경우 툴바가 디폴트로 보이지 않는다면,

    메뉴에서 View > Toolbar를 선택하면 보인다.




[1]구글지도를 액티비티에 띄우기

( 키값받기 -> 라이브러리추가 + 메타데이터에 키값 넘기기 -> 액티비티위에 SupportMapFragment올리기)


1. 새로운 프로젝트 GoogleMap_2018 을 만든다. 액티비티에 GoogleMaps Activity가 있지만, 

보통은 기존앱에 이식해서 쓰므로, 이것으로 만들면 이식할 수 가 없다.


2. 구글맵을 쓰기 위해서는 구글에서 권한(키)을 받아와야한다. 검색창에 구글api 라 검색해서, 콘솔의 대쉬보드로 오자.

지금은 구글APIs가 구글 Cloud Platform으로 바뀌었다.

경로 : https://console.cloud.google.com/apis/dashboard


(1) 우측상단의 [프로젝트 선택] > 만들기 > 프로젝트명 : GoogleMap2018 를 입력해서 만든다

.(2) 왼쪽메뉴중 [라이브러리]를 선택해서 [지도]의 [Google Maps Android API]를 선택하자. [사용설정]을 누른 다음, [사용 중지]가 뜬다.(활성화되었다)

(3) 왼쪽메뉴중 [사용자 인증정보]를 눌러서 [사용자 인증정보 만들기] > [API 키 생성]를 누르자., 키가 주어진다. 키값을 복사해놓자.

  - AIzaSyDK3(이런식)0HMgP(으로만들어진다)bWh27UmK-ED-U 

(4) 키값을 복사한 뒤, [대시보드]로 돌아와, [API] > GoogleMapsAndroidAPI (혼자 사용중표시있음)을 클릭하고 온 뒤 > API정보 우측에 [문서]를 클릭한다.

(5) 문서의 [가이드]탭을 열어놓자. ( 가이드는 액티비티만들때, GoogleMapsActivity를 만들어서 사용하라고 가이드해있다. 일케하면 기존앱에 이식불가)


3. 키값을 적용하기 위해 다시 안드로이드프로젝트로 와서, 구글맵SDK를 받아와야한다.

(1)[ alt + ctrl+ shift + s ] or  File>Open Module Settings       >   app > dependecny> + > library를 선택해서 [gms]를 검색하자

 - com.google.android.gms:play-services-maps:11.8.0 를 찾아서 선택하여 라이브러리를 추가해주자.


(2) 키값은 manifest에다가 넣어주는 것이다! 매니페스트에 <메타데이터>를 아래와 같이 추가해준다. 입력은 서비스와 같이, application 안, activity박!

  - name속성에는 정해진 코드를, value에다가는 콘솔-사용자인증정보에 있는 키값을 넣어준다. 

  - 메타데이터가 세팅되서 구글api에 인증정보를 넘겨준다.


        </activity>


        <meta-data

            android:name="com.google.android.geo.API_KEY"

            android:value="AIzaSyDK3stJvW6DL50HMgPCpCbWh27UmK-ED-U"/>  //인증받은 키값


    </application>


4. 구글맵을 띄울 틀을 만들어 주기 위해서, res>메인액티비티xml에 Design탭으로 온다.

(1) palette에서 fragment를 검색해서 올려준다. 디자인탭에서 추가하는 이유는 fragment를 드래그해서 올리면, fragment리스트들을 보여기 때문!

    <SupportMapFragemnt>를 찾아서 선택해준다. 그리고 화면에 꽉 채워준다.


(2) 앱을 실행시켜서 지도를 확인해보자. 만약 메타데이터를 주석처리하면 키값을 서버에 못넘겨줘서 권한을 못얻어 지도가 아예 안뜬다.



Q. 자신의 현재 위치 강의도 해주세요!! 요즘 이거때문에 애먹고 있거든요 ㅎㅎ

A .참고할 문서를 말씀드리면 https://developers.google.com/maps/documentation/android-api/location?hl=ko 참고하시구요.

   위치정보 가져올때는 에뮬레이터로 돌리시면 안됩니다. 스마트폰으로 하셔야 합니다.



[2] googlemap에 마커를 찍어보자.

나중에는 검색창에 [ git googlemap example ]을 검색해서 공식 예제를 보고 공부하자.(https://github.com/googlemaps/)


1. 올린 맵 프래그먼트에 id를 주자(map)

2. 메인액티비티 자바로 와서 onCreate()에서 프래그먼트 변수에 참조시키자.

(1) SupportMapFragment supportMapFragment = getSupportFragmentManager().findFragmentById(R.id.map);

- 난 이때, map이라는 id를 참조가 안됬다. Build>rebuild Project로 참조되도록 새로고침하자.

 - 캐스팅에러가 뜨면, alt + enter로 캐스팅 완료하자(겟서포트프래그먼트매니져().fi~)하고 alt + enter로 쉽게 캐스팅하자.


        SupportMapFragment supportMapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map);


3. 액티비티에 구글맵 인터페이스 중 OnMapReadyCallback를 implement해줘서 사용해야한다. 

  맵이 준비가되었을 때, 호출되는 인터페이스이다. 구현화시킨 OnMapReady()에서 좌표설정, 마커 등을 단다.

 - 인터페이스는 리스너처럼 귀라고 생각하자. 맵이 준비가 된 것을 들어주는 귀를 달아주자.

(1) alt+enter를 통해 onMapReady()를 오버라이딩하자

<인터페이스(귀)를 달아서, 콜백함수를 쓰는 이유는, 사람마다 맵을 로딩하는 속도가 다르기 때문에, 로딩이 완료되었을 때 마커를 달아주는 식으로 하기위해>


(2) 이제 프래그먼트 변수 supportMapFragment . 에 getMapAsync()를 달아주고 파라미터를 this로 준다. 

 *getMapAsync()는 구글맵프래그먼트에, 인터페이스를 달아서 구현한 콜백함수를 등록하게 해준다. 

 * 파라미터의 this->메인액티비티->온맵 래디 콜백-> 오버라이딩한 onMapReady()로 들어가게 해준다. 

 <오버라이딩 하여 사용할 매쏘드(onMapReady)를 가진, 인터페이스(onMapReadyCallback)를 달고 있는 놈(MainActivitty)의 context>


        supportMapFragment.getMapAsync(this);


4. 좌표를 찍어주기위해 특정위치의 좌표를 알아내자. 

(1) maps.google.com로 들어가서 원하는 곳을 검색해서 클릭하면 좌표가 뜬다. 

    만약 안뜬다면 그 지점을 우클릭> [이곳을 자세히 알고싶나요] 클릭 하면 하면 하단에 뜬다

.

(2) 호반아파트 앞 정류장 좌표는 35.050607, 126.722983 이다. 주석처리를 해서 프로젝트에 적어놓자.


    //호반정류장 좌표 35.050607, 126.722983

    @Override

    public void onMapReady(GoogleMap googleMap) {


    }




(3) 알아낸 좌표를 onMapReady()안에 넣어서, 맵이 로딩이 되었을 때, 해당 좌표를 담아줘야한다. 좌표를 담는 것은 

   구글맵스 gms클래스중 위도경도를 담는 클래스 LatLng의 객체를 생생하는 곳의 파라미터로 넣는다.  


LatLng hoban_bustop = new LatLng(35.050607, 126.722983);


(4) 마커는 구글맵 객체에 달아준다. onMapReady()호출시 파라미터로 넘어오는 GoogleMap googlemap을 이용해서, 

   옵션으로 (LatLng에 담긴 위치 + 타이틀)을 넣어,  마커를 달게한다

googleMap.addMarker(new MarkerOptions().position(hoban_bustop).title("호반아파트 정류장")); 


(5)앱을 실행시켜, 마커가 찍혀있는지 확인한다.




5. 이제 지도가 켜졌을 때, 마커가 찍힌점으로 맵 이동시켜보자.

(1)googleMap.moveCamera();를 이용한다. 파라미터로는 CameraUpdateFactory.newLatLngZoom( , )을 주는데 

   앞에는 LatLng의 좌표변수, 에는 줌의 정도를 준다. 

 - 나는 정류장이 다 찍히게 하기 위해서 여러정류장 사이의 좌표를 찾아와 지도를 옮겨놓았고 줌은 15정도가 적당했다.


        //정류장들 중앙좌표 35.048169, 126.720031

        LatLng center_bustop = new LatLng(35.048169, 126.720031);

        googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(center_bustop, 15)); 

        //구글맵의 위치를 로딩이 완료된 시점에서,  맵의 보이는 위치를 옮겨 놓는다. 좌표와 함께, 줌의 정도도 같이준다.


6. 이제 버튼을 누르면 3d효과를 주면서 확대하도록 해보자.

(1) 프래그먼트 위에 버튼을 올리자. (id는 button_3d)로 주자.


(2) onMapReady()에서 버튼을 생성하고 id를 참조하자.


(3) 3d효과에 대한 것은 구글공식문서에서 가져올 것이다. 공식문서의 왼쪽메뉴 중 [카메라 및 뷰]를 선택하자.

    (https://developers.google.com/maps/documentation/android-api/views?hl=ko)


(4) 문서중 가장 아래 [카메라 뷰 업데이트]로 와서 , 아래 코드만 가져와서 버튼클릭리스너에 넣어주자.

     결론적으로, 카메라포지션옵션을 넣고, 그것을 googleMap객체에 달아줘서 이동시킨다.


  - 수정해야할 것 = map -> googleMap + MOUNTAIN_VIEW은 확대시킬 좌표값 LtnLng변수 + 인터페이스안에 들어오기 위하여 변수들 final 달아주기

  *** 외국은 빌딩높이가 있어서 3d로 나오는데, 우리나라지역은 3d빌딩이 잘 안나온다. 


// Construct a CameraPosition focusing on Mountain View and animate the camera to that position.

CameraPosition cameraPosition = new CameraPosition.Builder()

    .target(MOUNTAIN_VIEW)      // 확대시킬 좌표(LatLng)

    .zoom(17)                   // 줌의 정도

    .bearing(90)                // 화면각도(90-east,180-north, 270-west, 360-sourth)

    .tilt(30)                   // 카메라각도 30도

    .build();                   // Creates a CameraPosition from the builder


googleMap.animateCamera (    CameraUpdateFactory.newCameraPosition(cameraPosition)   );




























  1. 공부중 2019.03.18 21:35

    복금점 풀어주시면 안될까요?

    • nittaku 2019.04.01 00:22 신고

      전체가 풀려서 안됩니다.. 저는 제 공부내용 정리용이며, 배포용이 아닙니다!

  2. 이인회 2019.04.23 12:16

    감사합니다 덕분에 많은것을 알아갑니다.

  3. shunhag 2020.11.20 20:06

    유용한 내용 되게 잘 배우고 가여

+ Recent posts