diff --git a/ApiDemos/kotlin/app/build.gradle b/ApiDemos/kotlin/app/build.gradle index d7aba0ca..da49aef1 100644 --- a/ApiDemos/kotlin/app/build.gradle +++ b/ApiDemos/kotlin/app/build.gradle @@ -30,5 +30,9 @@ dependencies { testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.1' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' - + // Below is the Google Play Services dependency required for using the Google Maps Android API + implementation 'com.google.android.gms:play-services-maps:11.8.0' + // Below is used to run the easypermissions library to manage location permissions + // EasyPermissions is needed to help us request for permission to access location + implementation 'pub.devrel:easypermissions:1.1.1' } \ No newline at end of file diff --git a/ApiDemos/kotlin/app/src/main/AndroidManifest.xml b/ApiDemos/kotlin/app/src/main/AndroidManifest.xml index 2d16c4c9..980b5815 100644 --- a/ApiDemos/kotlin/app/src/main/AndroidManifest.xml +++ b/ApiDemos/kotlin/app/src/main/AndroidManifest.xml @@ -16,6 +16,11 @@ + + + + + @@ -30,6 +38,14 @@ + + + + + + + + \ No newline at end of file diff --git a/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/CameraDemoActivity.kt b/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/CameraDemoActivity.kt new file mode 100644 index 00000000..93197ed4 --- /dev/null +++ b/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/CameraDemoActivity.kt @@ -0,0 +1,356 @@ +/* + * Copyright 2018 Google LLC + * + * 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 + * + * https://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.example.kotlindemos + +import android.graphics.Color +import android.os.Bundle +import android.support.v7.app.AppCompatActivity +import android.util.Log +import android.view.View +import android.widget.CompoundButton +import android.widget.SeekBar +import android.widget.Toast +import com.google.android.gms.maps.CameraUpdate +import com.google.android.gms.maps.CameraUpdateFactory +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.GoogleMap.CancelableCallback +import com.google.android.gms.maps.GoogleMap.OnCameraIdleListener +import com.google.android.gms.maps.GoogleMap.OnCameraMoveCanceledListener +import com.google.android.gms.maps.GoogleMap.OnCameraMoveListener +import com.google.android.gms.maps.GoogleMap.OnCameraMoveStartedListener +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.PolylineOptions + +/** + * This shows how to change the camera position for the map. + */ +class CameraDemoActivity : + AppCompatActivity(), + OnCameraMoveStartedListener, + OnCameraMoveListener, + OnCameraMoveCanceledListener, + OnCameraIdleListener, + OnMapReadyCallback { + + /** + * The amount by which to scroll the camera. Note that this amount is in raw pixels, not dp + * (density-independent pixels). + */ + private val SCROLL_BY_PX = 100 + private val TAG = CameraDemoActivity::class.java.name + private val sydneyLatLng = LatLng(-33.87365, 151.20689) + private val bondiLocation: CameraPosition = CameraPosition.Builder(). + target(LatLng(-33.891614, 151.276417)) + .zoom(15.5f) + .bearing(300f) + .tilt(50f) + .build() + + private val sydneyLocation: CameraPosition = CameraPosition.Builder(). + target(LatLng(-33.87365, 151.20689)) + .zoom(15.5f) + .bearing(0f) + .tilt(25f) + .build() + + + private lateinit var map: GoogleMap + + private lateinit var animateToggle: CompoundButton + private lateinit var customDurationToggle: CompoundButton + private lateinit var customDurationBar: SeekBar + private var currPolylineOptions: PolylineOptions? = null + private var isCanceled = false + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.camera_demo) + + animateToggle = findViewById(R.id.animate) + customDurationToggle = findViewById(R.id.duration_toggle) + customDurationBar = findViewById(R.id.duration_bar) + + updateEnabledState() + + val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment + mapFragment.getMapAsync(this) + } + + override fun onResume() { + super.onResume() + updateEnabledState() + } + + override fun onMapReady(googleMap: GoogleMap?) { + + // return early if the map was not initialised properly + map = googleMap ?: return + + with(googleMap) { + setOnCameraIdleListener(this@CameraDemoActivity) + setOnCameraMoveStartedListener(this@CameraDemoActivity) + setOnCameraMoveListener(this@CameraDemoActivity) + setOnCameraMoveCanceledListener(this@CameraDemoActivity) + + // We will provide our own zoom controls. + uiSettings.isZoomControlsEnabled = false + uiSettings.isMyLocationButtonEnabled = true + + // Show Sydney + moveCamera(CameraUpdateFactory.newLatLngZoom(sydneyLatLng, 10f)) + } + } + + /** + * When the map is not ready the CameraUpdateFactory cannot be used. This should be used to wrap + * all entry points that call methods on the Google Maps API. + * + * @param stuffToDo the code to be executed if the map is initialised + */ + private fun checkReadyThen(stuffToDo: () -> Unit) { + if (!::map.isInitialized) { + Toast.makeText(this, R.string.map_not_ready, Toast.LENGTH_SHORT).show() + } else { + stuffToDo() + } + } + + /** + * Called when the Go To Bondi button is clicked. + */ + @Suppress("UNUSED_PARAMETER") + fun onGoToBondi(view: View) { + checkReadyThen { + changeCamera(CameraUpdateFactory.newCameraPosition(bondiLocation)) + } + } + + /** + * Called when the Animate To Sydney button is clicked. + */ + @Suppress("UNUSED_PARAMETER") + fun onGoToSydney(view: View) { + checkReadyThen { + changeCamera(CameraUpdateFactory.newCameraPosition(sydneyLocation), + object : CancelableCallback { + override fun onFinish() { + Toast.makeText(baseContext, "Animation to Sydney complete", + Toast.LENGTH_SHORT).show() + } + + override fun onCancel() { + Toast.makeText(baseContext, "Animation to Sydney canceled", + Toast.LENGTH_SHORT).show() + } + }) + } + } + + /** + * Called when the stop button is clicked. + */ + @Suppress("UNUSED_PARAMETER") + fun onStopAnimation(view: View) = checkReadyThen { map.stopAnimation() } + + /** + * Called when the zoom in button (the one with the +) is clicked. + */ + @Suppress("UNUSED_PARAMETER") + fun onZoomIn(view: View) = checkReadyThen { changeCamera(CameraUpdateFactory.zoomIn()) } + + /** + * Called when the zoom out button (the one with the -) is clicked. + */ + @Suppress("UNUSED_PARAMETER") + fun onZoomOut(view: View) = checkReadyThen { changeCamera(CameraUpdateFactory.zoomOut()) } + + /** + * Called when the tilt more button (the one with the /) is clicked. + */ + @Suppress("UNUSED_PARAMETER") + fun onTiltMore(view: View) { + checkReadyThen { + + val newTilt = Math.min(map.cameraPosition.tilt + 10, 90F) + val cameraPosition = CameraPosition.Builder(map.cameraPosition).tilt(newTilt).build() + + changeCamera(CameraUpdateFactory.newCameraPosition(cameraPosition)) + } + } + + /** + * Called when the tilt less button (the one with the \) is clicked. + */ + @Suppress("UNUSED_PARAMETER") + fun onTiltLess(view: View) { + checkReadyThen { + + val newTilt = Math.max(map.cameraPosition.tilt - 10, 0F) + val cameraPosition = CameraPosition.Builder(map.cameraPosition).tilt(newTilt).build() + + changeCamera(CameraUpdateFactory.newCameraPosition(cameraPosition)) + } + } + + /** + * Called when the left arrow button is clicked. This causes the camera to move to the left + */ + @Suppress("UNUSED_PARAMETER") + fun onScrollLeft(view: View) { + checkReadyThen { + changeCamera(CameraUpdateFactory.scrollBy((-SCROLL_BY_PX).toFloat(),0f)) + } + } + + /** + * Called when the right arrow button is clicked. This causes the camera to move to the right. + */ + @Suppress("UNUSED_PARAMETER") + fun onScrollRight(view: View) { + checkReadyThen { + changeCamera(CameraUpdateFactory.scrollBy(SCROLL_BY_PX.toFloat(), 0f)) + } + } + + /** + * Called when the up arrow button is clicked. The causes the camera to move up. + */ + @Suppress("UNUSED_PARAMETER") + fun onScrollUp(view: View) { + checkReadyThen { + changeCamera(CameraUpdateFactory.scrollBy(0f, (-SCROLL_BY_PX).toFloat())) + } + } + + /** + * Called when the down arrow button is clicked. This causes the camera to move down. + */ + @Suppress("UNUSED_PARAMETER") + fun onScrollDown(view: View) { + checkReadyThen { + changeCamera(CameraUpdateFactory.scrollBy(0f, SCROLL_BY_PX.toFloat())) + } + } + + /** + * Called when the animate button is toggled + */ + @Suppress("UNUSED_PARAMETER") + fun onToggleAnimate(view: View) = updateEnabledState() + + /** + * Called when the custom duration checkbox is toggled + */ + @Suppress("UNUSED_PARAMETER") + fun onToggleCustomDuration(view: View) = updateEnabledState() + + /** + * Update the enabled state of the custom duration controls. + */ + private fun updateEnabledState() { + customDurationToggle.isEnabled = animateToggle.isChecked + customDurationBar.isEnabled = animateToggle.isChecked && customDurationToggle.isChecked + } + + /** + * Change the camera position by moving or animating the camera depending on the state of the + * animate toggle button. + */ + private fun changeCamera(update: CameraUpdate, callback: CancelableCallback? = null) { + if (animateToggle.isChecked) { + if (customDurationToggle.isChecked) { + // The duration must be strictly positive so we make it at least 1. + map.animateCamera(update, Math.max(customDurationBar.progress, 1), callback) + } else { + map.animateCamera(update, callback) + } + } else { + map.moveCamera(update) + } + } + + override fun onCameraMoveStarted(reason: Int) { + if (!isCanceled) map.clear() + + + var reasonText = "UNKNOWN_REASON" + currPolylineOptions = PolylineOptions().width(5f) + when (reason) { + OnCameraMoveStartedListener.REASON_GESTURE -> { + currPolylineOptions?.color(Color.BLUE) + reasonText = "GESTURE" + } + OnCameraMoveStartedListener.REASON_API_ANIMATION -> { + currPolylineOptions?.color(Color.RED) + reasonText = "API_ANIMATION" + } + OnCameraMoveStartedListener.REASON_DEVELOPER_ANIMATION -> { + currPolylineOptions?.color(Color.GREEN) + reasonText = "DEVELOPER_ANIMATION" + } + } + Log.i(TAG, "onCameraMoveStarted($reasonText)") + addCameraTargetToPath() + } + + /** + * Ensures that currPolyLine options is not null before accessing it + * + * @param stuffToDo the code to be executed if currPolylineOptions is not null + */ + private fun checkPolylineThen(stuffToDo: () -> Unit) { + if (currPolylineOptions != null) stuffToDo() + } + + + override fun onCameraMove() { + Log.i(TAG, "onCameraMove") + // When the camera is moving, add its target to the current path we'll draw on the map. + checkPolylineThen { addCameraTargetToPath() } + } + + override fun onCameraMoveCanceled() { + // When the camera stops moving, add its target to the current path, and draw it on the map. + checkPolylineThen { + addCameraTargetToPath() + map.addPolyline(currPolylineOptions) + } + + isCanceled = true // Set to clear the map when dragging starts again. + currPolylineOptions = null + Log.i(TAG, "onCameraMoveCancelled") + } + + override fun onCameraIdle() { + checkPolylineThen { + addCameraTargetToPath() + map.addPolyline(currPolylineOptions) + } + + currPolylineOptions = null + isCanceled = false // Set to *not* clear the map when dragging starts again. + Log.i(TAG, "onCameraIdle") + } + + private fun addCameraTargetToPath() { + currPolylineOptions?.add(map.cameraPosition.target) + } +} \ No newline at end of file diff --git a/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/DemoDetailsList.kt b/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/DemoDetailsList.kt index 901f7f4e..123ad938 100644 --- a/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/DemoDetailsList.kt +++ b/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/DemoDetailsList.kt @@ -21,6 +21,21 @@ package com.example.kotlindemos */ class DemoDetailsList { companion object { - val DEMOS = listOf() + val DEMOS = listOf( + DemoDetails(R.string.polygon_demo_label, R.string.polygon_demo_details, + PolygonDemoActivity::class.java), + DemoDetails(R.string.camera_demo_label, R.string.camera_demo_description, + CameraDemoActivity::class.java), + DemoDetails(R.string.markers_demo_label, R.string.markers_demo_description, + MarkerDemoActivity::class.java), + DemoDetails(R.string.layers_demo_label, R.string.layers_demo_description, + LayersDemoActivity::class.java), + DemoDetails(R.string.polyline_demo_label, R.string.polyline_demo_description, + PolylineDemoActivity::class.java), + DemoDetails(R.string.tags_demo_label, R.string.tags_demo_details, + TagsDemoActivity::class.java), + DemoDetails(R.string.region_demo_label, R.string.region_demo_details, + VisibleRegionDemoActivity::class.java) + ) } } \ No newline at end of file diff --git a/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/LayersDemoActivity.kt b/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/LayersDemoActivity.kt new file mode 100644 index 00000000..58e667f8 --- /dev/null +++ b/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/LayersDemoActivity.kt @@ -0,0 +1,215 @@ +/* + * Copyright 2018 Google LLC + * + * 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 + * + * https://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.example.kotlindemos + +import android.Manifest +import android.annotation.SuppressLint +import android.app.AlertDialog +import android.os.Bundle +import android.support.v7.app.AppCompatActivity +import android.util.Log +import android.view.View +import android.widget.AdapterView +import android.widget.ArrayAdapter +import android.widget.CheckBox +import android.widget.Spinner +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.GoogleMap.MAP_TYPE_HYBRID +import com.google.android.gms.maps.GoogleMap.MAP_TYPE_NONE +import com.google.android.gms.maps.GoogleMap.MAP_TYPE_NORMAL +import com.google.android.gms.maps.GoogleMap.MAP_TYPE_SATELLITE +import com.google.android.gms.maps.GoogleMap.MAP_TYPE_TERRAIN +import com.google.android.gms.maps.OnMapReadyCallback +import com.google.android.gms.maps.SupportMapFragment +import pub.devrel.easypermissions.AfterPermissionGranted +import pub.devrel.easypermissions.EasyPermissions + +private const val LOCATION_PERMISSION_REQUEST_CODE = 1 + +/** + * Demonstrates the different base layers of a map. + */ +class LayersDemoActivity : + AppCompatActivity(), + OnMapReadyCallback, + AdapterView.OnItemSelectedListener, + EasyPermissions.PermissionCallbacks { + + private val TAG = MarkerDemoActivity::class.java.name + + private lateinit var map: GoogleMap + + private lateinit var trafficCheckbox: CheckBox + private lateinit var myLocationCheckbox: CheckBox + private lateinit var buildingsCheckbox: CheckBox + private lateinit var indoorCheckbox: CheckBox + private lateinit var spinner: Spinner + + /** + * Flag indicating whether a requested permission has been denied after returning in + * [.onRequestPermissionsResult]. + */ + private var showPermissionDeniedDialog = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.layers_demo) + + spinner = findViewById(R.id.layers_spinner).apply { + adapter = ArrayAdapter.createFromResource(this@LayersDemoActivity, + R.array.layers_array, android.R.layout.simple_spinner_item).apply { + setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + } + + // set a listener for when the spinner to select map type is changed. + onItemSelectedListener = this@LayersDemoActivity + } + + myLocationCheckbox = findViewById(R.id.my_location) + buildingsCheckbox = findViewById(R.id.buildings) + indoorCheckbox = findViewById(R.id.indoor) + trafficCheckbox = findViewById(R.id.traffic) + + val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment + mapFragment.getMapAsync(this) + } + + /** + * Display a dialog box asking the user to grant permissions if they were denied + */ + override fun onResumeFragments() { + super.onResumeFragments() + if (showPermissionDeniedDialog) { + AlertDialog.Builder(this).apply { + setPositiveButton(R.string.ok, null) + setMessage(R.string.location_permission_denied) + create() + }.show() + showPermissionDeniedDialog = false + } + } + + @SuppressLint("MissingPermission") + override fun onMapReady(googleMap: GoogleMap?) { + + // exit early if the map was not initialised properly + map = googleMap ?: return + + updateMapType() + + // check the state of all checkboxes and update the map accordingly + with(map) { + isTrafficEnabled = trafficCheckbox.isChecked + isBuildingsEnabled = buildingsCheckbox.isChecked + isIndoorEnabled = indoorCheckbox.isChecked + } + + // Must deal with the location checkbox separately as must check that + // location permission have been granted before enabling the 'My Location' layer. + if (myLocationCheckbox.isChecked) enableMyLocation() + + + // attach a listener to each checkbox + trafficCheckbox.setOnClickListener { map.isTrafficEnabled = trafficCheckbox.isChecked } + + buildingsCheckbox.setOnClickListener { + map.isBuildingsEnabled = buildingsCheckbox.isChecked + } + + indoorCheckbox.setOnClickListener { map.isIndoorEnabled = indoorCheckbox.isChecked } + + // if this box is checked, must check for permission before enabling the My Location layer + myLocationCheckbox.setOnClickListener { + if (!myLocationCheckbox.isChecked) { + map.isMyLocationEnabled = false + } else { + enableMyLocation() + } + } + } + + @SuppressLint("MissingPermission") + @AfterPermissionGranted(LOCATION_PERMISSION_REQUEST_CODE) + private fun enableMyLocation() { + // Enable the location layer. Request the location permission if needed. + val permissions = arrayOf(Manifest.permission.ACCESS_FINE_LOCATION) + + if (EasyPermissions.hasPermissions(this, *permissions)) { + map.isMyLocationEnabled = true + + } else { + // if permissions are not currently granted, request permissions + EasyPermissions.requestPermissions(this, + getString(R.string.permission_rationale_location), + LOCATION_PERMISSION_REQUEST_CODE, *permissions) + } + } + + /** + * Change the type of the map depending on the currently selected item in the spinner + */ + private fun updateMapType() { + // This can also be called by the Android framework in onCreate() at which + // point map may not be ready yet. + if (!::map.isInitialized) return + + map.mapType = when (spinner.selectedItem) { + getString(R.string.normal) -> MAP_TYPE_NORMAL + getString(R.string.hybrid) -> MAP_TYPE_HYBRID + getString(R.string.satellite) -> MAP_TYPE_SATELLITE + getString(R.string.terrain) -> MAP_TYPE_TERRAIN + getString(R.string.none_map) -> MAP_TYPE_NONE + else -> { + map.mapType // do not change map type + Log.e(TAG, "Error setting layer with name ${spinner.selectedItem}") + } + } + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + EasyPermissions.onRequestPermissionsResult(requestCode, + permissions, grantResults, this) + } + + override fun onPermissionsDenied(requestCode: Int, list: List) { + // Un-check the box until the layer has been enabled + // and show dialog box with permission rationale. + myLocationCheckbox.isChecked = false + showPermissionDeniedDialog = true + } + + override fun onPermissionsGranted(requestCode: Int, perms: MutableList) { + // do nothing, handled in updateMyLocation + } + + /** + * Called as part of the AdapterView.OnItemSelectedListener + */ + override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) { + updateMapType() + } + + override fun onNothingSelected(parent: AdapterView<*>) { + // Do nothing. + } + +} \ No newline at end of file diff --git a/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/MarkerDemoActivity.kt b/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/MarkerDemoActivity.kt new file mode 100644 index 00000000..3ff7e67a --- /dev/null +++ b/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/MarkerDemoActivity.kt @@ -0,0 +1,494 @@ +/* + * Copyright 2018 Google LLC + * + * 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 + * + * https://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.example.kotlindemos + +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.drawable.Drawable +import android.media.Image +import android.os.Bundle +import android.os.Handler +import android.os.SystemClock +import android.support.annotation.ColorInt +import android.support.annotation.DrawableRes +import android.support.v4.content.res.ResourcesCompat +import android.support.v4.graphics.drawable.DrawableCompat +import android.support.v7.app.AppCompatActivity +import android.text.SpannableString +import android.text.style.ForegroundColorSpan +import android.util.Log +import android.view.View +import android.view.animation.BounceInterpolator +import android.widget.CheckBox +import android.widget.ImageView +import android.widget.RadioGroup +import android.widget.SeekBar +import android.widget.SeekBar.OnSeekBarChangeListener +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.GoogleMap.InfoWindowAdapter +import com.google.android.gms.maps.GoogleMap.OnInfoWindowClickListener +import com.google.android.gms.maps.GoogleMap.OnInfoWindowCloseListener +import com.google.android.gms.maps.GoogleMap.OnInfoWindowLongClickListener +import com.google.android.gms.maps.GoogleMap.OnMarkerClickListener +import com.google.android.gms.maps.GoogleMap.OnMarkerDragListener +import com.google.android.gms.maps.SupportMapFragment +import com.google.android.gms.maps.model.BitmapDescriptor +import com.google.android.gms.maps.model.BitmapDescriptorFactory +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.LatLngBounds +import com.google.android.gms.maps.model.Marker +import com.google.android.gms.maps.model.MarkerOptions +import java.util.ArrayList +import java.util.Random + +/** + * This stores the details of a place that used to draw a marker + */ +class PlaceDetails( + val position: LatLng, + val title: String = "Marker", + val snippet: String? = null, + val icon: BitmapDescriptor = BitmapDescriptorFactory.defaultMarker(), + val infoWindowAnchorX: Float = 0.5F, + val infoWindowAnchorY: Float = 0F, + val draggable: Boolean = false, + val zIndex: Float = 0F) + +/** + * This shows how to place markers on a map. + */ +class MarkerDemoActivity : + AppCompatActivity(), + OnMarkerClickListener, + OnInfoWindowClickListener, + OnMarkerDragListener, + OnInfoWindowLongClickListener, + OnInfoWindowCloseListener, + OnMapAndViewReadyListener.OnGlobalLayoutAndMapReadyListener { + + private val TAG = MarkerDemoActivity::class.java.name + + /** This is ok to be lateinit as it is initialised in onMapReady */ + private lateinit var map: GoogleMap + + /** + * Keeps track of the last selected marker (though it may no longer be selected). This is + * useful for refreshing the info window. + * + * Must be nullable as it is null when no marker has been selected + */ + private var lastSelectedMarker: Marker? = null + + private val markerRainbow = ArrayList() + + /** map to store place names and locations */ + private val places = mapOf( + "BRISBANE" to LatLng(-27.47093, 153.0235), + "MELBOURNE" to LatLng(-37.81319, 144.96298), + "DARWIN" to LatLng(-12.4634, 130.8456), + "SYDNEY" to LatLng(-33.87365, 151.20689), + "ADELAIDE" to LatLng(-34.92873, 138.59995), + "PERTH" to LatLng(-31.952854, 115.857342), + "ALICE_SPRINGS" to LatLng(-24.6980, 133.8807) + ) + + /** These can be lateinit as they are set in onCreate */ + private lateinit var topText: TextView + private lateinit var rotationBar: SeekBar + private lateinit var flatBox: CheckBox + private lateinit var options: RadioGroup + + private val random = Random() + + /** Demonstrates customizing the info window and/or its contents. */ + internal inner class CustomInfoWindowAdapter : InfoWindowAdapter { + + // These are both view groups containing an ImageView with id "badge" and two + // TextViews with id "title" and "snippet". + private val window: View = layoutInflater.inflate(R.layout.custom_info_window, null) + private val contents: View = layoutInflater.inflate(R.layout.custom_info_contents, null) + + override fun getInfoWindow(marker: Marker): View? { + if (options.checkedRadioButtonId != R.id.custom_info_window) { + // This means that getInfoContents will be called. + return null + } + render(marker, window) + return window + } + + override fun getInfoContents(marker: Marker): View? { + if (options.checkedRadioButtonId != R.id.custom_info_contents) { + // This means that the default info contents will be used. + return null + } + render(marker, contents) + return contents + } + + private fun render(marker: Marker, view: View) { + val badge = when (marker.title) { + "Brisbane" -> R.drawable.badge_qld + "Adelaide" -> R.drawable.badge_sa + "Sydney" -> R.drawable.badge_nsw + "Melbourne" -> R.drawable.badge_victoria + "Perth" -> R.drawable.badge_wa + in "Darwin Marker 1".."Darwin Marker 4" -> R.drawable.badge_nt + else -> 0 // Passing 0 to setImageResource will clear the image view. + } + + view.findViewById(R.id.badge).setImageResource(badge) + + // Set the title and snippet for the custom info window + val title: String? = marker.title + val titleUi = view.findViewById(R.id.title) + + if (title != null) { + // Spannable string allows us to edit the formatting of the text. + titleUi.text = SpannableString(title).apply { + setSpan(ForegroundColorSpan(Color.RED), 0, length, 0) + } + } else { + titleUi.text = "" + } + + val snippet: String? = marker.snippet + val snippetUi = view.findViewById(R.id.snippet) + if (snippet != null && snippet.length > 12) { + snippetUi.text = SpannableString(snippet).apply { + setSpan(ForegroundColorSpan(Color.MAGENTA), 0, 10, 0) + setSpan(ForegroundColorSpan(Color.BLUE), 12, snippet.length, 0) + } + } else { + snippetUi.text = "" + } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.marker_demo) + + topText = findViewById(R.id.top_text) + + rotationBar = findViewById(R.id.rotationSeekBar).apply { + max = 360 + setOnSeekBarChangeListener(object: OnSeekBarChangeListener { + + /** Called when the Rotation progress bar is moved */ + override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { + val rotation = seekBar?.progress?.toFloat() + checkReadyThen { markerRainbow.map { it.rotation = rotation ?: 0f } } + } + + override fun onStartTrackingTouch(p0: SeekBar?) { + // do nothing + } + + override fun onStopTrackingTouch(p0: SeekBar?) { + //do nothing + } + + } ) + } + + flatBox = findViewById(R.id.flat) + + options = findViewById(R.id.custom_info_window_options).apply { + setOnCheckedChangeListener { _, _ -> + if (lastSelectedMarker?.isInfoWindowShown == true) { + // Refresh the info window when the info window's content has changed. + // must deal with the possibility that lastSelectedMarker has changed in + // another thread between the null check and this line, do this with !! + lastSelectedMarker?.showInfoWindow() + } + } + } + + val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment + OnMapAndViewReadyListener(mapFragment, this) + } + + /** + * This is the callback that is triggered when the GoogleMap has loaded and is ready for use + */ + override fun onMapReady(googleMap: GoogleMap?) { + + // return early if the map was not initialised properly + map = googleMap ?: return + + // create bounds that encompass every location we reference + val boundsBuilder = LatLngBounds.Builder() + // include all places we have markers for on the map + places.keys.map { place -> boundsBuilder.include(places.getValue(place)) } + val bounds = boundsBuilder.build() + + with(map) { + // Hide the zoom controls as the button panel will cover it. + uiSettings.isZoomControlsEnabled = false + + // Setting an info window adapter allows us to change the both the contents and + // look of the info window. + setInfoWindowAdapter(CustomInfoWindowAdapter()) + + // Set listeners for marker events. See the bottom of this class for their behavior. + setOnMarkerClickListener(this@MarkerDemoActivity) + setOnInfoWindowClickListener(this@MarkerDemoActivity) + setOnMarkerDragListener(this@MarkerDemoActivity) + setOnInfoWindowCloseListener(this@MarkerDemoActivity) + setOnInfoWindowLongClickListener(this@MarkerDemoActivity) + + // Override the default content description on the view, for accessibility mode. + // Ideally this string would be localised. + setContentDescription("Map with lots of markers.") + + moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 50)) + } + + // Add lots of markers to the googleMap. + addMarkersToMap() + + } + + /** + * Show all the specified markers on the map + */ + private fun addMarkersToMap() { + + val placeDetailsMap = mutableMapOf( + // Uses a coloured icon + "BRISBANE" to PlaceDetails( + position = places.getValue("BRISBANE"), + title = "Brisbane", + snippet = "Population: 2,074,200", + icon = BitmapDescriptorFactory + .defaultMarker(BitmapDescriptorFactory.HUE_AZURE) + ), + + // Uses a custom icon with the info window popping out of the center of the icon. + "SYDNEY" to PlaceDetails( + position = places.getValue("SYDNEY"), + title = "Sydney", + snippet = "Population: 4,627,300", + icon = BitmapDescriptorFactory.fromResource(R.drawable.arrow), + infoWindowAnchorX = 0.5f, + infoWindowAnchorY = 0.5f + ), + + // Will create a draggable marker. Long press to drag. + "MELBOURNE" to PlaceDetails( + position = places.getValue("MELBOURNE"), + title = "Melbourne", + snippet = "Population: 4,137,400", + draggable = true + ), + + // Use a vector drawable resource as a marker icon. + "ALICE_SPRINGS" to PlaceDetails( + position = places.getValue("ALICE_SPRINGS"), + title = "Alice Springs", + icon = vectorToBitmap( + R.drawable.ic_android, Color.parseColor("#A4C639")) + ), + + // More markers for good measure + "PERTH" to PlaceDetails( + position = places.getValue("PERTH"), + title = "Perth", + snippet = "Population: 1,738,800" + ), + + "ADELAIDE" to PlaceDetails( + position = places.getValue("ADELAIDE"), + title = "Adelaide", + snippet = "Population: 1,213,000" + ) + + ) + + // add 4 markers on top of each other in Darwin with varying z-indexes + (0 until 4).map { + placeDetailsMap.put( + "DARWIN ${it + 1}", PlaceDetails( + position = places.getValue("DARWIN"), + title = "Darwin Marker ${it + 1}", + snippet = "z-index initially ${it + 1}", + zIndex = it.toFloat() + ) + ) + } + + // place markers for each of the defined locations + placeDetailsMap.keys.map { + with(placeDetailsMap.getValue(it)) { + map.addMarker(MarkerOptions() + .position(position) + .title(title) + .snippet(snippet) + .icon(icon) + .infoWindowAnchor(infoWindowAnchorX, infoWindowAnchorY) + .draggable(draggable) + .zIndex(zIndex)) + + } + } + + // Creates a marker rainbow demonstrating how to create default marker icons of different + // hues (colors). + val numMarkersInRainbow = 12 + (0 until numMarkersInRainbow).mapTo(markerRainbow) { + map.addMarker(MarkerOptions().apply{ + position(LatLng( + -30 + 10 * Math.sin(it * Math.PI / (numMarkersInRainbow - 1)), + 135 - 10 * Math.cos(it * Math.PI / (numMarkersInRainbow - 1)))) + title("Marker $it") + icon(BitmapDescriptorFactory.defaultMarker((it * 360 / numMarkersInRainbow) + .toFloat())) + flat(flatBox.isChecked) + rotation(rotationBar.progress.toFloat()) + }) + } + } + + /** + * Demonstrates converting a [Drawable] to a [BitmapDescriptor], + * for use as a marker icon. + */ + private fun vectorToBitmap(@DrawableRes id : Int, @ColorInt color : Int): BitmapDescriptor { + val vectorDrawable: Drawable? = ResourcesCompat.getDrawable(resources, id, null) + if (vectorDrawable == null) { + Log.e(TAG, "Resource not found") + return BitmapDescriptorFactory.defaultMarker() + } + val bitmap = Bitmap.createBitmap(vectorDrawable.intrinsicWidth, + vectorDrawable.intrinsicHeight, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap) + vectorDrawable.setBounds(0, 0, canvas.width, canvas.height) + DrawableCompat.setTint(vectorDrawable, color) + vectorDrawable.draw(canvas) + return BitmapDescriptorFactory.fromBitmap(bitmap) + } + + /** + * Checks if the map is ready, the executes the provided lambda function + * + * @param stuffToDo the code to be executed if the map is ready + */ + private fun checkReadyThen(stuffToDo : () -> Unit) { + if (!::map.isInitialized) { + Toast.makeText(this, R.string.map_not_ready, Toast.LENGTH_SHORT).show() + } else { + stuffToDo() + } + } + + /** Called when the Clear button is clicked. */ + @Suppress("UNUSED_PARAMETER") + fun onClearMap(view: View) { + checkReadyThen { map.clear() } + } + + /** Called when the Reset button is clicked. */ + @Suppress("UNUSED_PARAMETER") + fun onResetMap(view: View) { + checkReadyThen { + map.clear() + addMarkersToMap() + } + } + + /** Called when the Flat check box is checked or unchecked */ + @Suppress("UNUSED_PARAMETER") + fun onToggleFlat(view: View) { + checkReadyThen { markerRainbow.map { marker -> marker.isFlat = flatBox.isChecked } } + } + + // + // Marker related listeners. + // + override fun onMarkerClick(marker : Marker): Boolean { + + // Markers have a z-index that is settable and gettable. + marker.zIndex += 1.0f + Toast.makeText(this, "${marker.title} z-index set to ${marker.zIndex}", + Toast.LENGTH_SHORT).show() + + lastSelectedMarker = marker + + if (marker.position == places.getValue("PERTH")) { + // This causes the marker at Perth to bounce into position when it is clicked. + val handler = Handler() + val start = SystemClock.uptimeMillis() + val duration = 1500 + + val interpolator = BounceInterpolator() + + handler.post(object : Runnable { + override fun run() { + val elapsed = SystemClock.uptimeMillis() - start + val t = Math.max( + 1 - interpolator.getInterpolation(elapsed.toFloat() / duration), 0f) + marker.setAnchor(0.5f, 1.0f + 2 * t) + + // Post again 16ms later. + if (t > 0.0) { + handler.postDelayed(this, 16) + } + } + }) + } else if (marker.position == places.getValue("ADELAIDE")) { + // This causes the marker at Adelaide to change color and alpha. + marker.apply { + setIcon(BitmapDescriptorFactory.defaultMarker(random.nextFloat() * 360)) + alpha = random.nextFloat() + } + } + + // We return false to indicate that we have not consumed the event and that we wish + // for the default behavior to occur (which is for the camera to move such that the + // marker is centered and for the marker's info window to open, if it has one). + return false + } + + override fun onInfoWindowClick(marker : Marker) { + Toast.makeText(this, "Click Info Window", Toast.LENGTH_SHORT).show() + } + + override fun onInfoWindowClose(marker : Marker) { + Toast.makeText(this, "Close Info Window", Toast.LENGTH_SHORT).show() + } + + override fun onInfoWindowLongClick(marker : Marker) { + Toast.makeText(this, "Info Window long click", Toast.LENGTH_SHORT).show() + } + + override fun onMarkerDragStart(marker : Marker) { + topText.text = getString(R.string.on_marker_drag_start) + } + + override fun onMarkerDragEnd(marker : Marker) { + topText.text = getString(R.string.on_marker_drag_end) + } + + override fun onMarkerDrag(marker : Marker) { + topText.text = getString(R.string.on_marker_drag, marker.position.latitude, marker.position.longitude) + } +} \ No newline at end of file diff --git a/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/OnMapAndViewReadyListener.kt b/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/OnMapAndViewReadyListener.kt new file mode 100644 index 00000000..f340a4ee --- /dev/null +++ b/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/OnMapAndViewReadyListener.kt @@ -0,0 +1,93 @@ +/* + * Copyright 2018 Google LLC + * + * 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 + * + * https://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.example.kotlindemos + +import android.annotation.SuppressLint +import android.os.Build +import android.view.View +import android.view.ViewTreeObserver.OnGlobalLayoutListener +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.OnMapReadyCallback +import com.google.android.gms.maps.SupportMapFragment + +/** + * Helper class that will delay triggering the OnMapReady callback until both the GoogleMap and the + * View having completed initialization. This is only necessary if a developer wishes to immediately + * invoke any method on the GoogleMap that also requires the View to have finished layout + * (ie. anything that needs to know the View's true size like snapshotting). + */ +class OnMapAndViewReadyListener( + private val mapFragment: SupportMapFragment, + private val toBeNotified: OnGlobalLayoutAndMapReadyListener + ) : OnGlobalLayoutListener, + OnMapReadyCallback { + + private val mapView: View? = mapFragment.view + + private var isViewReady = false + private var isMapReady = false + private var googleMap: GoogleMap? = null + + /** A listener that needs to wait for both the GoogleMap and the View to be initialized. */ + interface OnGlobalLayoutAndMapReadyListener { + fun onMapReady(googleMap: GoogleMap?) + } + + init { + registerListeners() + } + + private fun registerListeners() { + // View layout. + if (mapView?.width != 0 && mapView?.height != 0) { + // View has already completed layout. + isViewReady = true + } else { + // Map has not undergone layout, register a View observer. + mapView.viewTreeObserver.addOnGlobalLayoutListener(this) + } + + // GoogleMap. Note if the GoogleMap is already ready it will still fire the callback later. + mapFragment.getMapAsync(this) + } + + override fun onMapReady(googleMap: GoogleMap) { + // NOTE: The GoogleMap API specifies the listener is removed just prior to invocation. + this.googleMap = googleMap + isMapReady = true + fireCallbackIfReady() + } + + // We use the new method when supported + @SuppressLint("NewApi") // We check which build version we are using. + override fun onGlobalLayout() { + // Remove our listener. + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { + mapView?.viewTreeObserver?.removeGlobalOnLayoutListener(this) + } else { + mapView?.viewTreeObserver?.removeOnGlobalLayoutListener(this) + } + isViewReady = true + fireCallbackIfReady() + } + + private fun fireCallbackIfReady() { + if (isViewReady && isMapReady) { + toBeNotified.onMapReady(googleMap) + } + } +} diff --git a/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/PolygonDemoActivity.kt b/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/PolygonDemoActivity.kt new file mode 100644 index 00000000..e6ce289d --- /dev/null +++ b/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/PolygonDemoActivity.kt @@ -0,0 +1,295 @@ +/* + * Copyright 2018 Google LLC + * + * 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 + * + * https://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.example.kotlindemos + +import android.graphics.Color +import android.os.Bundle +import android.support.v7.app.AppCompatActivity +import android.view.View +import android.widget.AdapterView +import android.widget.SeekBar +import android.widget.Spinner +import android.widget.CheckBox +import android.widget.ArrayAdapter +import com.google.android.gms.maps.CameraUpdateFactory +import com.google.android.gms.maps.model.Dash +import com.google.android.gms.maps.model.Dot +import com.google.android.gms.maps.model.Gap +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.model.JointType +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.OnMapReadyCallback +import com.google.android.gms.maps.model.PatternItem +import com.google.android.gms.maps.model.Polygon +import com.google.android.gms.maps.model.PolygonOptions +import com.google.android.gms.maps.SupportMapFragment + +import java.util.Arrays + +/** + * This shows how to draw polygons on a map. + */ +class PolygonDemoActivity : + AppCompatActivity(), + OnMapReadyCallback, + SeekBar.OnSeekBarChangeListener, + AdapterView.OnItemSelectedListener { + + private val center = LatLng(-20.0, 130.0) + private val MAX_WIDTH_PX = 100 + private val MAX_HUE_DEGREES = 360 + private val MAX_ALPHA = 255 + private val PATTERN_DASH_LENGTH_PX = 50 + private val PATTERN_GAP_LENGTH_PX = 10 + private val dot = Dot() + private val dash = Dash(PATTERN_DASH_LENGTH_PX.toFloat()) + private val gap = Gap(PATTERN_GAP_LENGTH_PX.toFloat()) + private val patternDotted = Arrays.asList(dot, gap) + private val patternDashed = Arrays.asList(dash, gap) + private val patternMixed = Arrays.asList(dot, gap, dot, dash, gap) + + private lateinit var mutablePolygon: Polygon + private lateinit var fillHueBar: SeekBar + private lateinit var fillAlphaBar: SeekBar + private lateinit var strokeWidthBar: SeekBar + private lateinit var strokeHueBar: SeekBar + private lateinit var strokeAlphaBar: SeekBar + private lateinit var strokeJointTypeSpinner: Spinner + private lateinit var strokePatternSpinner: Spinner + private lateinit var clickabilityCheckbox: CheckBox + + // These are the options for polygon stroke joints and patterns. We use their + // string resource IDs as identifiers. + + private val jointTypeNameResourceIds = intArrayOf(R.string.joint_type_default, // Default + R.string.joint_type_bevel, R.string.joint_type_round) + + private val patternTypeNameResourceIds = intArrayOf(R.string.pattern_solid, // Default + R.string.pattern_dashed, R.string.pattern_dotted, R.string.pattern_mixed) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.polygon_demo) + + fillHueBar = findViewById(R.id.fillHueSeekBar).apply { + max = MAX_HUE_DEGREES + progress = MAX_HUE_DEGREES / 2 + } + + fillAlphaBar = findViewById(R.id.fillAlphaSeekBar).apply { + max = MAX_ALPHA + progress = MAX_ALPHA / 2 + } + + strokeWidthBar = findViewById(R.id.strokeWidthSeekBar).apply { + max = MAX_WIDTH_PX + progress = MAX_WIDTH_PX / 3 + } + + strokeHueBar = findViewById(R.id.strokeHueSeekBar).apply { + max = MAX_HUE_DEGREES + progress = 0 + } + + strokeAlphaBar = findViewById(R.id.strokeAlphaSeekBar).apply { + max = MAX_ALPHA + progress = MAX_ALPHA + } + + strokeJointTypeSpinner = findViewById(R.id.strokeJointTypeSpinner).apply { + adapter = ArrayAdapter( + this@PolygonDemoActivity, android.R.layout.simple_spinner_item, + getResourceStrings(jointTypeNameResourceIds)) + } + + strokePatternSpinner = findViewById(R.id.strokePatternSpinner).apply { + adapter = ArrayAdapter( + this@PolygonDemoActivity, android.R.layout.simple_spinner_item, + getResourceStrings(patternTypeNameResourceIds)) + } + + clickabilityCheckbox = findViewById(R.id.toggleClickability) + + val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment + mapFragment.getMapAsync(this) + } + + private fun getResourceStrings(resourceIds: IntArray): List { + return resourceIds.map { getString(it) } + } + + override fun onMapReady(googleMap: GoogleMap?) { + + // return early if the map was not initialised properly + googleMap ?: return + + val fillColorArgb = Color.HSVToColor( + fillAlphaBar.progress, floatArrayOf(fillHueBar.progress.toFloat(), 1f, 1f)) + val strokeColorArgb = Color.HSVToColor( + strokeAlphaBar.progress, floatArrayOf(strokeHueBar.progress.toFloat(), 1f, 1f)) + + with(googleMap) { + // Override the default content description on the view, for accessibility mode. + setContentDescription(getString(R.string.polygon_demo_description)) + // Move the googleMap so that it is centered on the mutable polygon. + moveCamera(CameraUpdateFactory.newLatLngZoom(center, 4f)) + + // Create a rectangle with two rectangular holes. + mutablePolygon = addPolygon(PolygonOptions().apply { + addAll(createRectangle(center, 5.0, 5.0)) + addHole(createRectangle(LatLng(-22.0, 128.0), 1.0, 1.0)) + addHole(createRectangle(LatLng(-18.0, 133.0), 0.5, 1.5)) + fillColor(fillColorArgb) + strokeColor(strokeColorArgb) + strokeWidth(strokeWidthBar.progress.toFloat()) + clickable(clickabilityCheckbox.isChecked) + }) + + // Add a listener for polygon clicks that changes the clicked polygon's stroke color. + setOnPolygonClickListener { polygon -> + // Flip the red, green and blue components of the polygon's stroke color. + polygon.strokeColor = polygon.strokeColor xor 0x00ffffff + } + } + + // set listeners on seekBars + arrayOf(fillHueBar, fillAlphaBar, strokeWidthBar, strokeHueBar, strokeAlphaBar).map { + it.setOnSeekBarChangeListener(this) + } + + // set listeners on spinners + arrayOf(strokeJointTypeSpinner, strokePatternSpinner).map { + it.onItemSelectedListener = this + } + + // set line pattern and joint type based on current spinner position + with(mutablePolygon) { + strokeJointType = getSelectedJointType(strokeJointTypeSpinner.selectedItemPosition) + strokePattern = getSelectedPattern(strokePatternSpinner.selectedItemPosition) + } + + } + + /** + * Creates a List of LatLngs that form a rectangle with the given dimensions. + */ + private fun createRectangle( + center: LatLng, + halfWidth: Double, + halfHeight: Double + ): List { + return Arrays.asList( + LatLng(center.latitude - halfHeight, center.longitude - halfWidth), + LatLng(center.latitude - halfHeight, center.longitude + halfWidth), + LatLng(center.latitude + halfHeight, center.longitude + halfWidth), + LatLng(center.latitude + halfHeight, center.longitude - halfWidth), + LatLng(center.latitude - halfHeight, center.longitude - halfWidth)) + } + + private fun getSelectedJointType(pos: Int): Int { + return when (jointTypeNameResourceIds[pos]) { + R.string.joint_type_bevel -> JointType.BEVEL + R.string.joint_type_round -> JointType.ROUND + R.string.joint_type_default -> JointType.DEFAULT + else -> 0 + } + } + + private fun getSelectedPattern(pos: Int): List? { + return when (patternTypeNameResourceIds[pos]) { + R.string.pattern_solid -> null + R.string.pattern_dotted -> patternDotted + R.string.pattern_dashed -> patternDashed + R.string.pattern_mixed -> patternMixed + else -> null + } + } + + /** + * Toggles the clickability of the polygon based on the state of the View that triggered this + * call. + * This callback is defined on the CheckBox in the layout for this Activity. + */ + fun toggleClickability(view: View) { + if (view is CheckBox) { + mutablePolygon.isClickable = view.isChecked + } + } + + + /** + * Listener that is called when a seek bar is moved. + * Can change polygon fill color/transparency, stroke color/transparency and stroke width. + */ + override fun onProgressChanged(seekBar: SeekBar?, progress: Int, + fromUser: Boolean) { + + mutablePolygon.fillColor = when (seekBar) { + fillHueBar -> Color.HSVToColor(Color.alpha(mutablePolygon.fillColor), + floatArrayOf(progress.toFloat(), 1f, 1f)) + fillAlphaBar -> { + val prevColor = mutablePolygon.fillColor + Color.argb(progress, Color.red(prevColor), Color.green(prevColor), + Color.blue(prevColor)) + } + else -> mutablePolygon.fillColor + } + + mutablePolygon.strokeColor = when (seekBar) { + strokeHueBar -> Color.HSVToColor( + Color.alpha(mutablePolygon.strokeColor), + floatArrayOf(progress.toFloat(), 1f, 1f)) + strokeAlphaBar -> { + val prevColorArgb = mutablePolygon.strokeColor + Color.argb(progress, Color.red(prevColorArgb), + Color.green(prevColorArgb), Color.blue(prevColorArgb)) + } + else -> mutablePolygon.strokeColor + } + + if (seekBar == strokeWidthBar) mutablePolygon.strokeWidth = progress.toFloat() + + } + + override fun onStartTrackingTouch(seekBar: SeekBar?) { + // do nothing + } + + override fun onStopTrackingTouch(seekBar: SeekBar?) { + // do nothing + } + + /** + * Listener for when an item is selected using a spinner. + * Can change line pattern and joint type. + */ + override fun onItemSelected(parent: AdapterView<*>?, view: View?, pos: Int, + id: Long) { + when (parent?.id) { + R.id.strokeJointTypeSpinner -> + mutablePolygon.strokeJointType = getSelectedJointType(pos) + R.id.strokePatternSpinner -> + mutablePolygon.strokePattern = getSelectedPattern(pos) + } + } + + override fun onNothingSelected(parent: AdapterView<*>?) { + // don't do anything here + } + +} \ No newline at end of file diff --git a/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/PolylineDemoActivity.kt b/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/PolylineDemoActivity.kt new file mode 100644 index 00000000..37326aa8 --- /dev/null +++ b/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/PolylineDemoActivity.kt @@ -0,0 +1,291 @@ +/* + * Copyright 2018 Google LLC + * + * 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 + * + * https://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.example.kotlindemos + +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.ButtCap +import com.google.android.gms.maps.model.Cap +import com.google.android.gms.maps.model.CustomCap +import com.google.android.gms.maps.model.Dash +import com.google.android.gms.maps.model.Dot +import com.google.android.gms.maps.model.Gap +import com.google.android.gms.maps.model.JointType +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.PatternItem +import com.google.android.gms.maps.model.Polyline +import com.google.android.gms.maps.model.PolylineOptions +import com.google.android.gms.maps.model.RoundCap +import com.google.android.gms.maps.model.SquareCap +import android.graphics.Color +import android.os.Bundle +import android.support.v7.app.AppCompatActivity +import android.view.View +import android.widget.AdapterView +import android.widget.ArrayAdapter +import android.widget.CheckBox +import android.widget.SeekBar +import android.widget.Spinner +import java.util.Arrays + +/** + * This shows how to draw polylines on a map. + */ +class PolylineDemoActivity : + AppCompatActivity(), + OnMapReadyCallback, + SeekBar.OnSeekBarChangeListener, + AdapterView.OnItemSelectedListener { + + private val CUSTOM_CAP_IMAGE_REF_WIDTH_PX = 50 + private val INITIAL_STROKE_WIDTH_PX = 5 + private val MAX_WIDTH_PX = 100 + private val MAX_HUE_DEGREES = 360 + private val MAX_ALPHA = 255 + private val PATTERN_DASH_LENGTH_PX = 50 + private val PATTERN_GAP_LENGTH_PX = 20 + + // City locations for mutable polyline. + private val adelaideLatLng = LatLng(-34.92873, 138.59995) + private val darwinLatLng = LatLng(-12.4258647, 130.7932231) + private val melbourneLatLng = LatLng(-37.81319, 144.96298) + private val perthLatLng = LatLng(-31.95285, 115.85734) + + // Airport locations for geodesic polyline. + private val aklLatLng = LatLng(-37.006254, 174.783018) + private val jfkLatLng = LatLng(40.641051, -73.777485) + private val laxLatLng = LatLng(33.936524, -118.377686) + private val lhrLatLng = LatLng(51.471547, -0.460052) + + private val dot = Dot() + private val dash = Dash(PATTERN_DASH_LENGTH_PX.toFloat()) + private val gap = Gap(PATTERN_GAP_LENGTH_PX.toFloat()) + private val patternDotted = Arrays.asList(dot, gap) + private val patternDashed = Arrays.asList(dash, gap) + private val patternMixed = Arrays.asList(dot, gap, dot, dash, gap) + + private lateinit var mutablePolyline: Polyline + private lateinit var hueBar: SeekBar + private lateinit var alphaBar: SeekBar + private lateinit var widthBar: SeekBar + private lateinit var startCapSpinner: Spinner + private lateinit var endCapSpinner: Spinner + private lateinit var jointTypeSpinner: Spinner + private lateinit var patternSpinner: Spinner + private lateinit var clickabilityCheckbox: CheckBox + + // These are the options for polyline caps, joints and patterns. We use their + // string resource IDs as identifiers. + private val capTypeNameResourceIds = intArrayOf(R.string.cap_butt, // Default + R.string.cap_round, R.string.cap_square, R.string.cap_image) + + private val jointTypeNameResourceIds = intArrayOf(R.string.joint_type_default, // Default + R.string.joint_type_bevel, R.string.joint_type_round) + + private val patternTypeNameResourceIds = intArrayOf(R.string.pattern_solid, // Default + R.string.pattern_dashed, R.string.pattern_dotted, R.string.pattern_mixed) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.polyline_demo) + + hueBar = findViewById(R.id.hueSeekBar).apply { + max = MAX_HUE_DEGREES + progress = 0 + } + + alphaBar = findViewById(R.id.alphaSeekBar).apply { + max = MAX_ALPHA + progress = MAX_ALPHA + } + + widthBar = findViewById(R.id.widthSeekBar).apply { + max = MAX_WIDTH_PX + progress = MAX_WIDTH_PX / 2 + } + + startCapSpinner = findViewById(R.id.startCapSpinner).apply { + adapter = ArrayAdapter( + this@PolylineDemoActivity, android.R.layout.simple_spinner_item, + getResourceStrings(capTypeNameResourceIds)) + } + + endCapSpinner = findViewById(R.id.endCapSpinner).apply { + adapter = ArrayAdapter( + this@PolylineDemoActivity, android.R.layout.simple_spinner_item, + getResourceStrings(capTypeNameResourceIds)) + } + + jointTypeSpinner = findViewById(R.id.jointTypeSpinner).apply { + adapter = ArrayAdapter( + this@PolylineDemoActivity, android.R.layout.simple_spinner_item, + getResourceStrings(jointTypeNameResourceIds)) + } + + patternSpinner = findViewById(R.id.patternSpinner).apply { + adapter = ArrayAdapter( + this@PolylineDemoActivity, android.R.layout.simple_spinner_item, + getResourceStrings(patternTypeNameResourceIds)) + } + + clickabilityCheckbox = findViewById(R.id.toggleClickability) + + val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment + mapFragment.getMapAsync(this) + } + + private fun getResourceStrings(resourceIds: IntArray): List { + return resourceIds.map { getString(it) } + } + + override fun onMapReady(googleMap: GoogleMap?) { + + // exit early if the map was not initialised properly + googleMap ?: return + + with(googleMap) { + // Override the default content description on the view, for accessibility mode. + setContentDescription(getString(R.string.polyline_demo_description)) + + // A geodesic polyline that goes around the world. + addPolyline(PolylineOptions().apply { + add(lhrLatLng, aklLatLng, laxLatLng, jfkLatLng, lhrLatLng) + width(INITIAL_STROKE_WIDTH_PX.toFloat()) + color(Color.BLUE) + geodesic(true) + clickable(clickabilityCheckbox.isChecked) + }) + + // Move the googleMap so that it is centered on the mutable polyline. + moveCamera(CameraUpdateFactory.newLatLngZoom(melbourneLatLng, 3f)) + + // Add a listener for polyline clicks that changes the clicked polyline's color. + setOnPolylineClickListener { polyline -> + // Flip the values of the red, green and blue components of the polyline's color. + polyline.color = polyline.color xor 0x00ffffff + } + } + + // A simple polyline across Australia. This polyline will be mutable. + val color = Color.HSVToColor( + alphaBar.progress, floatArrayOf(hueBar.progress.toFloat(), 1f, 1f)) + + mutablePolyline = googleMap.addPolyline(PolylineOptions().apply{ + color(color) + width(widthBar.progress.toFloat()) + clickable(clickabilityCheckbox.isChecked) + add(melbourneLatLng, adelaideLatLng, perthLatLng, darwinLatLng) + }) + + arrayOf(hueBar, alphaBar, widthBar).map { + it.setOnSeekBarChangeListener(this) + } + + arrayOf(startCapSpinner, endCapSpinner, jointTypeSpinner, patternSpinner).map { + it.onItemSelectedListener = this + } + + with(mutablePolyline) { + startCap = getSelectedCap(startCapSpinner.selectedItemPosition) ?: ButtCap() + endCap = getSelectedCap(endCapSpinner.selectedItemPosition) ?: ButtCap() + jointType = getSelectedJointType(jointTypeSpinner.selectedItemPosition) + pattern = getSelectedPattern(patternSpinner.selectedItemPosition) + } + + clickabilityCheckbox.setOnClickListener { + view -> mutablePolyline.isClickable = (view as CheckBox).isChecked + } + } + + private fun getSelectedCap(pos: Int): Cap? { + return when (capTypeNameResourceIds[pos]) { + R.string.cap_butt -> ButtCap() + R.string.cap_square -> SquareCap() + R.string.cap_round -> RoundCap() + R.string.cap_image -> CustomCap( + BitmapDescriptorFactory.fromResource(R.drawable.chevron), + CUSTOM_CAP_IMAGE_REF_WIDTH_PX.toFloat()) + else -> null + } + } + + private fun getSelectedJointType(pos: Int): Int { + return when (jointTypeNameResourceIds[pos]) { + R.string.joint_type_bevel -> JointType.BEVEL + R.string.joint_type_round -> JointType.ROUND + R.string.joint_type_default -> JointType.DEFAULT + else -> 0 + } + } + + private fun getSelectedPattern(pos: Int): List? { + return when (patternTypeNameResourceIds[pos]) { + R.string.pattern_solid -> null + R.string.pattern_dotted -> patternDotted + R.string.pattern_dashed -> patternDashed + R.string.pattern_mixed -> patternMixed + else -> null + } + } + + /** + * Listener for changes in a seekbar's position. + * Can change polyline color, width and transparency. + */ + override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { + + when(seekBar) { + hueBar -> mutablePolyline.color = Color.HSVToColor( + Color.alpha(mutablePolyline.color), floatArrayOf(progress.toFloat(), 1f, 1f)) + alphaBar -> { + val prevHSV = FloatArray(3) + Color.colorToHSV(mutablePolyline.color, prevHSV) + mutablePolyline.color = Color.HSVToColor(progress, prevHSV) + } + widthBar -> mutablePolyline.width = progress.toFloat() + } + } + + override fun onStopTrackingTouch(seekBar: SeekBar) { + // Don't do anything here. + } + + override fun onStartTrackingTouch(seekBar: SeekBar) { + // Don't do anything here. + } + + /** + * Listener for changes in a spinner's position. + * Can change the polyline's start and end caps, pattern and joint type. + */ + override fun onItemSelected(parent: AdapterView<*>, view: View, pos: Int, id: Long) { + when (parent.id) { + R.id.startCapSpinner -> mutablePolyline.startCap = getSelectedCap(pos) ?: ButtCap() + R.id.endCapSpinner -> mutablePolyline.endCap = getSelectedCap(pos) ?: ButtCap() + R.id.jointTypeSpinner -> mutablePolyline.jointType = getSelectedJointType(pos) + R.id.patternSpinner -> mutablePolyline.pattern = getSelectedPattern(pos) + } + } + + override fun onNothingSelected(parent: AdapterView<*>) { + // Don't do anything here. + } + +} \ No newline at end of file diff --git a/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/TagsDemoActivity.kt b/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/TagsDemoActivity.kt new file mode 100644 index 00000000..d8006e80 --- /dev/null +++ b/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/TagsDemoActivity.kt @@ -0,0 +1,226 @@ +/* + * Copyright 2018 Google LLC + * + * 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 + * + * https://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.example.kotlindemos + +import android.graphics.Color +import android.os.Bundle +import android.support.v7.app.AppCompatActivity +import android.widget.TextView +import com.google.android.gms.maps.CameraUpdateFactory +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.SupportMapFragment +import com.google.android.gms.maps.model.BitmapDescriptorFactory +import com.google.android.gms.maps.model.Circle +import com.google.android.gms.maps.model.CircleOptions +import com.google.android.gms.maps.model.GroundOverlay +import com.google.android.gms.maps.model.GroundOverlayOptions +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.LatLngBounds +import com.google.android.gms.maps.model.Marker +import com.google.android.gms.maps.model.MarkerOptions +import com.google.android.gms.maps.model.Polygon +import com.google.android.gms.maps.model.PolygonOptions +import com.google.android.gms.maps.model.Polyline +import com.google.android.gms.maps.model.PolylineOptions + +/** + * This shows how to use setTag/getTag on API objects. + */ +class TagsDemoActivity : AppCompatActivity(), + GoogleMap.OnCircleClickListener, + GoogleMap.OnGroundOverlayClickListener, + GoogleMap.OnMarkerClickListener, + OnMapAndViewReadyListener.OnGlobalLayoutAndMapReadyListener, + GoogleMap.OnPolygonClickListener, + GoogleMap.OnPolylineClickListener { + + private lateinit var map: GoogleMap + + private lateinit var tagText: TextView + + private val places = mapOf( + "BRISBANE" to LatLng(-27.47093, 153.0235), + "MELBOURNE" to LatLng(-37.81319, 144.96298), + "DARWIN" to LatLng(-12.4634, 130.8456), + "SYDNEY" to LatLng(-33.87365, 151.20689), + "ADELAIDE" to LatLng(-34.92873, 138.59995), + "PERTH" to LatLng(-31.952854, 115.857342), + "ALICE_SPRINGS" to LatLng(-24.6980, 133.8807), + "HOBART" to LatLng(-42.8823388, 147.311042) + ) + + /** + * Class to store a tag to attach to a map object to keep track of + * how many times it has been clicked + */ + private class CustomTag(private val description: String) { + private var clickCount: Int = 0 + + fun incrementClickCount() { + clickCount++ + } + + override fun toString() = "The $description has been clicked $clickCount times." + + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.tags_demo) + + tagText = findViewById(R.id.tag_text) + + val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment + OnMapAndViewReadyListener(mapFragment, this) + } + + override fun onMapReady(googleMap: GoogleMap?) { + + // return early if the map was not initialised properly + map = googleMap ?: return + + // Add a circle, a ground overlay, a marker, a polygon and a polyline to the googleMap. + addObjectsToMap() + + with(map.uiSettings) { + // Turn off the map toolbar. + isMapToolbarEnabled = false + + // Disable interaction with the map - other than clicking. + isZoomControlsEnabled = false + isScrollGesturesEnabled = false + isZoomGesturesEnabled = false + isTiltGesturesEnabled = false + isRotateGesturesEnabled = false + } + + with(map) { + // Set listeners for click events. See the bottom of this class for their behavior. + setOnCircleClickListener(this@TagsDemoActivity) + setOnGroundOverlayClickListener(this@TagsDemoActivity) + setOnMarkerClickListener(this@TagsDemoActivity) + setOnPolygonClickListener(this@TagsDemoActivity) + setOnPolylineClickListener(this@TagsDemoActivity) + + // Override the default content description on the view, for accessibility mode. + // Ideally this string would be localised. + setContentDescription(getString(R.string.tags_demo_map_description)) + + // include all places we have markers for in the initial view of the map + val boundsBuilder = LatLngBounds.Builder() + places.keys.map { boundsBuilder.include(places.getValue(it)) } + // Move the camera to view all listed locations + moveCamera(CameraUpdateFactory.newLatLngBounds(boundsBuilder.build(), 100)) + + } + } + + private fun addObjectsToMap() { + with(map) { + + // A circle centered on Adelaide. + addCircle(CircleOptions().apply { + center(places.getValue("ADELAIDE")) + radius(500000.0) + fillColor(Color.argb(150, 66, 173, 244)) + strokeColor(Color.rgb(66, 173, 244)) + clickable(true) + }).run { + // add a tag to the circle to count clicks + tag = CustomTag("Adelaide circle") + } + + // A ground overlay at Sydney. + addGroundOverlay(GroundOverlayOptions().apply { + image(BitmapDescriptorFactory.fromResource(R.drawable.harbour_bridge)) + position(places.getValue("SYDNEY"), 700000f) + clickable(true) + }).run { + // add a tag to the overlay to count clicks + tag = CustomTag("Sydney ground overlay") + } + + // A marker at Hobart. + addMarker(MarkerOptions().apply { + position(places.getValue("HOBART")) + }).run { + // add a tag to the marker to count clicks + tag = CustomTag("Hobart marker") + } + + // A polygon centered at Darwin. + addPolygon(PolygonOptions().apply{ + add(LatLng(places.getValue("DARWIN").latitude + 3, + places.getValue("DARWIN").longitude - 3), + LatLng(places.getValue("DARWIN").latitude + 3, + places.getValue("DARWIN").longitude + 3), + LatLng(places.getValue("DARWIN").latitude - 3, + places.getValue("DARWIN").longitude + 3), + LatLng(places.getValue("DARWIN").latitude - 3, + places.getValue("DARWIN").longitude - 3)) + fillColor(Color.argb(150, 34, 173, 24)) + strokeColor(Color.rgb(34, 173, 24)) + clickable(true) + }).run { + // add a tag to the marker to count clicks + tag = CustomTag("Darwin polygon") + } + + // A polyline from Perth to Brisbane. + addPolyline(PolylineOptions().apply{ + add(places.getValue("PERTH"), places.getValue("BRISBANE")) + color(Color.rgb(103, 24, 173)) + width(30f) + clickable(true) + }).run { + // add a tag to the polyline to count clicks + tag = CustomTag("Perth to Brisbane polyline") + } + } + } + + // Click event listeners. + private fun onClick(tag: CustomTag) { + tag.incrementClickCount() + tagText.text = tag.toString() + } + + override fun onCircleClick(circle: Circle) { + onClick(circle.tag as CustomTag) + } + + override fun onGroundOverlayClick(groundOverlay: GroundOverlay) { + onClick(groundOverlay.tag as CustomTag) + } + + override fun onMarkerClick(marker: Marker): Boolean { + onClick(marker.tag as CustomTag) + // We return true to indicate that we have consumed the event and that we do not wish + // for the default behavior to occur (which is for the camera to move such that the + // marker is centered and for the marker's info window to open, if it has one). + return true + } + + override fun onPolygonClick(polygon: Polygon) { + onClick(polygon.tag as CustomTag) + } + + override fun onPolylineClick(polyline: Polyline) { + onClick(polyline.tag as CustomTag) + } + +} \ No newline at end of file diff --git a/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/VisibleRegionDemoActivity.kt b/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/VisibleRegionDemoActivity.kt new file mode 100644 index 00000000..ed4138ec --- /dev/null +++ b/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/VisibleRegionDemoActivity.kt @@ -0,0 +1,165 @@ +/* + * Copyright 2018 Google LLC + * + * 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 + * + * https://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.example.kotlindemos + +import com.google.android.gms.maps.CameraUpdateFactory +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.SupportMapFragment +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.LatLngBounds +import com.google.android.gms.maps.model.MarkerOptions + +import android.os.Bundle +import android.os.Handler +import android.os.SystemClock +import android.support.v7.app.AppCompatActivity +import android.view.View +import android.view.animation.OvershootInterpolator +import android.widget.Button +import android.widget.TextView + +/** + * This shows how to use setPadding to allow overlays that obscure part of the map without + * obscuring the map UI or copyright notices. + */ +class VisibleRegionDemoActivity : + AppCompatActivity(), + OnMapAndViewReadyListener.OnGlobalLayoutAndMapReadyListener { + + private val operaHouseLatLng = LatLng(-33.85704, 151.21522) + private val sfoLatLng = LatLng(37.614631, -122.385153) + private val australiaBounds = LatLngBounds(LatLng(-44.0, 113.0), + LatLng(-10.0, 154.0)) + + private lateinit var map: GoogleMap + + private lateinit var messageView: TextView + private lateinit var normalButton: Button + private lateinit var morePaddedButton: Button + private lateinit var operaHouseButton: Button + private lateinit var sfoButton: Button + private lateinit var australiaButton: Button + + /** Keep track of current values for padding, so we can animate from them. */ + private var currentLeft = 150 + private var currentTop = 0 + private var currentRight = 0 + private var currentBottom = 0 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.visible_region_demo) + messageView = findViewById(R.id.message_text) + + val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment + OnMapAndViewReadyListener(mapFragment, this) + + normalButton = findViewById(R.id.vr_normal_button) + morePaddedButton = findViewById(R.id.vr_more_padded_button) + operaHouseButton = findViewById(R.id.vr_soh_button) + sfoButton = findViewById(R.id.vr_sfo_button) + australiaButton = findViewById(R.id.vr_aus_button) + + } + + override fun onMapReady(googleMap: GoogleMap?) { + + // exit early if the map was not initialised properly + map = googleMap ?: return + + map.apply{ + // Set padding for the current camera view + setPadding(currentLeft, currentTop, currentRight, currentBottom) + // Move to a place with indoor (sfoLatLng airport). + moveCamera(CameraUpdateFactory.newLatLngZoom(sfoLatLng, 18f)) + // Add a marker to the Opera House. + addMarker(MarkerOptions().position(operaHouseLatLng).title("Sydney Opera House")) + // Add a camera idle listener that displays the current camera position in a TextView + setOnCameraIdleListener { + messageView.text = getString(R.string.camera_change_message, + this@VisibleRegionDemoActivity.map.cameraPosition) + } + } + + normalButton.setOnClickListener { + animatePadding(150, 0, 0, 0) + } + + // listener for when the 'more' padding button is clicked + // increases the amount of padding along the right and bottom of the map + morePaddedButton.setOnClickListener { + // get the view that contains the map + val mapView: View? = supportFragmentManager.findFragmentById(R.id.map).view + animatePadding(150, 0, (mapView?.width ?: 0) / 3, + (mapView?.height ?: 0)/ 4) + } + + operaHouseButton.setOnClickListener { + map.moveCamera(CameraUpdateFactory.newLatLngZoom(operaHouseLatLng, 16f)) + } + + sfoButton.setOnClickListener { + map.moveCamera(CameraUpdateFactory.newLatLngZoom(sfoLatLng, 18f)) + } + + australiaButton.setOnClickListener { + map.moveCamera(CameraUpdateFactory.newLatLngBounds(australiaBounds, 0)) + } + } + + // this function smoothly changes the amount of padding over a period of time + private fun animatePadding(toLeft: Int, toTop: Int, toRight: Int, toBottom: Int) { + + val handler = Handler() + val start = SystemClock.uptimeMillis() + val duration: Long = 1000 + + val interpolator = OvershootInterpolator() + + val startLeft: Int = currentLeft + val startTop: Int = currentTop + val startRight: Int = currentRight + val startBottom: Int = currentBottom + + currentLeft = toLeft + currentTop = toTop + currentRight = toRight + currentBottom = toBottom + + handler.post(object : Runnable { + override fun run() { + val elapsed = SystemClock.uptimeMillis() - start + val t: Float = interpolator.getInterpolation(elapsed.toFloat() / duration) + + val leftDiff = ((toLeft - startLeft) * t).toInt() + val topDiff = ((toTop - startTop) * t).toInt() + val rightDiff = ((toRight - startRight) * t).toInt() + val bottomDiff = ((toBottom - startBottom) * t).toInt() + + val left = startLeft + leftDiff + val top = startTop + topDiff + val right = startRight + rightDiff + val bottom = startBottom + bottomDiff + + map.setPadding(left, top, right, bottom) + + // Post again 16ms later. + if (elapsed < duration) { handler.postDelayed(this, 16) } + } + }) + } +} diff --git a/ApiDemos/kotlin/app/src/main/res/drawable/arrow.png b/ApiDemos/kotlin/app/src/main/res/drawable/arrow.png new file mode 100644 index 00000000..77b3f5aa Binary files /dev/null and b/ApiDemos/kotlin/app/src/main/res/drawable/arrow.png differ diff --git a/ApiDemos/kotlin/app/src/main/res/drawable/badge_nsw.png b/ApiDemos/kotlin/app/src/main/res/drawable/badge_nsw.png new file mode 100644 index 00000000..3a121046 Binary files /dev/null and b/ApiDemos/kotlin/app/src/main/res/drawable/badge_nsw.png differ diff --git a/ApiDemos/kotlin/app/src/main/res/drawable/badge_nt.png b/ApiDemos/kotlin/app/src/main/res/drawable/badge_nt.png new file mode 100644 index 00000000..b85cf6c7 Binary files /dev/null and b/ApiDemos/kotlin/app/src/main/res/drawable/badge_nt.png differ diff --git a/ApiDemos/kotlin/app/src/main/res/drawable/badge_qld.png b/ApiDemos/kotlin/app/src/main/res/drawable/badge_qld.png new file mode 100644 index 00000000..9787fb15 Binary files /dev/null and b/ApiDemos/kotlin/app/src/main/res/drawable/badge_qld.png differ diff --git a/ApiDemos/kotlin/app/src/main/res/drawable/badge_sa.png b/ApiDemos/kotlin/app/src/main/res/drawable/badge_sa.png new file mode 100644 index 00000000..11aa229f Binary files /dev/null and b/ApiDemos/kotlin/app/src/main/res/drawable/badge_sa.png differ diff --git a/ApiDemos/kotlin/app/src/main/res/drawable/badge_victoria.png b/ApiDemos/kotlin/app/src/main/res/drawable/badge_victoria.png new file mode 100644 index 00000000..80db77e7 Binary files /dev/null and b/ApiDemos/kotlin/app/src/main/res/drawable/badge_victoria.png differ diff --git a/ApiDemos/kotlin/app/src/main/res/drawable/badge_wa.png b/ApiDemos/kotlin/app/src/main/res/drawable/badge_wa.png new file mode 100644 index 00000000..7c0dc76c Binary files /dev/null and b/ApiDemos/kotlin/app/src/main/res/drawable/badge_wa.png differ diff --git a/ApiDemos/kotlin/app/src/main/res/drawable/chevron.png b/ApiDemos/kotlin/app/src/main/res/drawable/chevron.png new file mode 100644 index 00000000..259e277d Binary files /dev/null and b/ApiDemos/kotlin/app/src/main/res/drawable/chevron.png differ diff --git a/ApiDemos/kotlin/app/src/main/res/drawable/custom_info_bubble.9.png b/ApiDemos/kotlin/app/src/main/res/drawable/custom_info_bubble.9.png new file mode 100644 index 00000000..b6bf33b7 Binary files /dev/null and b/ApiDemos/kotlin/app/src/main/res/drawable/custom_info_bubble.9.png differ diff --git a/ApiDemos/kotlin/app/src/main/res/drawable/harbour_bridge.jpg b/ApiDemos/kotlin/app/src/main/res/drawable/harbour_bridge.jpg new file mode 100644 index 00000000..f1312590 Binary files /dev/null and b/ApiDemos/kotlin/app/src/main/res/drawable/harbour_bridge.jpg differ diff --git a/ApiDemos/kotlin/app/src/main/res/drawable/ic_android.xml b/ApiDemos/kotlin/app/src/main/res/drawable/ic_android.xml new file mode 100644 index 00000000..29d3c682 --- /dev/null +++ b/ApiDemos/kotlin/app/src/main/res/drawable/ic_android.xml @@ -0,0 +1,8 @@ + + + diff --git a/ApiDemos/kotlin/app/src/main/res/layout/camera_demo.xml b/ApiDemos/kotlin/app/src/main/res/layout/camera_demo.xml new file mode 100644 index 00000000..53653d01 --- /dev/null +++ b/ApiDemos/kotlin/app/src/main/res/layout/camera_demo.xml @@ -0,0 +1,195 @@ + + + + + + + + +