Skip to content

Commit 3644817

Browse files
committed
Add camera animations
1 parent 7eaae97 commit 3644817

19 files changed

Lines changed: 1030 additions & 9 deletions

File tree

.github/workflows/_build-examples.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ jobs:
4343
- name: Build Example03Markers
4444
run: ./gradlew :examples:Example03Markers:assembleDebug
4545

46+
- name: Build Example04Polylines
47+
run: ./gradlew :examples:Example04Polylines:assembleDebug
48+
49+
- name: Build Example05Camera
50+
run: ./gradlew :examples:Example05Camera:assembleDebug
51+
4652
- name: Upload example APKs
4753
uses: actions/upload-artifact@v4
4854
with:

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ The library is available on [Maven Central](https://central.sonatype.com/artifac
2424
- Drop-in compatible with Google `MapView` (non-deprecated methods only)
2525
- Lightweight, pure Kotlin implementation
2626
- OSM tiles via standard APIs
27+
- Smooth camera animations with customizable durations
2728
- Extensible marker, overlay, and gesture handling
2829
- MIT licensed (use freely in commercial apps)
2930

@@ -55,6 +56,12 @@ Demonstrates marker system with custom icons and click handling.
5556

5657
Shows how to draw vector shapes including polylines, filled polygons, and polygons with holes.
5758

59+
### [Example05Camera](examples/Example05Camera) - Camera Animations
60+
61+
![Example05Camera](examples/Example05Camera/screenshot.gif)
62+
63+
Demonstrates smooth camera animations with customizable durations and completion callbacks.
64+
5865
## Documentation
5966

6067
- [Contributing Guide](docs/CONTRIBUTING.md) - Code quality requirements, formatting, git hooks, and PR process

examples/Example01Pan/README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,10 @@ fun MapViewScreen() {
6666

6767
## Next Steps
6868

69-
- Try **Example02Zoom** for zoom controls and pinch-to-zoom gestures
70-
- Try **Example03Markers** for marker overlays and click handling
69+
- Try [Example02Zoom](../Example02Zoom) for zoom controls and pinch-to-zoom gestures
70+
- Try [Example03Markers](../Example03Markers) for marker overlays
71+
- Try [Example04Polylines](../Example04Polylines) for vector shapes
72+
- Try [Example05Camera](../Example05Camera) for camera animations
7173

7274
## Map Location
7375

examples/Example02Zoom/README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,10 @@ OpenMapView uses:
102102

103103
## Next Steps
104104

105-
- Try **Example01Pan** for basic panning without zoom controls
106-
- Try **Example03Markers** for marker overlays with zoom support
105+
- Try [Example01Pan](../Example01Pan) for basic panning without zoom controls
106+
- Try [Example03Markers](../Example03Markers) for marker overlays
107+
- Try [Example04Polylines](../Example04Polylines) for vector shapes
108+
- Try [Example05Camera](../Example05Camera) for camera animations
107109

108110
## Map Location
109111

examples/Example03Markers/README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,10 @@ Click detection uses:
154154

155155
## Next Steps
156156

157-
- Try **Example01Pan** for basic map panning
158-
- Try **Example02Zoom** for zoom controls
157+
- Try [Example01Pan](../Example01Pan) for basic map panning
158+
- Try [Example02Zoom](../Example02Zoom) for zoom controls
159+
- Try [Example04Polylines](../Example04Polylines) for vector shapes
160+
- Try [Example05Camera](../Example05Camera) for camera animations
159161
- Modify marker positions to show your own locations
160162
- Add custom marker icons from your app resources
161163

examples/Example04Polylines/README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -228,9 +228,10 @@ clearPolygons()
228228

229229
## Next Steps
230230

231-
- Try **Example01Pan** for basic map panning
232-
- Try **Example02Zoom** for zoom controls
233-
- Try **Example03Markers** for marker overlays
231+
- Try [Example01Pan](../Example01Pan) for basic map panning
232+
- Try [Example02Zoom](../Example02Zoom) for zoom controls
233+
- Try [Example03Markers](../Example03Markers) for marker overlays
234+
- Try [Example05Camera](../Example05Camera) for camera animations
234235
- Add your own geographic data (routes, boundaries, zones)
235236
- Experiment with different colors and transparency levels
236237

examples/Example05Camera/README.md

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
# Example05Camera - Camera Animations
2+
3+
This example demonstrates smooth camera animations in OpenMapView, showing how to animate map position and zoom level changes with customizable durations and completion callbacks.
4+
5+
## Features Demonstrated
6+
7+
- Animated camera movements to specific locations
8+
- Smooth zoom animations (zoom in/out)
9+
- Different animation durations (fast: 500ms, normal: 1000ms, slow: 2000ms)
10+
- Animation completion callbacks (onFinish, onCancel)
11+
- Animation cancellation with stopAnimation()
12+
- Multiple markers as animation targets
13+
14+
## Screenshot
15+
16+
![Example05Camera Demo](screenshot.gif)
17+
18+
## Quick Start
19+
20+
### Option 1: Run in Android Studio
21+
22+
1. Open the OpenMapView project in Android Studio
23+
2. Select `examples.Example05Camera` from the run configuration dropdown
24+
3. Click Run (green play button)
25+
4. Deploy to device or emulator
26+
27+
### Option 2: Build and Install from Command Line
28+
29+
```bash
30+
# From project root - build, install, and launch
31+
./gradlew :examples:Example05Camera:installDebug
32+
33+
# Launch the app
34+
adb shell am start -n de.afarber.openmapview.example05camera/.MainActivity
35+
```
36+
37+
## Code Highlights
38+
39+
### Animating to a Specific Location with Zoom
40+
41+
```kotlin
42+
mapView.animateCamera(
43+
CameraUpdateFactory.newLatLngZoom(targetLocation, 15.0),
44+
1000, // 1 second duration
45+
object : OnCameraAnimationListener {
46+
override fun onFinish() {
47+
Toast.makeText(context, "Animation finished", Toast.LENGTH_SHORT).show()
48+
}
49+
override fun onCancel() {
50+
Toast.makeText(context, "Animation cancelled", Toast.LENGTH_SHORT).show()
51+
}
52+
}
53+
)
54+
```
55+
56+
### Animating Zoom In/Out
57+
58+
```kotlin
59+
// Zoom in with 500ms animation
60+
mapView.animateCamera(
61+
CameraUpdateFactory.zoomIn(),
62+
500
63+
)
64+
65+
// Zoom out with 500ms animation
66+
mapView.animateCamera(
67+
CameraUpdateFactory.zoomOut(),
68+
500
69+
)
70+
```
71+
72+
### Stopping an Animation
73+
74+
```kotlin
75+
// Cancel any running animation
76+
mapView.stopAnimation()
77+
```
78+
79+
### Move Camera Instantly (No Animation)
80+
81+
```kotlin
82+
// Jump to location immediately without animation
83+
mapView.moveCamera(
84+
CameraUpdateFactory.newLatLngZoom(location, 14.0)
85+
)
86+
```
87+
88+
## Key Concepts
89+
90+
### CameraUpdateFactory
91+
92+
Factory for creating camera update operations:
93+
94+
- `newLatLng(LatLng)` - Move to location, keep current zoom
95+
- `newLatLngZoom(LatLng, Double)` - Move to location with specific zoom
96+
- `newCameraPosition(CameraPosition)` - Full control over camera state
97+
- `zoomIn()` - Increment zoom by 1
98+
- `zoomOut()` - Decrement zoom by 1
99+
- `zoomTo(Double)` - Set specific zoom level
100+
- `zoomBy(Double)` - Adjust zoom by amount (positive or negative)
101+
102+
### animateCamera() Overloads
103+
104+
```kotlin
105+
// Default 250ms duration, no callback
106+
animateCamera(cameraUpdate)
107+
108+
// Custom duration, no callback
109+
animateCamera(cameraUpdate, durationMs)
110+
111+
// Custom duration with callback
112+
animateCamera(cameraUpdate, durationMs, listener)
113+
```
114+
115+
### OnCameraAnimationListener
116+
117+
Callback interface for animation lifecycle:
118+
119+
- `onFinish()` - Called when animation completes normally
120+
- `onCancel()` - Called when animation is interrupted by stopAnimation() or new animation
121+
122+
### moveCamera vs animateCamera
123+
124+
- `moveCamera()` - Instant camera update, no animation
125+
- `animateCamera()` - Smooth interpolated transition
126+
127+
## What to Test
128+
129+
1. Location Buttons - Click "Loc 1", "Loc 2", "Loc 3" to animate to different markers
130+
2. Different Durations - Notice Location 3 uses 2 second animation (slower)
131+
3. Zoom Controls - Use + and - buttons for smooth zoom animations
132+
4. Callbacks - Location 1 shows toast messages on animation finish/cancel
133+
5. Stop Button - Click during animation to cancel and see onCancel callback
134+
6. Sequential Animations - Start animation, then immediately click another location (first animation cancels)
135+
136+
## Animation Details
137+
138+
This example sets up three marker locations around Bochum, Germany:
139+
140+
| Location | Coordinates | Animation Duration | Callbacks |
141+
| -------- | ----------- | ------------------ | --------- |
142+
| Location 1 | (51.4700, 7.2400) | 1000ms | Yes (toast) |
143+
| Location 2 | (51.4620, 7.2600) | 1000ms | No |
144+
| Location 3 | (51.4550, 7.2350) | 2000ms | No |
145+
146+
## Technical Details
147+
148+
### Animation Implementation
149+
150+
- Uses Kotlin coroutines for smooth frame-by-frame updates
151+
- Linear interpolation between start and end positions
152+
- 60 FPS target (16ms frame delay)
153+
- Automatically handles map tile loading during animation
154+
- Lifecycle-aware: animations cancelled when activity destroyed
155+
156+
### Lifecycle Integration
157+
158+
- Animations run in MapController's coroutine scope
159+
- Automatic cleanup via existing lifecycle hooks
160+
- No memory leaks when activity is destroyed
161+
- onDestroy() cancels all running animations
162+
163+
### Performance
164+
165+
- Efficient coroutine-based implementation
166+
- Minimal CPU usage during animation
167+
- Smooth rendering on all supported devices
168+
- Tile downloads continue during animation
169+
170+
## Advanced Usage
171+
172+
### Custom Animation Duration Based on Distance
173+
174+
```kotlin
175+
val distance = calculateDistance(currentPosition, targetPosition)
176+
val duration = (distance * 100).toInt().coerceIn(500, 3000)
177+
178+
mapView.animateCamera(
179+
CameraUpdateFactory.newLatLng(targetPosition),
180+
duration
181+
)
182+
```
183+
184+
### Chain Animations with Callbacks
185+
186+
```kotlin
187+
mapView.animateCamera(
188+
CameraUpdateFactory.newLatLng(location1),
189+
1000,
190+
object : OnCameraAnimationListener {
191+
override fun onFinish() {
192+
// Start second animation when first completes
193+
mapView.animateCamera(
194+
CameraUpdateFactory.zoomIn(),
195+
500
196+
)
197+
}
198+
override fun onCancel() {
199+
// Handle cancellation
200+
}
201+
}
202+
)
203+
```
204+
205+
### Animate to Show All Markers (Future Feature)
206+
207+
```kotlin
208+
// This will be available in future versions
209+
val bounds = LatLngBounds.builder()
210+
.include(marker1.position)
211+
.include(marker2.position)
212+
.include(marker3.position)
213+
.build()
214+
215+
mapView.animateCamera(
216+
CameraUpdateFactory.newLatLngBounds(bounds, padding)
217+
)
218+
```
219+
220+
## Next Steps
221+
222+
- Try [Example01Pan](../Example01Pan) for basic map panning
223+
- Try [Example02Zoom](../Example02Zoom) for zoom controls
224+
- Try [Example03Markers](../Example03Markers) for marker overlays
225+
- Try [Example04Polylines](../Example04Polylines) for vector shapes
226+
- Experiment with different animation durations
227+
- Add your own markers and create navigation sequences
228+
229+
## Map Location
230+
231+
Default Center: Bochum, Germany (51.4620°N, 7.2480°E) at zoom 13.0
232+
233+
All three marker locations are positioned around Bochum within approximately 2km radius.
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
plugins {
2+
id("com.android.application")
3+
id("org.jetbrains.kotlin.android")
4+
id("org.jetbrains.kotlin.plugin.compose")
5+
id("com.diffplug.spotless")
6+
}
7+
8+
android {
9+
namespace = "de.afarber.openmapview.example05camera"
10+
compileSdk = 35
11+
12+
defaultConfig {
13+
applicationId = "de.afarber.openmapview.example05camera"
14+
minSdk = 23
15+
targetSdk = 35
16+
versionCode = 1
17+
versionName = "1.0"
18+
}
19+
20+
buildTypes {
21+
release {
22+
isMinifyEnabled = false
23+
}
24+
}
25+
26+
compileOptions {
27+
sourceCompatibility = JavaVersion.VERSION_17
28+
targetCompatibility = JavaVersion.VERSION_17
29+
}
30+
31+
kotlinOptions {
32+
jvmTarget = "17"
33+
}
34+
35+
buildFeatures {
36+
compose = true
37+
}
38+
39+
composeOptions {
40+
kotlinCompilerExtensionVersion = "1.5.3"
41+
}
42+
43+
sourceSets {
44+
getByName("main").java.srcDirs("src/main/kotlin")
45+
}
46+
}
47+
48+
dependencies {
49+
implementation(project(":openmapview"))
50+
implementation("androidx.core:core-ktx:1.15.0")
51+
implementation("androidx.activity:activity-compose:1.9.3")
52+
implementation("androidx.compose.ui:ui:1.7.5")
53+
implementation("androidx.compose.material3:material3:1.3.1")
54+
implementation("androidx.compose.ui:ui-viewbinding:1.7.5")
55+
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.7")
56+
}
57+
58+
spotless {
59+
kotlin {
60+
target("src/**/*.kt")
61+
ktlint("1.3.1").editorConfigOverride(
62+
mapOf(
63+
"ktlint_function_naming_ignore_when_annotated_with" to "Composable",
64+
),
65+
)
66+
trimTrailingWhitespace()
67+
endWithNewline()
68+
indentWithSpaces(4)
69+
licenseHeaderFile(rootProject.file("spotless.license.kt"), "(package|import)")
70+
}
71+
kotlinGradle {
72+
target("*.kts")
73+
ktlint("1.3.1")
74+
}
75+
}

0 commit comments

Comments
 (0)