Skip to content

Commit d13fc41

Browse files
committed
CameraScreen: animate captured image
1 parent 5ba6340 commit d13fc41

File tree

1 file changed

+74
-28
lines changed

1 file changed

+74
-28
lines changed

app/src/main/java/org/mydomain/myscan/view/CameraScreen.kt

Lines changed: 74 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ import android.graphics.BitmapFactory
1919
import android.util.Log
2020
import androidx.camera.core.ImageProxy
2121
import androidx.camera.view.PreviewView
22+
import androidx.compose.animation.core.animateDp
23+
import androidx.compose.animation.core.animateFloat
24+
import androidx.compose.animation.core.tween
25+
import androidx.compose.animation.core.updateTransition
2226
import androidx.compose.foundation.Image
2327
import androidx.compose.foundation.LocalIndication
2428
import androidx.compose.foundation.background
@@ -32,6 +36,7 @@ import androidx.compose.foundation.layout.Row
3236
import androidx.compose.foundation.layout.fillMaxSize
3337
import androidx.compose.foundation.layout.fillMaxWidth
3438
import androidx.compose.foundation.layout.height
39+
import androidx.compose.foundation.layout.offset
3540
import androidx.compose.foundation.layout.padding
3641
import androidx.compose.foundation.layout.size
3742
import androidx.compose.foundation.layout.width
@@ -53,6 +58,7 @@ import androidx.compose.runtime.remember
5358
import androidx.compose.runtime.setValue
5459
import androidx.compose.ui.Alignment
5560
import androidx.compose.ui.Modifier
61+
import androidx.compose.ui.draw.scale
5662
import androidx.compose.ui.graphics.Color
5763
import androidx.compose.ui.graphics.asImageBitmap
5864
import androidx.compose.ui.platform.LocalConfiguration
@@ -73,6 +79,9 @@ data class CameraUiState(
7379
val captureState: CaptureState
7480
)
7581

82+
const val CAPTURED_IMAGE_DISPLAY_DURATION = 1500L
83+
const val ANIMATION_DURATION = 200
84+
7685
@Composable
7786
fun CameraScreen(
7887
viewModel: MainViewModel,
@@ -91,7 +100,7 @@ fun CameraScreen(
91100
val captureState by viewModel.captureState.collectAsStateWithLifecycle()
92101
if (captureState.isProcessed()) {
93102
LaunchedEffect(captureState) {
94-
delay(1500)
103+
delay(CAPTURED_IMAGE_DISPLAY_DURATION)
95104
viewModel.addProcessedImage()
96105
}
97106
}
@@ -138,37 +147,74 @@ private fun CameraScreenScaffold(
138147
onCapture: () -> Unit,
139148
onFinalizePressed: () -> Unit,
140149
) {
141-
Scaffold(
142-
bottomBar = {
143-
CameraScreenFooter(
144-
pageList = pageList,
145-
pageCount = cameraUiState.pageCount,
146-
onFinalizePressed = onFinalizePressed,
147-
)
148-
}
149-
) { innerPadding ->
150-
Box(modifier = Modifier.padding(bottom = innerPadding.calculateBottomPadding()).fillMaxSize()) {
151-
CameraPreviewWithOverlay(cameraPreview, cameraUiState)
152-
MessageBox(cameraUiState.liveAnalysisState.inferenceTime)
153-
CaptureButton(
154-
onClick = onCapture,
155-
modifier = Modifier
156-
.align(Alignment.BottomCenter)
157-
.padding(16.dp)
158-
)
159-
cameraUiState.captureState.processedImage?.let {
160-
Surface(
161-
color = Color.Black.copy(alpha = 0.3f),
162-
modifier = Modifier.fillMaxSize()
150+
Box {
151+
Scaffold(
152+
bottomBar = {
153+
CameraScreenFooter(
154+
pageList = pageList,
155+
pageCount = cameraUiState.pageCount,
156+
onFinalizePressed = onFinalizePressed,
163157
)
164-
{}
165-
Image(
166-
bitmap = it.asImageBitmap(),
167-
contentDescription = null,
168-
modifier = Modifier.fillMaxSize().padding(24.dp)
158+
}
159+
) { innerPadding ->
160+
Box(
161+
modifier = Modifier
162+
.padding(bottom = innerPadding.calculateBottomPadding())
163+
.fillMaxSize()
164+
) {
165+
CameraPreviewWithOverlay(cameraPreview, cameraUiState)
166+
MessageBox(cameraUiState.liveAnalysisState.inferenceTime)
167+
CaptureButton(
168+
onClick = onCapture,
169+
modifier = Modifier
170+
.align(Alignment.BottomCenter)
171+
.padding(16.dp)
169172
)
170173
}
171174
}
175+
CapturedImage(cameraUiState)
176+
}
177+
}
178+
179+
@Composable
180+
private fun CapturedImage(cameraUiState: CameraUiState) {
181+
cameraUiState.captureState.processedImage?.let { image ->
182+
Surface(
183+
color = Color.Black.copy(alpha = 0.3f),
184+
modifier = Modifier.fillMaxSize(),
185+
) {}
186+
187+
var isAnimating by remember { mutableStateOf(false) }
188+
LaunchedEffect(image) {
189+
delay(CAPTURED_IMAGE_DISPLAY_DURATION - ANIMATION_DURATION)
190+
isAnimating = true
191+
}
192+
val transition = updateTransition(targetState = isAnimating, label = "captureAnimation")
193+
val targetOffsetX = 0.dp // TODO real value
194+
val targetOffsetY = 200.dp // TODO real value
195+
196+
val offsetX by transition.animateDp(
197+
transitionSpec = { tween(durationMillis = ANIMATION_DURATION) },
198+
label = "offsetX"
199+
) { if (it) targetOffsetX else 0.dp }
200+
val offsetY by transition.animateDp(
201+
transitionSpec = { tween(durationMillis = ANIMATION_DURATION) },
202+
label = "offsetY"
203+
) { if (it) targetOffsetY else 0.dp }
204+
val scale by transition.animateFloat(
205+
transitionSpec = { tween(durationMillis = ANIMATION_DURATION) },
206+
label = "scale"
207+
) { if (it) 0.3f else 1f }
208+
209+
Image(
210+
bitmap = image.asImageBitmap(),
211+
contentDescription = null,
212+
modifier = Modifier
213+
.fillMaxSize()
214+
.padding(24.dp)
215+
.offset(x = offsetX, y = offsetY - 100.dp)
216+
.scale(scale)
217+
)
172218
}
173219
}
174220

0 commit comments

Comments
 (0)