Skip to content

Commit 0668a13

Browse files
authored
Merge pull request #8 from android/kim/animated-zoom-buttons
adds easing animations to the zoom button controls
2 parents b7f0c6a + cf00581 commit 0668a13

File tree

8 files changed

+131
-55
lines changed

8 files changed

+131
-55
lines changed

feature/camera/src/androidTest/java/com/android/developers/androidify/camera/CameraScreenTest.kt

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ class CameraScreenTest {
5858
requestFlipCamera = {},
5959
defaultZoomOptions = listOf(1f),
6060
zoomLevel = { 1f },
61-
onChangeZoomLevel = {},
61+
onAnimateZoom = {},
6262
requestCaptureImage = {},
6363
)
6464
}
@@ -79,7 +79,7 @@ class CameraScreenTest {
7979
requestFlipCamera = {},
8080
defaultZoomOptions = listOf(1f),
8181
zoomLevel = { 1f },
82-
onChangeZoomLevel = {},
82+
onAnimateZoom = {},
8383
requestCaptureImage = {},
8484
)
8585
}
@@ -102,7 +102,7 @@ class CameraScreenTest {
102102
requestFlipCamera = {},
103103
defaultZoomOptions = listOf(1f),
104104
zoomLevel = { 1f },
105-
onChangeZoomLevel = {},
105+
onAnimateZoom = {},
106106
)
107107
}
108108

@@ -124,7 +124,7 @@ class CameraScreenTest {
124124
requestFlipCamera = {},
125125
defaultZoomOptions = listOf(1f),
126126
zoomLevel = { 1f },
127-
onChangeZoomLevel = {},
127+
onAnimateZoom = {},
128128
requestCaptureImage = {},
129129
)
130130
}
@@ -145,7 +145,7 @@ class CameraScreenTest {
145145
requestFlipCamera = {},
146146
defaultZoomOptions = listOf(1f),
147147
zoomLevel = { 1f },
148-
onChangeZoomLevel = {},
148+
onAnimateZoom = {},
149149
requestCaptureImage = {},
150150
)
151151
}
@@ -168,7 +168,7 @@ class CameraScreenTest {
168168
detectedPose = true,
169169
defaultZoomOptions = listOf(1f),
170170
zoomLevel = { 1f },
171-
onChangeZoomLevel = {},
171+
onAnimateZoom = {},
172172
requestCaptureImage = {},
173173
)
174174
}
@@ -193,7 +193,7 @@ class CameraScreenTest {
193193
canFlipCamera = true,
194194
requestFlipCamera = {},
195195
detectedPose = true,
196-
onChangeZoomLevel = {},
196+
onAnimateZoom = {},
197197
requestCaptureImage = {},
198198
)
199199
}
@@ -215,7 +215,7 @@ class CameraScreenTest {
215215
canFlipCamera = true,
216216
requestFlipCamera = {},
217217
detectedPose = true,
218-
onChangeZoomLevel = {},
218+
onAnimateZoom = {},
219219
requestCaptureImage = {},
220220
)
221221
}
@@ -235,7 +235,7 @@ class CameraScreenTest {
235235
viewfinder = dummyViewfinder,
236236
defaultZoomOptions = zoomOptions,
237237
zoomLevel = { 1.0f }, // Start at second option to ensure first button click changes it
238-
onChangeZoomLevel = { changedZoomLevel = it }, // Callback to test
238+
onAnimateZoom = { changedZoomLevel = it }, // Callback to test
239239
// Default values for other params
240240
canFlipCamera = true,
241241
requestFlipCamera = {},
@@ -261,7 +261,7 @@ class CameraScreenTest {
261261
viewfinder = dummyViewfinder,
262262
defaultZoomOptions = zoomOptions,
263263
zoomLevel = { 0.6f }, // Start at first option
264-
onChangeZoomLevel = { changedZoomLevel = it }, // Callback to test
264+
onAnimateZoom = { changedZoomLevel = it }, // Callback to test
265265
// Default values for other params
266266
canFlipCamera = true,
267267
requestFlipCamera = {},
@@ -289,7 +289,7 @@ class CameraScreenTest {
289289
requestFlipCamera = {},
290290
defaultZoomOptions = listOf(1f),
291291
zoomLevel = { 1f },
292-
onChangeZoomLevel = {},
292+
onAnimateZoom = {},
293293
requestCaptureImage = {},
294294
)
295295
}
@@ -310,7 +310,7 @@ class CameraScreenTest {
310310
requestFlipCamera = {},
311311
defaultZoomOptions = listOf(1f),
312312
zoomLevel = { 1f },
313-
onChangeZoomLevel = {},
313+
onAnimateZoom = {},
314314
requestCaptureImage = {},
315315
)
316316
}

feature/camera/src/main/java/com/android/developers/androidify/camera/CameraControls.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ internal fun CameraControls(
3535
detectedPose: Boolean,
3636
defaultZoomOptions: List<Float>,
3737
zoomLevel: () -> Float,
38-
onChangeZoomLevel: (Float) -> Unit,
38+
onZoomLevelSelected: (Float) -> Unit,
3939
modifier: Modifier = Modifier,
4040
) {
4141
Column(
@@ -45,7 +45,7 @@ internal fun CameraControls(
4545
ZoomToolbar(
4646
defaultZoomOptions = defaultZoomOptions,
4747
zoomLevel = zoomLevel,
48-
onZoomLevelChanged = onChangeZoomLevel,
48+
onZoomLevelSelected = onZoomLevelSelected
4949
)
5050
Spacer(Modifier.height(12.dp))
5151
Row(verticalAlignment = Alignment.CenterVertically) {
@@ -74,8 +74,8 @@ private fun CameraControlsPreview() {
7474
canFlipCamera = true,
7575
flipCameraDirectionClicked = { },
7676
detectedPose = true,
77-
zoomLevel = { 0.4f },
78-
onChangeZoomLevel = { },
77+
zoomLevel = {0.4f},
78+
onZoomLevelSelected = {},
7979
defaultZoomOptions = listOf(.6f, 1f),
8080
)
8181
}

feature/camera/src/main/java/com/android/developers/androidify/camera/CameraScreen.kt

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ fun CameraPreviewScreen(
130130

131131
uiState.surfaceRequest?.let { surface ->
132132
CameraPreviewContent(
133+
modifier = Modifier.fillMaxSize(),
133134
surfaceRequest = surface,
134135
autofocusUiState = uiState.autofocusUiState,
135136
tapToFocus = viewModel::tapToFocus,
@@ -138,13 +139,14 @@ fun CameraPreviewScreen(
138139
requestFlipCamera = viewModel::flipCameraDirection,
139140
canFlipCamera = uiState.canFlipCamera,
140141
requestCaptureImage = viewModel::captureImage,
142+
zoomRange = uiState.zoomMinRatio..uiState.zoomMaxRatio,
141143
zoomLevel = { uiState.zoomLevel },
142144
onChangeZoomLevel = viewModel::setZoomLevel,
143145
foldingFeature = foldingFeature,
144-
modifier = Modifier.fillMaxSize(),
145146
shouldShowRearCameraFeature = viewModel::shouldShowRearDisplayFeature,
146147
toggleRearCameraFeature = { viewModel.toggleRearDisplayFeature(activity) },
147148
isRearCameraEnabled = uiState.isRearCameraActive,
149+
cameraSessionId = uiState.cameraSessionId
148150
)
149151
}
150152
} else {
@@ -198,7 +200,7 @@ fun StatelessCameraPreviewContent(
198200
detectedPose: Boolean,
199201
defaultZoomOptions: List<Float>,
200202
zoomLevel: () -> Float,
201-
onChangeZoomLevel: (Float) -> Unit,
203+
onAnimateZoom: (Float) -> Unit,
202204
requestCaptureImage: () -> Unit,
203205
modifier: Modifier = Modifier,
204206
foldingFeature: FoldingFeature? = null,
@@ -238,9 +240,9 @@ fun StatelessCameraPreviewContent(
238240
zoomButton = { zoomModifier ->
239241
ZoomToolbar(
240242
defaultZoomOptions = defaultZoomOptions,
241-
zoomLevel = zoomLevel,
242-
onZoomLevelChanged = onChangeZoomLevel,
243+
onZoomLevelSelected = onAnimateZoom,
243244
modifier = zoomModifier,
245+
zoomLevel = zoomLevel,
244246
)
245247
},
246248
guideText = { guideTextModifier ->
@@ -287,19 +289,29 @@ private fun CameraPreviewContent(
287289
surfaceRequest: SurfaceRequest,
288290
autofocusUiState: AutofocusUiState,
289291
tapToFocus: (Offset) -> Unit,
292+
cameraSessionId: Int,
290293
canFlipCamera: Boolean,
291294
requestFlipCamera: () -> Unit,
292295
detectedPose: Boolean,
293296
defaultZoomOptions: List<Float>,
297+
zoomRange: ClosedFloatingPointRange<Float>,
294298
zoomLevel: () -> Float,
295-
onChangeZoomLevel: (Float) -> Unit,
299+
onChangeZoomLevel: (zoomLevel: Float) -> Unit,
296300
requestCaptureImage: () -> Unit,
297301
modifier: Modifier = Modifier,
298302
foldingFeature: FoldingFeature? = null,
299303
shouldShowRearCameraFeature: () -> Boolean = { false },
300304
toggleRearCameraFeature: () -> Unit = {},
301305
isRearCameraEnabled: Boolean = false,
302306
) {
307+
val scope = rememberCoroutineScope()
308+
val zoomState = remember(cameraSessionId) {
309+
ZoomState(
310+
initialZoomLevel = zoomLevel(),
311+
onChangeZoomLevel = onChangeZoomLevel,
312+
zoomRange = zoomRange,
313+
)
314+
}
303315
// Delegate the layout to the stateless version
304316
StatelessCameraPreviewContent(
305317
viewfinder = { viewfinderModifier ->
@@ -309,8 +321,7 @@ private fun CameraPreviewContent(
309321
surfaceRequest = surfaceRequest,
310322
autofocusUiState = autofocusUiState,
311323
tapToFocus = tapToFocus,
312-
zoomLevel = zoomLevel,
313-
onChangeZoomLevel = onChangeZoomLevel,
324+
onScaleZoom = { scope.launch { zoomState.scaleZoom(it) }},
314325
modifier = viewfinderModifier.onSizeChanged { size -> // Apply modifier from slot
315326
if (size.height > 0) {
316327
aspectRatio = calculateCorrectAspectRatio(size.height, size.width, aspectRatio)
@@ -322,9 +333,9 @@ private fun CameraPreviewContent(
322333
canFlipCamera = canFlipCamera,
323334
requestFlipCamera = requestFlipCamera,
324335
detectedPose = detectedPose,
325-
defaultZoomOptions = defaultZoomOptions,
326336
zoomLevel = zoomLevel,
327-
onChangeZoomLevel = onChangeZoomLevel,
337+
defaultZoomOptions = defaultZoomOptions,
338+
onAnimateZoom = { scope.launch { zoomState.animatedZoom(it) } },
328339
requestCaptureImage = requestCaptureImage,
329340
foldingFeature = foldingFeature,
330341
shouldShowRearCameraFeature = shouldShowRearCameraFeature,

feature/camera/src/main/java/com/android/developers/androidify/camera/CameraViewModel.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ class CameraViewModel
148148
processCameraProvider.runWith(cameraType, cameraUseCaseGroup) { camera ->
149149
cameraControl = camera.cameraControl
150150
cameraInfo = camera.cameraInfo
151-
151+
_uiState.update { it.copy(cameraSessionId = it.cameraSessionId + 1) }
152152
// Suspend on zoom updates
153153
camera.cameraInfo.zoomState.asFlow().collectLatest { zoomState ->
154154
_uiState.update {
@@ -342,6 +342,7 @@ class CameraViewModel
342342
*/
343343
data class CameraUiState(
344344
val surfaceRequest: SurfaceRequest? = null,
345+
val cameraSessionId: Int = 0,
345346
val imageUri: Uri? = null,
346347
val detectedPose: Boolean = false,
347348
val zoomMaxRatio: Float = 1f,

feature/camera/src/main/java/com/android/developers/androidify/camera/CameraViewfinder.kt

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,16 +47,14 @@ private val TAP_TO_FOCUS_INDICATOR_SIZE = 48.dp
4747

4848
@Composable
4949
internal fun CameraViewfinder(
50-
surfaceRequest: SurfaceRequest,
51-
autofocusUiState: AutofocusUiState,
52-
tapToFocus: (tapCoords: Offset) -> Unit,
53-
zoomLevel: () -> Float,
54-
onChangeZoomLevel: (zoomLevel: Float) -> Unit,
55-
modifier: Modifier = Modifier,
50+
surfaceRequest: SurfaceRequest,
51+
autofocusUiState: AutofocusUiState,
52+
tapToFocus: (tapCoords: Offset) -> Unit,
53+
onScaleZoom: (zoomScaleFactor: Float) -> Unit,
54+
modifier: Modifier = Modifier,
5655
) {
56+
val onScaleCurrentZoom by rememberUpdatedState(onScaleZoom)
5757
val currentTapToFocus by rememberUpdatedState(tapToFocus)
58-
val currentOnChangeZoomLevel by rememberUpdatedState(onChangeZoomLevel)
59-
6058
val coordinateTransformer = remember { MutableCoordinateTransformer() }
6159
CameraXViewfinder(
6260
surfaceRequest = surfaceRequest,
@@ -72,7 +70,7 @@ internal fun CameraViewfinder(
7270
.transformable(
7371
rememberTransformableState(
7472
onTransformation = { zoomChange, _, _ ->
75-
currentOnChangeZoomLevel(zoomChange * zoomLevel())
73+
onScaleCurrentZoom(zoomChange)
7674
},
7775
),
7876
),

feature/camera/src/main/java/com/android/developers/androidify/camera/CameraZoomToolbar.kt

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ import kotlin.math.roundToInt
4646
internal fun ZoomToolbar(
4747
defaultZoomOptions: List<Float>,
4848
zoomLevel: () -> Float,
49-
onZoomLevelChanged: (Float) -> Unit,
49+
onZoomLevelSelected: (Float) -> Unit,
5050
modifier: Modifier = Modifier,
5151
) {
5252
// Only render the zoom toolbar when there's exactly two options
@@ -87,7 +87,7 @@ internal fun ZoomToolbar(
8787
) {
8888
ToggleButton(
8989
checked = selectedOptionIndex == 0,
90-
onCheckedChange = { onZoomLevelChanged(defaultZoomOptions[0]) },
90+
onCheckedChange = { onZoomLevelSelected(defaultZoomOptions[0]) },
9191
shapes = ButtonGroupDefaults.connectedLeadingButtonShapes(),
9292
colors = ToggleButtonDefaults.toggleButtonColors(),
9393
modifier = Modifier,
@@ -98,7 +98,7 @@ internal fun ZoomToolbar(
9898
}
9999
ToggleButton(
100100
checked = selectedOptionIndex == 1,
101-
onCheckedChange = { onZoomLevelChanged(defaultZoomOptions[1]) },
101+
onCheckedChange = { onZoomLevelSelected(defaultZoomOptions[1]) },
102102
shapes = ButtonGroupDefaults.connectedTrailingButtonShapes(),
103103
colors = ToggleButtonDefaults.toggleButtonColors(),
104104
modifier = Modifier,
@@ -120,24 +120,18 @@ private fun ZoomToolbarPreview() {
120120
ZoomToolbar(
121121
defaultZoomOptions = listOf(0.6f, 1f),
122122
zoomLevel = { zoomLevel },
123-
onZoomLevelChanged = {
124-
zoomLevel = it
125-
},
123+
onZoomLevelSelected = { zoomLevel = it },
126124
)
127125
ZoomToolbar(
128126
defaultZoomOptions = listOf(1f, 2f),
129127
zoomLevel = { zoomLevel },
130-
onZoomLevelChanged = {
131-
zoomLevel = it
132-
},
128+
onZoomLevelSelected = { zoomLevel = it },
133129
)
134130
// Doesn't render
135131
ZoomToolbar(
136132
defaultZoomOptions = listOf(1f),
137133
zoomLevel = { zoomLevel },
138-
onZoomLevelChanged = {
139-
zoomLevel = it
140-
},
134+
onZoomLevelSelected = { zoomLevel = it },
141135
)
142136
Row {
143137
Button(onClick = { zoomLevel -= 0.1f }) {

0 commit comments

Comments
 (0)