Merge "LayoutLib: Animation support in insert/move/removeChild actions."

This commit is contained in:
Xavier Ducrohet
2010-12-03 12:27:27 -08:00
committed by Android (Google) Code Review
4 changed files with 294 additions and 70 deletions

View File

@@ -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));
}
}

View File

@@ -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();
}

View File

@@ -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,83 +561,216 @@ 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*/);
// 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);
}
final View child = mInflater.inflate(blockParser, parentView, false /*attachToRoot*/);
invalidateRenderingSize();
SceneResult result = render();
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);
}
// add it to the parentView in the correct location
SceneResult result = addView(parentView, child, index);
if (result.isSuccess() == false) {
return result;
}
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();
LayoutParams layoutParams = null;
try {
ViewParent parent = childView.getParent();
if (parent instanceof ViewGroup) {
ViewGroup parentGroup = (ViewGroup) parent;
parentGroup.removeView(childView);
}
// add it to the parentView in the correct location
if (layoutParamsMap != null) {
// need to create a new LayoutParams object for the new parent.
layoutParams = parentView.generateLayoutParams(
new BridgeLayoutParamsMapAttributes(layoutParamsMap));
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);
}
invalidateRenderingSize();
SceneResult result = render();
LayoutParams layoutParams = null;
if (layoutParamsMap != null) {
// need to create a new LayoutParams object for the new parent.
layoutParams = parentView.generateLayoutParams(
new BridgeLayoutParamsMapAttributes(layoutParamsMap));
}
if (listener != null) {
final LayoutParams params = layoutParams;
new AnimationThread(this, "moveChild", listener) {
@Override
public SceneResult preAnimation() {
parentView.setLayoutTransition(new LayoutTransition());
return moveView(parentView, childView, index, params);
}
@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);
}
SceneResult result = moveView(parentView, childView, index, layoutParams);
if (result.isSuccess() == false) {
return result;
}
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.

View File

@@ -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.
}
}