Skip to content

Commit 9f080fa

Browse files
committed
add the Circle Demo
1 parent 1f728df commit 9f080fa

File tree

6 files changed

+464
-4
lines changed

6 files changed

+464
-4
lines changed

ApiDemos/kotlin/app/src/main/AndroidManifest.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
android:roundIcon="@mipmap/ic_launcher_round"
2424
android:supportsRtl="true"
2525
android:theme="@style/AppTheme">
26+
2627
<meta-data
2728
android:name="com.google.android.geo.API_KEY"
2829
android:value="@string/google_maps_key" />
@@ -34,7 +35,8 @@
3435
<category android:name="android.intent.category.LAUNCHER" />
3536
</intent-filter>
3637
</activity>
37-
<activity android:name=".BasicMapDemoActivity"></activity>
38+
<activity android:name=".BasicMapDemoActivity" />
39+
<activity android:name=".CircleDemoActivity" />
3840
</application>
3941

4042
</manifest>
Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
/*
2+
* Copyright 2018 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example.kotlindemos
18+
19+
import android.graphics.Color
20+
import android.graphics.Point
21+
import android.location.Location
22+
import android.support.v7.app.AppCompatActivity
23+
import android.os.Bundle
24+
import android.view.View
25+
import android.widget.AdapterView
26+
import android.widget.ArrayAdapter
27+
import android.widget.CheckBox
28+
import android.widget.SeekBar
29+
import android.widget.Spinner
30+
31+
import com.google.android.gms.maps.CameraUpdateFactory
32+
import com.google.android.gms.maps.GoogleMap
33+
import com.google.android.gms.maps.OnMapReadyCallback
34+
import com.google.android.gms.maps.SupportMapFragment
35+
import com.google.android.gms.maps.model.BitmapDescriptorFactory
36+
import com.google.android.gms.maps.model.Circle
37+
import com.google.android.gms.maps.model.CircleOptions
38+
import com.google.android.gms.maps.model.Dash
39+
import com.google.android.gms.maps.model.Dot
40+
import com.google.android.gms.maps.model.Gap
41+
import com.google.android.gms.maps.model.LatLng
42+
import com.google.android.gms.maps.model.Marker
43+
import com.google.android.gms.maps.model.MarkerOptions
44+
import com.google.android.gms.maps.model.PatternItem
45+
46+
import java.util.ArrayList
47+
import java.util.Arrays
48+
49+
/**
50+
* This shows how to draw circles on a map.
51+
*/
52+
class CircleDemoActivity :
53+
AppCompatActivity(),
54+
SeekBar.OnSeekBarChangeListener,
55+
AdapterView.OnItemSelectedListener,
56+
OnMapReadyCallback {
57+
58+
private val DEFAULT_RADIUS_METERS = 1000000.0
59+
60+
private val MAX_WIDTH_PX = 50
61+
private val MAX_HUE_DEGREE = 360
62+
63+
private val MAX_ALPHA = 255
64+
private val PATTERN_DASH_LENGTH = 100
65+
private val PATTERN_GAP_LENGTH = 200
66+
67+
private val sydney = LatLng(-33.87365, 151.20689)
68+
69+
private val dot = Dot()
70+
private val dash = Dash(PATTERN_DASH_LENGTH.toFloat())
71+
private val gap = Gap(PATTERN_GAP_LENGTH.toFloat())
72+
private val patternDotted = Arrays.asList(dot, gap)
73+
private val patternDashed = Arrays.asList(dash, gap)
74+
private val patternMixed = Arrays.asList(dot, gap, dot, dash, gap)
75+
76+
// These are the options for stroke patterns
77+
private val patterns: List<Pair<Int, List<PatternItem>?>> = listOf(
78+
Pair(R.string.pattern_solid, null),
79+
Pair(R.string.pattern_dashed, patternDashed),
80+
Pair(R.string.pattern_dotted, patternDotted),
81+
Pair(R.string.pattern_mixed, patternMixed)
82+
)
83+
84+
private lateinit var map: GoogleMap
85+
86+
private val circles = ArrayList<DraggableCircle>(1)
87+
88+
private var fillColorArgb : Int = 0
89+
private var strokeColorArgb: Int = 0
90+
91+
private lateinit var fillHueBar: SeekBar
92+
private lateinit var fillAlphaBar: SeekBar
93+
private lateinit var strokeWidthBar: SeekBar
94+
private lateinit var strokeHueBar: SeekBar
95+
private lateinit var strokeAlphaBar: SeekBar
96+
private lateinit var strokePatternSpinner: Spinner
97+
private lateinit var clickabilityCheckbox: CheckBox
98+
99+
/**
100+
* This class contains information about a circle, including its markers
101+
*/
102+
private inner class DraggableCircle(center: LatLng, private var radiusMeters: Double) {
103+
private val centerMarker: Marker = map.addMarker(MarkerOptions().apply {
104+
position(center)
105+
draggable(true)
106+
})
107+
108+
private val radiusMarker: Marker = map.addMarker(
109+
MarkerOptions().apply {
110+
position(center.getPointAtDistance(radiusMeters))
111+
icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE))
112+
draggable(true)
113+
})
114+
115+
private val circle: Circle = map.addCircle(
116+
CircleOptions().apply {
117+
center(center)
118+
radius(radiusMeters)
119+
strokeWidth(strokeWidthBar.progress.toFloat())
120+
strokeColor(strokeColorArgb)
121+
fillColor(fillColorArgb)
122+
clickable(clickabilityCheckbox.isChecked)
123+
strokePattern(getSelectedPattern(strokePatternSpinner.selectedItemPosition))
124+
})
125+
126+
fun onMarkerMoved(marker: Marker): Boolean {
127+
when (marker) {
128+
centerMarker -> {
129+
circle.center = marker.position
130+
radiusMarker.position = marker.position.getPointAtDistance(radiusMeters)
131+
}
132+
radiusMarker -> {
133+
radiusMeters = centerMarker.position.distanceFrom(radiusMarker.position)
134+
circle.radius = radiusMeters
135+
}
136+
else -> return false
137+
}
138+
return true
139+
}
140+
141+
fun onStyleChange() {
142+
with(circle) {
143+
strokeWidth = strokeWidthBar.progress.toFloat()
144+
strokeColor = strokeColorArgb
145+
fillColor = fillColorArgb
146+
}
147+
}
148+
149+
fun setStrokePattern(pattern: List<PatternItem>?) {
150+
circle.strokePattern = pattern
151+
}
152+
153+
fun setClickable(clickable: Boolean) {
154+
circle.isClickable = clickable
155+
}
156+
}
157+
158+
override fun onCreate(savedInstanceState: Bundle?) {
159+
super.onCreate(savedInstanceState)
160+
setContentView(R.layout.activity_circle_demo)
161+
162+
// Set all the SeekBars
163+
fillHueBar = findViewById<SeekBar>(R.id.fillHueSeekBar).apply {
164+
max = MAX_HUE_DEGREE
165+
progress = MAX_HUE_DEGREE / 2
166+
}
167+
fillAlphaBar = findViewById<SeekBar>(R.id.fillAlphaSeekBar).apply {
168+
max = MAX_ALPHA
169+
progress = MAX_ALPHA / 2
170+
}
171+
strokeWidthBar = findViewById<SeekBar>(R.id.strokeWidthSeekBar).apply {
172+
max = MAX_WIDTH_PX
173+
progress = MAX_WIDTH_PX / 3
174+
}
175+
strokeHueBar = findViewById<SeekBar>(R.id.strokeHueSeekBar).apply {
176+
max = MAX_HUE_DEGREE
177+
progress = 0
178+
}
179+
strokeAlphaBar = findViewById<SeekBar>(R.id.strokeAlphaSeekBar).apply {
180+
max = MAX_ALPHA
181+
progress = MAX_ALPHA
182+
}
183+
184+
strokePatternSpinner = findViewById<Spinner>(R.id.strokePatternSpinner).apply {
185+
adapter = ArrayAdapter(this@CircleDemoActivity,
186+
android.R.layout.simple_spinner_item,
187+
getResourceStrings())
188+
}
189+
190+
clickabilityCheckbox = findViewById(R.id.toggleClickability)
191+
192+
val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment
193+
mapFragment.getMapAsync(this)
194+
}
195+
196+
/** Get all the strings of patterns and return them as Array. */
197+
private fun getResourceStrings() = (patterns).map { getString(it.first) }.toTypedArray()
198+
199+
/**
200+
* When the map is ready, move the camera to put the Circle in the middle of the screen,
201+
* create a circle in Sydney, and set the listeners for the map, circles, and SeekBars.
202+
*/
203+
override fun onMapReady(googleMap: GoogleMap?) {
204+
map = googleMap ?: return
205+
// we need to initialise map before creating a circle
206+
with(map) {
207+
moveCamera(CameraUpdateFactory.newLatLngZoom(sydney, 4.0f))
208+
setContentDescription(getString(R.string.circle_demo_details))
209+
setOnMapLongClickListener { point ->
210+
// We know the center, let's place the outline at a point 3/4 along the view.
211+
val view: View = supportFragmentManager.findFragmentById(R.id.map).view as View
212+
val radiusLatLng = map.projection.fromScreenLocation(
213+
Point(view.height * 3 / 4, view.width * 3 / 4))
214+
// Create the circle.
215+
val newCircle = DraggableCircle(point, point.distanceFrom(radiusLatLng))
216+
circles.add(newCircle)
217+
}
218+
219+
setOnMarkerDragListener(object : GoogleMap.OnMarkerDragListener {
220+
override fun onMarkerDragStart(marker: Marker) {
221+
onMarkerMoved(marker)
222+
}
223+
224+
override fun onMarkerDragEnd(marker: Marker) {
225+
onMarkerMoved(marker)
226+
}
227+
228+
override fun onMarkerDrag(marker: Marker) {
229+
onMarkerMoved(marker)
230+
}
231+
})
232+
233+
// Flip the red, green and blue components of the circle's stroke color.
234+
setOnCircleClickListener { c -> c.strokeColor = c.strokeColor xor 0x00ffffff }
235+
}
236+
237+
fillColorArgb = Color.HSVToColor(fillAlphaBar.progress,
238+
floatArrayOf(fillHueBar.progress.toFloat(), 1f, 1f))
239+
strokeColorArgb = Color.HSVToColor(strokeAlphaBar.progress,
240+
floatArrayOf(strokeHueBar.progress.toFloat(), 1f, 1f))
241+
242+
val circle = DraggableCircle(sydney, DEFAULT_RADIUS_METERS)
243+
circles.add(circle)
244+
245+
// Set listeners for all the SeekBar
246+
fillHueBar.setOnSeekBarChangeListener(this)
247+
fillAlphaBar.setOnSeekBarChangeListener(this)
248+
249+
strokeWidthBar.setOnSeekBarChangeListener(this)
250+
strokeHueBar.setOnSeekBarChangeListener(this)
251+
strokeAlphaBar.setOnSeekBarChangeListener(this)
252+
253+
strokePatternSpinner.onItemSelectedListener = this
254+
}
255+
256+
private fun getSelectedPattern(pos: Int): List<PatternItem>? = patterns[pos].second
257+
258+
override fun onItemSelected(parent: AdapterView<*>, view: View, pos: Int, id: Long) {
259+
if (parent.id == R.id.strokePatternSpinner) {
260+
circles.map { it.setStrokePattern(getSelectedPattern(pos)) }
261+
}
262+
}
263+
264+
override fun onNothingSelected(parent: AdapterView<*>) {
265+
// Don't do anything here.
266+
}
267+
268+
override fun onStopTrackingTouch(seekBar: SeekBar) {
269+
// Don't do anything here.
270+
}
271+
272+
override fun onStartTrackingTouch(seekBar: SeekBar) {
273+
// Don't do anything here.
274+
}
275+
276+
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
277+
// Update the fillColorArgb if the SeekBars for it is changed, otherwise keep the old value
278+
fillColorArgb = when (seekBar) {
279+
fillHueBar -> Color.HSVToColor(Color.alpha(fillColorArgb),
280+
floatArrayOf(progress.toFloat(), 1f, 1f))
281+
fillAlphaBar -> Color.argb(progress, Color.red(fillColorArgb),
282+
Color.green(fillColorArgb), Color.blue(fillColorArgb))
283+
else -> fillColorArgb
284+
}
285+
286+
// Set the strokeColorArgb if the SeekBars for it is changed, otherwise keep the old value
287+
strokeColorArgb = when (seekBar) {
288+
strokeHueBar -> Color.HSVToColor(Color.alpha(strokeColorArgb),
289+
floatArrayOf(progress.toFloat(), 1f, 1f))
290+
strokeAlphaBar -> Color.argb(progress, Color.red(strokeColorArgb),
291+
Color.green(strokeColorArgb), Color.blue(strokeColorArgb))
292+
else -> strokeColorArgb
293+
}
294+
295+
circles.map { it.onStyleChange() }
296+
}
297+
298+
private fun onMarkerMoved(marker: Marker) {
299+
circles.forEach { if (it.onMarkerMoved(marker)) return }
300+
}
301+
302+
/** Listener for the Clickable CheckBox, to set if all the circles can be click */
303+
fun toggleClickability(view: View) {
304+
circles.map { it.setClickable((view as CheckBox).isChecked) }
305+
}
306+
}
307+
308+
/**
309+
* Extension function to find the distance from this to another LatLng object
310+
*/
311+
private fun LatLng.distanceFrom(other: LatLng): Double {
312+
val result = FloatArray(1)
313+
Location.distanceBetween(latitude, longitude, other.latitude, other.longitude, result)
314+
return result[0].toDouble()
315+
}
316+
317+
private fun LatLng.getPointAtDistance(distance: Double): LatLng {
318+
val radiusOfEarth = 6371009.0
319+
val radiusAngle = (Math.toDegrees(distance / radiusOfEarth)
320+
/ Math.cos(Math.toRadians(latitude)))
321+
return LatLng(latitude, longitude + radiusAngle)
322+
}

ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/DemoDetailsList.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ class DemoDetailsList {
2323
companion object {
2424
val DEMOS = listOf<DemoDetails>(
2525
DemoDetails(R.string.basic_demo_label, R.string.basic_demo_details,
26-
BasicMapDemoActivity::class.java)
26+
BasicMapDemoActivity::class.java),
27+
DemoDetails(R.string.circle_demo_label, R.string.circle_demo_details,
28+
CircleDemoActivity::class.java)
2729
)
2830
}
2931
}

ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/MainActivity.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemClickListener {
6161
class CustomArrayAdapter(context: Context, demos: List<DemoDetails>) :
6262
ArrayAdapter<DemoDetails>(context, R.id.title, demos) {
6363

64-
override fun getView(position: Int, convertView: View?, parent: ViewGroup) : View {
65-
val demo : DemoDetails = getItem(position)
64+
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
65+
val demo: DemoDetails = getItem(position)
6666
return (convertView as? FeatureView ?: FeatureView(context)).apply {
6767
setTitleId(demo.titleId)
6868
setDescriptionId(demo.descriptionId)

0 commit comments

Comments
 (0)