[go: nahoru, domu]

Zoomable minor rework.

This CL adds few params and renamed few things in zoomable. Namely, it renames ZoomableState to be ZoomableController, adds enabled and onZoomStarted params and makes sure we stop animation of disposal

Relnote: ZoomableState has been renamed to ZoomableController. Custom curve support has been added for smoothScale. `enabled` and `onZoomStarted` functionality has been added

Test: new tests added
Bug: 148542949
Change-Id: If8b8fa81e13136f225a94b87f55d7f1c51fb6747
diff --git a/ui/ui-foundation/api/0.1.0-dev16.txt b/ui/ui-foundation/api/0.1.0-dev16.txt
index c418a4c..7022063 100644
--- a/ui/ui-foundation/api/0.1.0-dev16.txt
+++ b/ui/ui-foundation/api/0.1.0-dev16.txt
@@ -264,16 +264,17 @@
     method public static androidx.ui.core.Modifier scrollable(androidx.ui.core.Modifier, androidx.ui.core.gesture.scrollorientationlocking.Orientation orientation, androidx.compose.foundation.gestures.ScrollableController controller, boolean enabled = true, boolean reverseDirection = false, kotlin.jvm.functions.Function1<? super androidx.ui.core.Direction,java.lang.Boolean> canScroll = { return enabled }, kotlin.jvm.functions.Function1<? super androidx.ui.geometry.Offset,kotlin.Unit>  kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> >
   }
 
-  public final class ZoomableKt {
-    method @androidx.compose.Composable public static androidx.compose.foundation.gestures.ZoomableState ZoomableState(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
-    method @androidx.compose.Composable public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, androidx.compose.foundation.gestures.ZoomableState zoomableState, kotlin.jvm.functions.Function0<kotlin.Unit>? >
-    method @androidx.compose.Composable public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
+  public final class ZoomableController {
+    ctor public ZoomableController(androidx.animation.AnimationClockObservable animationClock, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
+    method public kotlin.jvm.functions.Function1<java.lang.Float,kotlin.Unit> getOnZoomDelta();
+    method public void smoothScaleBy(float value, androidx.animation.AnimationSpec<java.lang.Float> spec = androidx.animation.SpringSpec(Spring.StiffnessLow), kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>? >
+    method public void stopAnimation();
   }
 
-  public final class ZoomableState {
-    ctor public ZoomableState(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta, androidx.animation.AnimationClockObservable animationClock);
-    method public kotlin.jvm.functions.Function1<java.lang.Float,kotlin.Unit> getOnZoomDelta();
-    method public void smoothScaleBy(float value, kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>? >
+  public final class ZoomableKt {
+    method @androidx.compose.Composable public static androidx.compose.foundation.gestures.ZoomableController rememberZoomableController(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
+    method public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, androidx.compose.foundation.gestures.ZoomableController controller, boolean enabled = true, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit>? >
+    method public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, boolean enabled = true, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
   }
 
 }
diff --git a/ui/ui-foundation/api/current.txt b/ui/ui-foundation/api/current.txt
index c418a4c..7022063 100644
--- a/ui/ui-foundation/api/current.txt
+++ b/ui/ui-foundation/api/current.txt
@@ -264,16 +264,17 @@
     method public static androidx.ui.core.Modifier scrollable(androidx.ui.core.Modifier, androidx.ui.core.gesture.scrollorientationlocking.Orientation orientation, androidx.compose.foundation.gestures.ScrollableController controller, boolean enabled = true, boolean reverseDirection = false, kotlin.jvm.functions.Function1<? super androidx.ui.core.Direction,java.lang.Boolean> canScroll = { return enabled }, kotlin.jvm.functions.Function1<? super androidx.ui.geometry.Offset,kotlin.Unit>  kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> >
   }
 
-  public final class ZoomableKt {
-    method @androidx.compose.Composable public static androidx.compose.foundation.gestures.ZoomableState ZoomableState(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
-    method @androidx.compose.Composable public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, androidx.compose.foundation.gestures.ZoomableState zoomableState, kotlin.jvm.functions.Function0<kotlin.Unit>? >
-    method @androidx.compose.Composable public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
+  public final class ZoomableController {
+    ctor public ZoomableController(androidx.animation.AnimationClockObservable animationClock, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
+    method public kotlin.jvm.functions.Function1<java.lang.Float,kotlin.Unit> getOnZoomDelta();
+    method public void smoothScaleBy(float value, androidx.animation.AnimationSpec<java.lang.Float> spec = androidx.animation.SpringSpec(Spring.StiffnessLow), kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>? >
+    method public void stopAnimation();
   }
 
-  public final class ZoomableState {
-    ctor public ZoomableState(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta, androidx.animation.AnimationClockObservable animationClock);
-    method public kotlin.jvm.functions.Function1<java.lang.Float,kotlin.Unit> getOnZoomDelta();
-    method public void smoothScaleBy(float value, kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>? >
+  public final class ZoomableKt {
+    method @androidx.compose.Composable public static androidx.compose.foundation.gestures.ZoomableController rememberZoomableController(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
+    method public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, androidx.compose.foundation.gestures.ZoomableController controller, boolean enabled = true, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit>? >
+    method public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, boolean enabled = true, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
   }
 
 }
diff --git a/ui/ui-foundation/api/public_plus_experimental_0.1.0-dev16.txt b/ui/ui-foundation/api/public_plus_experimental_0.1.0-dev16.txt
index c418a4c..7022063 100644
--- a/ui/ui-foundation/api/public_plus_experimental_0.1.0-dev16.txt
+++ b/ui/ui-foundation/api/public_plus_experimental_0.1.0-dev16.txt
@@ -264,16 +264,17 @@
     method public static androidx.ui.core.Modifier scrollable(androidx.ui.core.Modifier, androidx.ui.core.gesture.scrollorientationlocking.Orientation orientation, androidx.compose.foundation.gestures.ScrollableController controller, boolean enabled = true, boolean reverseDirection = false, kotlin.jvm.functions.Function1<? super androidx.ui.core.Direction,java.lang.Boolean> canScroll = { return enabled }, kotlin.jvm.functions.Function1<? super androidx.ui.geometry.Offset,kotlin.Unit>  kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> >
   }
 
-  public final class ZoomableKt {
-    method @androidx.compose.Composable public static androidx.compose.foundation.gestures.ZoomableState ZoomableState(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
-    method @androidx.compose.Composable public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, androidx.compose.foundation.gestures.ZoomableState zoomableState, kotlin.jvm.functions.Function0<kotlin.Unit>? >
-    method @androidx.compose.Composable public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
+  public final class ZoomableController {
+    ctor public ZoomableController(androidx.animation.AnimationClockObservable animationClock, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
+    method public kotlin.jvm.functions.Function1<java.lang.Float,kotlin.Unit> getOnZoomDelta();
+    method public void smoothScaleBy(float value, androidx.animation.AnimationSpec<java.lang.Float> spec = androidx.animation.SpringSpec(Spring.StiffnessLow), kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>? >
+    method public void stopAnimation();
   }
 
-  public final class ZoomableState {
-    ctor public ZoomableState(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta, androidx.animation.AnimationClockObservable animationClock);
-    method public kotlin.jvm.functions.Function1<java.lang.Float,kotlin.Unit> getOnZoomDelta();
-    method public void smoothScaleBy(float value, kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>? >
+  public final class ZoomableKt {
+    method @androidx.compose.Composable public static androidx.compose.foundation.gestures.ZoomableController rememberZoomableController(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
+    method public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, androidx.compose.foundation.gestures.ZoomableController controller, boolean enabled = true, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit>? >
+    method public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, boolean enabled = true, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
   }
 
 }
diff --git a/ui/ui-foundation/api/public_plus_experimental_current.txt b/ui/ui-foundation/api/public_plus_experimental_current.txt
index c418a4c..7022063 100644
--- a/ui/ui-foundation/api/public_plus_experimental_current.txt
+++ b/ui/ui-foundation/api/public_plus_experimental_current.txt
@@ -264,16 +264,17 @@
     method public static androidx.ui.core.Modifier scrollable(androidx.ui.core.Modifier, androidx.ui.core.gesture.scrollorientationlocking.Orientation orientation, androidx.compose.foundation.gestures.ScrollableController controller, boolean enabled = true, boolean reverseDirection = false, kotlin.jvm.functions.Function1<? super androidx.ui.core.Direction,java.lang.Boolean> canScroll = { return enabled }, kotlin.jvm.functions.Function1<? super androidx.ui.geometry.Offset,kotlin.Unit>  kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> >
   }
 
-  public final class ZoomableKt {
-    method @androidx.compose.Composable public static androidx.compose.foundation.gestures.ZoomableState ZoomableState(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
-    method @androidx.compose.Composable public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, androidx.compose.foundation.gestures.ZoomableState zoomableState, kotlin.jvm.functions.Function0<kotlin.Unit>? >
-    method @androidx.compose.Composable public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
+  public final class ZoomableController {
+    ctor public ZoomableController(androidx.animation.AnimationClockObservable animationClock, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
+    method public kotlin.jvm.functions.Function1<java.lang.Float,kotlin.Unit> getOnZoomDelta();
+    method public void smoothScaleBy(float value, androidx.animation.AnimationSpec<java.lang.Float> spec = androidx.animation.SpringSpec(Spring.StiffnessLow), kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>? >
+    method public void stopAnimation();
   }
 
-  public final class ZoomableState {
-    ctor public ZoomableState(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta, androidx.animation.AnimationClockObservable animationClock);
-    method public kotlin.jvm.functions.Function1<java.lang.Float,kotlin.Unit> getOnZoomDelta();
-    method public void smoothScaleBy(float value, kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>? >
+  public final class ZoomableKt {
+    method @androidx.compose.Composable public static androidx.compose.foundation.gestures.ZoomableController rememberZoomableController(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
+    method public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, androidx.compose.foundation.gestures.ZoomableController controller, boolean enabled = true, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit>? >
+    method public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, boolean enabled = true, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
   }
 
 }
diff --git a/ui/ui-foundation/api/restricted_0.1.0-dev16.txt b/ui/ui-foundation/api/restricted_0.1.0-dev16.txt
index c418a4c..7022063 100644
--- a/ui/ui-foundation/api/restricted_0.1.0-dev16.txt
+++ b/ui/ui-foundation/api/restricted_0.1.0-dev16.txt
@@ -264,16 +264,17 @@
     method public static androidx.ui.core.Modifier scrollable(androidx.ui.core.Modifier, androidx.ui.core.gesture.scrollorientationlocking.Orientation orientation, androidx.compose.foundation.gestures.ScrollableController controller, boolean enabled = true, boolean reverseDirection = false, kotlin.jvm.functions.Function1<? super androidx.ui.core.Direction,java.lang.Boolean> canScroll = { return enabled }, kotlin.jvm.functions.Function1<? super androidx.ui.geometry.Offset,kotlin.Unit>  kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> >
   }
 
-  public final class ZoomableKt {
-    method @androidx.compose.Composable public static androidx.compose.foundation.gestures.ZoomableState ZoomableState(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
-    method @androidx.compose.Composable public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, androidx.compose.foundation.gestures.ZoomableState zoomableState, kotlin.jvm.functions.Function0<kotlin.Unit>? >
-    method @androidx.compose.Composable public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
+  public final class ZoomableController {
+    ctor public ZoomableController(androidx.animation.AnimationClockObservable animationClock, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
+    method public kotlin.jvm.functions.Function1<java.lang.Float,kotlin.Unit> getOnZoomDelta();
+    method public void smoothScaleBy(float value, androidx.animation.AnimationSpec<java.lang.Float> spec = androidx.animation.SpringSpec(Spring.StiffnessLow), kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>? >
+    method public void stopAnimation();
   }
 
-  public final class ZoomableState {
-    ctor public ZoomableState(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta, androidx.animation.AnimationClockObservable animationClock);
-    method public kotlin.jvm.functions.Function1<java.lang.Float,kotlin.Unit> getOnZoomDelta();
-    method public void smoothScaleBy(float value, kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>? >
+  public final class ZoomableKt {
+    method @androidx.compose.Composable public static androidx.compose.foundation.gestures.ZoomableController rememberZoomableController(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
+    method public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, androidx.compose.foundation.gestures.ZoomableController controller, boolean enabled = true, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit>? >
+    method public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, boolean enabled = true, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
   }
 
 }
diff --git a/ui/ui-foundation/api/restricted_current.txt b/ui/ui-foundation/api/restricted_current.txt
index c418a4c..7022063 100644
--- a/ui/ui-foundation/api/restricted_current.txt
+++ b/ui/ui-foundation/api/restricted_current.txt
@@ -264,16 +264,17 @@
     method public static androidx.ui.core.Modifier scrollable(androidx.ui.core.Modifier, androidx.ui.core.gesture.scrollorientationlocking.Orientation orientation, androidx.compose.foundation.gestures.ScrollableController controller, boolean enabled = true, boolean reverseDirection = false, kotlin.jvm.functions.Function1<? super androidx.ui.core.Direction,java.lang.Boolean> canScroll = { return enabled }, kotlin.jvm.functions.Function1<? super androidx.ui.geometry.Offset,kotlin.Unit>  kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> >
   }
 
-  public final class ZoomableKt {
-    method @androidx.compose.Composable public static androidx.compose.foundation.gestures.ZoomableState ZoomableState(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
-    method @androidx.compose.Composable public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, androidx.compose.foundation.gestures.ZoomableState zoomableState, kotlin.jvm.functions.Function0<kotlin.Unit>? >
-    method @androidx.compose.Composable public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
+  public final class ZoomableController {
+    ctor public ZoomableController(androidx.animation.AnimationClockObservable animationClock, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
+    method public kotlin.jvm.functions.Function1<java.lang.Float,kotlin.Unit> getOnZoomDelta();
+    method public void smoothScaleBy(float value, androidx.animation.AnimationSpec<java.lang.Float> spec = androidx.animation.SpringSpec(Spring.StiffnessLow), kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>? >
+    method public void stopAnimation();
   }
 
-  public final class ZoomableState {
-    ctor public ZoomableState(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta, androidx.animation.AnimationClockObservable animationClock);
-    method public kotlin.jvm.functions.Function1<java.lang.Float,kotlin.Unit> getOnZoomDelta();
-    method public void smoothScaleBy(float value, kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>? >
+  public final class ZoomableKt {
+    method @androidx.compose.Composable public static androidx.compose.foundation.gestures.ZoomableController rememberZoomableController(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
+    method public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, androidx.compose.foundation.gestures.ZoomableController controller, boolean enabled = true, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit>? >
+    method public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, boolean enabled = true, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
   }
 
 }
diff --git a/ui/ui-foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt b/ui/ui-foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt
index b2d0212..14f0e3a 100644
--- a/ui/ui-foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt
+++ b/ui/ui-foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt
@@ -24,7 +24,7 @@
 import androidx.compose.foundation.samples.ScrollableColumnSample
 
 val FoundationDemos = DemoCategory("Foundation", listOf(
-    ComposableDemo("Draggable and Scrollable") { HighLevelGesturesDemo() },
+    ComposableDemo("Draggable, Scrollable, Zoomable") { HighLevelGesturesDemo() },
     ComposableDemo("Scrollable Column") { ScrollableColumnSample() },
     ComposableDemo("Controlled Scrollable Row") { ControlledScrollableRowSample() },
     ComposableDemo("Dialog") { DialogSample() },
diff --git a/ui/ui-foundation/samples/src/main/java/androidx/compose/foundation/samples/ZoomableSample.kt b/ui/ui-foundation/samples/src/main/java/androidx/compose/foundation/samples/ZoomableSample.kt
index 3d9193a..fbc2b8b 100644
--- a/ui/ui-foundation/samples/src/main/java/androidx/compose/foundation/samples/ZoomableSample.kt
+++ b/ui/ui-foundation/samples/src/main/java/androidx/compose/foundation/samples/ZoomableSample.kt
@@ -18,6 +18,15 @@
 
 import androidx.annotation.Sampled
 import androidx.compose.Composable
+import androidx.compose.foundation.Box
+import androidx.compose.foundation.ContentGravity
+import androidx.compose.foundation.Text
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.drawBorder
+import androidx.compose.foundation.gestures.rememberZoomableController
+import androidx.compose.foundation.gestures.zoomable
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.preferredSize
 import androidx.compose.getValue
 import androidx.compose.setValue
 import androidx.compose.state
@@ -25,16 +34,7 @@
 import androidx.ui.core.Modifier
 import androidx.ui.core.clipToBounds
 import androidx.ui.core.drawLayer
-import androidx.compose.foundation.Box
-import androidx.compose.foundation.ContentGravity
-import androidx.compose.foundation.Text
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.drawBorder
-import androidx.compose.foundation.gestures.ZoomableState
-import androidx.compose.foundation.gestures.zoomable
 import androidx.ui.graphics.Color
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.preferredSize
 import androidx.ui.unit.dp
 import androidx.ui.unit.sp
 
@@ -46,14 +46,13 @@
         backgroundColor = Color.LightGray
     ) {
         var scale by state(structuralEqualityPolicy()) { 1f }
-        val zoomableState = ZoomableState { scale *= it }
-
+        val zoomableController = rememberZoomableController { scale *= it }
         Box(
             Modifier
-                .zoomable(zoomableState)
+                .zoomable(zoomableController)
                 .clickable(
                     indication = null,
-                     zoomableState.smoothScaleBy(4f) },
+                     zoomableController.smoothScaleBy(4f) },
                     >
                 )
                 .fillMaxSize()
diff --git a/ui/ui-foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ZoomableTest.kt b/ui/ui-foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ZoomableTest.kt
index 0dc4545..8dc3a65 100644
--- a/ui/ui-foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ZoomableTest.kt
+++ b/ui/ui-foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ZoomableTest.kt
@@ -17,11 +17,12 @@
 package androidx.compose.foundation
 
 import androidx.compose.Composable
+import androidx.compose.mutableStateOf
 import androidx.test.filters.SmallTest
 import androidx.ui.core.Modifier
 import androidx.ui.core.testTag
-import androidx.compose.foundation.gestures.ZoomableState
 import androidx.compose.foundation.gestures.zoomable
+import androidx.compose.foundation.gestures.ZoomableController
 import androidx.ui.geometry.Offset
 import androidx.compose.foundation.layout.preferredSize
 import androidx.ui.test.AnimationClockTestRule
@@ -35,6 +36,7 @@
 import androidx.ui.test.size
 import androidx.ui.unit.dp
 import androidx.ui.unit.toSize
+import com.google.common.truth.Truth
 import com.google.common.truth.Truth.assertWithMessage
 import org.junit.Rule
 import org.junit.Test
@@ -57,12 +59,12 @@
     @Test
     fun zoomable_zoomIn() {
         var cumulativeScale = 1.0f
-        val state = ZoomableState(
+        val controller = ZoomableController(
              cumulativeScale *= it },
             animationClock = clockRule.clock
         )
 
-        setZoomableContent { Modifier.zoomable(state) }
+        setZoomableContent { Modifier.zoomable(controller) }
 
         onNodeWithTag(TEST_TAG).performGesture {
             val leftStartX = center.x - 10
@@ -88,12 +90,12 @@
     @Test
     fun zoomable_zoomOut() {
         var cumulativeScale = 1.0f
-        val state = ZoomableState(
+        val controller = ZoomableController(
              cumulativeScale *= it },
             animationClock = clockRule.clock
         )
 
-        setZoomableContent { Modifier.zoomable(state) }
+        setZoomableContent { Modifier.zoomable(controller) }
 
         onNodeWithTag(TEST_TAG).performGesture {
             val leftStartX = size.toSize().width * EDGE_FUZZ_FACTOR
@@ -119,10 +121,159 @@
     }
 
     @Test
+    fun zoomable_startStop_notify() {
+        var cumulativeScale = 1.0f
+        var startTriggered = 0f
+        var stopTriggered = 0f
+        val controller = ZoomableController(
+             cumulativeScale *= it },
+            animationClock = clockRule.clock
+        )
+
+        setZoomableContent {
+            Modifier
+                .zoomable(controller = controller,
+                     startTriggered++ },
+                     stopTriggered++ }
+                )
+        }
+
+        runOnIdle {
+            Truth.assertThat(startTriggered).isEqualTo(0)
+            Truth.assertThat(stopTriggered).isEqualTo(0)
+        }
+
+        onNodeWithTag(TEST_TAG).performGesture {
+            val leftStartX = size.toSize().width * EDGE_FUZZ_FACTOR
+            val leftEndX = center.x - 10
+            val rightStartX = size.toSize().width * (1 - EDGE_FUZZ_FACTOR)
+            val rightEndX = center.x + 10
+
+            pinch(
+                Offset(leftStartX, center.y),
+                Offset(leftEndX, center.y),
+                Offset(rightStartX, center.y),
+                Offset(rightEndX, center.y)
+            )
+        }
+
+        clockRule.advanceClock(milliseconds = 1000)
+
+        runOnIdle {
+            Truth.assertThat(startTriggered).isEqualTo(1)
+            Truth.assertThat(stopTriggered).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun zoomable_disabledWontCallLambda() {
+        val enabled = mutableStateOf(true)
+        var cumulativeScale = 1.0f
+        val controller = ZoomableController(
+             cumulativeScale *= it },
+            animationClock = clockRule.clock
+        )
+
+        setZoomableContent {
+            Modifier
+                .zoomable(controller = controller, enabled = enabled.value)
+        }
+
+        onNodeWithTag(TEST_TAG).performGesture {
+            val leftStartX = center.x - 10
+            val leftEndX = size.toSize().width * EDGE_FUZZ_FACTOR
+            val rightStartX = center.x + 10
+            val rightEndX = size.toSize().width * (1 - EDGE_FUZZ_FACTOR)
+
+            pinch(
+                Offset(leftStartX, center.y),
+                Offset(leftEndX, center.y),
+                Offset(rightStartX, center.y),
+                Offset(rightEndX, center.y)
+            )
+        }
+
+        clockRule.advanceClock(milliseconds = 1000)
+
+        val prevScale = runOnIdle {
+            assertWithMessage("Should have scaled at least 4x").that(cumulativeScale).isAtLeast(4f)
+            enabled.value = false
+            cumulativeScale
+        }
+
+        onNodeWithTag(TEST_TAG).performGesture {
+            val leftStartX = size.toSize().width * EDGE_FUZZ_FACTOR
+            val leftEndX = center.x - 10
+            val rightStartX = size.toSize().width * (1 - EDGE_FUZZ_FACTOR)
+            val rightEndX = center.x + 10
+
+            pinch(
+                Offset(leftStartX, center.y),
+                Offset(leftEndX, center.y),
+                Offset(rightStartX, center.y),
+                Offset(rightEndX, center.y)
+            )
+        }
+
+        runOnIdle {
+            assertWithMessage("When enabled = false, scale should stay the same")
+                .that(cumulativeScale)
+                .isEqualTo(prevScale)
+        }
+    }
+
+    @Test
+    fun zoomable_callsStop_whenRemoved() {
+        var cumulativeScale = 1.0f
+        var stopTriggered = 0f
+        val controller = ZoomableController(
+             cumulativeScale *= it },
+            animationClock = clockRule.clock
+        )
+
+        setZoomableContent {
+            if (cumulativeScale < 2f) {
+                Modifier
+                    .zoomable(
+                        controller = controller,
+                         stopTriggered++ }
+                    )
+            } else {
+                Modifier
+            }
+        }
+
+        runOnIdle {
+            Truth.assertThat(stopTriggered).isEqualTo(0)
+        }
+
+        onNodeWithTag(TEST_TAG).performGesture {
+            val leftStartX = center.x - 10
+            val leftEndX = size.toSize().width * EDGE_FUZZ_FACTOR
+            val rightStartX = center.x + 10
+            val rightEndX = size.toSize().width * (1 - EDGE_FUZZ_FACTOR)
+
+            pinch(
+                Offset(leftStartX, center.y),
+                Offset(leftEndX, center.y),
+                Offset(rightStartX, center.y),
+                Offset(rightEndX, center.y)
+            )
+        }
+
+        clockRule.advanceClock(milliseconds = 1000)
+
+        runOnIdle {
+            Truth.assertThat(cumulativeScale).isAtLeast(2f)
+            Truth.assertThat(stopTriggered).isEqualTo(1f)
+        }
+    }
+
+    @Test
     fun zoomable_animateTo() {
         var cumulativeScale = 1.0f
         var callbackCount = 0
-        val state = ZoomableState(
+        val state = ZoomableController(
             >
                 cumulativeScale *= it
                 callbackCount += 1
diff --git a/ui/ui-foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt b/ui/ui-foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
index 7aee278..d739d8b 100644
--- a/ui/ui-foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
+++ b/ui/ui-foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
@@ -157,7 +157,7 @@
  * @param controller [ScrollableController] object that is responsible for redirecting scroll
  * deltas to [ScrollableController.consumeScrollDelta] callback and provides smooth scrolling
  * capabilities
- * @param enabled whether of not scrolling in enabled
+ * @param enabled whether or not scrolling in enabled
  * @param reverseDirection reverse the direction of the scroll, so top to bottom scroll will
  * behave like bottom to top and left to right will behave like right to left.
  * @param canScroll callback to indicate whether or not scroll is allowed for given [Direction]
diff --git a/ui/ui-foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Zoomable.kt b/ui/ui-foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Zoomable.kt
index cdaae83..3f093d2 100644
--- a/ui/ui-foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Zoomable.kt
+++ b/ui/ui-foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Zoomable.kt
@@ -19,58 +19,75 @@
 import androidx.animation.AnimatedFloat
 import androidx.animation.AnimationClockObservable
 import androidx.animation.AnimationEndReason
+import androidx.animation.AnimationSpec
 import androidx.animation.Spring
 import androidx.animation.SpringSpec
 import androidx.compose.Composable
+import androidx.compose.onDispose
 import androidx.compose.remember
 import androidx.ui.animation.asDisposableClock
 import androidx.ui.core.AnimationClockAmbient
 import androidx.ui.core.Modifier
+import androidx.ui.core.composed
 import androidx.ui.core.gesture.ScaleObserver
 import androidx.ui.core.gesture.scaleGestureFilter
 
 /**
- * Create [ZoomableState] with default [AnimationClockObservable].
+ * Create and remember [ZoomableController] with default [AnimationClockObservable].
  *
  * @param onZoomDelta callback to be invoked when pinch/smooth zooming occurs. The callback
  * receives the delta as the ratio of the new size compared to the old. Callers should update
  * their state and UI in this callback.
  */
 @Composable
-fun ZoomableState(onZoomDelta: (Float) -> Unit): ZoomableState {
+fun rememberZoomableController(onZoomDelta: (Float) -> Unit): ZoomableController {
     val clocks = AnimationClockAmbient.current.asDisposableClock()
-    return remember(clocks) { ZoomableState(onZoomDelta, clocks) }
+    return remember(clocks) { ZoomableController(clocks, onZoomDelta) }
 }
 
 /**
- * State of the [zoomable] composable modifier. Provides smooth scaling capabilities.
+ * Controller to control [zoomable] modifier with. Provides smooth scaling capabilities.
  *
+ * @param animationClock clock observable to run animation on. Consider querying
+ * [AnimationClockAmbient] to get current composition value
  * @param onZoomDelta callback to be invoked when pinch/smooth zooming occurs. The callback
  * receives the delta as the ratio of the new size compared to the old. Callers should update
  * their state and UI in this callback.
- * @param animationClock clock observable to run animation on. Consider querying
- * [AnimationClockAmbient] to get current composition value
  */
-class ZoomableState(val onZoomDelta: (Float) -> Unit, animationClock: AnimationClockObservable) {
+class ZoomableController(
+    animationClock: AnimationClockObservable,
+    val onZoomDelta: (Float) -> Unit
+) {
 
     /**
      * Smooth scale by a ratio of [value] over the current size.
      *
      * @param value ratio over the current size by which to scale
+     * @param spec [AnimationSpec] to be used for smoothScale animation
      * @pram [onEnd] callback invoked when the smooth scaling has ended
      */
     fun smoothScaleBy(
         value: Float,
+        spec: AnimationSpec<Float> = SpringSpec(stiffness = Spring.StiffnessLow),
         onEnd: ((endReason: AnimationEndReason, finishValue: Float) -> Unit)? = null
     ) {
         val to = animatedFloat.value * value
         animatedFloat.animateTo(
             to,
             >
-            anim = SpringSpec(stiffness = Spring.StiffnessLow)
+            anim = spec
         )
     }
 
+    /**
+     * Stop any ongoing animation or smooth scaling for this controller
+     *
+     * Call this to stop receiving scrollable deltas in [onZoomDelta]
+     */
+    fun stopAnimation() {
+        animatedFloat.stop()
+    }
+
     internal fun onScale(scaleFactor: Float) = onZoomDelta(scaleFactor)
 
     private val animatedFloat = DeltaAnimatedScale(1f, animationClock, ::onScale)
@@ -79,27 +96,56 @@
 /**
  * Enable zooming of the modified UI element.
  *
- * [ZoomableState.onZoomDelta] will be invoked with the change in proportion of the UI element's
+ * [ZoomableController.onZoomDelta] will be invoked with the change in proportion of the UI element's
  * size at each change in either ratio of the gesture or smooth scaling. Callers should update
  * their state and UI in this callback.
  *
  * @sample androidx.compose.foundation.samples.ZoomableSample
  *
- * @param zoomableState [ZoomableState] object that holds the internal state of this zoomable,
+ * @param controller [ZoomableController] object that holds the internal state of this zoomable,
  * and provides smooth scaling capabilities.
+ * @param enabled whether zooming by gestures is enabled or not
+ * @param onZoomStarted callback to be invoked when zoom has started.
  * @param onZoomStopped callback to be invoked when zoom has stopped.
  */
-@Composable
-fun Modifier.zoomable(zoomableState: ZoomableState, onZoomStopped: (() -> Unit)? = null): Modifier {
-    return scaleGestureFilter(
-            scaleObserver = object : ScaleObserver {
-                override fun onScale(scaleFactor: Float) = zoomableState.onScale(scaleFactor)
+fun Modifier.zoomable(
+    controller: ZoomableController,
+    enabled: Boolean = true,
+    onZoomStarted: (() -> Unit)? = null,
+    onZoomStopped: (() -> Unit)? = null
+) = composed {
+    onDispose {
+        controller.stopAnimation()
+    }
+    scaleGestureFilter(
+        scaleObserver = object : ScaleObserver {
+            override fun onScale(scaleFactor: Float) {
+                if (enabled) {
+                    controller.stopAnimation()
+                    controller.onScale(scaleFactor)
+                }
+            }
 
-                override fun onStop() {
+            override fun onStop() {
+                if (enabled) {
                     onZoomStopped?.invoke()
                 }
             }
-        )
+
+            override fun onCancel() {
+                if (enabled) {
+                    onZoomStopped?.invoke()
+                }
+            }
+
+            override fun onStart() {
+                if (enabled) {
+                    controller.stopAnimation()
+                    onZoomStarted?.invoke()
+                }
+            }
+        }
+    )
 }
 
 /**
@@ -111,16 +157,26 @@
  *
  * @sample androidx.compose.foundation.samples.ZoomableSample
  *
+ * @param enabled whether zooming by gestures is enabled or not
+ * @param onZoomStarted callback to be invoked when zoom has started.
  * @param onZoomStopped callback to be invoked when zoom has stopped.
  * @param onZoomDelta callback to be invoked when pinch/smooth zooming occurs. The callback
  * receives the delta as the ratio of the new size compared to the old. Callers should update
  * their state and UI in this callback.
  */
-@Composable
 fun Modifier.zoomable(
+    enabled: Boolean = true,
+    onZoomStarted: (() -> Unit)? = null,
     onZoomStopped: (() -> Unit)? = null,
     onZoomDelta: (Float) -> Unit
-) = Modifier.zoomable(ZoomableState(onZoomDelta), onZoomStopped)
+) = composed {
+    Modifier.zoomable(
+        controller = rememberZoomableController(onZoomDelta),
+        enabled = enabled,
+        >
+        >
+    )
+}
 
 private class DeltaAnimatedScale(
     initial: Float,