Java tutorial
/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.hangulo.powercontact; import android.Manifest; import android.app.Activity; import android.content.Context; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.provider.ContactsContract; import android.provider.MediaStore; import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; import android.support.v4.app.Fragment; import android.support.v4.content.ContextCompat; import android.support.v4.graphics.drawable.RoundedBitmapDrawable; import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory; import android.text.SpannableString; import android.text.style.ForegroundColorSpan; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; 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.BitmapDescriptorFactory; 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.maps.android.clustering.Cluster; import com.google.maps.android.clustering.ClusterManager; import com.google.maps.android.clustering.view.DefaultClusterRenderer; import com.google.maps.android.ui.IconGenerator; import com.hangulo.powercontact.model.PowerContactAddress; import com.hangulo.powercontact.util.CircleTransform; import com.hangulo.powercontact.util.PermissionUtils; import com.squareup.picasso.Picasso; import java.io.IOException; import java.util.ArrayList; /* * ================================================ * Android Devlelopment Nanodegree * Project 8: Capstone, Stage 2 - Build * PowerContact by Kwanghyun Jung (ihangulo@gmail.com) * ================================================ * * date : Apr. 4th 2016 * * MapViewFragment.java * ------------- * Map view * */ /* http://developer.android.com/reference/android/location/Geocoder.html http://wptrafficanalyzer.in/blog/locating-user-input-address-in-google-maps-android-api-v2-with-geocoding-api/ https://github.com/udacity/google-play-services/tree/master/LocationLessons_Final/LocationLesson2_1 * Help site ? ? http://developer.android.com/training/location/display-address.html (Displaying a Location Address) https://github.com/googlesamples/android-play-location // https://github.com/googlemaps/android-samples/blob/master/ApiDemos/app/src/main/java/com/example/mapdemo/MyLocationDemoActivity.java */ public class MapViewFragment extends Fragment implements OnMapReadyCallback, ActivityCompat.OnRequestPermissionsResultCallback, GoogleMap.OnMyLocationButtonClickListener, ClusterManager.OnClusterClickListener<PowerContactAddress>, ClusterManager.OnClusterInfoWindowClickListener<PowerContactAddress>, ClusterManager.OnClusterItemClickListener<PowerContactAddress>, ClusterManager.OnClusterItemInfoWindowClickListener<PowerContactAddress> { private final String LOG_TAG = MapViewFragment.class.getSimpleName(); final static int DEFAULT_ZOOM_SIZE = 17; // default zoom level private static final int LOCATION_PERMISSION_REQUEST_CODE = 1; final static String CAMERA_POSITION_KEY = "CAMERA_POSITION"; final static String NEED_SHOW_INFO_WINDOW_KEY = "NEED_SHOW_INFO_WINDOW_KEY"; final static String NEED_TO_CLICK_ITEM_KEY = "NEED_TO_CLICK_ITEM_KEY"; final static String NEED_TO_REFRESH_DATA_KEY = "NEED_TO_REFRESH_DATA_KEY"; GoogleMap mMap; boolean mTwoPane; int mMarkerType = 1; // // 0: default marker 1:name 2: photo+name 3: photo 0:? 1:? 2:+? (?) 3: ( ?) private boolean mPermissionDenied = false; private ClusterManager<PowerContactAddress> mClusterManager; IconGenerator mIconFactory; View mPhotoProfile; // ? ? ? View rootView; LatLng mCurrentLatLng; //= new LatLng(37.4789134,126.8773348) ; // ? float mZoomLevel = 17.0f; // defalut zoom level static CameraPosition savedCameraPosition; // ? ? PowerContactClusterRenderer mClusterRenderer; PowerContactAddress mLastClickedItem = null; // save last marker boolean needRefreshData = false; // ?? ?? (onMapReady?) boolean needShowInfoWindowMarker = false; // ? ? ? ? ? .. (? ) PowerContactAddress mNeedToclickItem; // ? // callbacks -------------------------------- MapViewCallback mCallback; // callback public interface MapViewCallback { void selectListMarkerItem(PowerContactAddress item); // ? ?? ...?(scroll & change color) void searchListMarkerItemByCoord(LatLng data); // ?? ? . LatLng getCurrentLatLng(); // ?. void onMyLocationButtonClick(); // ??.. . void requestLocationPermissions(int requestCode); // ?? (>23) ArrayList<PowerContactAddress> getContactAddress(); int getMarkerType(); // ? . } // callbacks -------------------------------- @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { mTwoPane = getResources().getBoolean(R.bool.two_pane); // two_pane mode check rootView = inflater.inflate(R.layout.fragment_map, container, false); if (rootView == null) return null; if (savedInstanceState != null) { savedCameraPosition = savedInstanceState.getParcelable(CAMERA_POSITION_KEY); needShowInfoWindowMarker = savedInstanceState.getBoolean(NEED_SHOW_INFO_WINDOW_KEY, false); mNeedToclickItem = savedInstanceState.getParcelable(NEED_TO_CLICK_ITEM_KEY); needRefreshData = savedInstanceState.getBoolean(NEED_TO_REFRESH_DATA_KEY, false); } Log.v(LOG_TAG, "Mapview created"); return rootView; } // ? ? (?) public void setClusterManager(ArrayList<PowerContactAddress> powerContactArrayList, int markerType) { Log.v(LOG_TAG, "setClusterManager.. "); if (mClusterManager == null) { needRefreshData = true; // --> call when onMapReady() return; // error or reset } Log.v(LOG_TAG, "setClusterManager.. mClusterManager not null"); mMarkerType = markerType; mClusterManager.clearItems(); // delete previous data if (mMap != null) mMap.clear(); mClusterManager.addItems(powerContactArrayList); mClusterManager.cluster(); // force to be clustering needRefreshData = false; // reset } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); setUpMapIfNeeded(); } @Override public void onSaveInstanceState(Bundle outState) { // ?? , zoom level? . (? .) if (mMap != null) { savedCameraPosition = mMap.getCameraPosition(); outState.putParcelable(CAMERA_POSITION_KEY, savedCameraPosition); // ?? show? ?? . } outState.putBoolean(NEED_SHOW_INFO_WINDOW_KEY, needShowInfoWindowMarker); outState.putParcelable(NEED_TO_CLICK_ITEM_KEY, mNeedToclickItem); outState.putBoolean(NEED_TO_REFRESH_DATA_KEY, needRefreshData); //float zoom= cameraPosition.zoom; super.onSaveInstanceState(outState); } @Override public void onAttach(Context context) { super.onAttach(context); // http://stackoverflow.com/questions/32083053/android-fragment-onattach-deprecated if (context instanceof Activity) { try { mCallback = (MapViewCallback) context; } catch (ClassCastException e) { throw new ClassCastException( getActivity().toString() + " must implement OnHeadlineSelectedListener"); } } } private void setUpMapIfNeeded() { // Do a null check to confirm that we have not already instantiated the map. if (mMap == null) { // Try to obtain the map from the SupportMapFragment. //mMap = ((SupportMapFragment) getActivity().getSupportFragmentManager().findFragmentById(R.id.map)).getMap(); // http://codentrick.com/retrieve-supportmapfragment-when-show-google-map-on-android-fragment/ SupportMapFragment mapFrag = (SupportMapFragment) getChildFragmentManager().findFragmentById(R.id.map); if (mapFrag != null) { // mMap = mapFrag.getMap(); mapFrag.getMapAsync(this); } } } private void setUpClusterMap() { // ?? Log.v(LOG_TAG, "setupclustermap()... "); if (mMap == null) return; // error mClusterManager = new ClusterManager<>(getActivity(), mMap); //? ? . mClusterRenderer = new PowerContactClusterRenderer(); mClusterManager.setRenderer(mClusterRenderer); //mMap.setOnCameraChangeListener(mClusterManager); // ? ... 2016.2.21 // http://stackoverflow.com/questions/28560816/how-to-add-2-listeners-to-map mMap.setOnCameraChangeListener(new GoogleMap.OnCameraChangeListener() { @Override public void onCameraChange(CameraPosition cameraPosition) { mZoomLevel = cameraPosition.zoom; mClusterManager.onCameraChange(cameraPosition); } }); mMap.setOnMarkerClickListener(mClusterManager); // Setting an info window adapter allows us to change the both the contents and look of the // info window. mMap.setInfoWindowAdapter(new CustomInfoWindowAdapter()); mMap.setOnInfoWindowClickListener(mClusterManager); mClusterManager.setOnClusterClickListener(this); mClusterManager.setOnClusterInfoWindowClickListener(this); mClusterManager.setOnClusterItemClickListener(this); mClusterManager.setOnClusterItemInfoWindowClickListener(this); mIconFactory = new IconGenerator(getActivity()); if (savedCameraPosition != null) {// if there is saved position ? ? ... mMap.moveCamera(CameraUpdateFactory.newCameraPosition(savedCameraPosition)); mClusterManager.onCameraChange(savedCameraPosition); // move ??.. 2016.3.25 } } // ? http://stackoverflow.com/questions/23363188/does-the-google-maps-android-api-utility-cluster-manager-have-a-minimum-number-o // Show From Memory // Fetch From Database // --- ? ? /** * Draws profile photos inside markers (using IconGenerator). * When there are multiple people in the cluster, draw multiple photos (using MultiDrawable). */ private class PowerContactClusterRenderer extends DefaultClusterRenderer<PowerContactAddress> { private final IconGenerator mIconGenerator = new IconGenerator(getActivity()); private final IconGenerator mClusterIconGenerator = new IconGenerator(getActivity()); public PowerContactClusterRenderer() { super(getActivity(), mMap, mClusterManager); mPhotoProfile = getActivity().getLayoutInflater().inflate(R.layout.marker_photo_profile, null); // if (mPhotoProfile == null || mClusterIconGenerator == null) return; mClusterIconGenerator.setContentView(mPhotoProfile); // ? multiProfile mClusterIconGenerator.setBackground(null); } @Override protected void onBeforeClusterItemRendered(PowerContactAddress person, MarkerOptions markerOptions) { Bitmap icon; switch (mMarkerType) { case Constants.MARKER_TYPE_DEFAULT: // default marker // https://developers.google.com/android/reference/com/google/android/gms/maps/model/BitmapDescriptorFactory //markerOptions.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_ORANGE)); can change color markerOptions.icon(BitmapDescriptorFactory.defaultMarker()).title(person.getName()) .snippet(person.getAddr()); break; case Constants.MARKER_TYPE_NAME_ONLY: // name only mode markerOptions.icon(BitmapDescriptorFactory.fromBitmap(mIconFactory.makeIcon(person.getName()))) .anchor(mIconFactory.getAnchorU(), mIconFactory.getAnchorV()).title(person.getName()) .snippet(person.getAddr()); break; case Constants.MARKER_TYPE_NAME_WITH_PHOTO: {// photo + name ImageView mClusterImageView = (ImageView) mPhotoProfile.findViewById(R.id.image_custom_marker); // ? Uri photoUri; if (person.getPhoto() != null && person.getPhoto().length() > 0) { // if there is photo photoUri = Uri.parse(person.getPhoto()); Picasso.with(getActivity()).load(photoUri).transform(new CircleTransform()) .error(R.drawable.ic_person_40dp).into(mClusterImageView); } else { Picasso.with(getActivity()).load(R.drawable.ic_person_40dp).transform(new CircleTransform()) .into(mClusterImageView); } String title = person.getName(); // ? ?? ? ?? . TextView titleUi = ((TextView) mPhotoProfile.findViewById(R.id.text_custom_marker)); if (titleUi != null) if (title != null) { titleUi.setText(title); } else { titleUi.setText(""); } final Drawable TRANSPARENT_DRAWABLE = new ColorDrawable(Color.TRANSPARENT); // mIconGenerator.setBackground(TRANSPARENT_DRAWABLE); icon = mClusterIconGenerator.makeIcon(); markerOptions.icon(BitmapDescriptorFactory.fromBitmap(icon)).title(person.getName()) .anchor(0.5f, 0.5f).anchor(mIconFactory.getAnchorU(), mIconFactory.getAnchorV()) .snippet(person.getAddr()); } break; case Constants.MARKER_TYPE_PHOTO_ONLY: {// ... RoundedBitmapDrawable circularBitmapDrawable; Bitmap bitmap; Uri photoUri; try { if (person.getPhoto() != null && person.getPhoto().length() > 0) { photoUri = Uri.parse(person.getPhoto()); icon = MediaStore.Images.Media.getBitmap(getContext().getContentResolver(), photoUri); } else { icon = BitmapFactory.decodeResource(getResources(), R.drawable.ic_person_40dp); // default } // circiled drawable ? ~! circularBitmapDrawable = RoundedBitmapDrawableFactory.create(getContext().getResources(), icon); circularBitmapDrawable.setCircular(true); circularBitmapDrawable.setAntiAlias(true); // convert to bitmap // http://stackoverflow.com/questions/18053156/set-image-from-drawable-as-marker-in-google-map-version-2 Canvas canvas = new Canvas(); bitmap = Bitmap.createBitmap(circularBitmapDrawable.getIntrinsicWidth(), circularBitmapDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); canvas.setBitmap(bitmap); circularBitmapDrawable.setBounds(0, 0, circularBitmapDrawable.getIntrinsicWidth(), circularBitmapDrawable.getIntrinsicHeight()); circularBitmapDrawable.draw(canvas); } catch (IOException e) { e.printStackTrace(); // errror break; } markerOptions.icon(BitmapDescriptorFactory.fromBitmap(bitmap)).title(person.getName()) .anchor(0.5f, 0.5f).snippet(person.getAddr()); } break; case Constants.MARKER_TYPE_SMALL_CIRCLE: // tiny marker ? (drawable) markerOptions.icon(BitmapDescriptorFactory.fromResource(R.drawable.point_circle)) .title(person.getName()).snippet(person.getAddr()); break; } } @Override protected void onBeforeClusterRendered(Cluster<PowerContactAddress> cluster, MarkerOptions markerOptions) { IconGenerator iconFactory = new IconGenerator(getActivity()); // , ? ? ?? ? ? . if (cluster.getSize() > 1 && isLocationAllSame(cluster)) { // IconGeneratorDemoActivity ? iconFactory.setStyle(IconGenerator.STYLE_PURPLE); iconFactory.setTextAppearance(R.style.ClusterText); markerOptions .icon(BitmapDescriptorFactory .fromBitmap(mIconFactory.makeIcon(String.valueOf(cluster.getSize())))) .anchor(mIconFactory.getAnchorU(), mIconFactory.getAnchorV()); } else { // ? ? . super.onBeforeClusterRendered(cluster, markerOptions); } } // when clustring? --> more than 10 point same / but below zoom level 15.. then don't clustering // ?? ? // 10 ?? ? , but zoom Level? ? ??? ? ? . // ??.. 15 ? . // // // https://developers.google.com/maps/documentation/android-api/views // 1: World // 5: Landmass/continent // 10: City // 15: Streets // 20: Buildings @Override protected boolean shouldRenderAsCluster(Cluster cluster) { // 1.? ? ? --> ?? // 2. 10 ?? ?? --> ?? // 3. ? 16?.. --> ?? if (cluster.getSize() > 1) if (isLocationAllSame(cluster) || ((cluster.getSize() > 9 && mZoomLevel < 17))) return true; return false; } } //---end of rendere @Override public boolean onClusterClick(Cluster<PowerContactAddress> cluster) { if (isLocationAllSame(cluster)) { // same location data? //Toast.makeText(getActivity(), "same data", Toast.LENGTH_SHORT).show(); mCallback.searchListMarkerItemByCoord(cluster.getPosition()); // . } else { // if not same location data // http://stackoverflow.com/questions/25395357/android-how-to-uncluster-on-single-tap-on-a-cluster-marker-maps-v2 // when click cluster then zoom // ? ? // ZoomLevel // https://developers.google.com/maps/documentation/android-api/views // 1: World 5: Landmass/continent 10: City 15: Streets 20: Buildings if (mMap.getCameraPosition().zoom < 20) mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(cluster.getPosition(), (float) Math.floor(mMap.getCameraPosition().zoom + 1)), 300, null); } return true; } @Override public void onClusterInfoWindowClick(Cluster<PowerContactAddress> cluster) { // Does nothing, but you could go to a list of the users. } // ? ? @Override public boolean onClusterItemClick(PowerContactAddress item) { // ? . // change listview position mCallback.selectListMarkerItem(item); // scroll listview upto this item mLastClickedItem = item; // save( ) // // . // // This causes the marker at Adelaide to change color and alpha. // final Random mRandom = new Random(); // Marker marker = mClusterRenderer.getMarker(item); // ? .....!!!!! // marker.setIcon(BitmapDescriptorFactory.defaultMarker(mRandom.nextFloat() * 360)); return false; } // ? ? ? public boolean onClusterItemClickFromListView(final PowerContactAddress item) { Log.v(LOG_TAG, "onClusterItemClickFromListView(): will click"); // ? ? ? ? if (mMap == null) { // ? Log.v(LOG_TAG, "onClusterItemClickFromListView(): mMap=null / wait...."); needShowInfoWindowMarker = true; // mNeedToclickItem = item; return false; } makeClusterItemClickFromOthers(item, -1); return true; } // ? ? ? (? ) float cameraZoomLevel; public void makeClusterItemClickFromOthers(final PowerContactAddress item, float zoomLevel) { if (zoomLevel == -1) { cameraZoomLevel = mMap.getCameraPosition().zoom; // get current zoom level } else cameraZoomLevel = zoomLevel; mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(item.getPosition(), cameraZoomLevel), 100, new GoogleMap.CancelableCallback() { @Override public void onFinish() { Log.v(LOG_TAG, "onClusterItemClickFromListView():: onFinish"); Handler handler = new Handler(); handler.postDelayed(new Runnable() { @Override public void run() { // ? . Log.v(LOG_TAG, "start"); Marker myMarker = searchMarkersFromArrayList(item); if (myMarker != null) { // ? ui thread. myMarker.showInfoWindow(); // ? . Log.v(LOG_TAG, "onClusterItemClickFromListView():: ok. showinfowindow()" + cameraZoomLevel); } else { // ... if (cameraZoomLevel < mMap.getMaxZoomLevel()) { makeClusterItemClickFromOthers(item, mMap.getMaxZoomLevel()); // recursive } } Log.v(LOG_TAG, "onClusterItemClickFromListView():: end " + cameraZoomLevel + "/"); } }, 500); } @Override public void onCancel() { } }); } // ?? ?? ? ? ? ? public boolean isLocationAllSame(Cluster<PowerContactAddress> cluster) { PowerContactAddress prevItem = null; for (PowerContactAddress item : cluster.getItems()) { if (prevItem == null) prevItem = item; else { //firstName = firstName + "," +item.getName(); if (!prevItem.getLatLng().equals(item.getLatLng())) { return false; } } } return true; } // item? . // search marker on ArrayList and return it public Marker searchMarkersFromArrayList(PowerContactAddress Addressitem) { if (mClusterManager == null) { Log.v(LOG_TAG, "mClusterManager=null"); return null; } java.util.Collection<Marker> userCollection = mClusterManager.getMarkerCollection().getMarkers(); //MarkerManager.Collection markerCollection = mClusterManager.getMarkerCollection(); if (userCollection == null) return null; for (Marker marker : userCollection) { if (marker.getPosition().latitude == Addressitem.getPosition().latitude // ? && marker.getPosition().longitude == Addressitem.getPosition().longitude // ? && marker.getTitle().equals(Addressitem.getName()) // ? // ? ?,?? ? . () && marker.getSnippet().equals(Addressitem.getAddr())) // return marker; } return null; // not found } // address item? . 1:1 ??? . (?/ ? ?? ) // ?, ? ? . // find PowerContactAddress item from marer. // suppose that there is no contact item have same name & same address.. --> will imporve next version // also this marker is drawed on screen already. public PowerContactAddress searchAddressItemFromMarker(Marker marker) { // , ? ??? // ? . //java.util.Collection<Marker> userCollection = mClusterManager.getMarkerCollection().getMarkers(); //MarkerManager.Collection markerCollection = mClusterManager.getMarkerCollection(); for (PowerContactAddress addressItem : mCallback.getContactAddress()) { if (marker.getPosition().latitude == addressItem.getPosition().latitude // ? && marker.getPosition().longitude == addressItem.getPosition().longitude // ? && marker.getTitle().equals(addressItem.getName()) // ? // ? ?,?? ? . () && marker.getSnippet().equals(addressItem.getAddr())) // return addressItem; } return null; // not found } // ??? ? customize ? https://developers.google.com/maps/documentation/android-api/infowindows // ? ?? ? ? // marker? ?? ? URL? ?.. // http://androidfreakers.blogspot.kr/2013/08/display-custom-info-window-with.html @Override public void onClusterItemInfoWindowClick(PowerContactAddress item) { Uri lookupUri = ContactsContract.Contacts.getLookupUri(item.getContact_id(), item.getLookup_key()); // (lookup) // http://developer.android.com/reference/android/provider/ContactsContract.Contacts.html#getLookupUri(long, java.lang.String) if (lookupUri != null) ContactsContract.QuickContact.showQuickContact(getActivity(), mPhotoProfile, lookupUri, ContactsContract.QuickContact.MODE_LARGE, null); if (MainActivity.mPowerContactSettings.isDemoMode()) Toast.makeText(getActivity(), R.string.msg_demo_mode_clusteritem_clicked, Toast.LENGTH_SHORT).show(); } /** * Manipulates the map once available. * This callback is triggered when the map is ready to be used. * This is where we can add markers or lines, add listeners or move the camera. In this case, * we just add a marker near Sydney, Australia. * If Google Play services is not installed on the device, the user will be prompted to install * it inside the SupportMapFragment. This method will only be triggered once the user has * installed Google Play services and returned to the app. */ @Override public void onMapReady(GoogleMap googleMap) { mMap = googleMap; mMap.setOnMyLocationButtonClickListener(this); enableMyLocation(); // activate current location button mMap.getUiSettings().setCompassEnabled(true); mMap.getUiSettings().setZoomControlsEnabled(true); mCallback.getCurrentLatLng(); // if getLocation then callback moveMapLocation() Log.v(LOG_TAG, "onMapReady... setupclustermap()"); setUpClusterMap(); if (needRefreshData && mCallback != null) { setClusterManager(mCallback.getContactAddress(), mCallback.getMarkerType()); } // ? ??? ?? ? ?... ? ..? if (needShowInfoWindowMarker) { // ?.. , ? ? if (mNeedToclickItem != null) { makeClusterItemClickFromOthers(mNeedToclickItem, -1); needShowInfoWindowMarker = false; mNeedToclickItem = null; } } } /** * https://github.com/googlemaps/android-samples/blob/master/ApiDemos/app/src/main/java/com/example/mapdemo/MyLocationDemoActivity.java * Enables the My Location layer if the fine location permission has been granted. */ public void enableMyLocation() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { mMap.setMyLocationEnabled(true); } else { if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { mMap.setMyLocationEnabled(true); // ?.. ? grant? ?. } } } @Override public boolean onMyLocationButtonClick() { mCallback.onMyLocationButtonClick(); // if getLocation then callback moveMapLocation() // Return false so that we don't consume the event and the default behavior still occurs // (the camera animates to the user's current position). return false; } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode != LOCATION_PERMISSION_REQUEST_CODE) { return; } if (PermissionUtils.isPermissionGranted(permissions, grantResults, Manifest.permission.ACCESS_FINE_LOCATION)) { // Enable the my location layer if the permission has been granted. enableMyLocation(); } else { // Display the missing permission error dialog when the fragments resume. mPermissionDenied = true; } } @Override public void onResume() { super.onResume(); if (mPermissionDenied) { // Permission was not granted, display error dialog. showMissingPermissionError(); mPermissionDenied = false; } } /** * Displays a dialog with error message explaining that the location permission is missing. */ private void showMissingPermissionError() { PermissionUtils.PermissionDeniedDialog.newInstance(true).show(getActivity().getSupportFragmentManager(), "dialog"); } // ? (main?) ? . public void moveMapLocation(LatLng location) { if (location == null) return; mCurrentLatLng = location; // mCurrentLatLng = location; if (mMap != null) mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(location, DEFAULT_ZOOM_SIZE)); // ? ??! } // ??? customize // Infowindow customize // googlemap-android-samples API? MarkerDemoActivity? // get this from googlemap-android-samples API :MarkerDemoActivity // Demonstrates customizing the info window and/or its contents. class CustomInfoWindowAdapter implements GoogleMap.InfoWindowAdapter { private final View mContents; CustomInfoWindowAdapter() { mContents = getActivity().getLayoutInflater().inflate(R.layout.custom_info_contents, null); } @Override public View getInfoWindow(Marker marker) { return null; // default // if want to custom window, then like this // before this --> mWindow = getActivity().getLayoutInflater().inflate(R.layout.custom_info_window, null); // render(marker, mWindow); // return mWindow; } @Override public View getInfoContents(Marker marker) { render(marker, mContents); return mContents; // if default info contants, then return null } private void render(Marker marker, View view) { int badge; // marker contactList ?? . PowerContactAddress contactItem = searchAddressItemFromMarker(marker); ImageView imgBadge = (ImageView) view.findViewById(R.id.badge); badge = R.drawable.ic_person_40dp; // ? ??, ... ? ?... if (contactItem != null && contactItem.getPhoto() != null && contactItem.getPhoto().length() > 0) { // ? Uri photoUri = Uri.parse(contactItem.getPhoto()); // http://stackoverflow.com/questions/26112150/android-create-circular-image-with-picasso Picasso.with(getActivity()).load(photoUri).transform(new CircleTransform()).error(badge) .into(imgBadge); } else { Picasso.with(getActivity()).load(badge).transform(new CircleTransform()).placeholder(badge) .error(badge).into(imgBadge); } // Marker item? . // , ? ?? ? ? String title = marker.getTitle(); // ? ?? ? ?? . TextView titleUi = ((TextView) view.findViewById(R.id.title)); if (title != null) { // Spannable string allows us to edit the formatting of the text. SpannableString titleText = new SpannableString(title); titleText.setSpan(new ForegroundColorSpan(Color.BLUE), 0, titleText.length(), 0); titleUi.setText(titleText); } else titleUi.setText(""); String snippet = marker.getSnippet(); // ? . TextView snippetUi = ((TextView) view.findViewById(R.id.snippet)); if (snippet == null) snippet = ""; snippetUi.setText(snippet); } } }