Add transition when map tile range changes

This commit is contained in:
MM20 2024-06-16 00:27:57 +02:00
parent 964687ada0
commit 4065b75bdc
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389

View File

@ -4,9 +4,17 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.core.animateOffsetAsState import androidx.compose.animation.core.animateOffsetAsState
import androidx.compose.animation.core.animateValueAsState import androidx.compose.animation.core.animateValueAsState
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.animation.slideIn
import androidx.compose.animation.slideOut
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border import androidx.compose.foundation.border
@ -102,11 +110,10 @@ fun MapTiles(
val userLocation = userLocation() val userLocation = userLocation()
val (start, stop, zoom) = remember(location, userLocation) { val tileRange = remember(location, userLocation) {
getTileRange(location, userLocation, tiles, maxZoomLevel) getTileRange(location, userLocation, tiles, maxZoomLevel)
} }
val sideLength = stop.x - start.x + 1
val colorMatrix = remember(applyTheming, darkMode, tintColor) { val colorMatrix = remember(applyTheming, darkMode, tintColor) {
// darkreader css for openstreetmap tiles // darkreader css for openstreetmap tiles
@ -128,78 +135,123 @@ fun MapTiles(
modifier = modifier, modifier = modifier,
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
) { ) {
Column(modifier = Modifier.fillMaxWidth()) { AnimatedContent(
for (y in start.y..stop.y) { tileRange,
Row( transitionSpec = {
modifier = Modifier if (targetState.zoomLevel == initialState.zoomLevel) {
.fillMaxWidth() val initialCenterX = (initialState.stop.x + initialState.start.x) / 2f
) { val targetCenterX = (targetState.stop.x + targetState.start.x) / 2f
for (x in start.x..stop.x) { val initialCenterY = (initialState.stop.y + initialState.start.y) / 2f
AsyncImage( val targetCenterY = (targetState.stop.y + targetState.start.y) / 2f
modifier = Modifier val initialDeltaX = targetCenterX - initialCenterX
.weight(1f / sideLength) val targetDeltaX = targetCenterX - initialCenterX
.aspectRatio(1f) val initialDeltaY = targetCenterY - initialCenterY
.background(MaterialTheme.colorScheme.secondaryContainer), val targetDeltaY = targetCenterY - initialCenterY
imageLoader = MapTileLoader.loader,
model = MapTileLoader.getTileRequest(tileServerUrl, x, y, zoom), return@AnimatedContent slideIn {
contentDescription = null, IntOffset(
colorFilter = colorMatrix?.let { ColorFilter.colorMatrix(it) }, (targetDeltaX * (it.width / tiles.width)).toInt(),
filterQuality = FilterQuality.High, (targetDeltaY * (it.height / tiles.height)).toInt()
)
} togetherWith slideOut {
IntOffset(
-(initialDeltaX * (it.width / tiles.width)).toInt(),
-(initialDeltaY * (it.height / tiles.height)).toInt()
) )
} }
} }
val scale = 2f.pow(targetState.zoomLevel - initialState.zoomLevel)
fadeIn() + scaleIn(initialScale = 1f / scale) togetherWith
fadeOut() + scaleOut(targetScale = scale)
}
) { (start, stop, zoom) ->
val sideLength = stop.x - start.x + 1
Column(modifier = Modifier.fillMaxWidth()) {
for (y in start.y..stop.y) {
Row(
modifier = Modifier
.fillMaxWidth()
) {
for (x in start.x..stop.x) {
AsyncImage(
modifier = Modifier
.weight(1f / sideLength)
.aspectRatio(1f)
.background(MaterialTheme.colorScheme.secondaryContainer),
imageLoader = MapTileLoader.loader,
model = MapTileLoader.getTileRequest(tileServerUrl, x, y, zoom),
contentDescription = null,
colorFilter = colorMatrix?.let { ColorFilter.colorMatrix(it) },
filterQuality = FilterQuality.High,
)
}
}
}
} }
}
val tileSize = minWidth / tiles.width.toFloat() val tileSize = minWidth / tiles.width.toFloat()
val locationTileCoordinates = val locationTileCoordinates =
getTileCoordinates(location.latitude, location.longitude, zoom) getTileCoordinates(location.latitude, location.longitude, zoom)
Box( Box(
modifier = Modifier modifier = Modifier
.align(Alignment.TopStart) .align(Alignment.TopStart)
.width(12.dp) .width(12.dp)
.height(4.dp) .height(4.dp)
.absoluteOffset( .absoluteOffset(
x = (locationTileCoordinates.x - start.x) * tileSize - 6.dp, x = (locationTileCoordinates.x - start.x) * tileSize - 6.dp,
y = (locationTileCoordinates.y - start.y) * tileSize - 2.dp, y = (locationTileCoordinates.y - start.y) * tileSize - 2.dp,
)
.shadow(1.dp, CircleShape)
)
Icon(
imageVector = Icons.Rounded.Place,
contentDescription = null,
tint = MaterialTheme.colorScheme.tertiary,
modifier = Modifier
.align(Alignment.TopStart)
.size(24.dp)
.absoluteOffset(
x = (locationTileCoordinates.x - start.x) * tileSize - 12.dp,
y = (locationTileCoordinates.y - start.y) * tileSize - 22.dp,
)
)
if (userLocation != null) {
val userIndicatorOffset by animateOffsetAsState(
targetValue = getTileCoordinates(userLocation.lat, userLocation.lon, zoom).let {
Offset(
(it.x - start.x) * tileSize.value - 8f,
(it.y - start.y) * tileSize.value - 8f
) )
}, .shadow(1.dp, CircleShape)
animationSpec = tween()
) )
if (userLocation.heading != null) { Icon(
val headingAnim by animateValueAsState( imageVector = Icons.Rounded.Place,
targetValue = userLocation.heading, contentDescription = null,
typeConverter = Float.DegreesConverter tint = MaterialTheme.colorScheme.tertiary,
modifier = Modifier
.align(Alignment.TopStart)
.size(24.dp)
.absoluteOffset(
x = (locationTileCoordinates.x - start.x) * tileSize - 12.dp,
y = (locationTileCoordinates.y - start.y) * tileSize - 22.dp,
)
)
if (userLocation != null) {
val userIndicatorOffset by animateOffsetAsState(
targetValue = getTileCoordinates(userLocation.lat, userLocation.lon, zoom).let {
Offset(
(it.x - start.x) * tileSize.value - 8f,
(it.y - start.y) * tileSize.value - 8f
)
},
animationSpec = tween()
) )
Icon( if (userLocation.heading != null) {
imageVector = Icons.Rounded.Navigation, val headingAnim by animateValueAsState(
contentDescription = null, targetValue = userLocation.heading,
tint = MaterialTheme.colorScheme.tertiary, typeConverter = Float.DegreesConverter
)
Icon(
imageVector = Icons.Rounded.Navigation,
contentDescription = null,
tint = MaterialTheme.colorScheme.tertiary,
modifier = Modifier
.align(Alignment.TopStart)
.size(16.dp)
.absoluteOffset(
userIndicatorOffset.x.dp,
userIndicatorOffset.y.dp
)
.rotate(headingAnim)
.absoluteOffset(y = -8.dp)
)
}
Box(
modifier = Modifier modifier = Modifier
.align(Alignment.TopStart) .align(Alignment.TopStart)
.size(16.dp) .size(16.dp)
@ -207,34 +259,22 @@ fun MapTiles(
userIndicatorOffset.x.dp, userIndicatorOffset.x.dp,
userIndicatorOffset.y.dp userIndicatorOffset.y.dp
) )
.rotate(headingAnim) .background(MaterialTheme.colorScheme.tertiary, CircleShape)
.absoluteOffset(y = -8.dp) .border(2.dp, MaterialTheme.colorScheme.onTertiary, CircleShape)
.shadow(1.dp, CircleShape)
) )
}
Box( if (osmAttribution != null) {
modifier = Modifier Text(
.align(Alignment.TopStart) text = osmAttribution,
.size(16.dp) style = MaterialTheme.typography.labelSmall,
.absoluteOffset( color = MaterialTheme.colorScheme.onSurface,
userIndicatorOffset.x.dp, modifier = Modifier
userIndicatorOffset.y.dp .align(Alignment.BottomEnd)
.background(MaterialTheme.colorScheme.surfaceContainerHigh.copy(alpha = .5f))
.padding(top = 2.dp, bottom = 2.dp, start = 4.dp, end = 4.dp)
) )
.background(MaterialTheme.colorScheme.tertiary, CircleShape) }
.border(2.dp, MaterialTheme.colorScheme.onTertiary, CircleShape)
.shadow(1.dp, CircleShape)
)
if (osmAttribution != null) {
Text(
text = osmAttribution,
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurface,
modifier = Modifier
.align(Alignment.BottomEnd)
.background(MaterialTheme.colorScheme.surfaceContainerHigh.copy(alpha = .5f))
.padding(top = 2.dp, bottom = 2.dp, start = 4.dp, end = 4.dp)
)
} }
} }
} }