diff options
author | Xavier Ducrohet <xav@android.com> | 2010-12-03 11:35:29 -0800 |
---|---|---|
committer | Xavier Ducrohet <xav@android.com> | 2010-12-03 11:50:36 -0800 |
commit | e1179ea065392485a6e3bf1e28a1242179cd48b0 (patch) | |
tree | 2dfb7425ad0a3eccb6cbe75e4bfa761e80e9db47 /tools | |
parent | 01811aa86279af1b341a4fff344d66c0ebdd63da (diff) | |
download | frameworks_base-e1179ea065392485a6e3bf1e28a1242179cd48b0.zip frameworks_base-e1179ea065392485a6e3bf1e28a1242179cd48b0.tar.gz frameworks_base-e1179ea065392485a6e3bf1e28a1242179cd48b0.tar.bz2 |
LayoutLib: Animation support in insert/move/removeChild actions.
Also update to use the new SceneResult API.
Change-Id: Iaac6df0c250fbefc8758310c37e0cf47cae6875d
Diffstat (limited to 'tools')
4 files changed, 288 insertions, 64 deletions
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java index 7aa0e3c..2a94774 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java @@ -335,7 +335,7 @@ public final class Bridge extends LayoutBridge { t2 = t.getCause(); } return new BridgeLayoutScene(null, - new SceneResult(SceneStatus.ERROR_UNKNOWN, t2.getMessage(), t2)); + SceneStatus.ERROR_UNKNOWN.getResult(t2.getMessage(), t2)); } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/AnimationThread.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/AnimationThread.java index 2b9d52f..400a05f 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/AnimationThread.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/AnimationThread.java @@ -22,7 +22,6 @@ import com.android.layoutlib.api.LayoutScene.IAnimationListener; import com.android.layoutlib.api.SceneResult.SceneStatus; import com.android.layoutlib.bridge.Bridge; -import android.animation.Animator; import android.animation.ValueAnimator; import android.os.Handler; import android.os.Handler_Delegate; @@ -32,7 +31,19 @@ import android.os.Handler_Delegate.IHandlerCallback; import java.util.LinkedList; import java.util.Queue; -public class AnimationThread extends Thread { +/** + * Abstract animation thread. + * <p/> + * This does not actually start an animation, instead it fakes a looper that will play whatever + * animation is sending messages to its own {@link Handler}. + * <p/> + * Classes should implement {@link #preAnimation()} and {@link #postAnimation()}. + * <p/> + * If {@link #preAnimation()} does not start an animation something then the thread doesn't do + * anything. + * + */ +public abstract class AnimationThread extends Thread { private static class MessageBundle { final Handler mTarget; @@ -47,17 +58,19 @@ public class AnimationThread extends Thread { } private final LayoutSceneImpl mScene; - private final Animator mAnimator; Queue<MessageBundle> mQueue = new LinkedList<MessageBundle>(); private final IAnimationListener mListener; - public AnimationThread(LayoutSceneImpl scene, Animator animator, IAnimationListener listener) { + public AnimationThread(LayoutSceneImpl scene, String threadName, IAnimationListener listener) { + super(threadName); mScene = scene; - mAnimator = animator; mListener = listener; } + public abstract SceneResult preAnimation(); + public abstract void postAnimation(); + @Override public void run() { Bridge.prepareThread(); @@ -73,13 +86,20 @@ public class AnimationThread extends Thread { } }); - // start the animation. This will send a message to the handler right away, so - // mQueue is filled when this method returns. - mAnimator.start(); + // call out to the pre-animation work, which should start an animation or more. + SceneResult result = preAnimation(); + if (result.isSuccess() == false) { + mListener.done(result); + } // loop the animation LayoutScene scene = mScene.getScene(); do { + // check early. + if (mListener.isCanceled()) { + break; + } + // get the next message. MessageBundle bundle = mQueue.poll(); if (bundle == null) { @@ -97,8 +117,13 @@ public class AnimationThread extends Thread { } } + // check after sleeping. + if (mListener.isCanceled()) { + break; + } + // ready to do the work, acquire the scene. - SceneResult result = mScene.acquire(250); + result = mScene.acquire(250); if (result.isSuccess() == false) { mListener.done(result); return; @@ -107,6 +132,11 @@ public class AnimationThread extends Thread { // process the bundle. If the animation is not finished, this will enqueue // the next message, so mQueue will have another one. try { + // check after acquiring in case it took a while. + if (mListener.isCanceled()) { + break; + } + bundle.mTarget.handleMessage(bundle.mMessage); if (mScene.render().isSuccess()) { mListener.onNewFrame(scene); @@ -114,10 +144,11 @@ public class AnimationThread extends Thread { } finally { mScene.release(); } - } while (mQueue.size() > 0); + } while (mListener.isCanceled() == false && mQueue.size() > 0); mListener.done(SceneStatus.SUCCESS.getResult()); } finally { + postAnimation(); Handler_Delegate.setCallback(null); Bridge.cleanupThread(); } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutSceneImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutSceneImpl.java index 3c8e8cd..359180f 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutSceneImpl.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutSceneImpl.java @@ -43,8 +43,9 @@ import com.android.layoutlib.bridge.android.BridgeWindow; import com.android.layoutlib.bridge.android.BridgeWindowSession; import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; +import android.animation.Animator; import android.animation.AnimatorInflater; -import android.animation.ObjectAnimator; +import android.animation.LayoutTransition; import android.app.Fragment_Delegate; import android.graphics.Bitmap; import android.graphics.Bitmap_Delegate; @@ -56,7 +57,6 @@ import android.util.DisplayMetrics; import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; -import android.view.ViewParent; import android.view.View.AttachInfo; import android.view.View.MeasureSpec; import android.view.ViewGroup.LayoutParams; @@ -346,7 +346,7 @@ public class LayoutSceneImpl { return SceneStatus.SUCCESS.getResult(); } catch (PostInflateException e) { - return new SceneResult(SceneStatus.ERROR_INFLATION, e.getMessage(), e); + return SceneStatus.ERROR_INFLATION.getResult(e.getMessage(), e); } catch (Throwable e) { // get the real cause of the exception. Throwable t = e; @@ -357,7 +357,7 @@ public class LayoutSceneImpl { // log it mParams.getLogger().error(t); - return new SceneResult(SceneStatus.ERROR_INFLATION, t.getMessage(), t); + return SceneStatus.ERROR_INFLATION.getResult(t.getMessage(), t); } } @@ -370,13 +370,14 @@ public class LayoutSceneImpl { * the scene, or if {@link #acquire(long)} was not called. * * @see SceneParams#getRenderingMode() + * @see LayoutScene#render(long) */ public SceneResult render() { checkLock(); try { if (mViewRoot == null) { - return new SceneResult(SceneStatus.ERROR_NOT_INFLATED); + return SceneStatus.ERROR_NOT_INFLATED.getResult(); } // measure the views int w_spec, h_spec; @@ -482,7 +483,7 @@ public class LayoutSceneImpl { // log it mParams.getLogger().error(t); - return new SceneResult(SceneStatus.ERROR_UNKNOWN, t.getMessage(), t); + return SceneStatus.ERROR_UNKNOWN.getResult(t.getMessage(), t); } } @@ -493,6 +494,8 @@ public class LayoutSceneImpl { * * @throws IllegalStateException if the current context is different than the one owned by * the scene, or if {@link #acquire(long)} was not called. + * + * @see LayoutScene#animate(Object, String, boolean, IAnimationListener) */ public SceneResult animate(Object targetObject, String animationName, boolean isFrameworkAnimation, IAnimationListener listener) { @@ -515,12 +518,11 @@ public class LayoutSceneImpl { if (animationResource != null) { try { - ObjectAnimator anim = (ObjectAnimator) AnimatorInflater.loadAnimator( - mContext, animationId); + Animator anim = AnimatorInflater.loadAnimator(mContext, animationId); if (anim != null) { anim.setTarget(targetObject); - new AnimationThread(this, anim, listener).start(); + new PlayAnimationThread(anim, this, animationName, listener).start(); return SceneStatus.SUCCESS.getResult(); } @@ -531,15 +533,25 @@ public class LayoutSceneImpl { t = t.getCause(); } - return new SceneResult(SceneStatus.ERROR_UNKNOWN, t.getMessage(), t); + return SceneStatus.ERROR_UNKNOWN.getResult(t.getMessage(), t); } } - return new SceneResult(SceneStatus.ERROR_ANIM_NOT_FOUND); + return SceneStatus.ERROR_ANIM_NOT_FOUND.getResult(); } - public SceneResult insertChild(ViewGroup parentView, IXmlPullParser childXml, - int index, IAnimationListener listener) { + /** + * Insert a new child into an existing parent. + * <p> + * {@link #acquire(long)} must have been called before this. + * + * @throws IllegalStateException if the current context is different than the one owned by + * the scene, or if {@link #acquire(long)} was not called. + * + * @see LayoutScene#insertChild(Object, IXmlPullParser, int, IAnimationListener) + */ + public SceneResult insertChild(final ViewGroup parentView, IXmlPullParser childXml, + final int index, IAnimationListener listener) { checkLock(); // create a block parser for the XML @@ -549,84 +561,217 @@ public class LayoutSceneImpl { // inflate the child without adding it to the root since we want to control where it'll // get added. We do pass the parentView however to ensure that the layoutParams will // be created correctly. - View child = mInflater.inflate(blockParser, parentView, false /*attachToRoot*/); + final View child = mInflater.inflate(blockParser, parentView, false /*attachToRoot*/); - // add it to the parentView in the correct location - try { - parentView.addView(child, index); - } catch (UnsupportedOperationException e) { - // looks like this is a view class that doesn't support children manipulation! - return new SceneResult(SceneStatus.ERROR_VIEWGROUP_NO_CHILDREN); + invalidateRenderingSize(); + + if (listener != null) { + new AnimationThread(this, "insertChild", listener) { + + @Override + public SceneResult preAnimation() { + parentView.setLayoutTransition(new LayoutTransition()); + return addView(parentView, child, index); + } + + @Override + public void postAnimation() { + parentView.setLayoutTransition(null); + } + }.start(); + + // always return success since the real status will come through the listener. + return SceneStatus.SUCCESS.getResult(child); } - invalidateRenderingSize(); + // add it to the parentView in the correct location + SceneResult result = addView(parentView, child, index); + if (result.isSuccess() == false) { + return result; + } - SceneResult result = render(); + result = render(); if (result.isSuccess()) { - result.setData(child); + result = result.getCopyWithData(child); } return result; } - public SceneResult moveChild(ViewGroup parentView, View childView, int index, + /** + * Adds a given view to a given parent at a given index. + * + * @param parent the parent to receive the view + * @param view the view to add to the parent + * @param index the index where to do the add. + * + * @return a SceneResult with {@link SceneStatus#SUCCESS} or + * {@link SceneStatus#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support + * adding views. + */ + private SceneResult addView(ViewGroup parent, View view, int index) { + try { + parent.addView(view, index); + return SceneStatus.SUCCESS.getResult(); + } catch (UnsupportedOperationException e) { + // looks like this is a view class that doesn't support children manipulation! + return SceneStatus.ERROR_VIEWGROUP_NO_CHILDREN.getResult(); + } + } + + /** + * Moves a view to a new parent at a given location + * <p> + * {@link #acquire(long)} must have been called before this. + * + * @throws IllegalStateException if the current context is different than the one owned by + * the scene, or if {@link #acquire(long)} was not called. + * + * @see LayoutScene#moveChild(Object, Object, int, Map, IAnimationListener) + */ + public SceneResult moveChild(final ViewGroup parentView, final View childView, final int index, Map<String, String> layoutParamsMap, IAnimationListener listener) { checkLock(); + invalidateRenderingSize(); + LayoutParams layoutParams = null; - try { - ViewParent parent = childView.getParent(); - if (parent instanceof ViewGroup) { - ViewGroup parentGroup = (ViewGroup) parent; - parentGroup.removeView(childView); - } + if (layoutParamsMap != null) { + // need to create a new LayoutParams object for the new parent. + layoutParams = parentView.generateLayoutParams( + new BridgeLayoutParamsMapAttributes(layoutParamsMap)); + } - // add it to the parentView in the correct location + if (listener != null) { + final LayoutParams params = layoutParams; + new AnimationThread(this, "moveChild", listener) { - if (layoutParamsMap != null) { - // need to create a new LayoutParams object for the new parent. - layoutParams = parentView.generateLayoutParams( - new BridgeLayoutParamsMapAttributes(layoutParamsMap)); + @Override + public SceneResult preAnimation() { + parentView.setLayoutTransition(new LayoutTransition()); + return moveView(parentView, childView, index, params); + } - parentView.addView(childView, index, layoutParams); - } else { - parentView.addView(childView, index); - } - } catch (UnsupportedOperationException e) { - // looks like this is a view class that doesn't support children manipulation! - return new SceneResult(SceneStatus.ERROR_VIEWGROUP_NO_CHILDREN); + @Override + public void postAnimation() { + parentView.setLayoutTransition(null); + } + }.start(); + + // always return success since the real status will come through the listener. + return SceneStatus.SUCCESS.getResult(layoutParams); } - invalidateRenderingSize(); + SceneResult result = moveView(parentView, childView, index, layoutParams); + if (result.isSuccess() == false) { + return result; + } - SceneResult result = render(); + result = render(); if (layoutParams != null && result.isSuccess()) { - result.setData(layoutParams); + result = result.getCopyWithData(layoutParams); } return result; } - public SceneResult removeChild(View childView, IAnimationListener listener) { - checkLock(); - + /** + * Moves a View from its current parent to a new given parent at a new given location, with + * an optional new {@link LayoutParams} instance + * + * @param parent the new parent + * @param view the view to move + * @param index the new location in the new parent + * @param params an option (can be null) {@link LayoutParams} instance. + * + * @return a SceneResult with {@link SceneStatus#SUCCESS} or + * {@link SceneStatus#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support + * adding views. + */ + private SceneResult moveView(ViewGroup parent, View view, int index, LayoutParams params) { try { - ViewParent parent = childView.getParent(); - if (parent instanceof ViewGroup) { - ViewGroup parentGroup = (ViewGroup) parent; - parentGroup.removeView(childView); + ViewGroup previousParent = (ViewGroup) view.getParent(); + previousParent.removeView(view); + + // add it to the parentView in the correct location + + if (params != null) { + parent.addView(view, index, params); + } else { + parent.addView(view, index); } + + return SceneStatus.SUCCESS.getResult(); } catch (UnsupportedOperationException e) { // looks like this is a view class that doesn't support children manipulation! - return new SceneResult(SceneStatus.ERROR_VIEWGROUP_NO_CHILDREN); + return SceneStatus.ERROR_VIEWGROUP_NO_CHILDREN.getResult(); } + } + + /** + * Removes a child from its current parent. + * <p> + * {@link #acquire(long)} must have been called before this. + * + * @throws IllegalStateException if the current context is different than the one owned by + * the scene, or if {@link #acquire(long)} was not called. + * + * @see LayoutScene#removeChild(Object, IAnimationListener) + */ + public SceneResult removeChild(final View childView, IAnimationListener listener) { + checkLock(); invalidateRenderingSize(); + final ViewGroup parent = (ViewGroup) childView.getParent(); + + if (listener != null) { + new AnimationThread(this, "moveChild", listener) { + + @Override + public SceneResult preAnimation() { + parent.setLayoutTransition(new LayoutTransition()); + return removeView(parent, childView); + } + + @Override + public void postAnimation() { + parent.setLayoutTransition(null); + } + }.start(); + + // always return success since the real status will come through the listener. + return SceneStatus.SUCCESS.getResult(); + } + + SceneResult result = removeView(parent, childView); + if (result.isSuccess() == false) { + return result; + } + return render(); } /** + * Removes a given view from its current parent. + * + * @param view the view to remove from its parent + * + * @return a SceneResult with {@link SceneStatus#SUCCESS} or + * {@link SceneStatus#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support + * adding views. + */ + private SceneResult removeView(ViewGroup parent, View view) { + try { + parent.removeView(view); + return SceneStatus.SUCCESS.getResult(); + } catch (UnsupportedOperationException e) { + // looks like this is a view class that doesn't support children manipulation! + return SceneStatus.ERROR_VIEWGROUP_NO_CHILDREN.getResult(); + } + } + + /** * Checks that the lock is owned by the current thread and that the current context is the one * from this scene. * diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PlayAnimationThread.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PlayAnimationThread.java new file mode 100644 index 0000000..bbc7f4b --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PlayAnimationThread.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2010 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 com.android.layoutlib.bridge.impl; + +import com.android.layoutlib.api.SceneResult; +import com.android.layoutlib.api.LayoutScene.IAnimationListener; +import com.android.layoutlib.api.SceneResult.SceneStatus; + +import android.animation.Animator; + +public class PlayAnimationThread extends AnimationThread { + + private final Animator mAnimator; + + public PlayAnimationThread(Animator animator, LayoutSceneImpl scene, String animName, + IAnimationListener listener) { + super(scene, animName, listener); + mAnimator = animator; + } + + @Override + public SceneResult preAnimation() { + // start the animation. This will send a message to the handler right away, so + // the queue is filled when this method returns. + mAnimator.start(); + + return SceneStatus.SUCCESS.getResult(); + } + + @Override + public void postAnimation() { + // nothing to be done. + } +} |