Skip to content

Instantly share code, notes, and snippets.

@thebino
Last active June 25, 2022 05:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thebino/4c95e46cca53ec1d9baa29953f0ccb20 to your computer and use it in GitHub Desktop.
Save thebino/4c95e46cca53ec1d9baa29953f0ccb20 to your computer and use it in GitHub Desktop.
[Jetpack Compose] Using a swipeableState within a CoordinatorLayout will not detect any swipe gestures
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="48dp"
android:fitsSystemWindows="true"
android:theme="@style/AppTheme.AppBarOverlay">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:minHeight="60dp"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:toolbarId="@+id/toolbar">
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:scaleType="centerCrop"
android:src="@drawable/ic_launcher_foreground"
app:layout_collapseMode="parallax" />
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.compose.ui.platform.ComposeView
android:id="@+id/draggable_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="8dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="8dp" />
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
package com.example.compose
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.FractionalThreshold
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Send
import androidx.compose.material.rememberSwipeableState
import androidx.compose.material.swipeable
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import com.example.compose.databinding.MainActivityBinding
import kotlin.math.roundToInt
class MainActivity : ComponentActivity() {
private lateinit var binding: MainActivityBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = MainActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.draggableLayout.apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
SwipableContent()
}
}
}
}
@Composable
fun SwipableContent(
modifier: Modifier = Modifier,
onSwipeStateUpdated: (state: SwipeState) -> Unit = {}
) {
Box(
modifier = modifier
.border(border = BorderStroke(1.dp, Color.Green))
.height(300.dp),
contentAlignment = Alignment.Center
) {
val slideWidth: Dp = 80.dp
val iconSize = slideWidth - 10.dp
val swipeableState = rememberSwipeableState(
initialValue = SwipeState.START,
confirmStateChange = { newValue: SwipeState ->
onSwipeStateUpdated(newValue)
true
}
)
val slideDistance = with(LocalDensity.current) {
(slideWidth - iconSize - 55.dp).toPx()
}
Box(
modifier = Modifier
.border(border = BorderStroke(1.dp, Color.Magenta))
.offset {
IntOffset(0, swipeableState.offset.value.roundToInt())
}
.swipeable(
state = swipeableState,
anchors = mapOf(
0f to SwipeState.START,
slideDistance to SwipeState.END
),
thresholds = { _, _ ->
FractionalThreshold(0.9f)
},
orientation = Orientation.Vertical
)
.size(80.dp)
.clip(CircleShape)
.background(Color(0xFFFF0000)),
contentAlignment = Alignment.Center
) {
Icon(
imageVector = Icons.Default.Send,
contentDescription = "Icon",
tint = Color.White
)
}
}
}
@Preview
@Composable
fun SwipableContentPreview() {
MaterialTheme {
SwipableContent(modifier = Modifier.padding(16.dp))
}
}
enum class SwipeState {
START,
END
}
package com.example.compose
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.test.swipe
import com.example.compose.ui.theme.AppTheme
import com.google.common.truth.Truth
import org.junit.Rule
import org.junit.Test
class SwipeableContentStandaloneTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun testVerticalSwipe() {
// given
var swiState = SwipeState.START
// when
composeTestRule.setContent {
AppTheme {
SwipableContent(
onSwipeStateUpdated = {
swiState = it
}
)
}
}
// then
composeTestRule.onNodeWithContentDescription(label = "Icon").assertIsDisplayed()
Truth.assertThat(swiState).isEqualTo(SwipeState.START)
composeTestRule.onNodeWithContentDescription(label = "Icon").performTouchInput {
swipe(
start = center,
end = Offset(center.x, center.y - 500),
durationMillis = 3000
)
}
Truth.assertThat(swiState).isEqualTo(SwipeState.END)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment