From 9f080fa09e1ea20e45e59926c002fa57c661ec0f Mon Sep 17 00:00:00 2001 From: Bella Mangunsong Date: Wed, 10 Jan 2018 12:32:11 +1100 Subject: [PATCH] add the Circle Demo --- .../kotlin/app/src/main/AndroidManifest.xml | 4 +- .../example/kotlindemos/CircleDemoActivity.kt | 322 ++++++++++++++++++ .../example/kotlindemos/DemoDetailsList.kt | 4 +- .../com/example/kotlindemos/MainActivity.kt | 4 +- .../main/res/layout/activity_circle_demo.xml | 109 ++++++ .../app/src/main/res/values/strings.xml | 25 ++ 6 files changed, 464 insertions(+), 4 deletions(-) create mode 100644 ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/CircleDemoActivity.kt create mode 100644 ApiDemos/kotlin/app/src/main/res/layout/activity_circle_demo.xml diff --git a/ApiDemos/kotlin/app/src/main/AndroidManifest.xml b/ApiDemos/kotlin/app/src/main/AndroidManifest.xml index 554fb6d9..00e56083 100644 --- a/ApiDemos/kotlin/app/src/main/AndroidManifest.xml +++ b/ApiDemos/kotlin/app/src/main/AndroidManifest.xml @@ -23,6 +23,7 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> + @@ -34,7 +35,8 @@ - + + \ No newline at end of file diff --git a/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/CircleDemoActivity.kt b/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/CircleDemoActivity.kt new file mode 100644 index 00000000..d89851e8 --- /dev/null +++ b/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/CircleDemoActivity.kt @@ -0,0 +1,322 @@ +/* + * 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.graphics.Point +import android.location.Location +import android.support.v7.app.AppCompatActivity +import android.os.Bundle +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 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.Circle +import com.google.android.gms.maps.model.CircleOptions +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.LatLng +import com.google.android.gms.maps.model.Marker +import com.google.android.gms.maps.model.MarkerOptions +import com.google.android.gms.maps.model.PatternItem + +import java.util.ArrayList +import java.util.Arrays + +/** + * This shows how to draw circles on a map. + */ +class CircleDemoActivity : + AppCompatActivity(), + SeekBar.OnSeekBarChangeListener, + AdapterView.OnItemSelectedListener, + OnMapReadyCallback { + + private val DEFAULT_RADIUS_METERS = 1000000.0 + + private val MAX_WIDTH_PX = 50 + private val MAX_HUE_DEGREE = 360 + + private val MAX_ALPHA = 255 + private val PATTERN_DASH_LENGTH = 100 + private val PATTERN_GAP_LENGTH = 200 + + private val sydney = LatLng(-33.87365, 151.20689) + + private val dot = Dot() + private val dash = Dash(PATTERN_DASH_LENGTH.toFloat()) + private val gap = Gap(PATTERN_GAP_LENGTH.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) + + // These are the options for stroke patterns + private val patterns: List?>> = listOf( + Pair(R.string.pattern_solid, null), + Pair(R.string.pattern_dashed, patternDashed), + Pair(R.string.pattern_dotted, patternDotted), + Pair(R.string.pattern_mixed, patternMixed) + ) + + private lateinit var map: GoogleMap + + private val circles = ArrayList(1) + + private var fillColorArgb : Int = 0 + private var strokeColorArgb: Int = 0 + + 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 strokePatternSpinner: Spinner + private lateinit var clickabilityCheckbox: CheckBox + + /** + * This class contains information about a circle, including its markers + */ + private inner class DraggableCircle(center: LatLng, private var radiusMeters: Double) { + private val centerMarker: Marker = map.addMarker(MarkerOptions().apply { + position(center) + draggable(true) + }) + + private val radiusMarker: Marker = map.addMarker( + MarkerOptions().apply { + position(center.getPointAtDistance(radiusMeters)) + icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE)) + draggable(true) + }) + + private val circle: Circle = map.addCircle( + CircleOptions().apply { + center(center) + radius(radiusMeters) + strokeWidth(strokeWidthBar.progress.toFloat()) + strokeColor(strokeColorArgb) + fillColor(fillColorArgb) + clickable(clickabilityCheckbox.isChecked) + strokePattern(getSelectedPattern(strokePatternSpinner.selectedItemPosition)) + }) + + fun onMarkerMoved(marker: Marker): Boolean { + when (marker) { + centerMarker -> { + circle.center = marker.position + radiusMarker.position = marker.position.getPointAtDistance(radiusMeters) + } + radiusMarker -> { + radiusMeters = centerMarker.position.distanceFrom(radiusMarker.position) + circle.radius = radiusMeters + } + else -> return false + } + return true + } + + fun onStyleChange() { + with(circle) { + strokeWidth = strokeWidthBar.progress.toFloat() + strokeColor = strokeColorArgb + fillColor = fillColorArgb + } + } + + fun setStrokePattern(pattern: List?) { + circle.strokePattern = pattern + } + + fun setClickable(clickable: Boolean) { + circle.isClickable = clickable + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_circle_demo) + + // Set all the SeekBars + fillHueBar = findViewById(R.id.fillHueSeekBar).apply { + max = MAX_HUE_DEGREE + progress = MAX_HUE_DEGREE / 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_DEGREE + progress = 0 + } + strokeAlphaBar = findViewById(R.id.strokeAlphaSeekBar).apply { + max = MAX_ALPHA + progress = MAX_ALPHA + } + + strokePatternSpinner = findViewById(R.id.strokePatternSpinner).apply { + adapter = ArrayAdapter(this@CircleDemoActivity, + android.R.layout.simple_spinner_item, + getResourceStrings()) + } + + clickabilityCheckbox = findViewById(R.id.toggleClickability) + + val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment + mapFragment.getMapAsync(this) + } + + /** Get all the strings of patterns and return them as Array. */ + private fun getResourceStrings() = (patterns).map { getString(it.first) }.toTypedArray() + + /** + * When the map is ready, move the camera to put the Circle in the middle of the screen, + * create a circle in Sydney, and set the listeners for the map, circles, and SeekBars. + */ + override fun onMapReady(googleMap: GoogleMap?) { + map = googleMap ?: return + // we need to initialise map before creating a circle + with(map) { + moveCamera(CameraUpdateFactory.newLatLngZoom(sydney, 4.0f)) + setContentDescription(getString(R.string.circle_demo_details)) + setOnMapLongClickListener { point -> + // We know the center, let's place the outline at a point 3/4 along the view. + val view: View = supportFragmentManager.findFragmentById(R.id.map).view as View + val radiusLatLng = map.projection.fromScreenLocation( + Point(view.height * 3 / 4, view.width * 3 / 4)) + // Create the circle. + val newCircle = DraggableCircle(point, point.distanceFrom(radiusLatLng)) + circles.add(newCircle) + } + + setOnMarkerDragListener(object : GoogleMap.OnMarkerDragListener { + override fun onMarkerDragStart(marker: Marker) { + onMarkerMoved(marker) + } + + override fun onMarkerDragEnd(marker: Marker) { + onMarkerMoved(marker) + } + + override fun onMarkerDrag(marker: Marker) { + onMarkerMoved(marker) + } + }) + + // Flip the red, green and blue components of the circle's stroke color. + setOnCircleClickListener { c -> c.strokeColor = c.strokeColor xor 0x00ffffff } + } + + fillColorArgb = Color.HSVToColor(fillAlphaBar.progress, + floatArrayOf(fillHueBar.progress.toFloat(), 1f, 1f)) + strokeColorArgb = Color.HSVToColor(strokeAlphaBar.progress, + floatArrayOf(strokeHueBar.progress.toFloat(), 1f, 1f)) + + val circle = DraggableCircle(sydney, DEFAULT_RADIUS_METERS) + circles.add(circle) + + // Set listeners for all the SeekBar + fillHueBar.setOnSeekBarChangeListener(this) + fillAlphaBar.setOnSeekBarChangeListener(this) + + strokeWidthBar.setOnSeekBarChangeListener(this) + strokeHueBar.setOnSeekBarChangeListener(this) + strokeAlphaBar.setOnSeekBarChangeListener(this) + + strokePatternSpinner.onItemSelectedListener = this + } + + private fun getSelectedPattern(pos: Int): List? = patterns[pos].second + + override fun onItemSelected(parent: AdapterView<*>, view: View, pos: Int, id: Long) { + if (parent.id == R.id.strokePatternSpinner) { + circles.map { it.setStrokePattern(getSelectedPattern(pos)) } + } + } + + override fun onNothingSelected(parent: AdapterView<*>) { + // Don't do anything here. + } + + override fun onStopTrackingTouch(seekBar: SeekBar) { + // Don't do anything here. + } + + override fun onStartTrackingTouch(seekBar: SeekBar) { + // Don't do anything here. + } + + override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { + // Update the fillColorArgb if the SeekBars for it is changed, otherwise keep the old value + fillColorArgb = when (seekBar) { + fillHueBar -> Color.HSVToColor(Color.alpha(fillColorArgb), + floatArrayOf(progress.toFloat(), 1f, 1f)) + fillAlphaBar -> Color.argb(progress, Color.red(fillColorArgb), + Color.green(fillColorArgb), Color.blue(fillColorArgb)) + else -> fillColorArgb + } + + // Set the strokeColorArgb if the SeekBars for it is changed, otherwise keep the old value + strokeColorArgb = when (seekBar) { + strokeHueBar -> Color.HSVToColor(Color.alpha(strokeColorArgb), + floatArrayOf(progress.toFloat(), 1f, 1f)) + strokeAlphaBar -> Color.argb(progress, Color.red(strokeColorArgb), + Color.green(strokeColorArgb), Color.blue(strokeColorArgb)) + else -> strokeColorArgb + } + + circles.map { it.onStyleChange() } + } + + private fun onMarkerMoved(marker: Marker) { + circles.forEach { if (it.onMarkerMoved(marker)) return } + } + + /** Listener for the Clickable CheckBox, to set if all the circles can be click */ + fun toggleClickability(view: View) { + circles.map { it.setClickable((view as CheckBox).isChecked) } + } +} + +/** + * Extension function to find the distance from this to another LatLng object + */ +private fun LatLng.distanceFrom(other: LatLng): Double { + val result = FloatArray(1) + Location.distanceBetween(latitude, longitude, other.latitude, other.longitude, result) + return result[0].toDouble() +} + +private fun LatLng.getPointAtDistance(distance: Double): LatLng { + val radiusOfEarth = 6371009.0 + val radiusAngle = (Math.toDegrees(distance / radiusOfEarth) + / Math.cos(Math.toRadians(latitude))) + return LatLng(latitude, longitude + radiusAngle) +} \ 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 0d71a3b1..0e7600d3 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 @@ -23,7 +23,9 @@ class DemoDetailsList { companion object { val DEMOS = listOf( DemoDetails(R.string.basic_demo_label, R.string.basic_demo_details, - BasicMapDemoActivity::class.java) + BasicMapDemoActivity::class.java), + DemoDetails(R.string.circle_demo_label, R.string.circle_demo_details, + CircleDemoActivity::class.java) ) } } \ No newline at end of file diff --git a/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/MainActivity.kt b/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/MainActivity.kt index 4ead271d..ad93c28a 100644 --- a/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/MainActivity.kt +++ b/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/MainActivity.kt @@ -61,8 +61,8 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemClickListener { class CustomArrayAdapter(context: Context, demos: List) : ArrayAdapter(context, R.id.title, demos) { - override fun getView(position: Int, convertView: View?, parent: ViewGroup) : View { - val demo : DemoDetails = getItem(position) + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { + val demo: DemoDetails = getItem(position) return (convertView as? FeatureView ?: FeatureView(context)).apply { setTitleId(demo.titleId) setDescriptionId(demo.descriptionId) diff --git a/ApiDemos/kotlin/app/src/main/res/layout/activity_circle_demo.xml b/ApiDemos/kotlin/app/src/main/res/layout/activity_circle_demo.xml new file mode 100644 index 00000000..6b7c3c91 --- /dev/null +++ b/ApiDemos/kotlin/app/src/main/res/layout/activity_circle_demo.xml @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApiDemos/kotlin/app/src/main/res/values/strings.xml b/ApiDemos/kotlin/app/src/main/res/values/strings.xml index 918ce5be..d49a8ecd 100644 --- a/ApiDemos/kotlin/app/src/main/res/values/strings.xml +++ b/ApiDemos/kotlin/app/src/main/res/values/strings.xml @@ -23,4 +23,29 @@ Basic Map Launches a map with marker pointing at Sydney + + Circle Demo + Demonstrate how to add circles to a map + Properties for Circle(s) + + + Clickable + Fill Alpha + Fill Hue + Bevel + Default + Round + Dashed + Dotted + Mixed + Solid + Demonstrates how to add Polygons to a map. + Demonstrates how to add Polygons to a map + Polygons + Properties for Polygon over Australia + Stroke Alpha + Stroke Hue + Stroke Joint Type + Stroke Pattern + Stroke Width \ No newline at end of file