diff --git a/android/titanium/src/java/org/appcelerator/titanium/proxy/TiViewProxy.java b/android/titanium/src/java/org/appcelerator/titanium/proxy/TiViewProxy.java index 171cab8a7f0..5080429c042 100644 --- a/android/titanium/src/java/org/appcelerator/titanium/proxy/TiViewProxy.java +++ b/android/titanium/src/java/org/appcelerator/titanium/proxy/TiViewProxy.java @@ -854,11 +854,14 @@ public void onAnimationEnd(Animator animation) } @Kroll.method - public void animate(Object arg, @Kroll.argument(optional = true) KrollFunction callback) + public void animate(@Kroll.argument(optional = true) Object arg, + @Kroll.argument(optional = true) KrollFunction callback) { synchronized (pendingAnimationLock) { - if (arg instanceof HashMap) { + if (arg == null) { + stopAnimation(); + } else if (arg instanceof HashMap) { @SuppressWarnings("rawtypes") HashMap options = (HashMap) arg; pendingAnimation = new TiAnimationBuilder(); @@ -879,6 +882,16 @@ public void animate(Object arg, @Kroll.argument(optional = true) KrollFunction c } } + @Kroll.method + public void stopAnimation() + { + TiUIView tiv = peekView(); + + if (tiv != null) { + tiv.stopAnimation(); + } + } + public void handlePendingAnimation(boolean forceQueue) { if (pendingAnimation != null && peekView() != null) { diff --git a/android/titanium/src/java/org/appcelerator/titanium/util/TiAnimationBuilder.java b/android/titanium/src/java/org/appcelerator/titanium/util/TiAnimationBuilder.java index 4bd6414f97b..4375fa8fc7b 100644 --- a/android/titanium/src/java/org/appcelerator/titanium/util/TiAnimationBuilder.java +++ b/android/titanium/src/java/org/appcelerator/titanium/util/TiAnimationBuilder.java @@ -127,6 +127,7 @@ public class TiAnimationBuilder protected View view; protected AnimatorHelper animatorHelper; protected TiViewProxy viewProxy; + protected AnimatorSet animatorSet; public TiAnimationBuilder() { @@ -615,7 +616,7 @@ private AnimatorSet buildPropertyAnimators(int x, int y, int w, int h, int paren if (delay != null) { as.setStartDelay(delay.longValue()); } - + animatorSet = as; return as; } @@ -1020,6 +1021,20 @@ public void start(TiViewProxy viewProxy, View view) } } + public void stop(View view) + { + if (animatorSet != null) { + animatorSet.removeAllListeners(); + animatorSet.cancel(); + animatorSet = null; + } + view.clearAnimation(); + setAnimationRunningFor(view, false); + if (animationProxy != null) { + animationProxy.fireEvent(TiC.EVENT_CANCEL, null); + } + } + private void setAnchor(int width, int height) { setAnchor(width, height, anchorX, anchorY); diff --git a/android/titanium/src/java/org/appcelerator/titanium/view/TiUIView.java b/android/titanium/src/java/org/appcelerator/titanium/view/TiUIView.java index 43e3d2f682c..ac0541d1344 100644 --- a/android/titanium/src/java/org/appcelerator/titanium/view/TiUIView.java +++ b/android/titanium/src/java/org/appcelerator/titanium/view/TiUIView.java @@ -154,6 +154,7 @@ public TiBackgroundDrawable getBackground() private final AtomicBoolean bLayoutPending = new AtomicBoolean(); private final AtomicBoolean bTransformPending = new AtomicBoolean(); + private TiAnimationBuilder tiBuilder; /** * Constructs a TiUIView object with the associated proxy. * @param proxy the associated proxy. @@ -358,12 +359,12 @@ public void animate() return; } - TiAnimationBuilder builder = proxy.getPendingAnimation(); - if (builder == null) { + tiBuilder = proxy.getPendingAnimation(); + if (tiBuilder == null) { return; } - proxy.clearAnimation(builder); + proxy.clearAnimation(tiBuilder); // If a view is "visible" but not currently seen (such as because it's covered or // its position is currently set to be fully outside its parent's region), @@ -391,14 +392,22 @@ public void animate() if (Log.isDebugModeEnabled()) { Log.d(TAG, "starting animation", Log.DEBUG_MODE); } - - builder.start(proxy, outerView); + tiBuilder.start(proxy, outerView); if (invalidateParent) { ((View) viewParent).postInvalidate(); } } + public void stopAnimation() + { + View outerView = getOuterView(); + if (outerView == null || bTransformPending.get() || tiBuilder == null) { + return; + } + tiBuilder.stop(outerView); + } + public void listenerAdded(String type, int count, KrollProxy proxy) { } diff --git a/apidoc/Titanium/UI/Animation.yml b/apidoc/Titanium/UI/Animation.yml index cb007a2d459..f3574afa0dc 100644 --- a/apidoc/Titanium/UI/Animation.yml +++ b/apidoc/Titanium/UI/Animation.yml @@ -48,6 +48,11 @@ since: "0.9" platforms: [android, iphone, ipad, macos] events: + - name: cancel + summary: Fired when the animation is canceled. + since: "12.1.0" + platforms: [android] + - name: complete summary: Fired when the animation completes. diff --git a/apidoc/Titanium/UI/View.yml b/apidoc/Titanium/UI/View.yml index 21674981dc9..429df1529b5 100644 --- a/apidoc/Titanium/UI/View.yml +++ b/apidoc/Titanium/UI/View.yml @@ -1056,6 +1056,13 @@ methods: removed: "9.0.0" notes: Use the [Titanium.Proxy.applyProperties](Titanium.Proxy.applyProperties) method to batch-update layout properties. + - name: stopAnimation + summary: Stops a running animation. + description: | + Stops a running view [Animation](Titanium.UI.Animation). + platforms: [android] + since: { android: "12.1.0" } + - name: toImage summary: Returns an image of the rendered view, as a Blob. description: | diff --git a/tests/Resources/ti.ui.view.test.js b/tests/Resources/ti.ui.view.test.js index 3000bf6d98d..205442c3d28 100644 --- a/tests/Resources/ti.ui.view.test.js +++ b/tests/Resources/ti.ui.view.test.js @@ -730,6 +730,62 @@ describe('Titanium.UI.View', function () { win.open(); }); + it.android('animate and stopAnimation', function (finish) { + win = Ti.UI.createWindow({ backgroundColor: 'white' }); + const view = Ti.UI.createView({ + backgroundColor: 'orange', + top: 0, + left: 0, + width: 100, + height: 100, + }); + win.add(view); + win.addEventListener('open', () => { + const animation = Ti.UI.createAnimation({ + duration: 2000, + left: 200 + }); + animation.addEventListener('cancel', () => { + should(view.rect.x).be.above(0); + should(view.rect.x).be.below(200); + finish(); + }); + view.animate(animation); + setTimeout(function () { + view.stopAnimation(); + }, 500); + }); + win.open(); + }); + + it.android('animate and empty animation', function (finish) { + win = Ti.UI.createWindow({ backgroundColor: 'white' }); + const view = Ti.UI.createView({ + backgroundColor: 'orange', + top: 0, + left: 0, + width: 100, + height: 100, + }); + win.add(view); + win.addEventListener('open', () => { + const animation = Ti.UI.createAnimation({ + duration: 2000, + left: 200 + }); + animation.addEventListener('cancel', () => { + should(view.rect.x).be.above(0); + should(view.rect.x).be.below(200); + finish(); + }); + view.animate(animation); + setTimeout(function () { + view.animate(); + }, 500); + }); + win.open(); + }); + it.windowsBroken('convertPointToView', function (finish) { win = Ti.UI.createWindow(); const a = Ti.UI.createView({ backgroundColor: 'red' });