Replace Drawer with DrawerLayout.
Given the fact that Stack of content + Drawer won't work, we need to
provide some Scaffold-like component that will take ownership for
gestures so both Drawer and AppContent will receive gestures.
So, (Modal/Drawer)DrawerLayout is introduced in this CL.
Bug: 136986760
Test: ./gradlew ui:ui-material:connectedcheck + two tests to ensure
content is clickable
Change-Id: I27d0416a9035d296f951cb4ec92ee0f177b1067b
diff --git a/ui/ui-material/api/1.0.0-alpha01.txt b/ui/ui-material/api/1.0.0-alpha01.txt
index f062c9e..e3fa077 100644
--- a/ui/ui-material/api/1.0.0-alpha01.txt
+++ b/ui/ui-material/api/1.0.0-alpha01.txt
@@ -94,8 +94,8 @@
public final class DrawerKt {
ctor public DrawerKt();
- method public static void BottomDrawer(androidx.ui.material.DrawerState drawerState, kotlin.jvm.functions.Function1<? super androidx.ui.material.DrawerState,kotlin.Unit> onStateChange, kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent);
- method public static void ModalDrawer(androidx.ui.material.DrawerState drawerState, kotlin.jvm.functions.Function1<? super androidx.ui.material.DrawerState,kotlin.Unit> onStateChange, kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent);
+ method public static void BottomDrawerLayout(androidx.ui.material.DrawerState drawerState, kotlin.jvm.functions.Function1<? super androidx.ui.material.DrawerState,kotlin.Unit> onStateChange, kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent, kotlin.jvm.functions.Function0<kotlin.Unit> bodyContent);
+ method public static void ModalDrawerLayout(androidx.ui.material.DrawerState drawerState, kotlin.jvm.functions.Function1<? super androidx.ui.material.DrawerState,kotlin.Unit> onStateChange, kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent, kotlin.jvm.functions.Function0<kotlin.Unit> bodyContent);
method public static void StaticDrawer(kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent);
}
diff --git a/ui/ui-material/api/current.txt b/ui/ui-material/api/current.txt
index f062c9e..e3fa077 100644
--- a/ui/ui-material/api/current.txt
+++ b/ui/ui-material/api/current.txt
@@ -94,8 +94,8 @@
public final class DrawerKt {
ctor public DrawerKt();
- method public static void BottomDrawer(androidx.ui.material.DrawerState drawerState, kotlin.jvm.functions.Function1<? super androidx.ui.material.DrawerState,kotlin.Unit> onStateChange, kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent);
- method public static void ModalDrawer(androidx.ui.material.DrawerState drawerState, kotlin.jvm.functions.Function1<? super androidx.ui.material.DrawerState,kotlin.Unit> onStateChange, kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent);
+ method public static void BottomDrawerLayout(androidx.ui.material.DrawerState drawerState, kotlin.jvm.functions.Function1<? super androidx.ui.material.DrawerState,kotlin.Unit> onStateChange, kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent, kotlin.jvm.functions.Function0<kotlin.Unit> bodyContent);
+ method public static void ModalDrawerLayout(androidx.ui.material.DrawerState drawerState, kotlin.jvm.functions.Function1<? super androidx.ui.material.DrawerState,kotlin.Unit> onStateChange, kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent, kotlin.jvm.functions.Function0<kotlin.Unit> bodyContent);
method public static void StaticDrawer(kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent);
}
diff --git a/ui/ui-material/api/restricted_1.0.0-alpha01.txt b/ui/ui-material/api/restricted_1.0.0-alpha01.txt
index f062c9e..e3fa077 100644
--- a/ui/ui-material/api/restricted_1.0.0-alpha01.txt
+++ b/ui/ui-material/api/restricted_1.0.0-alpha01.txt
@@ -94,8 +94,8 @@
public final class DrawerKt {
ctor public DrawerKt();
- method public static void BottomDrawer(androidx.ui.material.DrawerState drawerState, kotlin.jvm.functions.Function1<? super androidx.ui.material.DrawerState,kotlin.Unit> onStateChange, kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent);
- method public static void ModalDrawer(androidx.ui.material.DrawerState drawerState, kotlin.jvm.functions.Function1<? super androidx.ui.material.DrawerState,kotlin.Unit> onStateChange, kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent);
+ method public static void BottomDrawerLayout(androidx.ui.material.DrawerState drawerState, kotlin.jvm.functions.Function1<? super androidx.ui.material.DrawerState,kotlin.Unit> onStateChange, kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent, kotlin.jvm.functions.Function0<kotlin.Unit> bodyContent);
+ method public static void ModalDrawerLayout(androidx.ui.material.DrawerState drawerState, kotlin.jvm.functions.Function1<? super androidx.ui.material.DrawerState,kotlin.Unit> onStateChange, kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent, kotlin.jvm.functions.Function0<kotlin.Unit> bodyContent);
method public static void StaticDrawer(kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent);
}
diff --git a/ui/ui-material/api/restricted_current.txt b/ui/ui-material/api/restricted_current.txt
index f062c9e..e3fa077 100644
--- a/ui/ui-material/api/restricted_current.txt
+++ b/ui/ui-material/api/restricted_current.txt
@@ -94,8 +94,8 @@
public final class DrawerKt {
ctor public DrawerKt();
- method public static void BottomDrawer(androidx.ui.material.DrawerState drawerState, kotlin.jvm.functions.Function1<? super androidx.ui.material.DrawerState,kotlin.Unit> onStateChange, kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent);
- method public static void ModalDrawer(androidx.ui.material.DrawerState drawerState, kotlin.jvm.functions.Function1<? super androidx.ui.material.DrawerState,kotlin.Unit> onStateChange, kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent);
+ method public static void BottomDrawerLayout(androidx.ui.material.DrawerState drawerState, kotlin.jvm.functions.Function1<? super androidx.ui.material.DrawerState,kotlin.Unit> onStateChange, kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent, kotlin.jvm.functions.Function0<kotlin.Unit> bodyContent);
+ method public static void ModalDrawerLayout(androidx.ui.material.DrawerState drawerState, kotlin.jvm.functions.Function1<? super androidx.ui.material.DrawerState,kotlin.Unit> onStateChange, kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent, kotlin.jvm.functions.Function0<kotlin.Unit> bodyContent);
method public static void StaticDrawer(kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent);
}
diff --git a/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/DrawerSamples.kt b/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/DrawerSamples.kt
index 932b0c7..9572048 100644
--- a/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/DrawerSamples.kt
+++ b/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/DrawerSamples.kt
@@ -25,17 +25,15 @@
import androidx.ui.core.dp
import androidx.ui.foundation.ColoredRect
import androidx.ui.graphics.Color
-import androidx.ui.layout.Alignment
import androidx.ui.layout.Center
import androidx.ui.layout.Column
+import androidx.ui.layout.Container
import androidx.ui.layout.HeightSpacer
import androidx.ui.layout.Row
-import androidx.ui.layout.Stack
-import androidx.ui.material.BottomDrawer
+import androidx.ui.material.BottomDrawerLayout
import androidx.ui.material.Button
import androidx.ui.material.DrawerState
-import androidx.ui.material.MaterialTheme
-import androidx.ui.material.ModalDrawer
+import androidx.ui.material.ModalDrawerLayout
import androidx.ui.material.StaticDrawer
@Sampled
@@ -58,23 +56,12 @@
val (state, onStateChange) = +state { DrawerState.Closed }
val appContentText =
if (state == DrawerState.Closed) ">>> Pull to open >>>" else "<<< Swipe to close <<<"
- Stack {
- aligned(Alignment.Center) {
- // your app content goes there
- YourAppContent(appContentText, onStateChange)
- }
- aligned(Alignment.CenterLeft) {
- ModalDrawer(state, onStateChange) {
- Column {
- Text(text = "Drawer Content")
- HeightSpacer(20.dp)
- Button(
- text = "Close Drawer",
- onStateChange(DrawerState.Closed) })
- }
- }
- }
- }
+ ModalDrawerLayout(
+ drawerState = state,
+ >
+ drawerContent = { YourDrawerContent(onStateChange) },
+ bodyContent = { YourAppContent(appContentText, onStateChange) }
+ )
}
@Sampled
@@ -83,33 +70,37 @@
val (state, onStateChange) = +state { DrawerState.Closed }
val appContentText =
if (state == DrawerState.Closed) "▲▲▲ Pull to open ▲▲▲" else "▼▼▼ Drag down to close ▼▼▼"
- Stack {
- aligned(Alignment.Center) {
- // your app content goes there
- YourAppContent(appContentText, onStateChange)
- }
- aligned(Alignment.BottomCenter) {
- BottomDrawer(state, onStateChange) {
- Column {
- Text(text = "Drawer Content")
- HeightSpacer(20.dp)
- Button(
- text = "Close Drawer",
- onStateChange(DrawerState.Closed) })
- }
- }
+ BottomDrawerLayout(
+ drawerState = state,
+ >
+ drawerContent = { YourDrawerContent(onStateChange) },
+ bodyContent = { YourAppContent(appContentText, onStateChange) }
+ )
+}
+
+@Composable
+private fun YourDrawerContent(onStateChange: (DrawerState) -> Unit) {
+ Container(expanded = true) {
+ Column {
+ Text(text = "Drawer Content")
+ HeightSpacer(20.dp)
+ Button(
+ text = "Close Drawer",
+ onStateChange(DrawerState.Closed) })
}
}
}
@Composable
private fun YourAppContent(text: String, onDrawerStateChange: (DrawerState) -> Unit) {
- Column {
- Text(text = text)
- HeightSpacer(20.dp)
- Button(
- text = "Click to open",
- onDrawerStateChange(DrawerState.Opened) }
- )
+ Center {
+ Column {
+ Text(text = text)
+ HeightSpacer(20.dp)
+ Button(
+ text = "Click to open",
+ onDrawerStateChange(DrawerState.Opened) }
+ )
+ }
}
}
diff --git a/ui/ui-material/src/androidTest/java/androidx/ui/material/DrawerTest.kt b/ui/ui-material/src/androidTest/java/androidx/ui/material/DrawerTest.kt
index 32e5cca..ef366be 100644
--- a/ui/ui-material/src/androidTest/java/androidx/ui/material/DrawerTest.kt
+++ b/ui/ui-material/src/androidTest/java/androidx/ui/material/DrawerTest.kt
@@ -16,16 +16,22 @@
package androidx.ui.material
+import androidx.compose.Model
import androidx.compose.composer
import androidx.test.filters.MediumTest
import androidx.ui.core.OnPositioned
import androidx.ui.core.PxPosition
import androidx.ui.core.PxSize
+import androidx.ui.core.TestTag
import androidx.ui.core.dp
import androidx.ui.core.round
import androidx.ui.core.withDensity
+import androidx.ui.foundation.Clickable
import androidx.ui.layout.Container
+import androidx.ui.semantics.Semantics
import androidx.ui.test.createComposeRule
+import androidx.ui.test.doClick
+import androidx.ui.test.findByTag
import com.google.common.truth.Truth
import org.junit.Rule
import org.junit.Test
@@ -33,24 +39,27 @@
import org.junit.runners.JUnit4
import kotlin.math.roundToInt
+@Model
+data class DrawerStateHolder(var state: DrawerState)
+
@MediumTest
@RunWith(JUnit4::class)
class DrawerTest {
@get:Rule
- val composeTestRule = createComposeRule()
+ val composeTestRule = createComposeRule(disableTransitions = true)
@Test
fun modalDrawer_testOffset_whenOpened() {
var position: PxPosition? = null
composeTestRule.setMaterialContent {
- ModalDrawer(DrawerState.Opened, {}) {
+ ModalDrawerLayout(DrawerState.Opened, {}, drawerContent = {
Container(expanded = true) {
OnPositioned { coords ->
position = coords.localToGlobal(PxPosition.Origin)
}
}
- }
+ }) {}
}
Truth.assertThat(position!!.x.value).isEqualTo(0f)
}
@@ -59,13 +68,13 @@
fun modalDrawer_testOffset_whenClosed() {
var position: PxPosition? = null
composeTestRule.setMaterialContent {
- ModalDrawer(DrawerState.Closed, {}) {
+ ModalDrawerLayout(DrawerState.Closed, {}, drawerContent = {
Container(expanded = true) {
OnPositioned { coords ->
position = coords.localToGlobal(PxPosition.Origin)
}
}
- }
+ }) {}
}
val width = composeTestRule.displayMetrics.widthPixels
Truth.assertThat(position!!.x.round().value).isEqualTo(-width)
@@ -75,13 +84,13 @@
fun modalDrawer_testEndPadding_whenOpened() {
var size: PxSize? = null
composeTestRule.setMaterialContent {
- ModalDrawer(DrawerState.Opened, {}) {
+ ModalDrawerLayout(DrawerState.Opened, {}, drawerContent = {
Container(expanded = true) {
OnPositioned { coords ->
size = coords.size
}
}
- }
+ }) {}
}
val width = composeTestRule.displayMetrics.widthPixels
@@ -95,13 +104,13 @@
fun bottomDrawer_testOffset_whenOpened() {
var position: PxPosition? = null
composeTestRule.setMaterialContent {
- BottomDrawer(DrawerState.Opened, {}) {
+ BottomDrawerLayout(DrawerState.Opened, {}, drawerContent = {
Container(expanded = true) {
OnPositioned { coords ->
position = coords.localToGlobal(PxPosition.Origin)
}
}
- }
+ }) {}
}
val width = composeTestRule.displayMetrics.widthPixels
val height = composeTestRule.displayMetrics.heightPixels
@@ -114,13 +123,13 @@
fun bottomDrawer_testOffset_whenClosed() {
var position: PxPosition? = null
composeTestRule.setMaterialContent {
- BottomDrawer(DrawerState.Closed, {}) {
+ BottomDrawerLayout(DrawerState.Closed, {}, drawerContent = {
Container(expanded = true) {
OnPositioned { coords ->
position = coords.localToGlobal(PxPosition.Origin)
}
}
- }
+ }) {}
}
val height = composeTestRule.displayMetrics.heightPixels
Truth.assertThat(position!!.y.round().value).isEqualTo(height)
@@ -136,4 +145,93 @@
}
.assertWidthEqualsTo(256.dp)
}
+
+ @Test
+ fun modalDrawer_bodyContent_clickable() {
+ var drawerClicks = 0
+ var bodyClicks = 0
+ val drawerState = DrawerStateHolder(DrawerState.Closed)
+ composeTestRule.setMaterialContent {
+ //emulate click on the screen
+ TestTag("Drawer") {
+ Semantics(container = true) {
+ ModalDrawerLayout(drawerState.state, { drawerState.state = it },
+ drawerContent = {
+ Clickable( drawerClicks += 1 }) {
+ Container(expanded = true) {}
+ }
+ },
+ bodyContent = {
+ Clickable( bodyClicks += 1 }) {
+ Container(expanded = true) {}
+ }
+ })
+ }
+ }
+ }
+
+ findByTag("Drawer")
+ .doClick()
+
+ Truth.assertThat(drawerClicks).isEqualTo(0)
+ Truth.assertThat(bodyClicks).isEqualTo(1)
+
+ composeTestRule.runOnUiThread {
+ drawerState.state = DrawerState.Opened
+ }
+ // TODO: we aren't correctly waiting for recompositions after clicking, so we need to wait
+ // again
+ Thread.sleep(100L)
+
+ findByTag("Drawer")
+ .doClick()
+
+
+ Truth.assertThat(drawerClicks).isEqualTo(1)
+ Truth.assertThat(bodyClicks).isEqualTo(1)
+ }
+
+ @Test
+ fun bottomDrawer_bodyContent_clickable() {
+ var drawerClicks = 0
+ var bodyClicks = 0
+ val drawerState = DrawerStateHolder(DrawerState.Closed)
+ composeTestRule.setMaterialContent {
+ //emulate click on the screen
+ TestTag("Drawer") {
+ Semantics(container = true) {
+ BottomDrawerLayout(drawerState.state, { drawerState.state = it },
+ drawerContent = {
+ Clickable( drawerClicks += 1 }) {
+ Container(expanded = true) {}
+ }
+ },
+ bodyContent = {
+ Clickable( bodyClicks += 1 }) {
+ Container(expanded = true) {}
+ }
+ })
+ }
+ }
+ }
+
+ findByTag("Drawer")
+ .doClick()
+
+ Truth.assertThat(drawerClicks).isEqualTo(0)
+ Truth.assertThat(bodyClicks).isEqualTo(1)
+
+ // TODO (malkov/pavlis) : uncomment this when custom onClick location will be implemented
+// composeTestRule.runOnUiThread {
+// drawerState.state = DrawerState.Opened
+// }
+// Thread.sleep(100L)
+//
+// findByTag("Drawer")
+// .doClick()
+//
+//
+// Truth.assertThat(drawerClicks).isEqualTo(1)
+// Truth.assertThat(bodyClicks).isEqualTo(1)
+ }
}
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/Drawer.kt b/ui/ui-material/src/main/java/androidx/ui/material/Drawer.kt
index 7e5e527..e5a5cf0 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/Drawer.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/Drawer.kt
@@ -22,7 +22,6 @@
import androidx.compose.memo
import androidx.compose.onCommit
import androidx.compose.unaryPlus
-import androidx.ui.animation.animatedFloat
import androidx.ui.core.Dp
import androidx.ui.core.IntPx
import androidx.ui.core.Layout
@@ -37,9 +36,9 @@
import androidx.ui.foundation.Clickable
import androidx.ui.foundation.ColoredRect
import androidx.ui.foundation.animation.AnchorsFlingConfig
-import androidx.ui.foundation.gestures.Draggable
import androidx.ui.foundation.animation.AnimatedFloatDragController
import androidx.ui.foundation.gestures.DragDirection
+import androidx.ui.foundation.gestures.Draggable
import androidx.ui.foundation.gestures.DraggableCallback
import androidx.ui.layout.Alignment
import androidx.ui.layout.Container
@@ -72,7 +71,7 @@
* at the same time. They can be used on tablet and desktop,
* but they are not suitable for mobile due to limited screen size.
*
- * @see [ModalDrawer] and [BottomDrawer] for more mobile friendly options
+ * See [ModalDrawerLayout] and [BottomDrawerLayout] for more mobile friendly options.
*
* @sample androidx.ui.material.samples.StaticDrawerSample
*
@@ -93,8 +92,9 @@
* Modal navigation drawers block interaction with the rest of an app’s content with a scrim.
* They are elevated above most of the app’s UI and don’t affect the screen’s layout grid.
*
- * @see [StaticDrawer] for always visible drawer, suitable for tables or desktop
- * @see [BottomDrawer] for drawer that is recommended when you have bottom navigation
+ * See [StaticDrawer] for always visible drawer, suitable for tablet or desktop.
+ * See [BottomDrawerLayout] for a layout that introduces a bottom drawer, suitable when
+ * using bottom navigation.
*
* @sample androidx.ui.material.samples.ModalDrawerSample
*
@@ -102,14 +102,16 @@
* @param onStateChange lambda to be invoked when the drawer requests to change its state,
* e.g. when the drawer is being swiped to the new state or when the scrim is clicked
* @param drawerContent composable that represents content inside the drawer
+ * @param bodyContent content of the rest of the UI
*
* @throws IllegalStateException when parent has [Px.Infinity] width
*/
@Composable
-fun ModalDrawer(
+fun ModalDrawerLayout(
drawerState: DrawerState,
onStateChange: (DrawerState) -> Unit,
- drawerContent: @Composable() () -> Unit
+ drawerContent: @Composable() () -> Unit,
+ bodyContent: @Composable() () -> Unit
) {
Container(expanded = true) {
WithConstraints { pxConstraints ->
@@ -148,6 +150,7 @@
Stack {
aligned(Alignment.TopLeft) {
+ bodyContent()
Scrim(drawerState, onStateChange, scrimAlpha)
DrawerContent(dpOffset, constraints, drawerContent)
}
@@ -167,8 +170,8 @@
* These drawers open upon tapping the navigation menu icon in the bottom app bar.
* They are only for use on mobile.
*
- * @see [StaticDrawer] for always visible drawer, suitable for tables or desktop
- * @see [ModalDrawer] for classic "from the side" drawer
+ * See [StaticDrawer] for always visible drawer, suitable for tablet or desktop
+ * See [ModalDrawerLayout] for a layout that introduces a classic from-the-side drawer.
*
* @sample androidx.ui.material.samples.BottomDrawerSample
*
@@ -176,14 +179,16 @@
* @param onStateChange lambda to be invoked when the drawer requests to change its state,
* e.g. when the drawer is being swiped to the new state or when the scrim is clicked
* @param drawerContent composable that represents content inside the drawer
+ * @param bodyContent content of the rest of the UI
*
* @throws IllegalStateException when parent has [Px.Infinity] height
*/
@Composable
-fun BottomDrawer(
+fun BottomDrawerLayout(
drawerState: DrawerState,
onStateChange: (DrawerState) -> Unit,
- drawerContent: @Composable() () -> Unit
+ drawerContent: @Composable() () -> Unit,
+ bodyContent: @Composable() () -> Unit
) {
Container(expanded = true) {
WithConstraints { pxConstraints ->
@@ -233,6 +238,7 @@
}
Stack {
aligned(Alignment.TopLeft) {
+ bodyContent()
Scrim(drawerState, onStateChange, scrimAlpha)
BottomDrawerContent(dpOffset, constraints, drawerContent)
}