Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e1179ea0 authored by Xavier Ducrohet's avatar Xavier Ducrohet
Browse files

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

Also update to use the new SceneResult API.

Change-Id: Iaac6df0c250fbefc8758310c37e0cf47cae6875d
parent 01811aa8
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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));
        }
    }

+41 −10
Original line number Diff line number Diff line
@@ -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();
        }
+198 −53
Original line number Diff line number Diff line
@@ -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*/);
        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);
                }

        invalidateRenderingSize();
                @Override
                public void postAnimation() {
                    parentView.setLayoutTransition(null);
                }
            }.start();

        SceneResult result = render();
        if (result.isSuccess()) {
            result.setData(child);
            // 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;
        }

    public SceneResult moveChild(ViewGroup parentView, View childView, int index,
            Map<String, String> layoutParamsMap, IAnimationListener listener) {
        checkLock();
        result = render();
        if (result.isSuccess()) {
            result = result.getCopyWithData(child);
        }

        LayoutParams layoutParams = null;
        return result;
    }

    /**
     * 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 {
            ViewParent parent = childView.getParent();
            if (parent instanceof ViewGroup) {
                ViewGroup parentGroup = (ViewGroup) parent;
                parentGroup.removeView(childView);
            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();
        }
    }

            // add it to the parentView in the correct location
    /**
     * 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;
        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);
        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);
                }
        } 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();

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

        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.
+48 −0
Original line number Diff line number Diff line
/*
 * 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.
    }
}