[go: nahoru, domu]

Deprecate Scroller. Introduce ScrollableColumn / ScrollableRow

This CL reworks Scrolling-related API. Scrollers and ScrollerPosition have been deprecated. Instead, ScrollableColumn, ScrollableRow and ScrollState have been introduced. Additionaly, for more flexibility and performance, Modifier.verticalScroll and Modifier.horizontalScroll have been introduced, so users can add scrolling to everything while saving layout node.

While moving from ScrollerPosition to ScrollState, few API were renamed and simplified and @Composable ScrollerPosition was replaced with rememberScrollState.

Relnote: 'VerticalScroller and HoriziontalScroller have been deprecated. Use ScrollableColumn and ScrollableRow for build-in experience with Column/Row behaviour and parameters, or Modifier.verticalScroll and Modifier.horizontalScroll on your own element. Similarly, ScrollerPosition has been deprecated in favor of ScrollState'
Fixes: 158571223
Fixes: 157225838
Bug: 148542949
Bug: 150706555
Fixes: 150458415
Fixes: 149460415
Fixes: 154105299
Test: Tests were fixed for new APIs
Change-Id: I400ce0e6c0e33aa865e0e49defef1eb92ac40a93
diff --git a/ui/integration-tests/demos/src/main/java/androidx/ui/demos/DemoApp.kt b/ui/integration-tests/demos/src/main/java/androidx/ui/demos/DemoApp.kt
index 7bc3203..375a966 100644
--- a/ui/integration-tests/demos/src/main/java/androidx/ui/demos/DemoApp.kt
+++ b/ui/integration-tests/demos/src/main/java/androidx/ui/demos/DemoApp.kt
@@ -31,8 +31,8 @@
 import androidx.ui.demos.common.DemoCategory
 import androidx.ui.demos.common.allLaunchableDemos
 import androidx.ui.foundation.Icon
+import androidx.ui.foundation.ScrollableColumn
 import androidx.ui.foundation.Text
-import androidx.ui.foundation.VerticalScroller
 import androidx.ui.input.TextFieldValue
 import androidx.ui.layout.fillMaxSize
 import androidx.ui.layout.padding
@@ -121,7 +121,7 @@
 
 @Composable
 private fun DisplayDemoCategory(category: DemoCategory, onNavigate: (Demo) -> Unit) {
-    VerticalScroller {
+    ScrollableColumn {
         category.demos.forEach { demo ->
             ListItem(
                 text = {
diff --git a/ui/integration-tests/demos/src/main/java/androidx/ui/demos/DemoFilter.kt b/ui/integration-tests/demos/src/main/java/androidx/ui/demos/DemoFilter.kt
index 7950fe3..1b3817e 100644
--- a/ui/integration-tests/demos/src/main/java/androidx/ui/demos/DemoFilter.kt
+++ b/ui/integration-tests/demos/src/main/java/androidx/ui/demos/DemoFilter.kt
@@ -21,12 +21,12 @@
 import androidx.compose.onCommit
 import androidx.ui.core.Alignment
 import androidx.ui.core.Modifier
-import androidx.ui.demos.common.Demo
 import androidx.ui.core.focus.FocusModifier
+import androidx.ui.demos.common.Demo
 import androidx.ui.foundation.Icon
+import androidx.ui.foundation.ScrollableColumn
 import androidx.ui.foundation.Text
 import androidx.ui.foundation.TextField
-import androidx.ui.foundation.VerticalScroller
 import androidx.ui.graphics.compositeOver
 import androidx.ui.input.TextFieldValue
 import androidx.ui.layout.fillMaxWidth
@@ -51,7 +51,7 @@
     val filteredDemos = launchableDemos
         .filter { it.title.contains(filterText, ignoreCase = true) }
         .sortedBy { it.title }
-    VerticalScroller {
+    ScrollableColumn {
         filteredDemos.forEach { demo ->
             FilteredDemoListItem(demo,
                 filterText = filterText,
diff --git a/ui/integration-tests/src/main/java/androidx/ui/integration/test/foundation/NestedScrollerTestCase.kt b/ui/integration-tests/src/main/java/androidx/ui/integration/test/foundation/NestedScrollerTestCase.kt
index c32678d..fe6400c 100644
--- a/ui/integration-tests/src/main/java/androidx/ui/integration/test/foundation/NestedScrollerTestCase.kt
+++ b/ui/integration-tests/src/main/java/androidx/ui/integration/test/foundation/NestedScrollerTestCase.kt
@@ -23,11 +23,12 @@
 import androidx.ui.core.DensityAmbient
 import androidx.ui.core.Modifier
 import androidx.ui.foundation.Box
-import androidx.ui.foundation.HorizontalScroller
-import androidx.ui.foundation.ScrollerPosition
+import androidx.ui.foundation.ScrollState
+import androidx.ui.foundation.ScrollableColumn
+import androidx.ui.foundation.ScrollableRow
 import androidx.ui.foundation.Text
-import androidx.ui.foundation.VerticalScroller
 import androidx.ui.foundation.drawBackground
+import androidx.ui.foundation.rememberScrollState
 import androidx.ui.graphics.Color
 import androidx.ui.integration.test.ToggleableTestCase
 import androidx.ui.layout.Column
@@ -46,14 +47,14 @@
  */
 class NestedScrollerTestCase : ComposeTestCase, ToggleableTestCase {
     // ScrollerPosition must now be constructed during composition to obtain the Density
-    private lateinit var scrollerPosition: ScrollerPosition
+    private lateinit var scrollState: ScrollState
 
     @Composable
     override fun emitContent() {
-        scrollerPosition = ScrollerPosition()
+        scrollState = rememberScrollState()
         MaterialTheme {
             Surface {
-                VerticalScroller {
+                ScrollableColumn {
                     repeat(5) { index ->
                         // key is needed because of b/154920561
                         key(index) {
@@ -66,7 +67,7 @@
     }
 
     override fun toggleState() {
-        scrollerPosition.scrollTo(if (scrollerPosition.value == 0f) 10f else 0f)
+        scrollState.scrollTo(if (scrollState.value == 0f) 10f else 0f)
     }
 
     @Composable
@@ -106,9 +107,9 @@
             }
         }
         if (useScrollerPosition) {
-            HorizontalScroller(scrollerPosition = scrollerPosition, children = content)
+            ScrollableRow(scrollState = scrollState, children = content)
         } else {
-            HorizontalScroller(children = content)
+            ScrollableRow(children = content)
         }
     }
 }
diff --git a/ui/integration-tests/src/main/java/androidx/ui/integration/test/foundation/ScrollerTestCase.kt b/ui/integration-tests/src/main/java/androidx/ui/integration/test/foundation/ScrollerTestCase.kt
index 3d138e6..d1fb300 100644
--- a/ui/integration-tests/src/main/java/androidx/ui/integration/test/foundation/ScrollerTestCase.kt
+++ b/ui/integration-tests/src/main/java/androidx/ui/integration/test/foundation/ScrollerTestCase.kt
@@ -19,8 +19,9 @@
 import androidx.compose.Composable
 import androidx.ui.core.Modifier
 import androidx.ui.foundation.Canvas
-import androidx.ui.foundation.ScrollerPosition
-import androidx.ui.foundation.VerticalScroller
+import androidx.ui.foundation.ScrollState
+import androidx.ui.foundation.ScrollableColumn
+import androidx.ui.foundation.rememberScrollState
 import androidx.ui.graphics.Color
 import androidx.ui.integration.test.ToggleableTestCase
 import androidx.ui.layout.Column
@@ -33,14 +34,12 @@
  * Test case that puts a large number of boxes in a column in a vertical scroller to force scrolling.
  */
 class ScrollerTestCase() : ComposeTestCase, ToggleableTestCase {
-    private lateinit var scrollerPosition: ScrollerPosition
+    private lateinit var scrollState: ScrollState
 
     @Composable
     override fun emitContent() {
-        scrollerPosition = ScrollerPosition()
-        VerticalScroller(
-            scrollerPosition = scrollerPosition
-        ) {
+        scrollState = rememberScrollState()
+        ScrollableColumn(scrollState = scrollState) {
             Column(Modifier.fillMaxHeight()) {
                 for (green in 0..0xFF) {
                     ColorStripe(0xFF, green, 0)
@@ -65,7 +64,7 @@
     }
 
     override fun toggleState() {
-        scrollerPosition.scrollTo(if (scrollerPosition.value == 0f) 10f else 0f)
+        scrollState.scrollTo(if (scrollState.value == 0f) 10f else 0f)
     }
 
     @Composable
diff --git a/ui/ui-android-view/integration-tests/android-view-demos/src/main/java/androidx/ui/androidview/demos/PointerInputInteropAndroidInCompose.kt b/ui/ui-android-view/integration-tests/android-view-demos/src/main/java/androidx/ui/androidview/demos/PointerInputInteropAndroidInCompose.kt
index cdaae2a..398107f 100644
--- a/ui/ui-android-view/integration-tests/android-view-demos/src/main/java/androidx/ui/androidview/demos/PointerInputInteropAndroidInCompose.kt
+++ b/ui/ui-android-view/integration-tests/android-view-demos/src/main/java/androidx/ui/androidview/demos/PointerInputInteropAndroidInCompose.kt
@@ -27,17 +27,17 @@
 import androidx.ui.demos.common.ComposableDemo
 import androidx.ui.demos.common.DemoCategory
 import androidx.ui.foundation.Box
-import androidx.ui.foundation.HorizontalScroller
+import androidx.ui.foundation.ScrollableColumn
+import androidx.ui.foundation.ScrollableRow
 import androidx.ui.foundation.Text
-import androidx.ui.foundation.VerticalScroller
 import androidx.ui.foundation.drawBackground
+import androidx.ui.geometry.Offset
 import androidx.ui.layout.Column
 import androidx.ui.layout.fillMaxSize
 import androidx.ui.layout.padding
 import androidx.ui.layout.preferredHeight
 import androidx.ui.layout.preferredSize
 import androidx.ui.layout.wrapContentSize
-import androidx.ui.geometry.Offset
 import androidx.ui.unit.dp
 import androidx.ui.viewinterop.AndroidView
 
@@ -138,7 +138,7 @@
                     ". If a pointer starts on a button and then drags horizontally, the button " +
                     "will not be clicked when released."
         )
-        HorizontalScroller {
+        ScrollableRow {
             AndroidView(R.layout.android_tap_in_compose_scroll) { view ->
                 view.setBackgroundColor(Color.YELLOW)
                 view.findViewById<View>(R.id.buttonRed).apply {
@@ -176,7 +176,7 @@
         Text("Demonstrates correct \"scroll orientation\" locking when something scrollable in " +
                 "Android is nested inside something scrollable in Compose.")
         Text("You should only be able to scroll in one orientation at a time.")
-        HorizontalScroller(modifier = Modifier.drawBackground(androidx.ui.graphics.Color.Blue)) {
+        ScrollableRow(modifier = Modifier.drawBackground(androidx.ui.graphics.Color.Blue)) {
             Box(modifier = Modifier.padding(96.dp).drawBackground(androidx.ui.graphics.Color.Red)) {
                 AndroidView(R.layout.android_scroll_in_compose_scroll_different_orientation)
             }
@@ -197,7 +197,7 @@
                     "that for Compose, and thus the child scrolls and prevents the parent from " +
                     "intercepting. "
         )
-        VerticalScroller(modifier = Modifier.drawBackground(androidx.ui.graphics.Color.Blue)) {
+        ScrollableColumn(modifier = Modifier.drawBackground(androidx.ui.graphics.Color.Blue)) {
             Box(
                 modifier = Modifier
                     .padding(96.dp)
diff --git a/ui/ui-android-view/integration-tests/android-view-demos/src/main/java/androidx/ui/androidview/demos/PointerInputInteropComposeInAndroid.kt b/ui/ui-android-view/integration-tests/android-view-demos/src/main/java/androidx/ui/androidview/demos/PointerInputInteropComposeInAndroid.kt
index 8532fcd..3962a9c 100644
--- a/ui/ui-android-view/integration-tests/android-view-demos/src/main/java/androidx/ui/androidview/demos/PointerInputInteropComposeInAndroid.kt
+++ b/ui/ui-android-view/integration-tests/android-view-demos/src/main/java/androidx/ui/androidview/demos/PointerInputInteropComposeInAndroid.kt
@@ -32,8 +32,8 @@
 import androidx.ui.demos.common.ActivityDemo
 import androidx.ui.demos.common.DemoCategory
 import androidx.ui.foundation.Box
-import androidx.ui.foundation.HorizontalScroller
-import androidx.ui.foundation.VerticalScroller
+import androidx.ui.foundation.ScrollableColumn
+import androidx.ui.foundation.ScrollableRow
 import androidx.ui.foundation.clickable
 import androidx.ui.foundation.drawBackground
 import androidx.ui.graphics.Color
@@ -243,7 +243,7 @@
 
         val container = findViewById<ViewGroup>(R.id.container)
         composition = container.setContent(Recomposer.current()) {
-            VerticalScroller(
+            ScrollableColumn(
                 modifier = Modifier
                     .padding(48.dp)
                     .drawBackground(Color.Gray, RectangleShape)
@@ -288,7 +288,7 @@
 
         val container = findViewById<ViewGroup>(R.id.container)
         composition = container.setContent(Recomposer.current()) {
-            HorizontalScroller(
+            ScrollableRow(
                 modifier = Modifier
                     .padding(48.dp)
                     .drawBackground(Color.Gray, RectangleShape)
diff --git a/ui/ui-core/integration-tests/ui-core-demos/src/main/java/androidx/ui/core/demos/PopupDemo.kt b/ui/ui-core/integration-tests/ui-core-demos/src/main/java/androidx/ui/core/demos/PopupDemo.kt
index 187b68e..e69e07b 100644
--- a/ui/ui-core/integration-tests/ui-core-demos/src/main/java/androidx/ui/core/demos/PopupDemo.kt
+++ b/ui/ui-core/integration-tests/ui-core-demos/src/main/java/androidx/ui/core/demos/PopupDemo.kt
@@ -23,12 +23,11 @@
 import androidx.ui.core.DropdownPopup
 import androidx.ui.core.Modifier
 import androidx.ui.core.Popup
-import androidx.ui.foundation.TextField
 import androidx.ui.foundation.Box
 import androidx.ui.foundation.ContentGravity
+import androidx.ui.foundation.ScrollableColumn
 import androidx.ui.foundation.Text
 import androidx.ui.foundation.TextField
-import androidx.ui.foundation.VerticalScroller
 import androidx.ui.foundation.clickable
 import androidx.ui.foundation.drawBackground
 import androidx.ui.foundation.shape.corner.CircleShape
@@ -406,7 +405,7 @@
 
 @Composable
 private fun ColumnScope.PopupInsideScroller() {
-    VerticalScroller(
+    ScrollableColumn(
         modifier = Modifier.preferredSize(200.dp, 400.dp).gravity(Alignment.CenterHorizontally)
     ) {
         Column(Modifier.fillMaxHeight()) {
diff --git a/ui/ui-foundation/api/0.1.0-dev15.txt b/ui/ui-foundation/api/0.1.0-dev15.txt
index 2fa0f01..cd9464e 100644
--- a/ui/ui-foundation/api/0.1.0-dev15.txt
+++ b/ui/ui-foundation/api/0.1.0-dev15.txt
@@ -119,29 +119,54 @@
   @kotlin.RequiresOptIn(message="This API is internal to library.") @kotlin.annotation.Target(allowedTargets={AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER}) public @interface InternalFoundationApi {
   }
 
-  public final class ScrollerKt {
-    method @androidx.compose.Composable public static void HorizontalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = ScrollerPosition(), androidx.ui.core.Modifier modifier = Modifier, boolean isScrollable = true, kotlin.jvm.functions.Function1<? super androidx.ui.layout.RowScope,kotlin.Unit> children);
-    method @androidx.compose.Composable public static androidx.ui.foundation.ScrollerPosition ScrollerPosition(float initial = 0f, boolean isReversed = false);
-    method @androidx.compose.Composable public static void VerticalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = ScrollerPosition(), androidx.ui.core.Modifier modifier = Modifier, boolean isScrollable = true, kotlin.jvm.functions.Function1<? super androidx.ui.layout.ColumnScope,kotlin.Unit> children);
+  public final class ScrollKt {
+    method @Deprecated @androidx.compose.Composable public static void HorizontalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = ScrollerPosition(), androidx.ui.core.Modifier modifier = Modifier, boolean isScrollable = true, kotlin.jvm.functions.Function1<? super androidx.ui.layout.RowScope,kotlin.Unit> children);
+    method @androidx.compose.Composable public static void ScrollableColumn(androidx.ui.core.Modifier modifier = Modifier, androidx.ui.foundation.ScrollState scrollState = rememberScrollState(0.0), androidx.ui.layout.Arrangement.Vertical verticalArrangement = Arrangement.Top, androidx.ui.core.Alignment.Horizontal horizontalGravity = Alignment.Start, boolean reverseScrollDirection = false, boolean isScrollEnabled = true, androidx.ui.layout.InnerPadding contentPadding = androidx.ui.layout.InnerPadding(0.dp), kotlin.jvm.functions.Function1<? super androidx.ui.layout.ColumnScope,kotlin.Unit> children);
+    method @androidx.compose.Composable public static void ScrollableRow(androidx.ui.core.Modifier modifier = Modifier, androidx.ui.foundation.ScrollState scrollState = rememberScrollState(0.0), androidx.ui.layout.Arrangement.Horizontal horizontalArrangement = Arrangement.Start, androidx.ui.core.Alignment.Vertical verticalGravity = Alignment.Top, boolean reverseScrollDirection = false, boolean isScrollEnabled = true, androidx.ui.layout.InnerPadding contentPadding = androidx.ui.layout.InnerPadding(0.dp), kotlin.jvm.functions.Function1<? super androidx.ui.layout.RowScope,kotlin.Unit> children);
+    method @Deprecated @androidx.compose.Composable public static androidx.ui.foundation.ScrollerPosition ScrollerPosition(float initial = 0f, boolean isReversed = false);
+    method @Deprecated @androidx.compose.Composable public static void VerticalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = ScrollerPosition(), androidx.ui.core.Modifier modifier = Modifier, boolean isScrollable = true, kotlin.jvm.functions.Function1<? super androidx.ui.layout.ColumnScope,kotlin.Unit> children);
+    method public static androidx.ui.core.Modifier horizontalScroll(androidx.ui.core.Modifier, androidx.ui.foundation.ScrollState state, boolean enabled = true, boolean reverseScrolling = false);
+    method @androidx.compose.Composable public static androidx.ui.foundation.ScrollState rememberScrollState(float initial = 0f);
+    method public static androidx.ui.core.Modifier verticalScroll(androidx.ui.core.Modifier, androidx.ui.foundation.ScrollState state, boolean enabled = true, boolean reverseScrolling = false);
   }
 
-  @androidx.compose.Stable public final class ScrollerPosition {
-    ctor public ScrollerPosition(androidx.ui.foundation.animation.FlingConfig flingConfig, float initial, internal boolean isReversed, androidx.animation.AnimationClockObservable animationClock);
-    method public float getMaxPosition();
+  @androidx.compose.Stable public final class ScrollState {
+    ctor public ScrollState(float initial, internal androidx.ui.foundation.animation.FlingConfig flingConfig, androidx.animation.AnimationClockObservable animationClock);
+    method public float getMaxValue();
     method public float getValue();
-    method public boolean isAnimating();
+    method public boolean isAnimationRunning();
     method public void scrollBy(float value);
     method public void scrollTo(float value);
-    method public void smoothScrollBy(float value, kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>  _, _ ->  });
-    method public void smoothScrollTo(float value, kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>  _, _ ->  });
-    property public final boolean isAnimating;
-    property public final float maxPosition;
+    method public void smoothScrollBy(float value, androidx.animation.AnimationSpec<java.lang.Float> spec = androidx.animation.SpringSpec(), kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>  _, _ ->  });
+    method public void smoothScrollTo(float value, androidx.animation.AnimationSpec<java.lang.Float> spec = androidx.animation.SpringSpec(), kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>  _, _ ->  });
+    method public void stopAnimation();
+    property public final boolean isAnimationRunning;
+    property public final float maxValue;
     property public final float value;
-    field public static final androidx.ui.foundation.ScrollerPosition.Companion Companion;
+    field public static final androidx.ui.foundation.ScrollState.Companion Companion;
   }
 
-  public static final class ScrollerPosition.Companion {
-    method @androidx.compose.Composable public androidx.ui.savedinstancestate.Saver<androidx.ui.foundation.ScrollerPosition,?> Saver(androidx.ui.foundation.animation.FlingConfig flingConfig, boolean isReversed, androidx.animation.AnimationClockObservable animationClock);
+  public static final class ScrollState.Companion {
+    method public androidx.ui.savedinstancestate.Saver<androidx.ui.foundation.ScrollState,?> Saver(androidx.ui.foundation.animation.FlingConfig flingConfig, androidx.animation.AnimationClockObservable animationClock);
+  }
+
+  @Deprecated @androidx.compose.Stable public final class ScrollerPosition {
+    ctor @Deprecated public ScrollerPosition(internal androidx.ui.foundation.animation.FlingConfig flingConfig, float initial, internal boolean isReversed, internal androidx.animation.AnimationClockObservable animationClock);
+    method @Deprecated public float getMaxPosition();
+    method @Deprecated public float getValue();
+    method @Deprecated public void scrollBy(float value);
+    method @Deprecated public void scrollTo(float value);
+    method @Deprecated public void setMaxPosition(float newMax);
+    method @Deprecated public void setValue(float value);
+    method @Deprecated public void smoothScrollBy(float value, kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>  _, _ ->  });
+    method @Deprecated public void smoothScrollTo(float value, kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>  _, _ ->  });
+    property public final float maxPosition;
+    property public final float value;
+    field @Deprecated public static final androidx.ui.foundation.ScrollerPosition.Companion Companion;
+  }
+
+  @Deprecated public static final class ScrollerPosition.Companion {
+    method @Deprecated @androidx.compose.Composable public androidx.ui.savedinstancestate.Saver<androidx.ui.foundation.ScrollerPosition,?> Saver(androidx.ui.foundation.animation.FlingConfig flingConfig, boolean isReversed, androidx.animation.AnimationClockObservable animationClock);
   }
 
   public final class Strings {
diff --git a/ui/ui-foundation/api/current.txt b/ui/ui-foundation/api/current.txt
index 2fa0f01..cd9464e 100644
--- a/ui/ui-foundation/api/current.txt
+++ b/ui/ui-foundation/api/current.txt
@@ -119,29 +119,54 @@
   @kotlin.RequiresOptIn(message="This API is internal to library.") @kotlin.annotation.Target(allowedTargets={AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER}) public @interface InternalFoundationApi {
   }
 
-  public final class ScrollerKt {
-    method @androidx.compose.Composable public static void HorizontalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = ScrollerPosition(), androidx.ui.core.Modifier modifier = Modifier, boolean isScrollable = true, kotlin.jvm.functions.Function1<? super androidx.ui.layout.RowScope,kotlin.Unit> children);
-    method @androidx.compose.Composable public static androidx.ui.foundation.ScrollerPosition ScrollerPosition(float initial = 0f, boolean isReversed = false);
-    method @androidx.compose.Composable public static void VerticalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = ScrollerPosition(), androidx.ui.core.Modifier modifier = Modifier, boolean isScrollable = true, kotlin.jvm.functions.Function1<? super androidx.ui.layout.ColumnScope,kotlin.Unit> children);
+  public final class ScrollKt {
+    method @Deprecated @androidx.compose.Composable public static void HorizontalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = ScrollerPosition(), androidx.ui.core.Modifier modifier = Modifier, boolean isScrollable = true, kotlin.jvm.functions.Function1<? super androidx.ui.layout.RowScope,kotlin.Unit> children);
+    method @androidx.compose.Composable public static void ScrollableColumn(androidx.ui.core.Modifier modifier = Modifier, androidx.ui.foundation.ScrollState scrollState = rememberScrollState(0.0), androidx.ui.layout.Arrangement.Vertical verticalArrangement = Arrangement.Top, androidx.ui.core.Alignment.Horizontal horizontalGravity = Alignment.Start, boolean reverseScrollDirection = false, boolean isScrollEnabled = true, androidx.ui.layout.InnerPadding contentPadding = androidx.ui.layout.InnerPadding(0.dp), kotlin.jvm.functions.Function1<? super androidx.ui.layout.ColumnScope,kotlin.Unit> children);
+    method @androidx.compose.Composable public static void ScrollableRow(androidx.ui.core.Modifier modifier = Modifier, androidx.ui.foundation.ScrollState scrollState = rememberScrollState(0.0), androidx.ui.layout.Arrangement.Horizontal horizontalArrangement = Arrangement.Start, androidx.ui.core.Alignment.Vertical verticalGravity = Alignment.Top, boolean reverseScrollDirection = false, boolean isScrollEnabled = true, androidx.ui.layout.InnerPadding contentPadding = androidx.ui.layout.InnerPadding(0.dp), kotlin.jvm.functions.Function1<? super androidx.ui.layout.RowScope,kotlin.Unit> children);
+    method @Deprecated @androidx.compose.Composable public static androidx.ui.foundation.ScrollerPosition ScrollerPosition(float initial = 0f, boolean isReversed = false);
+    method @Deprecated @androidx.compose.Composable public static void VerticalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = ScrollerPosition(), androidx.ui.core.Modifier modifier = Modifier, boolean isScrollable = true, kotlin.jvm.functions.Function1<? super androidx.ui.layout.ColumnScope,kotlin.Unit> children);
+    method public static androidx.ui.core.Modifier horizontalScroll(androidx.ui.core.Modifier, androidx.ui.foundation.ScrollState state, boolean enabled = true, boolean reverseScrolling = false);
+    method @androidx.compose.Composable public static androidx.ui.foundation.ScrollState rememberScrollState(float initial = 0f);
+    method public static androidx.ui.core.Modifier verticalScroll(androidx.ui.core.Modifier, androidx.ui.foundation.ScrollState state, boolean enabled = true, boolean reverseScrolling = false);
   }
 
-  @androidx.compose.Stable public final class ScrollerPosition {
-    ctor public ScrollerPosition(androidx.ui.foundation.animation.FlingConfig flingConfig, float initial, internal boolean isReversed, androidx.animation.AnimationClockObservable animationClock);
-    method public float getMaxPosition();
+  @androidx.compose.Stable public final class ScrollState {
+    ctor public ScrollState(float initial, internal androidx.ui.foundation.animation.FlingConfig flingConfig, androidx.animation.AnimationClockObservable animationClock);
+    method public float getMaxValue();
     method public float getValue();
-    method public boolean isAnimating();
+    method public boolean isAnimationRunning();
     method public void scrollBy(float value);
     method public void scrollTo(float value);
-    method public void smoothScrollBy(float value, kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>  _, _ ->  });
-    method public void smoothScrollTo(float value, kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>  _, _ ->  });
-    property public final boolean isAnimating;
-    property public final float maxPosition;
+    method public void smoothScrollBy(float value, androidx.animation.AnimationSpec<java.lang.Float> spec = androidx.animation.SpringSpec(), kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>  _, _ ->  });
+    method public void smoothScrollTo(float value, androidx.animation.AnimationSpec<java.lang.Float> spec = androidx.animation.SpringSpec(), kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>  _, _ ->  });
+    method public void stopAnimation();
+    property public final boolean isAnimationRunning;
+    property public final float maxValue;
     property public final float value;
-    field public static final androidx.ui.foundation.ScrollerPosition.Companion Companion;
+    field public static final androidx.ui.foundation.ScrollState.Companion Companion;
   }
 
-  public static final class ScrollerPosition.Companion {
-    method @androidx.compose.Composable public androidx.ui.savedinstancestate.Saver<androidx.ui.foundation.ScrollerPosition,?> Saver(androidx.ui.foundation.animation.FlingConfig flingConfig, boolean isReversed, androidx.animation.AnimationClockObservable animationClock);
+  public static final class ScrollState.Companion {
+    method public androidx.ui.savedinstancestate.Saver<androidx.ui.foundation.ScrollState,?> Saver(androidx.ui.foundation.animation.FlingConfig flingConfig, androidx.animation.AnimationClockObservable animationClock);
+  }
+
+  @Deprecated @androidx.compose.Stable public final class ScrollerPosition {
+    ctor @Deprecated public ScrollerPosition(internal androidx.ui.foundation.animation.FlingConfig flingConfig, float initial, internal boolean isReversed, internal androidx.animation.AnimationClockObservable animationClock);
+    method @Deprecated public float getMaxPosition();
+    method @Deprecated public float getValue();
+    method @Deprecated public void scrollBy(float value);
+    method @Deprecated public void scrollTo(float value);
+    method @Deprecated public void setMaxPosition(float newMax);
+    method @Deprecated public void setValue(float value);
+    method @Deprecated public void smoothScrollBy(float value, kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>  _, _ ->  });
+    method @Deprecated public void smoothScrollTo(float value, kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>  _, _ ->  });
+    property public final float maxPosition;
+    property public final float value;
+    field @Deprecated public static final androidx.ui.foundation.ScrollerPosition.Companion Companion;
+  }
+
+  @Deprecated public static final class ScrollerPosition.Companion {
+    method @Deprecated @androidx.compose.Composable public androidx.ui.savedinstancestate.Saver<androidx.ui.foundation.ScrollerPosition,?> Saver(androidx.ui.foundation.animation.FlingConfig flingConfig, boolean isReversed, androidx.animation.AnimationClockObservable animationClock);
   }
 
   public final class Strings {
diff --git a/ui/ui-foundation/api/public_plus_experimental_0.1.0-dev15.txt b/ui/ui-foundation/api/public_plus_experimental_0.1.0-dev15.txt
index 2fa0f01..cd9464e 100644
--- a/ui/ui-foundation/api/public_plus_experimental_0.1.0-dev15.txt
+++ b/ui/ui-foundation/api/public_plus_experimental_0.1.0-dev15.txt
@@ -119,29 +119,54 @@
   @kotlin.RequiresOptIn(message="This API is internal to library.") @kotlin.annotation.Target(allowedTargets={AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER}) public @interface InternalFoundationApi {
   }
 
-  public final class ScrollerKt {
-    method @androidx.compose.Composable public static void HorizontalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = ScrollerPosition(), androidx.ui.core.Modifier modifier = Modifier, boolean isScrollable = true, kotlin.jvm.functions.Function1<? super androidx.ui.layout.RowScope,kotlin.Unit> children);
-    method @androidx.compose.Composable public static androidx.ui.foundation.ScrollerPosition ScrollerPosition(float initial = 0f, boolean isReversed = false);
-    method @androidx.compose.Composable public static void VerticalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = ScrollerPosition(), androidx.ui.core.Modifier modifier = Modifier, boolean isScrollable = true, kotlin.jvm.functions.Function1<? super androidx.ui.layout.ColumnScope,kotlin.Unit> children);
+  public final class ScrollKt {
+    method @Deprecated @androidx.compose.Composable public static void HorizontalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = ScrollerPosition(), androidx.ui.core.Modifier modifier = Modifier, boolean isScrollable = true, kotlin.jvm.functions.Function1<? super androidx.ui.layout.RowScope,kotlin.Unit> children);
+    method @androidx.compose.Composable public static void ScrollableColumn(androidx.ui.core.Modifier modifier = Modifier, androidx.ui.foundation.ScrollState scrollState = rememberScrollState(0.0), androidx.ui.layout.Arrangement.Vertical verticalArrangement = Arrangement.Top, androidx.ui.core.Alignment.Horizontal horizontalGravity = Alignment.Start, boolean reverseScrollDirection = false, boolean isScrollEnabled = true, androidx.ui.layout.InnerPadding contentPadding = androidx.ui.layout.InnerPadding(0.dp), kotlin.jvm.functions.Function1<? super androidx.ui.layout.ColumnScope,kotlin.Unit> children);
+    method @androidx.compose.Composable public static void ScrollableRow(androidx.ui.core.Modifier modifier = Modifier, androidx.ui.foundation.ScrollState scrollState = rememberScrollState(0.0), androidx.ui.layout.Arrangement.Horizontal horizontalArrangement = Arrangement.Start, androidx.ui.core.Alignment.Vertical verticalGravity = Alignment.Top, boolean reverseScrollDirection = false, boolean isScrollEnabled = true, androidx.ui.layout.InnerPadding contentPadding = androidx.ui.layout.InnerPadding(0.dp), kotlin.jvm.functions.Function1<? super androidx.ui.layout.RowScope,kotlin.Unit> children);
+    method @Deprecated @androidx.compose.Composable public static androidx.ui.foundation.ScrollerPosition ScrollerPosition(float initial = 0f, boolean isReversed = false);
+    method @Deprecated @androidx.compose.Composable public static void VerticalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = ScrollerPosition(), androidx.ui.core.Modifier modifier = Modifier, boolean isScrollable = true, kotlin.jvm.functions.Function1<? super androidx.ui.layout.ColumnScope,kotlin.Unit> children);
+    method public static androidx.ui.core.Modifier horizontalScroll(androidx.ui.core.Modifier, androidx.ui.foundation.ScrollState state, boolean enabled = true, boolean reverseScrolling = false);
+    method @androidx.compose.Composable public static androidx.ui.foundation.ScrollState rememberScrollState(float initial = 0f);
+    method public static androidx.ui.core.Modifier verticalScroll(androidx.ui.core.Modifier, androidx.ui.foundation.ScrollState state, boolean enabled = true, boolean reverseScrolling = false);
   }
 
-  @androidx.compose.Stable public final class ScrollerPosition {
-    ctor public ScrollerPosition(androidx.ui.foundation.animation.FlingConfig flingConfig, float initial, internal boolean isReversed, androidx.animation.AnimationClockObservable animationClock);
-    method public float getMaxPosition();
+  @androidx.compose.Stable public final class ScrollState {
+    ctor public ScrollState(float initial, internal androidx.ui.foundation.animation.FlingConfig flingConfig, androidx.animation.AnimationClockObservable animationClock);
+    method public float getMaxValue();
     method public float getValue();
-    method public boolean isAnimating();
+    method public boolean isAnimationRunning();
     method public void scrollBy(float value);
     method public void scrollTo(float value);
-    method public void smoothScrollBy(float value, kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>  _, _ ->  });
-    method public void smoothScrollTo(float value, kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>  _, _ ->  });
-    property public final boolean isAnimating;
-    property public final float maxPosition;
+    method public void smoothScrollBy(float value, androidx.animation.AnimationSpec<java.lang.Float> spec = androidx.animation.SpringSpec(), kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>  _, _ ->  });
+    method public void smoothScrollTo(float value, androidx.animation.AnimationSpec<java.lang.Float> spec = androidx.animation.SpringSpec(), kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>  _, _ ->  });
+    method public void stopAnimation();
+    property public final boolean isAnimationRunning;
+    property public final float maxValue;
     property public final float value;
-    field public static final androidx.ui.foundation.ScrollerPosition.Companion Companion;
+    field public static final androidx.ui.foundation.ScrollState.Companion Companion;
   }
 
-  public static final class ScrollerPosition.Companion {
-    method @androidx.compose.Composable public androidx.ui.savedinstancestate.Saver<androidx.ui.foundation.ScrollerPosition,?> Saver(androidx.ui.foundation.animation.FlingConfig flingConfig, boolean isReversed, androidx.animation.AnimationClockObservable animationClock);
+  public static final class ScrollState.Companion {
+    method public androidx.ui.savedinstancestate.Saver<androidx.ui.foundation.ScrollState,?> Saver(androidx.ui.foundation.animation.FlingConfig flingConfig, androidx.animation.AnimationClockObservable animationClock);
+  }
+
+  @Deprecated @androidx.compose.Stable public final class ScrollerPosition {
+    ctor @Deprecated public ScrollerPosition(internal androidx.ui.foundation.animation.FlingConfig flingConfig, float initial, internal boolean isReversed, internal androidx.animation.AnimationClockObservable animationClock);
+    method @Deprecated public float getMaxPosition();
+    method @Deprecated public float getValue();
+    method @Deprecated public void scrollBy(float value);
+    method @Deprecated public void scrollTo(float value);
+    method @Deprecated public void setMaxPosition(float newMax);
+    method @Deprecated public void setValue(float value);
+    method @Deprecated public void smoothScrollBy(float value, kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>  _, _ ->  });
+    method @Deprecated public void smoothScrollTo(float value, kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>  _, _ ->  });
+    property public final float maxPosition;
+    property public final float value;
+    field @Deprecated public static final androidx.ui.foundation.ScrollerPosition.Companion Companion;
+  }
+
+  @Deprecated public static final class ScrollerPosition.Companion {
+    method @Deprecated @androidx.compose.Composable public androidx.ui.savedinstancestate.Saver<androidx.ui.foundation.ScrollerPosition,?> Saver(androidx.ui.foundation.animation.FlingConfig flingConfig, boolean isReversed, androidx.animation.AnimationClockObservable animationClock);
   }
 
   public final class Strings {
diff --git a/ui/ui-foundation/api/public_plus_experimental_current.txt b/ui/ui-foundation/api/public_plus_experimental_current.txt
index 2fa0f01..cd9464e 100644
--- a/ui/ui-foundation/api/public_plus_experimental_current.txt
+++ b/ui/ui-foundation/api/public_plus_experimental_current.txt
@@ -119,29 +119,54 @@
   @kotlin.RequiresOptIn(message="This API is internal to library.") @kotlin.annotation.Target(allowedTargets={AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER}) public @interface InternalFoundationApi {
   }
 
-  public final class ScrollerKt {
-    method @androidx.compose.Composable public static void HorizontalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = ScrollerPosition(), androidx.ui.core.Modifier modifier = Modifier, boolean isScrollable = true, kotlin.jvm.functions.Function1<? super androidx.ui.layout.RowScope,kotlin.Unit> children);
-    method @androidx.compose.Composable public static androidx.ui.foundation.ScrollerPosition ScrollerPosition(float initial = 0f, boolean isReversed = false);
-    method @androidx.compose.Composable public static void VerticalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = ScrollerPosition(), androidx.ui.core.Modifier modifier = Modifier, boolean isScrollable = true, kotlin.jvm.functions.Function1<? super androidx.ui.layout.ColumnScope,kotlin.Unit> children);
+  public final class ScrollKt {
+    method @Deprecated @androidx.compose.Composable public static void HorizontalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = ScrollerPosition(), androidx.ui.core.Modifier modifier = Modifier, boolean isScrollable = true, kotlin.jvm.functions.Function1<? super androidx.ui.layout.RowScope,kotlin.Unit> children);
+    method @androidx.compose.Composable public static void ScrollableColumn(androidx.ui.core.Modifier modifier = Modifier, androidx.ui.foundation.ScrollState scrollState = rememberScrollState(0.0), androidx.ui.layout.Arrangement.Vertical verticalArrangement = Arrangement.Top, androidx.ui.core.Alignment.Horizontal horizontalGravity = Alignment.Start, boolean reverseScrollDirection = false, boolean isScrollEnabled = true, androidx.ui.layout.InnerPadding contentPadding = androidx.ui.layout.InnerPadding(0.dp), kotlin.jvm.functions.Function1<? super androidx.ui.layout.ColumnScope,kotlin.Unit> children);
+    method @androidx.compose.Composable public static void ScrollableRow(androidx.ui.core.Modifier modifier = Modifier, androidx.ui.foundation.ScrollState scrollState = rememberScrollState(0.0), androidx.ui.layout.Arrangement.Horizontal horizontalArrangement = Arrangement.Start, androidx.ui.core.Alignment.Vertical verticalGravity = Alignment.Top, boolean reverseScrollDirection = false, boolean isScrollEnabled = true, androidx.ui.layout.InnerPadding contentPadding = androidx.ui.layout.InnerPadding(0.dp), kotlin.jvm.functions.Function1<? super androidx.ui.layout.RowScope,kotlin.Unit> children);
+    method @Deprecated @androidx.compose.Composable public static androidx.ui.foundation.ScrollerPosition ScrollerPosition(float initial = 0f, boolean isReversed = false);
+    method @Deprecated @androidx.compose.Composable public static void VerticalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = ScrollerPosition(), androidx.ui.core.Modifier modifier = Modifier, boolean isScrollable = true, kotlin.jvm.functions.Function1<? super androidx.ui.layout.ColumnScope,kotlin.Unit> children);
+    method public static androidx.ui.core.Modifier horizontalScroll(androidx.ui.core.Modifier, androidx.ui.foundation.ScrollState state, boolean enabled = true, boolean reverseScrolling = false);
+    method @androidx.compose.Composable public static androidx.ui.foundation.ScrollState rememberScrollState(float initial = 0f);
+    method public static androidx.ui.core.Modifier verticalScroll(androidx.ui.core.Modifier, androidx.ui.foundation.ScrollState state, boolean enabled = true, boolean reverseScrolling = false);
   }
 
-  @androidx.compose.Stable public final class ScrollerPosition {
-    ctor public ScrollerPosition(androidx.ui.foundation.animation.FlingConfig flingConfig, float initial, internal boolean isReversed, androidx.animation.AnimationClockObservable animationClock);
-    method public float getMaxPosition();
+  @androidx.compose.Stable public final class ScrollState {
+    ctor public ScrollState(float initial, internal androidx.ui.foundation.animation.FlingConfig flingConfig, androidx.animation.AnimationClockObservable animationClock);
+    method public float getMaxValue();
     method public float getValue();
-    method public boolean isAnimating();
+    method public boolean isAnimationRunning();
     method public void scrollBy(float value);
     method public void scrollTo(float value);
-    method public void smoothScrollBy(float value, kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>  _, _ ->  });
-    method public void smoothScrollTo(float value, kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>  _, _ ->  });
-    property public final boolean isAnimating;
-    property public final float maxPosition;
+    method public void smoothScrollBy(float value, androidx.animation.AnimationSpec<java.lang.Float> spec = androidx.animation.SpringSpec(), kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>  _, _ ->  });
+    method public void smoothScrollTo(float value, androidx.animation.AnimationSpec<java.lang.Float> spec = androidx.animation.SpringSpec(), kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>  _, _ ->  });
+    method public void stopAnimation();
+    property public final boolean isAnimationRunning;
+    property public final float maxValue;
     property public final float value;
-    field public static final androidx.ui.foundation.ScrollerPosition.Companion Companion;
+    field public static final androidx.ui.foundation.ScrollState.Companion Companion;
   }
 
-  public static final class ScrollerPosition.Companion {
-    method @androidx.compose.Composable public androidx.ui.savedinstancestate.Saver<androidx.ui.foundation.ScrollerPosition,?> Saver(androidx.ui.foundation.animation.FlingConfig flingConfig, boolean isReversed, androidx.animation.AnimationClockObservable animationClock);
+  public static final class ScrollState.Companion {
+    method public androidx.ui.savedinstancestate.Saver<androidx.ui.foundation.ScrollState,?> Saver(androidx.ui.foundation.animation.FlingConfig flingConfig, androidx.animation.AnimationClockObservable animationClock);
+  }
+
+  @Deprecated @androidx.compose.Stable public final class ScrollerPosition {
+    ctor @Deprecated public ScrollerPosition(internal androidx.ui.foundation.animation.FlingConfig flingConfig, float initial, internal boolean isReversed, internal androidx.animation.AnimationClockObservable animationClock);
+    method @Deprecated public float getMaxPosition();
+    method @Deprecated public float getValue();
+    method @Deprecated public void scrollBy(float value);
+    method @Deprecated public void scrollTo(float value);
+    method @Deprecated public void setMaxPosition(float newMax);
+    method @Deprecated public void setValue(float value);
+    method @Deprecated public void smoothScrollBy(float value, kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>  _, _ ->  });
+    method @Deprecated public void smoothScrollTo(float value, kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>  _, _ ->  });
+    property public final float maxPosition;
+    property public final float value;
+    field @Deprecated public static final androidx.ui.foundation.ScrollerPosition.Companion Companion;
+  }
+
+  @Deprecated public static final class ScrollerPosition.Companion {
+    method @Deprecated @androidx.compose.Composable public androidx.ui.savedinstancestate.Saver<androidx.ui.foundation.ScrollerPosition,?> Saver(androidx.ui.foundation.animation.FlingConfig flingConfig, boolean isReversed, androidx.animation.AnimationClockObservable animationClock);
   }
 
   public final class Strings {
diff --git a/ui/ui-foundation/api/restricted_0.1.0-dev15.txt b/ui/ui-foundation/api/restricted_0.1.0-dev15.txt
index 2fa0f01..cd9464e 100644
--- a/ui/ui-foundation/api/restricted_0.1.0-dev15.txt
+++ b/ui/ui-foundation/api/restricted_0.1.0-dev15.txt
@@ -119,29 +119,54 @@
   @kotlin.RequiresOptIn(message="This API is internal to library.") @kotlin.annotation.Target(allowedTargets={AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER}) public @interface InternalFoundationApi {
   }
 
-  public final class ScrollerKt {
-    method @androidx.compose.Composable public static void HorizontalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = ScrollerPosition(), androidx.ui.core.Modifier modifier = Modifier, boolean isScrollable = true, kotlin.jvm.functions.Function1<? super androidx.ui.layout.RowScope,kotlin.Unit> children);
-    method @androidx.compose.Composable public static androidx.ui.foundation.ScrollerPosition ScrollerPosition(float initial = 0f, boolean isReversed = false);
-    method @androidx.compose.Composable public static void VerticalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = ScrollerPosition(), androidx.ui.core.Modifier modifier = Modifier, boolean isScrollable = true, kotlin.jvm.functions.Function1<? super androidx.ui.layout.ColumnScope,kotlin.Unit> children);
+  public final class ScrollKt {
+    method @Deprecated @androidx.compose.Composable public static void HorizontalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = ScrollerPosition(), androidx.ui.core.Modifier modifier = Modifier, boolean isScrollable = true, kotlin.jvm.functions.Function1<? super androidx.ui.layout.RowScope,kotlin.Unit> children);
+    method @androidx.compose.Composable public static void ScrollableColumn(androidx.ui.core.Modifier modifier = Modifier, androidx.ui.foundation.ScrollState scrollState = rememberScrollState(0.0), androidx.ui.layout.Arrangement.Vertical verticalArrangement = Arrangement.Top, androidx.ui.core.Alignment.Horizontal horizontalGravity = Alignment.Start, boolean reverseScrollDirection = false, boolean isScrollEnabled = true, androidx.ui.layout.InnerPadding contentPadding = androidx.ui.layout.InnerPadding(0.dp), kotlin.jvm.functions.Function1<? super androidx.ui.layout.ColumnScope,kotlin.Unit> children);
+    method @androidx.compose.Composable public static void ScrollableRow(androidx.ui.core.Modifier modifier = Modifier, androidx.ui.foundation.ScrollState scrollState = rememberScrollState(0.0), androidx.ui.layout.Arrangement.Horizontal horizontalArrangement = Arrangement.Start, androidx.ui.core.Alignment.Vertical verticalGravity = Alignment.Top, boolean reverseScrollDirection = false, boolean isScrollEnabled = true, androidx.ui.layout.InnerPadding contentPadding = androidx.ui.layout.InnerPadding(0.dp), kotlin.jvm.functions.Function1<? super androidx.ui.layout.RowScope,kotlin.Unit> children);
+    method @Deprecated @androidx.compose.Composable public static androidx.ui.foundation.ScrollerPosition ScrollerPosition(float initial = 0f, boolean isReversed = false);
+    method @Deprecated @androidx.compose.Composable public static void VerticalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = ScrollerPosition(), androidx.ui.core.Modifier modifier = Modifier, boolean isScrollable = true, kotlin.jvm.functions.Function1<? super androidx.ui.layout.ColumnScope,kotlin.Unit> children);
+    method public static androidx.ui.core.Modifier horizontalScroll(androidx.ui.core.Modifier, androidx.ui.foundation.ScrollState state, boolean enabled = true, boolean reverseScrolling = false);
+    method @androidx.compose.Composable public static androidx.ui.foundation.ScrollState rememberScrollState(float initial = 0f);
+    method public static androidx.ui.core.Modifier verticalScroll(androidx.ui.core.Modifier, androidx.ui.foundation.ScrollState state, boolean enabled = true, boolean reverseScrolling = false);
   }
 
-  @androidx.compose.Stable public final class ScrollerPosition {
-    ctor public ScrollerPosition(androidx.ui.foundation.animation.FlingConfig flingConfig, float initial, internal boolean isReversed, androidx.animation.AnimationClockObservable animationClock);
-    method public float getMaxPosition();
+  @androidx.compose.Stable public final class ScrollState {
+    ctor public ScrollState(float initial, internal androidx.ui.foundation.animation.FlingConfig flingConfig, androidx.animation.AnimationClockObservable animationClock);
+    method public float getMaxValue();
     method public float getValue();
-    method public boolean isAnimating();
+    method public boolean isAnimationRunning();
     method public void scrollBy(float value);
     method public void scrollTo(float value);
-    method public void smoothScrollBy(float value, kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>  _, _ ->  });
-    method public void smoothScrollTo(float value, kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>  _, _ ->  });
-    property public final boolean isAnimating;
-    property public final float maxPosition;
+    method public void smoothScrollBy(float value, androidx.animation.AnimationSpec<java.lang.Float> spec = androidx.animation.SpringSpec(), kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>  _, _ ->  });
+    method public void smoothScrollTo(float value, androidx.animation.AnimationSpec<java.lang.Float> spec = androidx.animation.SpringSpec(), kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>  _, _ ->  });
+    method public void stopAnimation();
+    property public final boolean isAnimationRunning;
+    property public final float maxValue;
     property public final float value;
-    field public static final androidx.ui.foundation.ScrollerPosition.Companion Companion;
+    field public static final androidx.ui.foundation.ScrollState.Companion Companion;
   }
 
-  public static final class ScrollerPosition.Companion {
-    method @androidx.compose.Composable public androidx.ui.savedinstancestate.Saver<androidx.ui.foundation.ScrollerPosition,?> Saver(androidx.ui.foundation.animation.FlingConfig flingConfig, boolean isReversed, androidx.animation.AnimationClockObservable animationClock);
+  public static final class ScrollState.Companion {
+    method public androidx.ui.savedinstancestate.Saver<androidx.ui.foundation.ScrollState,?> Saver(androidx.ui.foundation.animation.FlingConfig flingConfig, androidx.animation.AnimationClockObservable animationClock);
+  }
+
+  @Deprecated @androidx.compose.Stable public final class ScrollerPosition {
+    ctor @Deprecated public ScrollerPosition(internal androidx.ui.foundation.animation.FlingConfig flingConfig, float initial, internal boolean isReversed, internal androidx.animation.AnimationClockObservable animationClock);
+    method @Deprecated public float getMaxPosition();
+    method @Deprecated public float getValue();
+    method @Deprecated public void scrollBy(float value);
+    method @Deprecated public void scrollTo(float value);
+    method @Deprecated public void setMaxPosition(float newMax);
+    method @Deprecated public void setValue(float value);
+    method @Deprecated public void smoothScrollBy(float value, kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>  _, _ ->  });
+    method @Deprecated public void smoothScrollTo(float value, kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>  _, _ ->  });
+    property public final float maxPosition;
+    property public final float value;
+    field @Deprecated public static final androidx.ui.foundation.ScrollerPosition.Companion Companion;
+  }
+
+  @Deprecated public static final class ScrollerPosition.Companion {
+    method @Deprecated @androidx.compose.Composable public androidx.ui.savedinstancestate.Saver<androidx.ui.foundation.ScrollerPosition,?> Saver(androidx.ui.foundation.animation.FlingConfig flingConfig, boolean isReversed, androidx.animation.AnimationClockObservable animationClock);
   }
 
   public final class Strings {
diff --git a/ui/ui-foundation/api/restricted_current.txt b/ui/ui-foundation/api/restricted_current.txt
index 2fa0f01..cd9464e 100644
--- a/ui/ui-foundation/api/restricted_current.txt
+++ b/ui/ui-foundation/api/restricted_current.txt
@@ -119,29 +119,54 @@
   @kotlin.RequiresOptIn(message="This API is internal to library.") @kotlin.annotation.Target(allowedTargets={AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER}) public @interface InternalFoundationApi {
   }
 
-  public final class ScrollerKt {
-    method @androidx.compose.Composable public static void HorizontalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = ScrollerPosition(), androidx.ui.core.Modifier modifier = Modifier, boolean isScrollable = true, kotlin.jvm.functions.Function1<? super androidx.ui.layout.RowScope,kotlin.Unit> children);
-    method @androidx.compose.Composable public static androidx.ui.foundation.ScrollerPosition ScrollerPosition(float initial = 0f, boolean isReversed = false);
-    method @androidx.compose.Composable public static void VerticalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = ScrollerPosition(), androidx.ui.core.Modifier modifier = Modifier, boolean isScrollable = true, kotlin.jvm.functions.Function1<? super androidx.ui.layout.ColumnScope,kotlin.Unit> children);
+  public final class ScrollKt {
+    method @Deprecated @androidx.compose.Composable public static void HorizontalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = ScrollerPosition(), androidx.ui.core.Modifier modifier = Modifier, boolean isScrollable = true, kotlin.jvm.functions.Function1<? super androidx.ui.layout.RowScope,kotlin.Unit> children);
+    method @androidx.compose.Composable public static void ScrollableColumn(androidx.ui.core.Modifier modifier = Modifier, androidx.ui.foundation.ScrollState scrollState = rememberScrollState(0.0), androidx.ui.layout.Arrangement.Vertical verticalArrangement = Arrangement.Top, androidx.ui.core.Alignment.Horizontal horizontalGravity = Alignment.Start, boolean reverseScrollDirection = false, boolean isScrollEnabled = true, androidx.ui.layout.InnerPadding contentPadding = androidx.ui.layout.InnerPadding(0.dp), kotlin.jvm.functions.Function1<? super androidx.ui.layout.ColumnScope,kotlin.Unit> children);
+    method @androidx.compose.Composable public static void ScrollableRow(androidx.ui.core.Modifier modifier = Modifier, androidx.ui.foundation.ScrollState scrollState = rememberScrollState(0.0), androidx.ui.layout.Arrangement.Horizontal horizontalArrangement = Arrangement.Start, androidx.ui.core.Alignment.Vertical verticalGravity = Alignment.Top, boolean reverseScrollDirection = false, boolean isScrollEnabled = true, androidx.ui.layout.InnerPadding contentPadding = androidx.ui.layout.InnerPadding(0.dp), kotlin.jvm.functions.Function1<? super androidx.ui.layout.RowScope,kotlin.Unit> children);
+    method @Deprecated @androidx.compose.Composable public static androidx.ui.foundation.ScrollerPosition ScrollerPosition(float initial = 0f, boolean isReversed = false);
+    method @Deprecated @androidx.compose.Composable public static void VerticalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = ScrollerPosition(), androidx.ui.core.Modifier modifier = Modifier, boolean isScrollable = true, kotlin.jvm.functions.Function1<? super androidx.ui.layout.ColumnScope,kotlin.Unit> children);
+    method public static androidx.ui.core.Modifier horizontalScroll(androidx.ui.core.Modifier, androidx.ui.foundation.ScrollState state, boolean enabled = true, boolean reverseScrolling = false);
+    method @androidx.compose.Composable public static androidx.ui.foundation.ScrollState rememberScrollState(float initial = 0f);
+    method public static androidx.ui.core.Modifier verticalScroll(androidx.ui.core.Modifier, androidx.ui.foundation.ScrollState state, boolean enabled = true, boolean reverseScrolling = false);
   }
 
-  @androidx.compose.Stable public final class ScrollerPosition {
-    ctor public ScrollerPosition(androidx.ui.foundation.animation.FlingConfig flingConfig, float initial, internal boolean isReversed, androidx.animation.AnimationClockObservable animationClock);
-    method public float getMaxPosition();
+  @androidx.compose.Stable public final class ScrollState {
+    ctor public ScrollState(float initial, internal androidx.ui.foundation.animation.FlingConfig flingConfig, androidx.animation.AnimationClockObservable animationClock);
+    method public float getMaxValue();
     method public float getValue();
-    method public boolean isAnimating();
+    method public boolean isAnimationRunning();
     method public void scrollBy(float value);
     method public void scrollTo(float value);
-    method public void smoothScrollBy(float value, kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>  _, _ ->  });
-    method public void smoothScrollTo(float value, kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>  _, _ ->  });
-    property public final boolean isAnimating;
-    property public final float maxPosition;
+    method public void smoothScrollBy(float value, androidx.animation.AnimationSpec<java.lang.Float> spec = androidx.animation.SpringSpec(), kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>  _, _ ->  });
+    method public void smoothScrollTo(float value, androidx.animation.AnimationSpec<java.lang.Float> spec = androidx.animation.SpringSpec(), kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>  _, _ ->  });
+    method public void stopAnimation();
+    property public final boolean isAnimationRunning;
+    property public final float maxValue;
     property public final float value;
-    field public static final androidx.ui.foundation.ScrollerPosition.Companion Companion;
+    field public static final androidx.ui.foundation.ScrollState.Companion Companion;
   }
 
-  public static final class ScrollerPosition.Companion {
-    method @androidx.compose.Composable public androidx.ui.savedinstancestate.Saver<androidx.ui.foundation.ScrollerPosition,?> Saver(androidx.ui.foundation.animation.FlingConfig flingConfig, boolean isReversed, androidx.animation.AnimationClockObservable animationClock);
+  public static final class ScrollState.Companion {
+    method public androidx.ui.savedinstancestate.Saver<androidx.ui.foundation.ScrollState,?> Saver(androidx.ui.foundation.animation.FlingConfig flingConfig, androidx.animation.AnimationClockObservable animationClock);
+  }
+
+  @Deprecated @androidx.compose.Stable public final class ScrollerPosition {
+    ctor @Deprecated public ScrollerPosition(internal androidx.ui.foundation.animation.FlingConfig flingConfig, float initial, internal boolean isReversed, internal androidx.animation.AnimationClockObservable animationClock);
+    method @Deprecated public float getMaxPosition();
+    method @Deprecated public float getValue();
+    method @Deprecated public void scrollBy(float value);
+    method @Deprecated public void scrollTo(float value);
+    method @Deprecated public void setMaxPosition(float newMax);
+    method @Deprecated public void setValue(float value);
+    method @Deprecated public void smoothScrollBy(float value, kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>  _, _ ->  });
+    method @Deprecated public void smoothScrollTo(float value, kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>  _, _ ->  });
+    property public final float maxPosition;
+    property public final float value;
+    field @Deprecated public static final androidx.ui.foundation.ScrollerPosition.Companion Companion;
+  }
+
+  @Deprecated public static final class ScrollerPosition.Companion {
+    method @Deprecated @androidx.compose.Composable public androidx.ui.savedinstancestate.Saver<androidx.ui.foundation.ScrollerPosition,?> Saver(androidx.ui.foundation.animation.FlingConfig flingConfig, boolean isReversed, androidx.animation.AnimationClockObservable animationClock);
   }
 
   public final class Strings {
diff --git a/ui/ui-foundation/integration-tests/foundation-demos/src/main/java/androidx/ui/foundation/demos/FoundationDemos.kt b/ui/ui-foundation/integration-tests/foundation-demos/src/main/java/androidx/ui/foundation/demos/FoundationDemos.kt
index 7b74a18..520b1bb3 100644
--- a/ui/ui-foundation/integration-tests/foundation-demos/src/main/java/androidx/ui/foundation/demos/FoundationDemos.kt
+++ b/ui/ui-foundation/integration-tests/foundation-demos/src/main/java/androidx/ui/foundation/demos/FoundationDemos.kt
@@ -18,15 +18,15 @@
 
 import androidx.ui.demos.common.ComposableDemo
 import androidx.ui.demos.common.DemoCategory
-import androidx.ui.foundation.samples.ControlledHorizontalScrollerSample
+import androidx.ui.foundation.samples.ControlledScrollableRowSample
 import androidx.ui.foundation.samples.DialogSample
 import androidx.ui.foundation.samples.InteractionStateSample
-import androidx.ui.foundation.samples.VerticalScrollerSample
+import androidx.ui.foundation.samples.ScrollableColumnSample
 
 val FoundationDemos = DemoCategory("Foundation", listOf(
     ComposableDemo("Draggable and Scrollable") { HighLevelGesturesDemo() },
-    ComposableDemo("VerticalScroller") { VerticalScrollerSample() },
-    ComposableDemo("HorizontalScroller") { ControlledHorizontalScrollerSample() },
+    ComposableDemo("Scrollable Column") { ScrollableColumnSample() },
+    ComposableDemo("Controlled Scrollable Row") { ControlledScrollableRowSample() },
     ComposableDemo("Dialog") { DialogSample() },
     ComposableDemo("Draw Modifiers") { DrawModifiersDemo() },
     ComposableDemo("Boxes") { BoxDemo() },
diff --git a/ui/ui-foundation/samples/src/main/java/androidx/ui/foundation/samples/ScrollerSamples.kt b/ui/ui-foundation/samples/src/main/java/androidx/ui/foundation/samples/ScrollerSamples.kt
index 10f793b..c27e0ee 100644
--- a/ui/ui-foundation/samples/src/main/java/androidx/ui/foundation/samples/ScrollerSamples.kt
+++ b/ui/ui-foundation/samples/src/main/java/androidx/ui/foundation/samples/ScrollerSamples.kt
@@ -18,22 +18,29 @@
 
 import androidx.annotation.Sampled
 import androidx.compose.Composable
-import androidx.compose.MutableState
-import androidx.compose.state
+import androidx.ui.core.Alignment
 import androidx.ui.core.Modifier
 import androidx.ui.foundation.Box
 import androidx.ui.foundation.ContentGravity
-import androidx.ui.foundation.HorizontalScroller
-import androidx.ui.foundation.ScrollerPosition
+import androidx.ui.foundation.ScrollableColumn
+import androidx.ui.foundation.ScrollableRow
 import androidx.ui.foundation.Text
-import androidx.ui.foundation.VerticalScroller
 import androidx.ui.foundation.clickable
+import androidx.ui.foundation.drawBackground
+import androidx.ui.foundation.horizontalScroll
+import androidx.ui.foundation.rememberScrollState
+import androidx.ui.foundation.verticalScroll
 import androidx.ui.graphics.Color
+import androidx.ui.graphics.HorizontalGradient
+import androidx.ui.graphics.TileMode
+import androidx.ui.graphics.VerticalGradient
 import androidx.ui.layout.Column
 import androidx.ui.layout.Row
+import androidx.ui.layout.fillMaxWidth
+import androidx.ui.layout.height
 import androidx.ui.layout.padding
 import androidx.ui.layout.preferredSize
-import androidx.ui.text.TextStyle
+import androidx.ui.layout.size
 import androidx.ui.unit.dp
 import androidx.ui.unit.sp
 
@@ -80,45 +87,85 @@
 
 @Sampled
 @Composable
-fun VerticalScrollerSample() {
-    val style = TextStyle(fontSize = 30.sp)
-    // Scroller will be clipped to this padding
-    VerticalScroller {
+fun HorizontalScrollSample() {
+    val scrollState = rememberScrollState()
+    val gradient = HorizontalGradient(
+        listOf(Color.Red, Color.Blue, Color.Green), 0.0f, 10000.0f, TileMode.Repeated
+    )
+    Box(
+        Modifier
+            .horizontalScroll(scrollState)
+            .size(width = 10000.dp, height = 200.dp)
+            .drawBackground(gradient)
+    )
+}
+
+@Sampled
+@Composable
+fun VerticalScrollExample() {
+    val scrollState = rememberScrollState()
+    val gradient = VerticalGradient(
+        listOf(Color.Red, Color.Blue, Color.Green), 0.0f, 10000.0f, TileMode.Repeated
+    )
+    Box(
+        Modifier
+            .verticalScroll(scrollState)
+            .fillMaxWidth()
+            .height(10000.dp)
+            .drawBackground(gradient)
+    )
+}
+
+@Sampled
+@Composable
+fun ScrollableColumnSample() {
+    ScrollableColumn {
         phrases.forEach { phrase ->
-            Text(phrase, style)
+            Text(phrase, fontSize = 30.sp)
         }
     }
 }
 
 @Sampled
 @Composable
-fun SimpleHorizontalScrollerSample() {
-    HorizontalScroller {
-        Row {
-            repeat(100) { index ->
-                Square(index)
-            }
+fun ScrollableRowSample() {
+    ScrollableRow {
+        repeat(100) { index ->
+            Square(index)
         }
     }
 }
 
 @Sampled
 @Composable
-fun ControlledHorizontalScrollerSample() {
-    // Create and own ScrollerPosition to call `smoothScrollTo` later
-    val position = ScrollerPosition()
-    val scrollable = state { true }
+fun ControlledScrollableRowSample() {
+    // Create ScrollState to own it and be able to control scroll behaviour of ScrollableRow below
+    val scrollState = rememberScrollState()
     Column {
-        HorizontalScroller(
-            scrollerPosition = position,
-            isScrollable = scrollable.value
-        ) {
+        ScrollableRow(scrollState = scrollState) {
             repeat(1000) { index ->
                 Square(index)
             }
         }
-        // Controls that will call `smoothScrollTo`, `scrollTo` or toggle `scrollable` state
-        ScrollControl(position, scrollable)
+        // Controls for scrolling
+        Row(verticalGravity = Alignment.CenterVertically) {
+            Text("Scroll")
+            Button( scrollState.scrollTo(scrollState.value - 1000) }) {
+                Text("< -")
+            }
+            Button( scrollState.scrollBy(10000f) }) {
+                Text("--- >")
+            }
+        }
+        Row(verticalGravity = Alignment.CenterVertically) {
+            Text("Smooth Scroll")
+            Button( scrollState.smoothScrollTo(scrollState.value - 1000) }) {
+                Text("< -")
+            }
+            Button( scrollState.smoothScrollBy(10000f) }) {
+                Text("--- >")
+            }
+        }
     }
 }
 
@@ -134,46 +181,13 @@
 }
 
 @Composable
-private fun ScrollControl(position: ScrollerPosition, scrollable: MutableState<Boolean>) {
-    Column {
-        Row {
-            Text("Scroll")
-            SquareButton("< -", Color.Red) {
-                position.scrollTo(position.value - 1000)
-            }
-            SquareButton("--- >", Color.Green) {
-                position.scrollBy(10000f)
-            }
-        }
-        Row {
-            Text("Smooth Scroll")
-            SquareButton("< -", Color.Red) {
-                position.smoothScrollTo(position.value - 1000)
-            }
-            SquareButton("--- >", Color.Green) {
-                position.smoothScrollBy(10000f)
-            }
-        }
-        Row {
-            SquareButton("Scroll: ${scrollable.value}") {
-                scrollable.value = !scrollable.value
-            }
-        }
-    }
-}
-
-@Composable
-private fun SquareButton(text: String, color: Color = Color.LightGray, onClick: () -> Unit) {
+private fun Button(onClick: () -> Unit, text: @Composable () -> Unit) {
     Box(
-        Modifier.padding(5.dp).preferredSize(120.dp, 60.dp).clickable(>
-        backgroundColor = color,
-        gravity = ContentGravity.Center
-    ) {
-        Text(text, fontSize = 20.sp)
-    }
-}
-
-@Composable
-private fun Text(text: String, style: TextStyle) {
-    Text(text, style = style, modifier = Modifier.clickable { })
+        Modifier.padding(5.dp)
+            .preferredSize(120.dp, 60.dp)
+            .clickable(>
+            .drawBackground(Color.LightGray),
+        gravity = ContentGravity.Center,
+        children = text
+    )
 }
\ No newline at end of file
diff --git a/ui/ui-foundation/src/androidAndroidTest/kotlin/androidx/ui/foundation/ScrollerTest.kt b/ui/ui-foundation/src/androidAndroidTest/kotlin/androidx/ui/foundation/ScrollTest.kt
similarity index 67%
rename from ui/ui-foundation/src/androidAndroidTest/kotlin/androidx/ui/foundation/ScrollerTest.kt
rename to ui/ui-foundation/src/androidAndroidTest/kotlin/androidx/ui/foundation/ScrollTest.kt
index 138f7df..07027f9 100644
--- a/ui/ui-foundation/src/androidAndroidTest/kotlin/androidx/ui/foundation/ScrollerTest.kt
+++ b/ui/ui-foundation/src/androidAndroidTest/kotlin/androidx/ui/foundation/ScrollTest.kt
@@ -67,7 +67,7 @@
 
 @SmallTest
 @RunWith(JUnit4::class)
-class ScrollerTest {
+class ScrollTest {
 
     @get:Rule
     val composeTestRule = createComposeRule()
@@ -101,15 +101,16 @@
 
     @Test
     fun verticalScroller_SmallContent_Unscrollable() {
-        val scrollerPosition = ScrollerPosition(
-            FlingConfig(ExponentialDecay()),
+        val scrollState = ScrollState(
+            initial = 0f,
+            flingConfig = FlingConfig(ExponentialDecay()),
             animationClock = ManualAnimationClock(0)
         )
 
-        composeVerticalScroller(scrollerPosition)
+        composeVerticalScroller(scrollState)
 
         runOnIdle {
-            assertTrue(scrollerPosition.maxPosition == 0f)
+            assertTrue(scrollState.maxValue == 0f)
         }
     }
 
@@ -126,20 +127,21 @@
     @SdkSuppress(minSdkVersion = 26)
     @Test
     fun verticalScroller_LargeContent_ScrollToEnd() {
-        val scrollerPosition = ScrollerPosition(
-            FlingConfig(ExponentialDecay()),
+        val scrollState = ScrollState(
+            initial = 0f,
+            flingConfig = FlingConfig(ExponentialDecay()),
             animationClock = ManualAnimationClock(0)
         )
         val height = 30
         val scrollDistance = 10
 
-        composeVerticalScroller(scrollerPosition, height = height)
+        composeVerticalScroller(scrollState, height = height)
 
         validateVerticalScroller(height = height)
 
         runOnIdle {
-            assertEquals(scrollDistance.toFloat(), scrollerPosition.maxPosition)
-            scrollerPosition.scrollTo(scrollDistance.toFloat())
+            assertEquals(scrollDistance.toFloat(), scrollState.maxValue)
+            scrollState.scrollTo(scrollDistance.toFloat())
         }
 
         runOnIdle {} // Just so the block below is correct
@@ -149,15 +151,15 @@
     @SdkSuppress(minSdkVersion = 26)
     @Test
     fun verticalScroller_Reversed() {
-        val scrollerPosition = ScrollerPosition(
-            FlingConfig(ExponentialDecay()),
-            animationClock = ManualAnimationClock(0),
-            isReversed = true
+        val scrollState = ScrollState(
+            initial = 0f,
+            flingConfig = FlingConfig(ExponentialDecay()),
+            animationClock = ManualAnimationClock(0)
         )
         val height = 30
         val expectedOffset = defaultCellSize * colors.size - height
 
-        composeVerticalScroller(scrollerPosition, height = height)
+        composeVerticalScroller(scrollState, height = height, isReversed = true)
 
         validateVerticalScroller(offset = expectedOffset, height = height)
     }
@@ -165,19 +167,19 @@
     @SdkSuppress(minSdkVersion = 26)
     @Test
     fun verticalScroller_LargeContent_Reversed_ScrollToEnd() {
-        val scrollerPosition = ScrollerPosition(
-            FlingConfig(ExponentialDecay()),
-            animationClock = ManualAnimationClock(0),
-            isReversed = true
+        val scrollState = ScrollState(
+            initial = 0f,
+            flingConfig = FlingConfig(ExponentialDecay()),
+            animationClock = ManualAnimationClock(0)
         )
         val height = 20
         val scrollDistance = 10
         val expectedOffset = defaultCellSize * colors.size - height - scrollDistance
 
-        composeVerticalScroller(scrollerPosition, height = height)
+        composeVerticalScroller(scrollState, height = height, isReversed = true)
 
         runOnIdle {
-            scrollerPosition.scrollTo(scrollDistance.toFloat())
+            scrollState.scrollTo(scrollDistance.toFloat())
         }
 
         runOnIdle {} // Just so the block below is correct
@@ -210,18 +212,19 @@
         val width = 30
         val scrollDistance = 10
 
-        val scrollerPosition = ScrollerPosition(
-            FlingConfig(ExponentialDecay()),
+        val scrollState = ScrollState(
+            initial = 0f,
+            flingConfig = FlingConfig(ExponentialDecay()),
             animationClock = ManualAnimationClock(0)
         )
 
-        composeHorizontalScroller(scrollerPosition, width = width)
+        composeHorizontalScroller(scrollState, width = width)
 
         validateHorizontalScroller(width = width)
 
         runOnIdle {
-            assertEquals(scrollDistance.toFloat(), scrollerPosition.maxPosition)
-            scrollerPosition.scrollTo(scrollDistance.toFloat())
+            assertEquals(scrollDistance.toFloat(), scrollState.maxValue)
+            scrollState.scrollTo(scrollDistance.toFloat())
         }
 
         runOnIdle {} // Just so the block below is correct
@@ -231,15 +234,15 @@
     @SdkSuppress(minSdkVersion = 26)
     @Test
     fun horizontalScroller_reversed() {
-        val scrollerPosition = ScrollerPosition(
-            FlingConfig(ExponentialDecay()),
-            animationClock = ManualAnimationClock(0),
-            isReversed = true
+        val scrollState = ScrollState(
+            initial = 0f,
+            flingConfig = FlingConfig(ExponentialDecay()),
+            animationClock = ManualAnimationClock(0)
         )
         val width = 30
         val expectedOffset = defaultCellSize * colors.size - width
 
-        composeHorizontalScroller(scrollerPosition, width = width)
+        composeHorizontalScroller(scrollState, width = width, isReversed = true)
 
         validateHorizontalScroller(offset = expectedOffset, width = width)
     }
@@ -250,18 +253,18 @@
         val width = 30
         val scrollDistance = 10
 
-        val scrollerPosition = ScrollerPosition(
-            FlingConfig(ExponentialDecay()),
-            animationClock = ManualAnimationClock(0),
-            isReversed = true
+        val scrollState = ScrollState(
+            initial = 0f,
+            flingConfig = FlingConfig(ExponentialDecay()),
+            animationClock = ManualAnimationClock(0)
         )
 
         val expectedOffset = defaultCellSize * colors.size - width - scrollDistance
 
-        composeHorizontalScroller(scrollerPosition, width = width)
+        composeHorizontalScroller(scrollState, width = width, isReversed = true)
 
         runOnIdle {
-            scrollerPosition.scrollTo(scrollDistance.toFloat())
+            scrollState.scrollTo(scrollDistance.toFloat())
         }
 
         runOnIdle {} // Just so the block below is correct
@@ -292,11 +295,13 @@
     @Test
     fun verticalScroller_reversed_scrollTo_scrollForward() {
         createScrollableContent(
-            isVertical = true, scrollerPosition = ScrollerPosition(
-                FlingConfig(ExponentialDecay()),
-                animationClock = ManualAnimationClock(0),
-                isReversed = true
-            )
+            isVertical = true,
+            scrollState = ScrollState(
+                initial = 0f,
+                flingConfig = FlingConfig(ExponentialDecay()),
+                animationClock = ManualAnimationClock(0)
+            ),
+            isReversed = true
         )
 
         onNodeWithText("50")
@@ -309,11 +314,13 @@
     @Test
     fun horizontalScroller_reversed_scrollTo_scrollForward() {
         createScrollableContent(
-            isVertical = false, scrollerPosition = ScrollerPosition(
-                FlingConfig(ExponentialDecay()),
-                animationClock = ManualAnimationClock(0),
-                isReversed = true
-            )
+            isVertical = false,
+            scrollState = ScrollState(
+                initial = 0f,
+                flingConfig = FlingConfig(ExponentialDecay()),
+                animationClock = ManualAnimationClock(0)
+            ),
+            isReversed = true
         )
 
         onNodeWithText("50")
@@ -323,6 +330,7 @@
     }
 
     @Test
+    @Ignore("When b/157687898 is fixed, performScrollTo must be adjusted to use semantic bounds")
     fun verticalScroller_scrollTo_scrollBack() {
         createScrollableContent(isVertical = true)
 
@@ -338,6 +346,7 @@
     }
 
     @Test
+    @Ignore("When b/157687898 is fixed, performScrollTo must be adjusted to use semantic bounds")
     fun horizontalScroller_scrollTo_scrollBack() {
         createScrollableContent(isVertical = false)
 
@@ -365,61 +374,63 @@
     @Test
     fun scroller_coerce_whenScrollTo() {
         val clock = ManualAnimationClock(0)
-        val scrollerPosition = ScrollerPosition(
-            FlingConfig(ExponentialDecay()),
+        val scrollState = ScrollState(
+            initial = 0f,
+            flingConfig = FlingConfig(ExponentialDecay()),
             animationClock = clock
         )
 
-        createScrollableContent(isVertical = true, scrollerPosition = scrollerPosition)
+        createScrollableContent(isVertical = true, scrollState = scrollState)
 
         runOnIdle {
-            assertThat(scrollerPosition.value).isEqualTo(0f)
-            assertThat(scrollerPosition.maxPosition).isGreaterThan(0f)
+            assertThat(scrollState.value).isEqualTo(0f)
+            assertThat(scrollState.maxValue).isGreaterThan(0f)
         }
         runOnUiThread {
-            scrollerPosition.scrollTo(-100f)
+            scrollState.scrollTo(-100f)
         }
         runOnIdle {
-            assertThat(scrollerPosition.value).isEqualTo(0f)
+            assertThat(scrollState.value).isEqualTo(0f)
         }
         runOnUiThread {
-            scrollerPosition.scrollBy(-100f)
+            scrollState.scrollBy(-100f)
         }
         runOnIdle {
-            assertThat(scrollerPosition.value).isEqualTo(0f)
+            assertThat(scrollState.value).isEqualTo(0f)
         }
         runOnUiThread {
-            scrollerPosition.scrollTo(scrollerPosition.maxPosition)
+            scrollState.scrollTo(scrollState.maxValue)
         }
         runOnIdle {
-            assertThat(scrollerPosition.value).isEqualTo(scrollerPosition.maxPosition)
+            assertThat(scrollState.value).isEqualTo(scrollState.maxValue)
         }
         runOnUiThread {
-            scrollerPosition.scrollTo(scrollerPosition.maxPosition + 1000)
+            scrollState.scrollTo(scrollState.maxValue + 1000)
         }
         runOnIdle {
-            assertThat(scrollerPosition.value).isEqualTo(scrollerPosition.maxPosition)
+            assertThat(scrollState.value).isEqualTo(scrollState.maxValue)
         }
         runOnUiThread {
-            scrollerPosition.scrollBy(100f)
+            scrollState.scrollBy(100f)
         }
         runOnIdle {
-            assertThat(scrollerPosition.value).isEqualTo(scrollerPosition.maxPosition)
+            assertThat(scrollState.value).isEqualTo(scrollState.maxValue)
         }
     }
 
     @Test
     fun verticalScroller_LargeContent_coerceWhenMaxChanges() {
         val clock = ManualAnimationClock(0)
-        val scrollerPosition = ScrollerPosition(
-            FlingConfig(ExponentialDecay()),
+        val scrollState = ScrollState(
+            initial = 0f,
+            flingConfig = FlingConfig(ExponentialDecay()),
             animationClock = clock
         )
         val itemCount = mutableStateOf(100)
         composeTestRule.setContent {
             Stack {
-                VerticalScroller(
-                    scrollerPosition = scrollerPosition,
+                ScrollableColumn(
+                    scrollState = scrollState,
                     modifier = Modifier.preferredSize(100.dp).testTag(scrollerTag)
                 ) {
                     for (i in 0..itemCount.value) {
@@ -430,73 +441,75 @@
         }
 
         val max = runOnIdle {
-            assertThat(scrollerPosition.value).isEqualTo(0f)
-            assertThat(scrollerPosition.maxPosition).isGreaterThan(0f)
-            scrollerPosition.maxPosition
+            assertThat(scrollState.value).isEqualTo(0f)
+            assertThat(scrollState.maxValue).isGreaterThan(0f)
+            scrollState.maxValue
         }
 
         runOnUiThread {
-            scrollerPosition.scrollTo(max)
+            scrollState.scrollTo(max)
         }
         runOnUiThread {
             itemCount.value -= 2
         }
         runOnIdle {
-            val newMax = scrollerPosition.maxPosition
+            val newMax = scrollState.maxValue
             assertThat(newMax).isLessThan(max)
-            assertThat(scrollerPosition.value).isEqualTo(newMax)
+            assertThat(scrollState.value).isEqualTo(newMax)
         }
     }
 
     @Test
     fun scroller_coerce_whenScrollSmoothTo() {
         val clock = ManualAnimationClock(0)
-        val scrollerPosition = ScrollerPosition(
-            FlingConfig(ExponentialDecay()),
+        val scrollState = ScrollState(
+            initial = 0f,
+            flingConfig = FlingConfig(ExponentialDecay()),
             animationClock = clock
         )
 
-        createScrollableContent(isVertical = true, scrollerPosition = scrollerPosition)
+        createScrollableContent(isVertical = true, scrollState = scrollState)
 
         val max = runOnIdle {
-            assertThat(scrollerPosition.value).isEqualTo(0f)
-            assertThat(scrollerPosition.maxPosition).isGreaterThan(0f)
-            scrollerPosition.maxPosition
+            assertThat(scrollState.value).isEqualTo(0f)
+            assertThat(scrollState.maxValue).isGreaterThan(0f)
+            scrollState.maxValue
         }
 
-        performWithAnimationWaitAndAssertPosition(0f, scrollerPosition, clock) {
-            scrollerPosition.smoothScrollTo(-100f)
+        performWithAnimationWaitAndAssertPosition(0f, scrollState, clock) {
+            scrollState.smoothScrollTo(-100f)
         }
 
-        performWithAnimationWaitAndAssertPosition(0f, scrollerPosition, clock) {
-            scrollerPosition.smoothScrollBy(-100f)
+        performWithAnimationWaitAndAssertPosition(0f, scrollState, clock) {
+            scrollState.smoothScrollBy(-100f)
         }
 
-        performWithAnimationWaitAndAssertPosition(max, scrollerPosition, clock) {
-            scrollerPosition.smoothScrollTo(scrollerPosition.maxPosition)
+        performWithAnimationWaitAndAssertPosition(max, scrollState, clock) {
+            scrollState.smoothScrollTo(scrollState.maxValue)
         }
 
-        performWithAnimationWaitAndAssertPosition(max, scrollerPosition, clock) {
-            scrollerPosition.smoothScrollTo(scrollerPosition.maxPosition + 1000)
+        performWithAnimationWaitAndAssertPosition(max, scrollState, clock) {
+            scrollState.smoothScrollTo(scrollState.maxValue + 1000)
         }
-        performWithAnimationWaitAndAssertPosition(max, scrollerPosition, clock) {
-            scrollerPosition.smoothScrollBy(100f)
+        performWithAnimationWaitAndAssertPosition(max, scrollState, clock) {
+            scrollState.smoothScrollBy(100f)
         }
     }
 
     @Test
     fun scroller_whenFling_stopsByTouchDown() {
         val clock = ManualAnimationClock(0)
-        val scrollerPosition = ScrollerPosition(
-            FlingConfig(ExponentialDecay()),
+        val scrollState = ScrollState(
+            initial = 0f,
+            flingConfig = FlingConfig(ExponentialDecay()),
             animationClock = clock
         )
 
-        createScrollableContent(isVertical = true, scrollerPosition = scrollerPosition)
+        createScrollableContent(isVertical = true, scrollState = scrollState)
 
         runOnIdle {
-            assertThat(scrollerPosition.value).isEqualTo(0f)
-            assertThat(scrollerPosition.isAnimating).isEqualTo(false)
+            assertThat(scrollState.value).isEqualTo(0f)
+            assertThat(scrollState.isAnimationRunning).isEqualTo(false)
         }
 
         onNodeWithTag(scrollerTag)
@@ -504,7 +517,7 @@
 
         runOnIdle {
             clock.clockTimeMillis += 100
-            assertThat(scrollerPosition.isAnimating).isEqualTo(true)
+            assertThat(scrollState.isAnimationRunning).isEqualTo(true)
         }
 
         // TODO (matvei/jelle): this should be down, and not click to be 100% fair
@@ -512,18 +525,18 @@
             .performGesture { click() }
 
         runOnIdle {
-            assertThat(scrollerPosition.isAnimating).isEqualTo(false)
+            assertThat(scrollState.isAnimationRunning).isEqualTo(false)
         }
     }
 
     @Test
     fun scroller_restoresScrollerPosition() {
         val restorationTester = StateRestorationTester(composeTestRule)
-        var scrollerPosition: ScrollerPosition? = null
+        var scrollState: ScrollState? = null
 
         restorationTester.setContent {
-            scrollerPosition = ScrollerPosition()
-            VerticalScroller(scrollerPosition!!) {
+            scrollState = rememberScrollState()
+            ScrollableColumn(scrollState = scrollState!!) {
                 repeat(50) {
                     Box(Modifier.preferredHeight(100.dp))
                 }
@@ -531,20 +544,20 @@
         }
 
         runOnIdle {
-            scrollerPosition!!.scrollTo(70f)
-            scrollerPosition = null
+            scrollState!!.scrollTo(70f)
+            scrollState = null
         }
 
         restorationTester.emulateSavedInstanceStateRestore()
 
         runOnIdle {
-            assertThat(scrollerPosition!!.value).isEqualTo(70f)
+            assertThat(scrollState!!.value).isEqualTo(70f)
         }
     }
 
     private fun performWithAnimationWaitAndAssertPosition(
         assertValue: Float,
-        scrollerPosition: ScrollerPosition,
+        scrollState: ScrollState,
         clock: ManualAnimationClock,
         uiAction: () -> Unit
     ) {
@@ -554,9 +567,10 @@
         runOnIdle {
             clock.clockTimeMillis += 5000
         }
-        onNodeWithTag(scrollerTag).awaitScrollAnimation(scrollerPosition)
+
+        onNodeWithTag(scrollerTag).awaitScrollAnimation(scrollState)
         runOnIdle {
-            assertThat(scrollerPosition.value).isEqualTo(assertValue)
+            assertThat(scrollState.value).isEqualTo(assertValue)
         }
     }
 
@@ -566,15 +580,16 @@
         secondSwipe: GestureScope.() -> Unit
     ) {
         val clock = ManualAnimationClock(0)
-        val scrollerPosition = ScrollerPosition(
-            FlingConfig(ExponentialDecay()),
+        val scrollState = ScrollState(
+            initial = 0f,
+            flingConfig = FlingConfig(ExponentialDecay()),
             animationClock = clock
         )
 
-        createScrollableContent(isVertical, scrollerPosition = scrollerPosition)
+        createScrollableContent(isVertical, scrollState = scrollState)
 
         runOnIdle {
-            assertThat(scrollerPosition.value).isEqualTo(0f)
+            assertThat(scrollState.value).isEqualTo(0f)
         }
 
         onNodeWithTag(scrollerTag)
@@ -585,10 +600,10 @@
         }
 
         onNodeWithTag(scrollerTag)
-            .awaitScrollAnimation(scrollerPosition)
+            .awaitScrollAnimation(scrollState)
 
         val scrolledValue = runOnIdle {
-            scrollerPosition.value
+            scrollState.value
         }
         assertThat(scrolledValue).isGreaterThan(0f)
 
@@ -600,18 +615,20 @@
         }
 
         onNodeWithTag(scrollerTag)
-            .awaitScrollAnimation(scrollerPosition)
+            .awaitScrollAnimation(scrollState)
 
         runOnIdle {
-            assertThat(scrollerPosition.value).isLessThan(scrolledValue)
+            assertThat(scrollState.value).isLessThan(scrolledValue)
         }
     }
 
     private fun composeVerticalScroller(
-        scrollerPosition: ScrollerPosition = ScrollerPosition(
-            FlingConfig(ExponentialDecay()),
+        scrollState: ScrollState = ScrollState(
+            initial = 0f,
+            flingConfig = FlingConfig(ExponentialDecay()),
             animationClock = ManualAnimationClock(0)
         ),
+        isReversed: Boolean = false,
         width: Int = defaultCrossAxisSize,
         height: Int = defaultMainAxisSize,
         rowHeight: Int = defaultCellSize
@@ -620,8 +637,9 @@
         with(composeTestRule.density) {
             composeTestRule.setContent {
                 Stack {
-                    VerticalScroller(
-                        scrollerPosition = scrollerPosition,
+                    ScrollableColumn(
+                        scrollState = scrollState,
+                        reverseScrollDirection = isReversed,
                         modifier = Modifier
                             .preferredSize(width.toDp(), height.toDp())
                             .testTag(scrollerTag)
@@ -639,10 +657,12 @@
     }
 
     private fun composeHorizontalScroller(
-        scrollerPosition: ScrollerPosition = ScrollerPosition(
-            FlingConfig(ExponentialDecay()),
+        scrollState: ScrollState = ScrollState(
+            initial = 0f,
+            flingConfig = FlingConfig(ExponentialDecay()),
             animationClock = ManualAnimationClock(0)
         ),
+        isReversed: Boolean = false,
         width: Int = defaultMainAxisSize,
         height: Int = defaultCrossAxisSize,
         columnWidth: Int = defaultCellSize
@@ -651,8 +671,9 @@
         with(composeTestRule.density) {
             composeTestRule.setContent {
                 Stack {
-                    HorizontalScroller(
-                        scrollerPosition = scrollerPosition,
+                    ScrollableRow(
+                        reverseScrollDirection = isReversed,
+                        scrollState = scrollState,
                         modifier = Modifier
                             .preferredSize(width.toDp(), height.toDp())
                             .testTag(scrollerTag)
@@ -704,8 +725,10 @@
         itemCount: Int = 100,
         width: Dp = 100.dp,
         height: Dp = 100.dp,
-        scrollerPosition: ScrollerPosition = ScrollerPosition(
-            FlingConfig(ExponentialDecay()),
+        isReversed: Boolean = false,
+        scrollState: ScrollState = ScrollState(
+            initial = 0f,
+            flingConfig = FlingConfig(ExponentialDecay()),
             animationClock = ManualAnimationClock(0)
         )
     ) {
@@ -721,18 +744,22 @@
                     backgroundColor = Color.White
                 ) {
                     if (isVertical) {
-                        VerticalScroller(
-                            scrollerPosition,
-                            modifier = Modifier.testTag(scrollerTag)
-                        ) {
-                            content()
+                        Box(Modifier.testTag(scrollerTag)) {
+                            ScrollableColumn(
+                                scrollState = scrollState,
+                                reverseScrollDirection = isReversed
+                            ) {
+                                content()
+                            }
                         }
                     } else {
-                        HorizontalScroller(
-                            scrollerPosition,
-                            modifier = Modifier.testTag(scrollerTag)
-                        ) {
-                            content()
+                        Box(Modifier.testTag(scrollerTag)) {
+                            ScrollableRow(
+                                scrollState = scrollState,
+                                reverseScrollDirection = isReversed
+                            ) {
+                                content()
+                            }
                         }
                     }
                 }
@@ -742,13 +769,13 @@
 
     // TODO(b/147291885): This should not be needed in the future.
     private fun SemanticsNodeInteraction.awaitScrollAnimation(
-        scroller: ScrollerPosition
+        scroller: ScrollState
     ): SemanticsNodeInteraction {
         val latch = CountDownLatch(1)
         val handler = Handler(Looper.getMainLooper())
         handler.post(object : Runnable {
             override fun run() {
-                if (scroller.isAnimating) {
+                if (scroller.isAnimationRunning) {
                     handler.post(this)
                 } else {
                     latch.countDown()
diff --git a/ui/ui-foundation/src/androidMain/kotlin/androidx/ui/foundation/animation/AndroidFlingConfig.kt b/ui/ui-foundation/src/androidMain/kotlin/androidx/ui/foundation/animation/AndroidFlingConfig.kt
index c408bce..3cfb50c 100644
--- a/ui/ui-foundation/src/androidMain/kotlin/androidx/ui/foundation/animation/AndroidFlingConfig.kt
+++ b/ui/ui-foundation/src/androidMain/kotlin/androidx/ui/foundation/animation/AndroidFlingConfig.kt
@@ -26,8 +26,8 @@
     // This function will internally update the calculation of fling decay when the density changes,
     // but the reference to the returned FlingConfig will not change across calls.
     val density = DensityAmbient.current
-    val decayAnimation = remember(density.density) { AndroidFlingDecaySpec(density) }
-    return remember(decayAnimation) {
+    return remember(density.density) {
+        val decayAnimation = AndroidFlingDecaySpec(density)
         FlingConfig(
             decayAnimation = decayAnimation,
             adjustTarget = adjustTarget
diff --git a/ui/ui-foundation/src/commonMain/kotlin/androidx/ui/foundation/Scroll.kt b/ui/ui-foundation/src/commonMain/kotlin/androidx/ui/foundation/Scroll.kt
new file mode 100644
index 0000000..386f4d51
--- /dev/null
+++ b/ui/ui-foundation/src/commonMain/kotlin/androidx/ui/foundation/Scroll.kt
@@ -0,0 +1,621 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:Suppress("DEPRECATION")
+
+package androidx.ui.foundation
+
+import androidx.animation.AnimationClockObservable
+import androidx.animation.AnimationEndReason
+import androidx.animation.AnimationSpec
+import androidx.animation.SpringSpec
+import androidx.compose.Composable
+import androidx.compose.Stable
+import androidx.compose.getValue
+import androidx.compose.mutableStateOf
+import androidx.compose.remember
+import androidx.compose.setValue
+import androidx.compose.structuralEqualityPolicy
+import androidx.ui.animation.asDisposableClock
+import androidx.ui.core.Alignment
+import androidx.ui.core.AnimationClockAmbient
+import androidx.ui.core.Constraints
+import androidx.ui.core.LayoutDirection
+import androidx.ui.core.LayoutModifier
+import androidx.ui.core.Measurable
+import androidx.ui.core.MeasureScope
+import androidx.ui.core.Modifier
+import androidx.ui.core.clipToBounds
+import androidx.ui.core.composed
+import androidx.ui.core.gesture.scrollorientationlocking.Orientation
+import androidx.ui.core.semantics.semantics
+import androidx.ui.foundation.animation.FlingConfig
+import androidx.ui.foundation.animation.defaultFlingConfig
+import androidx.ui.foundation.gestures.ScrollableController
+import androidx.ui.foundation.gestures.scrollable
+import androidx.ui.layout.Arrangement
+import androidx.ui.layout.Column
+import androidx.ui.layout.ColumnScope
+import androidx.ui.layout.Constraints
+import androidx.ui.layout.InnerPadding
+import androidx.ui.layout.Row
+import androidx.ui.layout.RowScope
+import androidx.ui.layout.padding
+import androidx.ui.savedinstancestate.Saver
+import androidx.ui.savedinstancestate.rememberSavedInstanceState
+import androidx.ui.semantics.scrollBy
+import androidx.ui.unit.dp
+import kotlin.math.roundToInt
+
+/**
+ * Create and [remember] the [ScrollState] based on the currently appropriate scroll
+ * configuration to allow changing scroll position or observing scroll behavior.
+ *
+ * Learn how to control [ScrollableColumn] or [ScrollableRow]:
+ * @sample androidx.ui.foundation.samples.ControlledScrollableRowSample
+ *
+ * @param initial initial scroller position to start with
+ */
+@Composable
+fun rememberScrollState(initial: Float = 0f): ScrollState {
+    val clock = AnimationClockAmbient.current.asDisposableClock()
+    val config = defaultFlingConfig()
+    return rememberSavedInstanceState(
+        clock, config,
+        saver = ScrollState.Saver(config, clock)
+    ) {
+        ScrollState(
+            flingConfig = config,
+            initial = initial,
+            animationClock = clock
+        )
+    }
+}
+
+/**
+ * State of the scroll. Allows the developer to change the scroll position or get current state by
+ * calling methods on this object. To be hosted and passed to [ScrollableRow], [ScrollableColumn],
+ * [Modifier.verticalScroll] or [Modifier.horizontalScroll]
+ *
+ * To create and automatically remember [ScrollState] with default parameters use
+ * [rememberScrollState].
+ *
+ * Learn how to control [ScrollableColumn] or [ScrollableRow]:
+ * @sample androidx.ui.foundation.samples.ControlledScrollableRowSample
+ *
+ * @param initial value of the scroll
+ * @param flingConfig fling configuration to use for flinging
+ * @param animationClock animation clock to run flinging and smooth scrolling on
+ */
+@Stable
+class ScrollState(
+    initial: Float,
+    internal val flingConfig: FlingConfig,
+    animationClock: AnimationClockObservable
+) {
+
+    /**
+     * current scroll position value in pixels
+     */
+    var value by mutableStateOf(initial, structuralEqualityPolicy())
+        private set
+
+    /**
+     * maximum bound for [value], or [Float.POSITIVE_INFINITY] if still unknown
+     */
+    var maxValue: Float
+        get() = _maxValueState.value
+        internal set(newMax) {
+            _maxValueState.value = newMax
+            if (value > newMax) {
+                value = newMax
+            }
+        }
+
+    private var _maxValueState = mutableStateOf(Float.POSITIVE_INFINITY, structuralEqualityPolicy())
+
+    internal val scrollableController =
+        ScrollableController(
+            flingConfig = flingConfig,
+            animationClock = animationClock,
+            consumeScrollDelta = {
+                val absolute = (value + it)
+                val newValue = absolute.coerceIn(0f, maxValue)
+                if (absolute != newValue) stopAnimation()
+                val consumed = newValue - value
+                value += consumed
+                consumed
+            })
+
+    /**
+     * Stop any ongoing animation, smooth scrolling or fling occurring on this [ScrollState]
+     */
+    fun stopAnimation() {
+        scrollableController.stopAnimation()
+    }
+
+    /**
+     * whether this [ScrollState] is currently animating/flinging
+     */
+    val isAnimationRunning
+        get() = scrollableController.isAnimationRunning
+
+    /**
+     * Smooth scroll to position in pixels
+     *
+     * @param value target value in pixels to smooth scroll to, value will be coerced to
+     * 0..maxPosition
+     * @param spec animation curve for smooth scroll animation
+     * @param onEnd callback to be invoked when smooth scroll has finished
+     */
+    fun smoothScrollTo(
+        value: Float,
+        spec: AnimationSpec<Float> = SpringSpec(),
+        onEnd: (endReason: AnimationEndReason, finishValue: Float) -> Unit = { _, _ -> }
+    ) {
+        smoothScrollBy(value - this.value, spec, onEnd)
+    }
+
+    /**
+     * Smooth scroll by some amount of pixels
+     *
+     * @param value delta in pixels to scroll by, total value will be coerced to 0..maxPosition
+     * @param spec animation curve for smooth scroll animation
+     * @param onEnd callback to be invoked when smooth scroll has finished
+     */
+    fun smoothScrollBy(
+        value: Float,
+        spec: AnimationSpec<Float> = SpringSpec(),
+        onEnd: (endReason: AnimationEndReason, finishValue: Float) -> Unit = { _, _ -> }
+    ) {
+        scrollableController.smoothScrollBy(value, spec, onEnd)
+    }
+
+    /**
+     * Instantly jump to position in pixels
+     *
+     * @param value target value in pixels to jump to, value will be coerced to 0..maxPosition
+     */
+    fun scrollTo(value: Float) {
+        this.value = value.coerceIn(0f, maxValue)
+    }
+
+    /**
+     * Instantly jump by some amount of pixels
+     *
+     * @param value delta in pixels to jump by, total value will be coerced to 0..maxPosition
+     */
+    fun scrollBy(value: Float) {
+        scrollTo(this.value + value)
+    }
+
+    companion object {
+        /**
+         * The default [Saver] implementation for [ScrollState].
+         */
+        fun Saver(
+            flingConfig: FlingConfig,
+            animationClock: AnimationClockObservable
+        ): Saver<ScrollState, *> = Saver<ScrollState, Float>(
+            save = { it.value },
+            restore = { ScrollState(it, flingConfig, animationClock) }
+        )
+    }
+}
+
+/**
+ * Variation of [Column] that scrolls when content is bigger than its height.
+ *
+ * The content of the [ScrollableColumn] is clipped to its bounds.
+ *
+ * @sample androidx.ui.foundation.samples.ScrollableColumnSample
+ *
+ * @param modifier modifier for this [ScrollableColumn]
+ * @param scrollState state of the scroll, such as current offset and max offset
+ * @param verticalArrangement The vertical arrangement of the layout's children
+ * @param horizontalGravity The horizontal gravity of the layout's children
+ * @param reverseScrollDirection reverse the direction of scrolling, when `true`, [ScrollState
+ * .value] = 0 will mean bottom, when `false`, [ScrollState.value] = 0 will mean top
+ * @param isScrollEnabled param to enable or disable touch input scrolling. If you own
+ * [ScrollState], you still can call [ScrollState.smoothScrollTo] and other methods on it.
+ * @param contentPadding convenience param to specify padding around content. This will add
+ * padding for the content after it has been clipped, which is not possible via [modifier] param
+ */
+@Composable
+fun ScrollableColumn(
+    modifier: Modifier = Modifier,
+    scrollState: ScrollState = rememberScrollState(0f),
+    verticalArrangement: Arrangement.Vertical = Arrangement.Top,
+    horizontalGravity: Alignment.Horizontal = Alignment.Start,
+    reverseScrollDirection: Boolean = false,
+    isScrollEnabled: Boolean = true,
+    contentPadding: InnerPadding = InnerPadding(0.dp),
+    children: @Composable ColumnScope.() -> Unit
+) {
+    Column(
+        modifier = modifier
+            .verticalScroll(
+                scrollState,
+                isScrollEnabled,
+                reverseScrolling = reverseScrollDirection
+            )
+            .clipToBounds()
+            .padding(contentPadding),
+        verticalArrangement = verticalArrangement,
+        horizontalGravity = horizontalGravity,
+        children = children
+    )
+}
+
+/**
+ * Variation of [Row] that scrolls when content is bigger than its width.
+ *
+ * The content of the [ScrollableRow] is clipped to its bounds.
+ *
+ * @sample androidx.ui.foundation.samples.ScrollableRowSample
+ *
+ * @param modifier modifier for this [ScrollableRow]
+ * @param scrollState state of the scroll, such as current offset and max offset
+ * @param horizontalArrangement The horizontal arrangement of the layout's children
+ * @param verticalGravity The vertical gravity of the layout's children
+ * @param reverseScrollDirection reverse the direction of scrolling, when `true`, [ScrollState
+ * .value] = 0 will mean right, when `false`, [ScrollState.value] = 0 will mean left
+ * @param isScrollEnabled param to enable or disable touch input scrolling. If you own
+ * [ScrollState], you still can call [ScrollState.smoothScrollTo] and other methods on it.
+ * @param contentPadding convenience param to specify padding around content. This will add
+ * padding for the content after it has been clipped, which is not possible via [modifier] param.
+ */
+@Composable
+fun ScrollableRow(
+    modifier: Modifier = Modifier,
+    scrollState: ScrollState = rememberScrollState(0f),
+    horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
+    verticalGravity: Alignment.Vertical = Alignment.Top,
+    reverseScrollDirection: Boolean = false,
+    isScrollEnabled: Boolean = true,
+    contentPadding: InnerPadding = InnerPadding(0.dp),
+    children: @Composable RowScope.() -> Unit
+) {
+    Row(
+        modifier = modifier
+            .horizontalScroll(
+                scrollState,
+                isScrollEnabled,
+                reverseScrolling = reverseScrollDirection
+            )
+            .clipToBounds()
+            .padding(contentPadding),
+        horizontalArrangement = horizontalArrangement,
+        verticalGravity = verticalGravity,
+        children = children
+    )
+}
+
+/**
+ * Modify element to allow to scroll vertically when height of the content is bigger than max
+ * constraints allow.
+ *
+ * @sample androidx.ui.foundation.samples.VerticalScrollExample
+ *
+ * In order to use this modifier, you need to create and own [ScrollState]
+ * @see [rememberScrollState]
+ *
+ * @param state state of the scroll
+ * @param enabled whether or not scrolling via touch input is enabled
+ * @param reverseScrolling reverse the direction of scrolling, when `true`, 0 [ScrollState.value]
+ * will mean bottom, when `false`, 0 [ScrollState.value] will mean top
+ */
+fun Modifier.verticalScroll(
+    state: ScrollState,
+    enabled: Boolean = true,
+    reverseScrolling: Boolean = false
+) = scroll(
+    state = state,
+    isScrollable = enabled,
+    reverseScrolling = reverseScrolling,
+    isVertical = true
+)
+
+/**
+ * Modify element to allow to scroll horizontally when width of the content is bigger than max
+ * constraints allow.
+ *
+ * @sample androidx.ui.foundation.samples.HorizontalScrollSample
+ *
+ * In order to use this modifier, you need to create and own [ScrollState]
+ * @see [rememberScrollState]
+ *
+ * @param state state of the scroll
+ * @param enabled whether or not scrolling via touch input is enabled
+ * @param reverseScrolling reverse the direction of scrolling, when `true`, 0 [ScrollState.value]
+ * will mean right, when `false`, 0 [ScrollState.value] will mean left
+ */
+fun Modifier.horizontalScroll(
+    state: ScrollState,
+    enabled: Boolean = true,
+    reverseScrolling: Boolean = false
+) = scroll(
+    state = state,
+    isScrollable = enabled,
+    reverseScrolling = reverseScrolling,
+    isVertical = false
+)
+
+private fun Modifier.scroll(
+    state: ScrollState,
+    reverseScrolling: Boolean,
+    isScrollable: Boolean,
+    isVertical: Boolean
+) = composed {
+    val semantics = Modifier.semantics {
+        if (isScrollable) {
+            // when b/156389287 is fixed, this should be proper scrollTo with reverse handling
+            scrollBy(action = { x: Float, y: Float ->
+                if (isVertical) {
+                    state.scrollBy(y)
+                } else {
+                    state.scrollBy(x)
+                }
+                return@scrollBy true
+            })
+        }
+    }
+    val scrolling = Modifier.scrollable(
+        orientation = if (isVertical) Orientation.Vertical else Orientation.Horizontal,
+        reverseDirection = !reverseScrolling,
+        enabled = isScrollable,
+        controller = state.scrollableController
+    )
+    val layout = ScrollingLayoutModifier(state, reverseScrolling, isVertical)
+    semantics.plus(scrolling).clipToBounds().plus(layout)
+}
+
+private data class ScrollingLayoutModifier(
+    val scrollerState: ScrollState,
+    val isReversed: Boolean,
+    val isVertical: Boolean
+) : LayoutModifier {
+    override fun MeasureScope.measure(
+        measurable: Measurable,
+        constraints: Constraints,
+        layoutDirection: LayoutDirection
+    ): MeasureScope.MeasureResult {
+        val childConstraints = constraints.copy(
+            maxHeight = if (isVertical) Constraints.Infinity else constraints.maxHeight,
+            maxWidth = if (isVertical) constraints.maxWidth else Constraints.Infinity
+        )
+        val placeable = measurable.measure(childConstraints)
+        val width = placeable.width.coerceAtMost(constraints.maxWidth)
+        val height = placeable.height.coerceAtMost(constraints.maxHeight)
+        val scrollHeight = placeable.height.toFloat() - height.toFloat()
+        val scrollWidth = placeable.width.toFloat() - width.toFloat()
+        val side = if (isVertical) scrollHeight else scrollWidth
+        return layout(width, height) {
+            scrollerState.maxValue = side
+            val scroll = scrollerState.value.coerceIn(0f, side)
+            val absScroll = if (isReversed) scroll - side else -scroll
+            val xOffset = if (isVertical) 0 else absScroll.roundToInt()
+            val yOffset = if (isVertical) absScroll.roundToInt() else 0
+            placeable.place(xOffset, yOffset)
+        }
+    }
+}
+
+/**
+ * Create and [remember] the state for a [VerticalScroller] or [HorizontalScroller] based on the
+ * currently appropriate scroll configuration to allow changing scroll position or observing
+ * scroll behavior.
+ *
+ * @param initial initial scroller position to start with
+ * @param isReversed whether position will be reversed, e.g. 0 will mean bottom for
+ * [VerticalScroller] and end for [HorizontalScroller]
+ */
+@Deprecated(
+    "Use rememberScrollState instead", replaceWith = ReplaceWith(
+        "rememberScrollState(initial = initial",
+        "androidx.ui.foundation.rememberScrollState"
+    )
+)
+@Composable
+fun ScrollerPosition(
+    initial: Float = 0f,
+    isReversed: Boolean = false
+): ScrollerPosition {
+    val clock = AnimationClockAmbient.current.asDisposableClock()
+    val config = defaultFlingConfig()
+    return rememberSavedInstanceState(
+        clock, config,
+        saver = ScrollerPosition.Saver(config, isReversed, clock)
+    ) {
+        ScrollerPosition(
+            flingConfig = config,
+            initial = initial,
+            animationClock = clock,
+            isReversed = isReversed
+        )
+    }
+}
+
+/**
+ * This is the state of a [VerticalScroller] and [HorizontalScroller] that
+ * allows the developer to change the scroll position by calling methods on this object.
+ *
+ * @param flingConfig configuration that specifies fling logic when scrolling ends with velocity
+ * @param initial initial scroller position in pixels to start with
+ * @param isReversed whether position will be reversed, e.g. 0 will mean bottom for
+ * [VerticalScroller] and end for [HorizontalScroller]
+ * @param animationClock clock observable to run animation on. Consider querying
+ * [AnimationClockAmbient] to get current composition value
+ */
+@Stable
+@Deprecated(
+    "Use ScrollState instead", replaceWith = ReplaceWith(
+        "ScrollState(" +
+                "initial = initial," +
+                " flingConfig = flingConfig, " +
+                "animationClock = animationClock)", "androidx.ui.foundation.ScrollState"
+    )
+)
+class ScrollerPosition(
+    /** Configuration that specifies fling logic when scrolling ends with velocity. */
+    internal val flingConfig: FlingConfig,
+    initial: Float = 0f,
+    internal val isReversed: Boolean = false,
+    internal val animationClock: AnimationClockObservable
+) {
+
+    private fun directionalValue(value: Float) = if (isReversed) value else -value
+
+    internal val state = ScrollState(initial, flingConfig, animationClock)
+
+    /**
+     * current scroller position value in pixels
+     */
+    var value: Float
+        get() = state.value
+        set(value) {
+            state.scrollTo(value)
+        }
+
+    /**
+     * maxPosition this scroller that consume this ScrollerPosition can reach in pixels, or
+     * [Float.POSITIVE_INFINITY] if still unknown
+     */
+    var maxPosition: Float
+        get() = state.maxValue
+        set(newMax) {
+            state.maxValue = newMax
+        }
+
+    /**
+     * Smooth scroll to position in pixels
+     *
+     * @param value target value in pixels to smooth scroll to, value will be coerced to
+     * 0..maxPosition
+     */
+    // TODO (malkov/tianliu) : think about allowing to scroll with custom animation timings/curves
+    fun smoothScrollTo(
+        value: Float,
+        onEnd: (endReason: AnimationEndReason, finishValue: Float) -> Unit = { _, _ -> }
+    ) {
+        smoothScrollBy(value - this.value, onEnd)
+    }
+
+    /**
+     * Smooth scroll by some amount of pixels
+     *
+     * @param value delta in pixels to scroll by, total value will be coerced to 0..maxPosition
+     */
+    fun smoothScrollBy(
+        value: Float,
+        onEnd: (endReason: AnimationEndReason, finishValue: Float) -> Unit = { _, _ -> }
+    ) {
+        state.smoothScrollBy(
+            value = directionalValue(value),
+            >
+        )
+    }
+
+    /**
+     * Instantly jump to position in pixels
+     *
+     * @param value target value in pixels to jump to, value will be coerced to 0..maxPosition
+     */
+    fun scrollTo(value: Float) {
+        this.value = value.coerceIn(0f, maxPosition)
+    }
+
+    /**
+     * Instantly jump by some amount of pixels
+     *
+     * @param value delta in pixels to jump by, total value will be coerced to 0..maxPosition
+     */
+    fun scrollBy(value: Float) {
+        scrollTo(this.value + value)
+    }
+
+    companion object {
+        /**
+         * The default [Saver] implementation for [ScrollerPosition].
+         */
+        @Composable
+        fun Saver(
+            flingConfig: FlingConfig,
+            isReversed: Boolean,
+            animationClock: AnimationClockObservable
+        ): Saver<ScrollerPosition, *> = Saver<ScrollerPosition, Float>(
+            save = { it.value },
+            restore = { ScrollerPosition(flingConfig, it, isReversed, animationClock) }
+        )
+    }
+}
+
+@Deprecated(
+    "Use ScrollableColumn instead", replaceWith = ReplaceWith(
+        "ScrollableColumn(modifier = modifier, children = children)",
+        "androidx.ui.foundation.ScrollableColumn"
+    )
+)
+@Composable
+fun VerticalScroller(
+    scrollerPosition: ScrollerPosition = ScrollerPosition(),
+    modifier: Modifier = Modifier,
+    isScrollable: Boolean = true,
+    children: @Composable ColumnScope.() -> Unit
+) {
+    Column(
+        modifier = modifier
+            .verticalScroll(scrollerPosition.state, isScrollable)
+            .clipToBounds(),
+        children = children
+    )
+}
+
+/**
+ * A container that composes all of its contents and lays it out, fitting the height of the child.
+ * If the child's width is less than the [Constraints.maxWidth], the child's width is used,
+ * or the [Constraints.maxWidth] otherwise. If the contents don't fit the width, the drag gesture
+ * allows scrolling its content horizontally. The contents of the HorizontalScroller are clipped to
+ * the HorizontalScroller's bounds.
+ *
+ * If you want to control scrolling position from the code, e.g smooth scroll to position,
+ * you must own memorized instance of [ScrollerPosition] and then use it to call `scrollTo...`
+ * functions on it. Same tactic can be applied to the [VerticalScroller]
+ *
+ * @param scrollerPosition state of this Scroller that holds current scroll position and provides
+ * user with useful methods like smooth scrolling
+ * @param modifier Modifier to be applied to the Scroller content layout
+ * @param isScrollable param to enabled or disable touch input scrolling, default is true
+ */
+@Deprecated(
+    "Use ScrollableRow instead", replaceWith = ReplaceWith(
+        "ScrollableRow(modifier = modifier, children = children)",
+        "androidx.ui.foundation.ScrollableRow"
+    )
+)
+@Composable
+fun HorizontalScroller(
+    scrollerPosition: ScrollerPosition = ScrollerPosition(),
+    modifier: Modifier = Modifier,
+    isScrollable: Boolean = true,
+    children: @Composable RowScope.() -> Unit
+) {
+    Row(
+        modifier = modifier
+            .horizontalScroll(scrollerPosition.state, isScrollable)
+            .clipToBounds(),
+        children = children
+    )
+}
\ No newline at end of file
diff --git a/ui/ui-foundation/src/commonMain/kotlin/androidx/ui/foundation/Scroller.kt b/ui/ui-foundation/src/commonMain/kotlin/androidx/ui/foundation/Scroller.kt
deleted file mode 100644
index 8b1bab8..0000000
--- a/ui/ui-foundation/src/commonMain/kotlin/androidx/ui/foundation/Scroller.kt
+++ /dev/null
@@ -1,348 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.ui.foundation
-
-import androidx.animation.AnimationClockObservable
-import androidx.animation.AnimationEndReason
-import androidx.compose.Composable
-import androidx.compose.ExperimentalComposeApi
-import androidx.compose.Stable
-import androidx.compose.getValue
-import androidx.compose.mutableStateOf
-import androidx.compose.remember
-import androidx.compose.setValue
-import androidx.compose.structuralEqualityPolicy
-import androidx.ui.animation.asDisposableClock
-import androidx.ui.core.AnimationClockAmbient
-import androidx.ui.core.Constraints
-import androidx.ui.core.Layout
-import androidx.ui.core.Modifier
-import androidx.ui.core.clipToBounds
-import androidx.ui.core.gesture.scrollorientationlocking.Orientation
-import androidx.ui.core.semantics.semantics
-import androidx.ui.foundation.animation.FlingConfig
-import androidx.ui.foundation.animation.defaultFlingConfig
-import androidx.ui.foundation.gestures.ScrollableController
-import androidx.ui.foundation.gestures.scrollable
-import androidx.ui.layout.Column
-import androidx.ui.layout.ColumnScope
-import androidx.ui.layout.Constraints
-import androidx.ui.layout.Row
-import androidx.ui.layout.RowScope
-import androidx.ui.savedinstancestate.Saver
-import androidx.ui.savedinstancestate.rememberSavedInstanceState
-import androidx.ui.semantics.scrollBy
-import kotlin.math.roundToInt
-
-/**
- * Create and [remember] the state for a [VerticalScroller] or [HorizontalScroller] based on the
- * currently appropriate scroll configuration to allow changing scroll position or observing
- * scroll behavior.
- *
- * @param initial initial scroller position to start with
- * @param isReversed whether position will be reversed, e.g. 0 will mean bottom for
- * [VerticalScroller] and end for [HorizontalScroller]
- */
-@Composable
-fun ScrollerPosition(
-    initial: Float = 0f,
-    isReversed: Boolean = false
-): ScrollerPosition {
-    val clock = AnimationClockAmbient.current.asDisposableClock()
-    val config = defaultFlingConfig()
-    return rememberSavedInstanceState(
-        clock, config,
-        saver = ScrollerPosition.Saver(config, isReversed, clock)
-    ) {
-        ScrollerPosition(
-            flingConfig = config,
-            initial = initial,
-            animationClock = clock,
-            isReversed = isReversed
-        )
-    }
-}
-
-/**
- * This is the state of a [VerticalScroller] and [HorizontalScroller] that
- * allows the developer to change the scroll position by calling methods on this object.
- *
- * @param flingConfig configuration that specifies fling logic when scrolling ends with velocity
- * @param initial initial scroller position in pixels to start with
- * @param isReversed whether position will be reversed, e.g. 0 will mean bottom for
- * [VerticalScroller] and end for [HorizontalScroller]
- * @param animationClock clock observable to run animation on. Consider querying
- * [AnimationClockAmbient] to get current composition value
- */
-@Stable
-class ScrollerPosition(
-    /** Configuration that specifies fling logic when scrolling ends with velocity. */
-    flingConfig: FlingConfig,
-    initial: Float = 0f,
-    internal val isReversed: Boolean = false,
-    animationClock: AnimationClockObservable
-) {
-
-    private fun directionalValue(value: Float) = if (isReversed) value else -value
-
-    private val consumeDelta: (Float) -> Float = {
-        val reverseDelta = directionalValue(it)
-        val newValue = value + reverseDelta
-        val max = maxPosition
-        val min = 0f
-        val consumed = when {
-            newValue > max -> max - value
-            newValue < min -> min - value
-            else -> reverseDelta
-        }
-        value += consumed
-        directionalValue(consumed)
-    }
-
-    internal val scrollableController =
-        ScrollableController(consumeDelta, flingConfig, animationClock)
-
-    /**
-     * current scroller position value in pixels
-     */
-    var value by mutableStateOf(
-        initial,
-        @OptIn(ExperimentalComposeApi::class) structuralEqualityPolicy()
-    )
-        private set
-
-    /**
-     * whether this [ScrollerPosition] is currently animating/flinging
-     */
-    val isAnimating
-        get() = scrollableController.isAnimationRunning
-
-    /**
-     * maxPosition this scroller that consume this ScrollerPosition can reach in pixels, or
-     * [Float.POSITIVE_INFINITY] if still unknown
-     */
-    var maxPosition by mutableStateOf(
-        Float.POSITIVE_INFINITY,
-        @OptIn(ExperimentalComposeApi::class) structuralEqualityPolicy()
-    )
-        private set
-
-    /**
-     * Smooth scroll to position in pixels
-     *
-     * @param value target value in pixels to smooth scroll to, value will be coerced to
-     * 0..maxPosition
-     */
-    // TODO (malkov/tianliu) : think about allowing to scroll with custom animation timings/curves
-    fun smoothScrollTo(
-        value: Float,
-        onEnd: (endReason: AnimationEndReason, finishValue: Float) -> Unit = { _, _ -> }
-    ) {
-        smoothScrollBy(value - this.value, onEnd)
-    }
-
-    /**
-     * Smooth scroll by some amount of pixels
-     *
-     * @param value delta in pixels to scroll by, total value will be coerced to 0..maxPosition
-     */
-    fun smoothScrollBy(
-        value: Float,
-        onEnd: (endReason: AnimationEndReason, finishValue: Float) -> Unit = { _, _ -> }
-    ) {
-        scrollableController.smoothScrollBy(
-            value = directionalValue(value),
-            >
-        )
-    }
-
-    /**
-     * Instantly jump to position in pixels
-     *
-     * @param value target value in pixels to jump to, value will be coerced to 0..maxPosition
-     */
-    fun scrollTo(value: Float) {
-        this.value = value.coerceIn(0f, maxPosition)
-    }
-
-    /**
-     * Instantly jump by some amount of pixels
-     *
-     * @param value delta in pixels to jump by, total value will be coerced to 0..maxPosition
-     */
-    fun scrollBy(value: Float) {
-        scrollTo(this.value + value)
-    }
-
-    internal fun updateMaxPosition(newMax: Float) {
-        maxPosition = newMax
-        if (value > newMax) {
-            value = newMax
-        }
-    }
-
-    companion object {
-        /**
-         * The default [Saver] implementation for [ScrollerPosition].
-         */
-        @Composable
-        fun Saver(
-            flingConfig: FlingConfig,
-            isReversed: Boolean,
-            animationClock: AnimationClockObservable
-        ): Saver<ScrollerPosition, *> = Saver<ScrollerPosition, Float>(
-            save = { it.value },
-            restore = { ScrollerPosition(flingConfig, it, isReversed, animationClock) }
-        )
-    }
-}
-
-// TODO(malkov): Test behavior during animation more extensively (including pressing on the scroller
-//  during an animation when b/144878730 is fixed.
-/**
- * A container that composes all of its contents and lays it out, fitting the width of the child.
- * If the child's height is less than the [Constraints.maxHeight], the child's height is used,
- * or the [Constraints.maxHeight] otherwise. If the contents don't fit the height, the drag gesture
- * allows scrolling its content vertically. The contents of the VerticalScroller are clipped to
- * the VerticalScroller's bounds.
- *
- * @sample androidx.ui.foundation.samples.VerticalScrollerSample
- *
- * @param scrollerPosition state of this Scroller that holds current scroll position and provides
- * user with useful methods like smooth scrolling
- * @param modifier Modifier to be applied to the Scroller content layout
- * @param isScrollable param to enabled or disable touch input scrolling, default is true
- */
-@Composable
-fun VerticalScroller(
-    scrollerPosition: ScrollerPosition = ScrollerPosition(),
-    modifier: Modifier = Modifier,
-    isScrollable: Boolean = true,
-    children: @Composable ColumnScope.() -> Unit
-) {
-    Scroller(scrollerPosition, modifier, true, isScrollable) {
-        Column(
-            modifier = Modifier.clipToBounds(),
-            children = children
-        )
-    }
-}
-
-/**
- * A container that composes all of its contents and lays it out, fitting the height of the child.
- * If the child's width is less than the [Constraints.maxWidth], the child's width is used,
- * or the [Constraints.maxWidth] otherwise. If the contents don't fit the width, the drag gesture
- * allows scrolling its content horizontally. The contents of the HorizontalScroller are clipped to
- * the HorizontalScroller's bounds.
- *
- * @sample androidx.ui.foundation.samples.SimpleHorizontalScrollerSample
- *
- * If you want to control scrolling position from the code, e.g smooth scroll to position,
- * you must own memorized instance of [ScrollerPosition] and then use it to call `scrollTo...`
- * functions on it. Same tactic can be applied to the [VerticalScroller]
- *
- * @sample androidx.ui.foundation.samples.ControlledHorizontalScrollerSample
- *
- * @param scrollerPosition state of this Scroller that holds current scroll position and provides
- * user with useful methods like smooth scrolling
- * @param modifier Modifier to be applied to the Scroller content layout
- * @param isScrollable param to enabled or disable touch input scrolling, default is true
- */
-@Composable
-fun HorizontalScroller(
-    scrollerPosition: ScrollerPosition = ScrollerPosition(),
-    modifier: Modifier = Modifier,
-    isScrollable: Boolean = true,
-    children: @Composable RowScope.() -> Unit
-) {
-    Scroller(scrollerPosition, modifier, false, isScrollable) {
-        Row(
-            modifier = Modifier.clipToBounds(),
-            children = children
-        )
-    }
-}
-
-@Composable
-private fun Scroller(
-    scrollerPosition: ScrollerPosition,
-    modifier: Modifier,
-    isVertical: Boolean,
-    isScrollable: Boolean,
-    child: @Composable () -> Unit
-) {
-    ScrollerLayout(
-        scrollerPosition = scrollerPosition,
-        modifier = modifier
-            .semantics {
-                if (isScrollable) {
-                    // when b/156389287 is fixed, this should be proper scrollTo with reverse handling
-                    scrollBy(action = { x, y ->
-                        if (isVertical) {
-                            scrollerPosition.scrollBy(y)
-                        } else {
-                            scrollerPosition.scrollBy(x)
-                        }
-                        return@scrollBy true
-                    })
-                }
-            }
-            .scrollable(
-                orientation = if (isVertical) Orientation.Vertical else Orientation.Horizontal,
-                reverseDirection = scrollerPosition.isReversed,
-                enabled = isScrollable,
-                controller = scrollerPosition.scrollableController
-            ),
-        isVertical = isVertical,
-        child = child
-    )
-}
-
-@Composable
-private fun ScrollerLayout(
-    scrollerPosition: ScrollerPosition,
-    modifier: Modifier,
-    isVertical: Boolean,
-    child: @Composable () -> Unit
-) {
-    Layout(
-        modifier = modifier.clipToBounds(),
-        children = child,
-        measureBlock = { measurables, constraints ->
-            val childConstraints = constraints.copy(
-                maxHeight = if (isVertical) Constraints.Infinity else constraints.maxHeight,
-                maxWidth = if (isVertical) constraints.maxWidth else Constraints.Infinity
-            )
-            require(measurables.size == 1)
-            val placeable = measurables.first().measure(childConstraints)
-            val width = placeable.width.coerceAtMost(constraints.maxWidth)
-            val height = placeable.height.coerceAtMost(constraints.maxHeight)
-            val scrollHeight = placeable.height.toFloat() - height.toFloat()
-            val scrollWidth = placeable.width.toFloat() - width.toFloat()
-            val side = if (isVertical) scrollHeight else scrollWidth
-            layout(width, height) {
-                scrollerPosition.updateMaxPosition(side)
-                val scroll = scrollerPosition.value.coerceIn(0f, side)
-                val absScroll =
-                    if (scrollerPosition.isReversed) scroll - side else -scroll
-                val xOffset = if (isVertical) 0 else absScroll.roundToInt()
-                val yOffset = if (isVertical) absScroll.roundToInt() else 0
-                placeable.place(xOffset, yOffset)
-            }
-        }
-    )
-}
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ButtonDemo.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ButtonDemo.kt
index 9c7df68..8ac78af 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ButtonDemo.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ButtonDemo.kt
@@ -19,16 +19,15 @@
 import androidx.compose.Composable
 import androidx.ui.core.Modifier
 import androidx.ui.foundation.Border
+import androidx.ui.foundation.ScrollableColumn
 import androidx.ui.foundation.Text
-import androidx.ui.foundation.VerticalScroller
 import androidx.ui.foundation.shape.GenericShape
 import androidx.ui.graphics.Color
 import androidx.ui.layout.Arrangement
-import androidx.ui.layout.Column
+import androidx.ui.layout.InnerPadding
 import androidx.ui.layout.Row
 import androidx.ui.layout.Spacer
 import androidx.ui.layout.fillMaxWidth
-import androidx.ui.layout.padding
 import androidx.ui.layout.preferredHeight
 import androidx.ui.layout.preferredSize
 import androidx.ui.material.Button
@@ -51,16 +50,14 @@
 
 @Composable
 fun ButtonDemo() {
-    VerticalScroller {
-        Column(Modifier.padding(10.dp)) {
-            Buttons()
-            Spacer(Modifier.preferredHeight(DefaultSpace))
-            Fabs()
-            Spacer(Modifier.preferredHeight(DefaultSpace))
-            IconButtons()
-            Spacer(Modifier.preferredHeight(DefaultSpace))
-            CustomShapeButton()
-        }
+    ScrollableColumn(contentPadding = InnerPadding(10.dp)) {
+        Buttons()
+        Spacer(Modifier.preferredHeight(DefaultSpace))
+        Fabs()
+        Spacer(Modifier.preferredHeight(DefaultSpace))
+        IconButtons()
+        Spacer(Modifier.preferredHeight(DefaultSpace))
+        CustomShapeButton()
     }
 }
 
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/DynamicThemeActivity.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/DynamicThemeActivity.kt
index 7cb0d86..f3e5f2c 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/DynamicThemeActivity.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/DynamicThemeActivity.kt
@@ -27,9 +27,9 @@
 import androidx.ui.core.setContent
 import androidx.ui.foundation.Box
 import androidx.ui.foundation.ContentGravity
-import androidx.ui.foundation.ScrollerPosition
+import androidx.ui.foundation.ScrollableColumn
 import androidx.ui.foundation.Text
-import androidx.ui.foundation.VerticalScroller
+import androidx.ui.foundation.rememberScrollState
 import androidx.ui.foundation.shape.corner.CircleShape
 import androidx.ui.foundation.shape.corner.RoundedCornerShape
 import androidx.ui.graphics.Color
@@ -85,9 +85,9 @@
 @Composable
 private fun DynamicThemeApp(scrollFraction: ScrollFraction, palette: ColorPalette) {
     MaterialTheme(palette) {
-        val scrollerPosition = ScrollerPosition()
+        val scrollState = rememberScrollState()
         val fraction =
-            round((scrollerPosition.value / scrollerPosition.maxPosition) * 100) / 100
+            round((scrollState.value / scrollState.maxValue) * 100) / 100
         remember(fraction) { scrollFraction.value = fraction }
         Scaffold(
             topBar = { TopAppBar({ Text("Scroll down!") }) },
@@ -96,13 +96,13 @@
             floatingActionButtonPosition = Scaffold.FabPosition.Center,
             isFloatingActionButtonDocked = true,
             bodyContent = { innerPadding ->
-                VerticalScroller(scrollerPosition) {
+                ScrollableColumn(scrollState = scrollState, children = {
                     Column(Modifier.padding(innerPadding)) {
                         repeat(20) { index ->
                             Card(index)
                         }
                     }
-                }
+                })
             }
         )
     }
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ElevationDemo.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ElevationDemo.kt
index fe2a3fc..4ce5f43 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ElevationDemo.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ElevationDemo.kt
@@ -21,12 +21,13 @@
 import androidx.ui.foundation.Border
 import androidx.ui.foundation.Box
 import androidx.ui.foundation.ContentGravity
+import androidx.ui.foundation.ScrollableColumn
 import androidx.ui.foundation.Text
-import androidx.ui.foundation.VerticalScroller
 import androidx.ui.foundation.clickable
 import androidx.ui.foundation.shape.corner.RoundedCornerShape
 import androidx.ui.graphics.Color
 import androidx.ui.layout.Column
+import androidx.ui.layout.InnerPadding
 import androidx.ui.layout.fillMaxSize
 import androidx.ui.layout.fillMaxWidth
 import androidx.ui.layout.padding
@@ -43,11 +44,12 @@
             val text = getMessage(MaterialTheme.colors.isLight)
             Text(text)
         }
-        VerticalScroller {
-            Column(Modifier.padding(25.dp).fillMaxSize()) {
-                elevations.forEach { elevation ->
-                    ElevatedCard(elevation)
-                }
+        ScrollableColumn(
+            modifier = Modifier.fillMaxSize(),
+            contentPadding = InnerPadding(25.dp)
+        ) {
+            elevations.forEach { elevation ->
+                ElevatedCard(elevation)
             }
         }
     }
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ListItemDemo.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ListItemDemo.kt
index eeead1f..d286555 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ListItemDemo.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ListItemDemo.kt
@@ -17,7 +17,7 @@
 package androidx.ui.material.demos
 
 import androidx.compose.Composable
-import androidx.ui.foundation.VerticalScroller
+import androidx.ui.foundation.ScrollableColumn
 import androidx.ui.material.icons.Icons
 import androidx.ui.material.icons.filled.Call
 import androidx.ui.material.samples.OneLineListItems
@@ -34,7 +34,7 @@
     val icon40 = imageResource(R.drawable.ic_account_box)
     val icon56 = imageResource(R.drawable.ic_android)
     val vectorIcon = Icons.Default.Call
-    VerticalScroller {
+    ScrollableColumn {
         OneLineListItems(icon24, icon40, icon56, vectorIcon)
         TwoLineListItems(icon24, icon40)
         ThreeLineListItems(icon24, vectorIcon)
@@ -45,7 +45,7 @@
 fun MixedRtlLtrListItemDemo() {
     val icon24 = imageResource(R.drawable.ic_bluetooth)
     val icon40 = imageResource(R.drawable.ic_account_box)
-    VerticalScroller {
+    ScrollableColumn {
         OneLineRtlLtrListItems(icon24, icon40)
         TwoLineRtlLtrListItems(icon40)
         ThreeLineRtlLtrListItems(icon40)
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialTextField.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialTextField.kt
index fea676e..df22042 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialTextField.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialTextField.kt
@@ -23,12 +23,13 @@
 import androidx.ui.core.Modifier
 import androidx.ui.foundation.Box
 import androidx.ui.foundation.Icon
+import androidx.ui.foundation.ScrollableColumn
 import androidx.ui.foundation.Text
-import androidx.ui.foundation.VerticalScroller
 import androidx.ui.foundation.selection.selectable
 import androidx.ui.graphics.Color
 import androidx.ui.layout.Column
 import androidx.ui.layout.ColumnScope.gravity
+import androidx.ui.layout.InnerPadding
 import androidx.ui.layout.Row
 import androidx.ui.layout.Spacer
 import androidx.ui.layout.fillMaxHeight
@@ -58,148 +59,145 @@
 
 @Composable
 fun TextFieldsDemo() {
-    VerticalScroller {
-        Column(
-            modifier = Modifier.fillMaxHeight().padding(10.dp)
-        ) {
-            Text("Password text field")
-            PasswordFilledTextField()
-            Text("Text field with leading and trailing icons")
-            FilledTextFieldWithIcons()
-            Text("Outlined text field")
-            SimpleOutlinedTextFieldSample()
-            Text("Text field with placeholder")
-            FilledTextFieldWithPlaceholder()
-            Text("Text field with error state handling")
-            FilledTextFieldWithErrorState()
-            Text("Text field with helper/error message")
-            TextFieldWithHelperMessage()
-            Text("Hide keyboard on IME action")
-            TextFieldWithHideKeyboardOnImeAction()
-            Text("TextFieldValue overload")
-            FilledTextFieldSample()
-        }
+    ScrollableColumn(
+        modifier = Modifier.fillMaxHeight(),
+        contentPadding = InnerPadding(10.dp)
+    ) {
+        Text("Password text field")
+        PasswordFilledTextField()
+        Text("Text field with leading and trailing icons")
+        FilledTextFieldWithIcons()
+        Text("Outlined text field")
+        SimpleOutlinedTextFieldSample()
+        Text("Text field with placeholder")
+        FilledTextFieldWithPlaceholder()
+        Text("Text field with error state handling")
+        FilledTextFieldWithErrorState()
+        Text("Text field with helper/error message")
+        TextFieldWithHelperMessage()
+        Text("Hide keyboard on IME action")
+        TextFieldWithHideKeyboardOnImeAction()
+        Text("TextFieldValue overload")
+        FilledTextFieldSample()
     }
 }
 
 @Composable
 fun MaterialTextFieldDemo() {
-    VerticalScroller {
-        Column(Modifier.padding(10.dp)) {
-            var text by savedInstanceState { "" }
-            var leadingChecked by savedInstanceState { false }
-            var trailingChecked by savedInstanceState { false }
-            val characterCounterChecked by savedInstanceState { false }
-            var selectedOption by savedInstanceState { Option.None }
-            var selectedTextField by savedInstanceState { TextFieldType.Filled }
+    ScrollableColumn(contentPadding = InnerPadding(10.dp)) {
+        var text by savedInstanceState { "" }
+        var leadingChecked by savedInstanceState { false }
+        var trailingChecked by savedInstanceState { false }
+        val characterCounterChecked by savedInstanceState { false }
+        var selectedOption by savedInstanceState { Option.None }
+        var selectedTextField by savedInstanceState { TextFieldType.Filled }
 
-            val textField: @Composable () -> Unit = @Composable {
-                when (selectedTextField) {
-                    TextFieldType.Filled ->
-                        FilledTextField(
-                            value = text,
-                             text = it },
-                            label = {
-                                val label =
-                                    "Label" + if (selectedOption == Option.Error) "*" else ""
-                                Text(text = label)
-                            },
-                            leadingIcon = { if (leadingChecked) Icon(Icons.Filled.Favorite) },
-                            trailingIcon = { if (trailingChecked) Icon(Icons.Filled.Info) },
-                            isErrorValue = selectedOption == Option.Error
-                        )
-                    TextFieldType.Outlined ->
-                        OutlinedTextField(
-                            value = text,
-                             text = it },
-                            label = {
-                                val label =
-                                    "Label" + if (selectedOption == Option.Error) "*" else ""
-                                Text(text = label)
-                            },
-                            leadingIcon = { if (leadingChecked) Icon(Icons.Filled.Favorite) },
-                            trailingIcon = { if (trailingChecked) Icon(Icons.Filled.Info) },
-                            isErrorValue = selectedOption == Option.Error
-                        )
-                }
+        val textField: @Composable () -> Unit = @Composable {
+            when (selectedTextField) {
+                TextFieldType.Filled ->
+                    FilledTextField(
+                        value = text,
+                         text = it },
+                        label = {
+                            val label =
+                                "Label" + if (selectedOption == Option.Error) "*" else ""
+                            Text(text = label)
+                        },
+                        leadingIcon = { if (leadingChecked) Icon(Icons.Filled.Favorite) },
+                        trailingIcon = { if (trailingChecked) Icon(Icons.Filled.Info) },
+                        isErrorValue = selectedOption == Option.Error
+                    )
+                TextFieldType.Outlined ->
+                    OutlinedTextField(
+                        value = text,
+                         text = it },
+                        label = {
+                            val label =
+                                "Label" + if (selectedOption == Option.Error) "*" else ""
+                            Text(text = label)
+                        },
+                        leadingIcon = { if (leadingChecked) Icon(Icons.Filled.Favorite) },
+                        trailingIcon = { if (trailingChecked) Icon(Icons.Filled.Info) },
+                        isErrorValue = selectedOption == Option.Error
+                    )
             }
+        }
 
-            Box(Modifier.preferredHeight(150.dp).gravity(Alignment.CenterHorizontally)) {
-                if (selectedOption == Option.None) {
-                    textField()
-                } else {
-                    TextFieldWithMessage(textField, selectedOption)
-                }
+        Box(Modifier.preferredHeight(150.dp).gravity(Alignment.CenterHorizontally)) {
+            if (selectedOption == Option.None) {
+                textField()
+            } else {
+                TextFieldWithMessage(textField, selectedOption)
             }
+        }
 
+        Column {
+            Title("Text field type")
             Column {
-                Title("Text field type")
-                Column {
-                    TextFieldType.values().map { it.name }.forEach { textType ->
-                        Row(Modifier
-                            .fillMaxWidth()
-                            .selectable(
-                                selected = (textType == selectedTextField.name),
-                                >
-                                    selectedTextField = TextFieldType.valueOf(textType)
-                                }
-                            )
-                            .padding(horizontal = 16.dp)
-                        ) {
-                            RadioButton(
-                                selected = (textType == selectedTextField.name),
-                                 selectedTextField = TextFieldType.valueOf(textType) }
-                            )
-                            Text(
-                                text = textType,
-                                style = MaterialTheme.typography.body1.merge(),
-                                modifier = Modifier.padding(start = 16.dp)
-                            )
-                        }
+                TextFieldType.values().map { it.name }.forEach { textType ->
+                    Row(Modifier
+                        .fillMaxWidth()
+                        .selectable(
+                            selected = (textType == selectedTextField.name),
+                            >
+                                selectedTextField = TextFieldType.valueOf(textType)
+                            }
+                        )
+                        .padding(horizontal = 16.dp)
+                    ) {
+                        RadioButton(
+                            selected = (textType == selectedTextField.name),
+                             selectedTextField = TextFieldType.valueOf(textType) }
+                        )
+                        Text(
+                            text = textType,
+                            style = MaterialTheme.typography.body1.merge(),
+                            modifier = Modifier.padding(start = 16.dp)
+                        )
                     }
                 }
+            }
 
-                Title("Options")
-                OptionRow(
-                    title = "Leading icon",
-                    checked = leadingChecked,
-                     leadingChecked = it }
-                )
-                OptionRow(
-                    title = "Trailing icon",
-                    checked = trailingChecked,
-                     trailingChecked = it }
-                )
-                OptionRow(
-                    title = "Character counter (TODO)",
-                    checked = characterCounterChecked,
-                    enabled = false,
-                     /* TODO */ }
-                )
+            Title("Options")
+            OptionRow(
+                title = "Leading icon",
+                checked = leadingChecked,
+                 leadingChecked = it }
+            )
+            OptionRow(
+                title = "Trailing icon",
+                checked = trailingChecked,
+                 trailingChecked = it }
+            )
+            OptionRow(
+                title = "Character counter (TODO)",
+                checked = characterCounterChecked,
+                enabled = false,
+                 /* TODO */ }
+            )
 
-                Spacer(Modifier.preferredHeight(20.dp))
+            Spacer(Modifier.preferredHeight(20.dp))
 
-                Title("Assistive text")
-                Column {
-                    Option.values().map { it.name }.forEach { text ->
-                        Row(Modifier
-                            .fillMaxWidth()
-                            .selectable(
-                                selected = (text == selectedOption.name),
-                                 selectedOption = Option.valueOf(text) }
-                            )
-                            .padding(horizontal = 16.dp)
-                        ) {
-                            RadioButton(
-                                selected = (text == selectedOption.name),
-                                 selectedOption = Option.valueOf(text) }
-                            )
-                            Text(
-                                text = text,
-                                style = MaterialTheme.typography.body1.merge(),
-                                modifier = Modifier.padding(start = 16.dp)
-                            )
-                        }
+            Title("Assistive text")
+            Column {
+                Option.values().map { it.name }.forEach { text ->
+                    Row(Modifier
+                        .fillMaxWidth()
+                        .selectable(
+                            selected = (text == selectedOption.name),
+                             selectedOption = Option.valueOf(text) }
+                        )
+                        .padding(horizontal = 16.dp)
+                    ) {
+                        RadioButton(
+                            selected = (text == selectedOption.name),
+                             selectedOption = Option.valueOf(text) }
+                        )
+                        Text(
+                            text = text,
+                            style = MaterialTheme.typography.body1.merge(),
+                            modifier = Modifier.padding(start = 16.dp)
+                        )
                     }
                 }
             }
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ProgressIndicatorDemo.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ProgressIndicatorDemo.kt
index 4331d1b..f186c0c 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ProgressIndicatorDemo.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ProgressIndicatorDemo.kt
@@ -28,7 +28,7 @@
 import androidx.compose.setValue
 import androidx.ui.core.Alignment
 import androidx.ui.core.Modifier
-import androidx.ui.foundation.VerticalScroller
+import androidx.ui.foundation.ScrollableColumn
 import androidx.ui.graphics.Color
 import androidx.ui.layout.Arrangement
 import androidx.ui.layout.Row
@@ -43,7 +43,7 @@
     onActive { state.start() }
     onDispose { state.stop() }
 
-    VerticalScroller {
+    ScrollableColumn {
         val modifier = Modifier.weight(1f, true)
             .gravity(Alignment.CenterHorizontally)
             .fillMaxWidth()
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SelectionControlsDemo.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SelectionControlsDemo.kt
index 485a171..2fd4f6b 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SelectionControlsDemo.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SelectionControlsDemo.kt
@@ -18,16 +18,15 @@
 
 import androidx.compose.Composable
 import androidx.ui.core.Modifier
+import androidx.ui.foundation.ScrollableColumn
 import androidx.ui.foundation.Text
-import androidx.ui.foundation.VerticalScroller
 import androidx.ui.layout.Arrangement
-import androidx.ui.layout.Column
+import androidx.ui.layout.InnerPadding
 import androidx.ui.layout.Spacer
 import androidx.ui.layout.height
-import androidx.ui.layout.padding
 import androidx.ui.material.MaterialTheme
-import androidx.ui.material.samples.RadioGroupSample
 import androidx.ui.material.samples.RadioButtonSample
+import androidx.ui.material.samples.RadioGroupSample
 import androidx.ui.material.samples.SwitchSample
 import androidx.ui.material.samples.TriStateCheckboxSample
 import androidx.ui.unit.dp
@@ -35,19 +34,20 @@
 @Composable
 fun SelectionControlsDemo() {
     val headerStyle = MaterialTheme.typography.h6
-    VerticalScroller {
-        Column(Modifier.padding(10.dp), verticalArrangement = Arrangement.SpaceEvenly) {
-            Text(text = "Checkbox", style = headerStyle)
-            TriStateCheckboxSample()
-            Spacer(Modifier.height(16.dp))
-            Text(text = "Switch", style = headerStyle)
-            SwitchSample()
-            Spacer(Modifier.height(16.dp))
-            Text(text = "RadioButtons with custom colors", style = headerStyle)
-            RadioButtonSample()
-            Spacer(Modifier.height(16.dp))
-            Text(text = "Radio group", style = headerStyle)
-            RadioGroupSample()
-        }
+    ScrollableColumn(
+        contentPadding = InnerPadding(10.dp),
+        verticalArrangement = Arrangement.SpaceEvenly
+    ) {
+        Text(text = "Checkbox", style = headerStyle)
+        TriStateCheckboxSample()
+        Spacer(Modifier.height(16.dp))
+        Text(text = "Switch", style = headerStyle)
+        SwitchSample()
+        Spacer(Modifier.height(16.dp))
+        Text(text = "RadioButtons with custom colors", style = headerStyle)
+        RadioButtonSample()
+        Spacer(Modifier.height(16.dp))
+        Text(text = "Radio group", style = headerStyle)
+        RadioGroupSample()
     }
 }
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SnackbarDemo.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SnackbarDemo.kt
index 26c8ba3..5879d51 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SnackbarDemo.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SnackbarDemo.kt
@@ -18,9 +18,9 @@
 
 import androidx.compose.Composable
 import androidx.ui.core.Modifier
+import androidx.ui.foundation.ScrollableColumn
 import androidx.ui.foundation.Text
-import androidx.ui.foundation.VerticalScroller
-import androidx.ui.layout.Column
+import androidx.ui.layout.InnerPadding
 import androidx.ui.layout.padding
 import androidx.ui.material.MaterialTheme
 import androidx.ui.material.Snackbar
@@ -31,24 +31,22 @@
 
 @Composable
 fun SnackbarDemo() {
-    VerticalScroller {
-        Column(Modifier.padding(12.dp, 0.dp, 12.dp, 0.dp)) {
-            val textSpacing = Modifier.padding(top = 12.dp, bottom = 12.dp)
-            Text("Default Snackbar", modifier = textSpacing)
-            SimpleSnackbar()
-            Text("Snackbar with long text", modifier = textSpacing)
-            Snackbar(
-                text = { Text("This song already exists in the current playlist") },
-                action = {
-                    TextButton(
-                        contentColor = snackbarPrimaryColorFor(MaterialTheme.colors),
-                         /* perform undo */ }
-                    ) {
-                        Text("ADD THIS SONG ANYWAY")
-                    }
-                },
-                actionOnNewLine = true
-            )
-        }
+    ScrollableColumn(contentPadding = InnerPadding(12.dp, 0.dp, 12.dp, 0.dp)) {
+        val textSpacing = Modifier.padding(top = 12.dp, bottom = 12.dp)
+        Text("Default Snackbar", modifier = textSpacing)
+        SimpleSnackbar()
+        Text("Snackbar with long text", modifier = textSpacing)
+        Snackbar(
+            text = { Text("This song already exists in the current playlist") },
+            action = {
+                TextButton(
+                    contentColor = snackbarPrimaryColorFor(MaterialTheme.colors),
+                     /* perform undo */ }
+                ) {
+                    Text("ADD THIS SONG ANYWAY")
+                }
+            },
+            actionOnNewLine = true
+        )
     }
 }
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/TabDemo.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/TabDemo.kt
index 545d0f6..1c251d3 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/TabDemo.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/TabDemo.kt
@@ -20,8 +20,8 @@
 import androidx.compose.state
 import androidx.ui.core.Alignment
 import androidx.ui.core.Modifier
+import androidx.ui.foundation.ScrollableColumn
 import androidx.ui.foundation.Text
-import androidx.ui.foundation.VerticalScroller
 import androidx.ui.graphics.Color
 import androidx.ui.layout.Spacer
 import androidx.ui.layout.height
@@ -39,7 +39,7 @@
 
 @Composable
 fun TabDemo() {
-    VerticalScroller {
+    ScrollableColumn {
         val showingSimple = state { true }
         val buttonText = "Show ${if (showingSimple.value) "custom" else "simple"} tabs"
 
diff --git a/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/AccountsScreen.kt b/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/AccountsScreen.kt
index a838d92..5fec320 100644
--- a/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/AccountsScreen.kt
+++ b/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/AccountsScreen.kt
@@ -20,7 +20,8 @@
 import androidx.ui.core.Alignment
 import androidx.ui.core.Modifier
 import androidx.ui.foundation.Text
-import androidx.ui.foundation.VerticalScroller
+import androidx.ui.foundation.rememberScrollState
+import androidx.ui.foundation.verticalScroll
 import androidx.ui.layout.Column
 import androidx.ui.layout.Spacer
 import androidx.ui.layout.Stack
@@ -36,39 +37,37 @@
  */
 @Composable
 fun AccountsBody(accounts: List<Account>) {
-    VerticalScroller {
-        Stack(Modifier.padding(16.dp)) {
-            val accountsProportion = accounts.extractProportions { it.balance }
-            val colors = accounts.map { it.color }
-            AnimatedCircle(
-                Modifier.preferredHeight(300.dp).gravity(Alignment.Center).fillMaxWidth(),
-                accountsProportion,
-                colors
+    Stack(Modifier.verticalScroll(rememberScrollState(0f)).padding(16.dp)) {
+        val accountsProportion = accounts.extractProportions { it.balance }
+        val colors = accounts.map { it.color }
+        AnimatedCircle(
+            Modifier.preferredHeight(300.dp).gravity(Alignment.Center).fillMaxWidth(),
+            accountsProportion,
+            colors
+        )
+        Column(modifier = Modifier.gravity(Alignment.Center)) {
+            Text(
+                text = "Total",
+                style = MaterialTheme.typography.body1,
+                modifier = Modifier.gravity(Alignment.CenterHorizontally)
             )
-            Column(modifier = Modifier.gravity(Alignment.Center)) {
-                Text(
-                    text = "Total",
-                    style = MaterialTheme.typography.body1,
-                    modifier = Modifier.gravity(Alignment.CenterHorizontally)
-                )
-                Text(
-                    text = "$12,132.49",
-                    style = MaterialTheme.typography.h2,
-                    modifier = Modifier.gravity(Alignment.CenterHorizontally)
-                )
-            }
+            Text(
+                text = "$12,132.49",
+                style = MaterialTheme.typography.h2,
+                modifier = Modifier.gravity(Alignment.CenterHorizontally)
+            )
         }
-        Spacer(Modifier.preferredHeight(10.dp))
-        Card {
-            Column(modifier = Modifier.padding(12.dp)) {
-                accounts.forEach { account ->
-                    AccountRow(
-                        name = account.name,
-                        number = account.number,
-                        amount = account.balance,
-                        color = account.color
-                    )
-                }
+    }
+    Spacer(Modifier.preferredHeight(10.dp))
+    Card {
+        Column(modifier = Modifier.padding(12.dp)) {
+            accounts.forEach { account ->
+                AccountRow(
+                    name = account.name,
+                    number = account.number,
+                    amount = account.balance,
+                    color = account.color
+                )
             }
         }
     }
diff --git a/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/BillsScreen.kt b/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/BillsScreen.kt
index e34f881..ecd46eb 100644
--- a/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/BillsScreen.kt
+++ b/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/BillsScreen.kt
@@ -20,7 +20,8 @@
 import androidx.ui.core.Alignment
 import androidx.ui.core.Modifier
 import androidx.ui.foundation.Text
-import androidx.ui.foundation.VerticalScroller
+import androidx.ui.foundation.rememberScrollState
+import androidx.ui.foundation.verticalScroll
 import androidx.ui.layout.Column
 import androidx.ui.layout.Spacer
 import androidx.ui.layout.Stack
@@ -36,39 +37,37 @@
  */
 @Composable
 fun BillsBody(bills: List<Bill>) {
-    VerticalScroller {
-        Stack(Modifier.padding(16.dp)) {
-            val accountsProportion = bills.extractProportions { it.amount }
-            val colors = bills.map { it.color }
-            AnimatedCircle(
-                Modifier.gravity(Alignment.Center).preferredHeight(300.dp).fillMaxWidth(),
-                accountsProportion,
-                colors
+    Stack(Modifier.verticalScroll(rememberScrollState()).padding(16.dp)) {
+        val accountsProportion = bills.extractProportions { it.amount }
+        val colors = bills.map { it.color }
+        AnimatedCircle(
+            Modifier.gravity(Alignment.Center).preferredHeight(300.dp).fillMaxWidth(),
+            accountsProportion,
+            colors
+        )
+        Column(modifier = Modifier.gravity(Alignment.Center)) {
+            Text(
+                text = "Due",
+                style = MaterialTheme.typography.body1,
+                modifier = Modifier.gravity(Alignment.CenterHorizontally)
             )
-            Column(modifier = Modifier.gravity(Alignment.Center)) {
-                Text(
-                    text = "Due",
-                    style = MaterialTheme.typography.body1,
-                    modifier = Modifier.gravity(Alignment.CenterHorizontally)
-                )
-                Text(
-                    text = "$1,810.00",
-                    style = MaterialTheme.typography.h2,
-                    modifier = Modifier.gravity(Alignment.CenterHorizontally)
-                )
-            }
+            Text(
+                text = "$1,810.00",
+                style = MaterialTheme.typography.h2,
+                modifier = Modifier.gravity(Alignment.CenterHorizontally)
+            )
         }
-        Spacer(Modifier.preferredHeight(10.dp))
-        Card {
-            Column(modifier = Modifier.padding(12.dp)) {
-                bills.forEach { bill ->
-                    BillRow(
-                        name = bill.name,
-                        due = bill.due,
-                        amount = bill.amount,
-                        color = bill.color
-                    )
-                }
+    }
+    Spacer(Modifier.preferredHeight(10.dp))
+    Card {
+        Column(modifier = Modifier.padding(12.dp)) {
+            bills.forEach { bill ->
+                BillRow(
+                    name = bill.name,
+                    due = bill.due,
+                    amount = bill.amount,
+                    color = bill.color
+                )
             }
         }
     }
diff --git a/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/OverviewScreen.kt b/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/OverviewScreen.kt
index 27f3290..da2a498 100644
--- a/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/OverviewScreen.kt
+++ b/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/OverviewScreen.kt
@@ -23,8 +23,8 @@
 import androidx.compose.state
 import androidx.ui.core.Alignment
 import androidx.ui.core.Modifier
+import androidx.ui.foundation.ScrollableColumn
 import androidx.ui.foundation.Text
-import androidx.ui.foundation.VerticalScroller
 import androidx.ui.layout.Arrangement
 import androidx.ui.layout.Column
 import androidx.ui.layout.InnerPadding
@@ -44,14 +44,12 @@
 
 @Composable
 fun OverviewBody() {
-    VerticalScroller {
-        Column(Modifier.padding(16.dp)) {
-            AlertCard()
-            Spacer(Modifier.preferredHeight(RallyDefaultPadding))
-            AccountsCard()
-            Spacer(Modifier.preferredHeight(RallyDefaultPadding))
-            BillsCard()
-        }
+    ScrollableColumn(contentPadding = InnerPadding(16.dp)) {
+        AlertCard()
+        Spacer(Modifier.preferredHeight(RallyDefaultPadding))
+        AccountsCard()
+        Spacer(Modifier.preferredHeight(RallyDefaultPadding))
+        BillsCard()
     }
 }
 
diff --git a/ui/ui-material/samples/src/main/java/androidx/ui/material/samples/ScaffoldSamples.kt b/ui/ui-material/samples/src/main/java/androidx/ui/material/samples/ScaffoldSamples.kt
index 8ae8af9..d4d2586d 100644
--- a/ui/ui-material/samples/src/main/java/androidx/ui/material/samples/ScaffoldSamples.kt
+++ b/ui/ui-material/samples/src/main/java/androidx/ui/material/samples/ScaffoldSamples.kt
@@ -24,15 +24,13 @@
 import androidx.ui.core.Modifier
 import androidx.ui.foundation.Box
 import androidx.ui.foundation.Icon
+import androidx.ui.foundation.ScrollableColumn
 import androidx.ui.foundation.Text
-import androidx.ui.foundation.VerticalScroller
 import androidx.ui.foundation.shape.corner.CircleShape
 import androidx.ui.foundation.shape.corner.CutCornerShape
 import androidx.ui.foundation.shape.corner.RoundedCornerShape
 import androidx.ui.graphics.Color
-import androidx.ui.layout.Column
 import androidx.ui.layout.fillMaxWidth
-import androidx.ui.layout.padding
 import androidx.ui.layout.preferredHeight
 import androidx.ui.material.BottomAppBar
 import androidx.ui.material.DrawerState
@@ -82,14 +80,12 @@
             )
         },
         bodyContent = { innerPadding ->
-            VerticalScroller {
-                Column(Modifier.padding(innerPadding)) {
-                    repeat(100) {
-                        Box(
-                            Modifier.fillMaxWidth().preferredHeight(50.dp),
-                            backgroundColor = colors[it % colors.size]
-                        )
-                    }
+            ScrollableColumn(contentPadding = innerPadding) {
+                repeat(100) {
+                    Box(
+                        Modifier.fillMaxWidth().preferredHeight(50.dp),
+                        backgroundColor = colors[it % colors.size]
+                    )
                 }
             }
         }
@@ -151,14 +147,12 @@
         floatingActionButtonPosition = Scaffold.FabPosition.Center,
         isFloatingActionButtonDocked = true,
         bodyContent = { innerPadding ->
-            VerticalScroller {
-                Column(Modifier.padding(innerPadding)) {
-                    repeat(100) {
-                        Box(
-                            Modifier.fillMaxWidth().preferredHeight(50.dp),
-                            backgroundColor = colors[it % colors.size]
-                        )
-                    }
+            ScrollableColumn(contentPadding = innerPadding) {
+                repeat(100) {
+                    Box(
+                        Modifier.fillMaxWidth().preferredHeight(50.dp),
+                        backgroundColor = colors[it % colors.size]
+                    )
                 }
             }
         }
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/Menu.kt b/ui/ui-material/src/main/java/androidx/ui/material/Menu.kt
index b801841..a009453 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/Menu.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/Menu.kt
@@ -36,11 +36,10 @@
 import androidx.ui.core.Popup
 import androidx.ui.core.PopupPositionProvider
 import androidx.ui.core.TransformOrigin
-import androidx.ui.unit.Position
 import androidx.ui.foundation.Box
 import androidx.ui.foundation.ContentGravity
 import androidx.ui.foundation.ProvideTextStyle
-import androidx.ui.foundation.VerticalScroller
+import androidx.ui.foundation.ScrollableColumn
 import androidx.ui.foundation.clickable
 import androidx.ui.layout.ColumnScope
 import androidx.ui.layout.ExperimentalLayout
@@ -54,6 +53,7 @@
 import androidx.ui.unit.IntBounds
 import androidx.ui.unit.IntOffset
 import androidx.ui.unit.IntSize
+import androidx.ui.unit.Position
 import androidx.ui.unit.dp
 import androidx.ui.unit.height
 import androidx.ui.unit.width
@@ -143,7 +143,7 @@
                     elevation = MenuElevation
                 ) {
                     @OptIn(ExperimentalLayout::class)
-                    VerticalScroller(
+                    ScrollableColumn(
                         modifier = dropdownModifier
                             .padding(vertical = DropdownMenuVerticalPadding)
                             .preferredWidth(IntrinsicSize.Max),
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/Tab.kt b/ui/ui-material/src/main/java/androidx/ui/material/Tab.kt
index 0707adb..741d08a 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/Tab.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/Tab.kt
@@ -43,11 +43,12 @@
 import androidx.ui.foundation.Box
 import androidx.ui.foundation.ContentColorAmbient
 import androidx.ui.foundation.ContentGravity
-import androidx.ui.foundation.HorizontalScroller
 import androidx.ui.foundation.ProvideTextStyle
-import androidx.ui.foundation.ScrollerPosition
+import androidx.ui.foundation.ScrollState
+import androidx.ui.foundation.ScrollableRow
 import androidx.ui.foundation.contentColor
 import androidx.ui.foundation.drawBackground
+import androidx.ui.foundation.rememberScrollState
 import androidx.ui.foundation.selection.selectable
 import androidx.ui.graphics.Color
 import androidx.ui.layout.Row
@@ -216,10 +217,10 @@
     indicatorContainer: @Composable (tabPositions: List<TabPosition>) -> Unit,
     divider: @Composable () -> Unit
 ) {
-    val scrollerPosition = ScrollerPosition()
-    val scrollableTabData = remember(scrollerPosition) {
+    val scrollState = rememberScrollState()
+    val scrollableTabData = remember(scrollState) {
         ScrollableTabData(
-            scrollerPosition = scrollerPosition,
+            scrollState = scrollState,
             selectedTab = selectedIndex
         )
     }
@@ -235,14 +236,11 @@
         }
     }
 
-    HorizontalScroller(
-        scrollerPosition = scrollerPosition,
-        modifier = Modifier.fillMaxWidth()
-    ) {
-        val indicatorTag = "indicator"
-        val dividerTag = "divider"
+    val indicatorTag = "indicator"
+    val dividerTag = "divider"
+    ScrollableRow(modifier = Modifier.fillMaxWidth(), scrollState = scrollState) {
         Layout(
-            {
+            children = {
                 tabs()
                 Box(Modifier.layoutId(indicatorTag), children = indicator)
                 Box(Modifier.layoutId(dividerTag), children = divider)
@@ -317,7 +315,7 @@
  * Class holding onto state needed for [ScrollableTabRow]
  */
 private class ScrollableTabData(
-    private val scrollerPosition: ScrollerPosition,
+    private val scrollState: ScrollState,
     private var selectedTab: Int
 ) {
     fun onLaidOut(
@@ -332,7 +330,7 @@
                 // Scrolls to the tab with [tabPosition], trying to place it in the center of the
                 // screen or as close to the center as possible.
                 val calculatedOffset = it.calculateTabOffset(density, edgeOffset, tabPositions)
-                scrollerPosition.smoothScrollTo(calculatedOffset)
+                scrollState.smoothScrollTo(calculatedOffset)
             }
         }
     }
@@ -348,7 +346,7 @@
         tabPositions: List<TabPosition>
     ): Float = with(density) {
         val totalTabRowWidth = tabPositions.last().right.toIntPx() + edgeOffset
-        val visibleWidth = totalTabRowWidth - scrollerPosition.maxPosition.toInt()
+        val visibleWidth = totalTabRowWidth - scrollState.maxValue.toInt()
         val tabOffset = left.toIntPx()
         val scrollerCenter = visibleWidth / 2
         val tabWidth = width.toIntPx()
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/TextFieldImpl.kt b/ui/ui-material/src/main/java/androidx/ui/material/TextFieldImpl.kt
index 029ba9e..ff2ebab 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/TextFieldImpl.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/TextFieldImpl.kt
@@ -251,7 +251,7 @@
 }
 
 /**
- * Similar to [androidx.ui.foundation.VerticalScroller] but does not lose the minWidth constraints.
+ * Similar to [androidx.ui.foundation.ScrollableColumn] but does not lose the minWidth constraints.
  */
 @VisibleForTesting
 @Composable
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/IsDisplayedTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/IsDisplayedTest.kt
index 67ae035..03670aa 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/IsDisplayedTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/IsDisplayedTest.kt
@@ -37,7 +37,7 @@
 import androidx.ui.core.Modifier
 import androidx.ui.core.setContent
 import androidx.ui.foundation.Box
-import androidx.ui.foundation.VerticalScroller
+import androidx.ui.foundation.ScrollableColumn
 import androidx.ui.graphics.Color
 import androidx.ui.layout.Row
 import androidx.ui.layout.Stack
@@ -108,7 +108,7 @@
     @Test
     fun componentInScrollable_isDisplayed() {
         composeTestRule.setContent {
-            VerticalScroller(modifier = Modifier.size(100.dp)) {
+            ScrollableColumn(modifier = Modifier.size(100.dp)) {
                 repeat(10) { Item(it, height = 30.dp) }
             }
         }
@@ -120,7 +120,7 @@
     @Test
     fun componentInScrollable_isNotDisplayed() {
         composeTestRule.setContent {
-            VerticalScroller(modifier = Modifier.size(100.dp)) {
+            ScrollableColumn(modifier = Modifier.size(100.dp)) {
                 repeat(10) { Item(it, height = 30.dp) }
             }
         }
diff --git a/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/ComposeInputField.kt b/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/ComposeInputField.kt
index 7519126..3dd01e0 100644
--- a/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/ComposeInputField.kt
+++ b/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/ComposeInputField.kt
@@ -19,8 +19,8 @@
 import androidx.compose.Composable
 import androidx.compose.key
 import androidx.compose.state
+import androidx.ui.foundation.ScrollableColumn
 import androidx.ui.foundation.TextField
-import androidx.ui.foundation.VerticalScroller
 import androidx.ui.input.ImeAction
 import androidx.ui.input.KeyboardType
 import androidx.ui.input.TextFieldValue
@@ -51,7 +51,7 @@
 
 @Composable
 fun InputFieldDemo() {
-    VerticalScroller {
+    ScrollableColumn {
         TagLine(tag = "simple editing")
         EditLine()
         TagLine(tag = "simple editing2")
diff --git a/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/ComposeInputFieldFocusTransition.kt b/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/ComposeInputFieldFocusTransition.kt
index 9d3ed06..0eb9253 100644
--- a/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/ComposeInputFieldFocusTransition.kt
+++ b/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/ComposeInputFieldFocusTransition.kt
@@ -19,8 +19,8 @@
 import androidx.compose.Composable
 import androidx.compose.state
 import androidx.ui.core.focus.FocusModifier
+import androidx.ui.foundation.ScrollableColumn
 import androidx.ui.foundation.TextField
-import androidx.ui.foundation.VerticalScroller
 import androidx.ui.graphics.Color
 import androidx.ui.input.ImeAction
 import androidx.ui.input.TextFieldValue
@@ -32,7 +32,7 @@
 fun TextFieldFocusTransition() {
     val focusModifiers = List(6) { FocusModifier() }
 
-    VerticalScroller {
+    ScrollableColumn {
         TextFieldWithFocusId(focusModifiers[0], focusModifiers[1])
         TextFieldWithFocusId(focusModifiers[1], focusModifiers[2])
         TextFieldWithFocusId(focusModifiers[2], focusModifiers[3])
diff --git a/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/ComposeInputFieldTrickyUseCase.kt b/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/ComposeInputFieldTrickyUseCase.kt
index 1944a0e..012c2ec 100644
--- a/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/ComposeInputFieldTrickyUseCase.kt
+++ b/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/ComposeInputFieldTrickyUseCase.kt
@@ -17,8 +17,8 @@
 package androidx.ui.text.demos
 
 import androidx.compose.Composable
+import androidx.ui.foundation.ScrollableColumn
 import androidx.ui.foundation.TextField
-import androidx.ui.foundation.VerticalScroller
 import androidx.ui.input.KeyboardType
 import androidx.ui.input.TextFieldValue
 import androidx.ui.savedinstancestate.savedInstanceState
@@ -27,7 +27,7 @@
 
 @Composable
 fun InputFieldTrickyUseCase() {
-    VerticalScroller {
+    ScrollableColumn {
         TagLine(tag = "don't set if non number is added")
         RejectNonDigits()
 
diff --git a/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/ComposeMultiParagraph.kt b/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/ComposeMultiParagraph.kt
index 60cb5ff..f16a0d0 100644
--- a/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/ComposeMultiParagraph.kt
+++ b/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/ComposeMultiParagraph.kt
@@ -17,8 +17,8 @@
 package androidx.ui.text.demos
 
 import androidx.compose.Composable
+import androidx.ui.foundation.ScrollableColumn
 import androidx.ui.foundation.Text
-import androidx.ui.foundation.VerticalScroller
 import androidx.ui.text.AnnotatedString
 import androidx.ui.text.ParagraphStyle
 import androidx.ui.text.TextStyle
@@ -33,7 +33,7 @@
 
 @Composable
 fun MultiParagraphDemo() {
-    VerticalScroller {
+    ScrollableColumn {
         TagLine(tag = "multiple paragraphs basic")
         TextDemoParagraph()
         TagLine(tag = "multiple paragraphs TextAlign")
diff --git a/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/ComposeText.kt b/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/ComposeText.kt
index 7234018..245a5667 100644
--- a/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/ComposeText.kt
+++ b/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/ComposeText.kt
@@ -18,8 +18,8 @@
 
 import androidx.compose.Composable
 import androidx.ui.core.Modifier
+import androidx.ui.foundation.ScrollableColumn
 import androidx.ui.foundation.Text
-import androidx.ui.foundation.VerticalScroller
 import androidx.ui.geometry.Offset
 import androidx.ui.graphics.Color
 import androidx.ui.graphics.Shadow
@@ -61,7 +61,7 @@
 
 @Composable
 fun TextDemo() {
-    VerticalScroller {
+    ScrollableColumn {
         TagLine(tag = "color, fontSize, fontWeight and fontStyle")
         TextDemoBasic()
         TagLine(
diff --git a/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/ComposeTextSelection.kt b/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/ComposeTextSelection.kt
index ccf6119..b4b6134 100644
--- a/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/ComposeTextSelection.kt
+++ b/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/ComposeTextSelection.kt
@@ -21,14 +21,14 @@
 import androidx.ui.core.Modifier
 import androidx.ui.core.selection.Selection
 import androidx.ui.core.selection.SelectionContainer
+import androidx.ui.foundation.ScrollableColumn
 import androidx.ui.foundation.Text
-import androidx.ui.foundation.VerticalScroller
 import androidx.ui.graphics.Color
+import androidx.ui.intl.LocaleList
 import androidx.ui.layout.Column
 import androidx.ui.layout.Row
 import androidx.ui.layout.fillMaxHeight
 import androidx.ui.layout.fillMaxWidth
-import androidx.ui.intl.LocaleList
 import androidx.ui.text.SpanStyle
 import androidx.ui.text.TextStyle
 import androidx.ui.text.annotatedString
@@ -38,7 +38,7 @@
 
 @Composable
 fun TextSelectionDemo() {
-    VerticalScroller {
+    ScrollableColumn {
         TagLine(tag = "selection")
         TextDemoSelection()
         TagLine(tag = "selection with string input")
diff --git a/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/ComposeTextSelectionSample.kt b/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/ComposeTextSelectionSample.kt
index 8e04a21..84e248f 100644
--- a/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/ComposeTextSelectionSample.kt
+++ b/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/ComposeTextSelectionSample.kt
@@ -22,8 +22,8 @@
 import androidx.ui.core.selection.Selection
 import androidx.ui.core.selection.SelectionContainer
 import androidx.ui.foundation.Box
+import androidx.ui.foundation.ScrollableColumn
 import androidx.ui.foundation.Text
-import androidx.ui.foundation.VerticalScroller
 import androidx.ui.graphics.Color
 import androidx.ui.layout.Column
 import androidx.ui.layout.Row
@@ -73,7 +73,7 @@
 @Composable
 fun TextSelectionSample() {
     val selection = state<Selection?> { null }
-    VerticalScroller {
+    ScrollableColumn {
         SelectionContainer(
             selection = selection.value,
              selection.value = it }
diff --git a/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/ComposeVariousInputField.kt b/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/ComposeVariousInputField.kt
index c18c442..01351ca 100644
--- a/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/ComposeVariousInputField.kt
+++ b/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/ComposeVariousInputField.kt
@@ -25,9 +25,9 @@
 import androidx.ui.core.id
 import androidx.ui.core.layoutId
 import androidx.ui.foundation.Box
+import androidx.ui.foundation.ScrollableColumn
 import androidx.ui.foundation.Text
 import androidx.ui.foundation.TextField
-import androidx.ui.foundation.VerticalScroller
 import androidx.ui.foundation.drawBackground
 import androidx.ui.geometry.Rect
 import androidx.ui.graphics.Color
@@ -38,9 +38,9 @@
 import androidx.ui.input.TextFieldValue
 import androidx.ui.input.TransformedText
 import androidx.ui.input.VisualTransformation
-import androidx.ui.text.AnnotatedString
 import androidx.ui.intl.LocaleList
 import androidx.ui.savedinstancestate.savedInstanceState
+import androidx.ui.text.AnnotatedString
 import androidx.ui.text.TextLayoutResult
 import androidx.ui.text.TextRange
 import androidx.ui.text.TextStyle
@@ -181,7 +181,7 @@
 
 @Composable
 fun VariousInputFieldDemo() {
-    VerticalScroller {
+    ScrollableColumn {
         TagLine(tag = "Capitalization")
         VariousEditLine(
             keyboardType = KeyboardType.Ascii,
diff --git a/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/InteractiveText.kt b/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/InteractiveText.kt
index 2315b7e..8063f46 100644
--- a/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/InteractiveText.kt
+++ b/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/InteractiveText.kt
@@ -19,8 +19,8 @@
 import androidx.compose.Composable
 import androidx.compose.state
 import androidx.ui.foundation.ClickableText
+import androidx.ui.foundation.ScrollableColumn
 import androidx.ui.foundation.Text
-import androidx.ui.foundation.VerticalScroller
 import androidx.ui.text.AnnotatedString
 
 @Composable
@@ -31,7 +31,7 @@
 @Composable
 fun TextOnClick() {
     val clickedOffset = state { -1 }
-    VerticalScroller {
+    ScrollableColumn {
         Text("Clicked Offset: ${clickedOffset.value}")
         ClickableText(
             text = AnnotatedString("Click Me")
diff --git a/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/TextFieldWIthScroller.kt b/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/TextFieldWIthScroller.kt
index efcb7fd..ab7feb5 100644
--- a/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/TextFieldWIthScroller.kt
+++ b/ui/ui-text/integration-tests/ui-text-compose-demos/src/main/java/androidx/ui/text/demos/TextFieldWIthScroller.kt
@@ -19,18 +19,20 @@
 import androidx.compose.Composable
 import androidx.compose.state
 import androidx.ui.core.Modifier
+import androidx.ui.foundation.ScrollableColumn
 import androidx.ui.foundation.TextField
-import androidx.ui.foundation.VerticalScroller
 import androidx.ui.input.TextFieldValue
 import androidx.ui.layout.padding
 import androidx.ui.unit.dp
 
 @Composable
 fun TextFieldWithScrollerDemo() {
-    VerticalScroller {
-        val state = state { TextFieldValue(
-            text = List(100) { "Line: $it" }.joinToString("\n")
-        ) }
+    ScrollableColumn {
+        val state = state {
+            TextFieldValue(
+                text = List(100) { "Line: $it" }.joinToString("\n")
+            )
+        }
         TextField(
             value = state.value,
              state.value = it },