The Rotary Input is one more way to the user interact with their wearables devices. You must handle this type of input since your users will expect this even unconsciously.
Currently the users can perform the Rotary Input on smartwatches in 3 ways: crown, physical bezel, touch bezel. All of these option are transparent for us when handling the input, that is, for a simple handle of this input we don't need to know which one the user is using to interact with our app.
So, let's see some examples of how to handle this with ScalingLazyColumn
and Picker
.
💡 The following examples are using the version 1.1.0-rc01 of Composer for Wear OS
ScalingLazyColumn
First of all, in order the handle the Rotary input, the component must have the focus. We can achieve this by manually requesting the focus to a component. So, let's create a instance of the FocusRequester:
val focusRequester = remember { FocusRequester() }
And then, set up this in our ScalingLazyColumn
:
ScalingLazyColumn(
modifier = Modifier
.focusRequester(focusRequester)
.focusable(),
{...}
){
{...}
}
Next, we need to force the request of the focus.
LaunchedEffect(Unit){
focusRequester.requestFocus()
}
Now our ScalingLazyColumn
will always have the focus. The handle is not done yet, now we need to actually react to the Rotary Input and scroll our list with it.
First we need a instance of the ScalingLazyListState
and use the modifier onRotaryScrollEvent
to listen to the Rotary Inputs. Be aware that we need to perform this scroll with a Coroutine Scope.
val focusRequester = remember **{** FocusRequester() **}**
val scrollState = rememberScalingLazyListState()
val coroutineScope = rememberCoroutineScope()
ScalingLazyColumn(
modifier = Modifier
.onRotaryScrollEvent {
coroutineScope.launch {
scrollState.scrollBy(it.verticalScrollPixels)
}
true // it means that we are handling the event with this callback
}
.focusRequester(focusRequester)
.focusable(),
state = scrollState,
{...}
){
{...}
}
LaunchedEffect(Unit){
focusRequester.requestFocus()
}
That's all. With a small configuration the ScalingLazyColumn
is reacting to the Rotary Input.
Picker
For the Picker
component the setup will be basically the same:
val pickerState = rememberPickerState(
initialNumberOfOptions = 100,
initiallySelectedOption = 50
)
val focusRequester = remember { FocusRequester() }
val coroutineScope = rememberCoroutineScope()
Picker(
state = pickerState,
contentDescription = pickerState.selectedOption.toString(),
modifier = Modifier
.onRotaryScrollEvent{
coroutineScope.launch{
pickerState.scrollBy(it.verticalScrollPixels)
}
true
}
.focusRequester(focusRequester)
.focusable()
{...}
) {
{...}
}
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
Conclusion
You notice some things:
- There is no haptic feedback
- If you test other apps that handle this input you will notice a haptic feedback during the scrolling, sadly this is not automatic, you need to do this manually or use the Horologist.
- The Picker is not snapping to the selected item
- With this approach sometimes the Picker doesn't snaps to the selected item, you can improve this with Horologist, but by my test it isn't working 100%.
The Rotary Input callbacks from the foundation library and Horologist are still in Experimental state, so soon they can change and be improved.
References
Code
You can check these and more example on my repository
Vnicius / rotary-input-samples
Sample codes showing the usage of Rotary input on Wear OS with Compose ⌚︎
Rotary Input Samples
Sample codes showing the usage of Rotary input on Wear OS with Compose ⌚︎
Samples
-
Basic Example - This sample contains the basic handle of Rotart input with
ScalingLazyColumn
andPicker
.
Top comments (1)
How to perform the rotary input inside an android ui test?