Merge pull request #104 from yschimke/simpler_scroll
Simpler implementation of Wear scrolling
This commit is contained in:
commit
93aac4c617
4 changed files with 38 additions and 69 deletions
|
@ -34,16 +34,11 @@ class MainActivity : ComponentActivity() {
|
|||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContent {
|
||||
val rotaryEventDispatcher = RotaryEventDispatcher()
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalImageLoader provides imageLoader,
|
||||
LocalRotaryEventDispatcher provides rotaryEventDispatcher,
|
||||
) {
|
||||
val navController = rememberSwipeDismissableNavController()
|
||||
|
||||
RotaryEventHandlerSetup(rotaryEventDispatcher)
|
||||
|
||||
SwipeDismissableNavHost(
|
||||
navController = navController,
|
||||
startDestination = Screen.PersonList.route
|
||||
|
|
|
@ -23,6 +23,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
@ -50,7 +51,6 @@ fun PersonDetailsScreen(personName: String) {
|
|||
}
|
||||
|
||||
val scrollState = rememberScrollState()
|
||||
RotaryEventState(scrollState)
|
||||
PersonDetailsScreen(person, scrollState)
|
||||
}
|
||||
|
||||
|
@ -60,6 +60,9 @@ private fun PersonDetailsScreen(
|
|||
person: Assignment?,
|
||||
scrollState: ScrollState = rememberScrollState(),
|
||||
) {
|
||||
// Activate scrolling
|
||||
LocalView.current.requestFocus()
|
||||
|
||||
MaterialTheme {
|
||||
Scaffold(
|
||||
vignette = {
|
||||
|
@ -71,6 +74,7 @@ private fun PersonDetailsScreen(
|
|||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.scrollHandler(scrollState)
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = if (LocalConfiguration.current.isScreenRound) 18.dp else 8.dp)
|
||||
.verticalScroll(scrollState),
|
||||
|
|
|
@ -16,6 +16,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
|
@ -23,8 +24,8 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalInspectionMode
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
|
@ -55,9 +56,7 @@ fun PersonListScreen(
|
|||
peopleInSpaceViewModel: PeopleInSpaceViewModel = getViewModel()
|
||||
) {
|
||||
val peopleState by peopleInSpaceViewModel.peopleInSpace.collectAsState()
|
||||
val scrollState = rememberScalingLazyListState()
|
||||
RotaryEventState(scrollState)
|
||||
PersonListScreen(peopleState, personSelected, issMapClick, scrollState)
|
||||
PersonListScreen(peopleState, personSelected, issMapClick)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
|
@ -66,7 +65,6 @@ fun PersonListScreen(
|
|||
people: List<Assignment>?,
|
||||
personSelected: (person: Assignment) -> Unit,
|
||||
issMapClick: () -> Unit,
|
||||
scrollState: ScalingLazyListState = rememberScalingLazyListState(),
|
||||
) {
|
||||
MaterialTheme {
|
||||
AnimatedVisibility(
|
||||
|
@ -75,7 +73,7 @@ fun PersonListScreen(
|
|||
) {
|
||||
if (people != null) {
|
||||
if (people.isNotEmpty()) {
|
||||
PersonList(people, personSelected, issMapClick, scrollState)
|
||||
PersonList(people, personSelected, issMapClick)
|
||||
} else {
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Card(
|
||||
|
@ -98,15 +96,22 @@ fun PersonList(
|
|||
issMapClick: () -> Unit,
|
||||
scrollState: ScalingLazyListState = rememberScalingLazyListState(),
|
||||
) {
|
||||
// Activate scrolling
|
||||
LocalView.current.requestFocus()
|
||||
|
||||
ScalingLazyColumn(
|
||||
modifier = Modifier
|
||||
.testTag(PersonListTag)
|
||||
.scrollHandler(scrollState),
|
||||
contentPadding = PaddingValues(8.dp),
|
||||
modifier = Modifier.testTag(PersonListTag),
|
||||
state = scrollState,
|
||||
) {
|
||||
item {
|
||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
|
||||
Button(
|
||||
modifier = Modifier.size(ButtonDefaults.SmallButtonSize).wrapContentSize(),
|
||||
modifier = Modifier
|
||||
.size(ButtonDefaults.SmallButtonSize)
|
||||
.wrapContentSize(),
|
||||
onClick = issMapClick
|
||||
) {
|
||||
// https://www.svgrepo.com/svg/170716/international-space-station
|
||||
|
|
|
@ -4,72 +4,37 @@ import android.view.MotionEvent
|
|||
import android.view.ViewConfiguration
|
||||
import androidx.compose.foundation.gestures.ScrollableState
|
||||
import androidx.compose.foundation.gestures.scrollBy
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.input.pointer.RequestDisallowInterceptTouchEvent
|
||||
import androidx.compose.ui.input.pointer.pointerInteropFilter
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.core.view.InputDeviceCompat
|
||||
import androidx.core.view.MotionEventCompat
|
||||
import androidx.core.view.ViewConfigurationCompat
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
val LocalRotaryEventDispatcher = staticCompositionLocalOf<RotaryEventDispatcher> {
|
||||
noLocalProvidedFor("LocalRotaryEventDispatcher")
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatcher to link rotary event to [ScrollableState].
|
||||
* The instance should be set up by calling [RotaryEventHandlerSetup] function.
|
||||
*/
|
||||
class RotaryEventDispatcher(
|
||||
var scrollState: ScrollableState? = null
|
||||
) {
|
||||
suspend fun onRotate(delta: Float): Float? =
|
||||
scrollState?.scrollBy(delta)
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom rotary event setup (Currently, Column / LazyColumn doesn't handle rotary event.)
|
||||
* Refer to https://developer.android.com/training/wearables/user-input/rotary-input
|
||||
*/
|
||||
@Composable
|
||||
fun RotaryEventHandlerSetup(rotaryEventDispatcher: RotaryEventDispatcher) {
|
||||
val view = LocalView.current
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
fun Modifier.scrollHandler(scrollState: ScrollableState): Modifier = composed {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val scaledVerticalScrollFactor =
|
||||
remember { ViewConfiguration.get(context).getScaledVerticalScrollFactor() }
|
||||
|
||||
view.requestFocus()
|
||||
view.setOnGenericMotionListener { _, event ->
|
||||
if (event?.action != MotionEvent.ACTION_SCROLL ||
|
||||
this.pointerInteropFilter(RequestDisallowInterceptTouchEvent()) { event ->
|
||||
if (event.action != MotionEvent.ACTION_SCROLL ||
|
||||
!event.isFromSource(InputDeviceCompat.SOURCE_ROTARY_ENCODER)
|
||||
) {
|
||||
return@setOnGenericMotionListener false
|
||||
false
|
||||
} else {
|
||||
val delta = -event.getAxisValue(MotionEventCompat.AXIS_SCROLL) *
|
||||
scaledVerticalScrollFactor
|
||||
scope.launch {
|
||||
scrollState.scrollBy(delta)
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
val delta = -event.getAxisValue(MotionEventCompat.AXIS_SCROLL) *
|
||||
ViewConfigurationCompat.getScaledVerticalScrollFactor(
|
||||
ViewConfiguration.get(context), context
|
||||
)
|
||||
scope.launch {
|
||||
rotaryEventDispatcher.onRotate(delta)
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a [ScrollableState] to [LocalRotaryEventDispatcher]
|
||||
*/
|
||||
@Composable
|
||||
fun RotaryEventState(scrollState: ScrollableState?) {
|
||||
val dispatcher = LocalRotaryEventDispatcher.current
|
||||
SideEffect {
|
||||
dispatcher.scrollState = scrollState
|
||||
}
|
||||
}
|
||||
|
||||
private fun noLocalProvidedFor(name: String): Nothing {
|
||||
error("CompositionLocal $name not present")
|
||||
}
|
Loading…
Reference in a new issue