繁体   English   中英

在父母之外动画儿童视图

[英]Animating child view outside of parent

我试图在其父视图之外设置一个视图的动画,当我这样做时,子视图无法在其父视图之外进行动画处理。 我通过使用setClipChildren(false)解决了这个问题,并且它有效......当视图动画化 当我为视图设置动画 ,图像仍然是隐藏的。

这是有效的代码。 此代码会将平铺按钮设置为屏幕顶部的动画:

private void setGameBoard(){

        brickWall.setClipChildren(false);
        brickWall.setClipToPadding(false);

        //Build game board
        for(int ii = 0; ii < brickRows;ii++){
            final int x = ii;

            //Build table rows
            row = new TableRow(this.getApplicationContext());
            row.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, 50));
            row.setClipChildren(false);
            row.setClipToPadding(false);

           // row.setBackgroundColor(ResourcesCompat.getColor(getResources(), R.color.colorAccent, null));

            //Build table tiles
            for(int jj=0; jj < brickColumns; jj++){
                final int y = jj;
                final Brick tile = new Brick(this);
                tile.setClipBounds(null);

                tile.setBackgroundColor(ResourcesCompat.getColor(getResources(), R.color.colorPrimary, null));

                //Set margins to create look of tic-tac-toe
                TableRow.LayoutParams lp = new TableRow.LayoutParams(
                                           150, 75);
                lp.setMargins(0,0,0,0);


                //lp.weight = 1;
                tile.setLayoutParams(lp);

                tile.setWidth(3);
                tile.setHeight(10);
                tile.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        if(tile.getHits() == 0){
                            tile.setBackgroundColor(ResourcesCompat.getColor(getResources(),
                                    R.color.colorGreen, null));
                            tile.addHit();
                        } else if (tile.getHits() == 1){
                            tile.setBackgroundColor(ResourcesCompat.getColor(getResources(),
                                    R.color.colorYellow, null));
                            tile.addHit();
                        }else if(tile.getHits() == 2){
                            brokenBricks++;

                            float bottomOfScreen = getResources().getDisplayMetrics()
                                    .heightPixels;

                            ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(tile, "translationY",
                                    -2000);
// 2
                            objectAnimator.setDuration(2000);
                            tile.setHapticFeedbackEnabled(true);
                            objectAnimator.start();
                            //tile.setVisibility(View.INVISIBLE);
                            if(isRevealComplete()){
                                brickWall.setVisibility(View.INVISIBLE);
                            }
                        }
                    }
                });

                row.addView(tile);
            }

            brickWall.addView(row);
        }
    } 

但是当我调整视图到屏幕底部的位置时,它下面的视图被“吞噬”并且当它到达父视图的底部时被隐藏:

 private void setGameBoard(){

        brickWall.setClipChildren(false);
        brickWall.setClipToPadding(false);

        //Build game board
        for(int ii = 0; ii < brickRows;ii++){
            final int x = ii;

            //Build table rows
            row = new TableRow(this.getApplicationContext());
            row.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, 50));
            row.setClipChildren(false);
            row.setClipToPadding(false);

           // row.setBackgroundColor(ResourcesCompat.getColor(getResources(), R.color.colorAccent, null));

            //Build table tiles
            for(int jj=0; jj < brickColumns; jj++){
                final int y = jj;
                final Brick tile = new Brick(this);
                tile.setClipBounds(null);

                tile.setBackgroundColor(ResourcesCompat.getColor(getResources(), R.color.colorPrimary, null));

                //Set margins to create look of tic-tac-toe
                TableRow.LayoutParams lp = new TableRow.LayoutParams(
                                           150, 75);
                lp.setMargins(0,0,0,0);


                //lp.weight = 1;
                tile.setLayoutParams(lp);

                tile.setWidth(3);
                tile.setHeight(10);
                tile.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        if(tile.getHits() == 0){
                            tile.setBackgroundColor(ResourcesCompat.getColor(getResources(),
                                    R.color.colorGreen, null));
                            tile.addHit();
                        } else if (tile.getHits() == 1){
                            tile.setBackgroundColor(ResourcesCompat.getColor(getResources(),
                                    R.color.colorYellow, null));
                            tile.addHit();
                        }else if(tile.getHits() == 2){
                            brokenBricks++;

                            float bottomOfScreen = getResources().getDisplayMetrics()
                                    .heightPixels;

                            ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(tile, "translationY",
                                    2000);
                            objectAnimator.setDuration(2000);
                            tile.setHapticFeedbackEnabled(true);
                            objectAnimator.start();
                            //tile.setVisibility(View.INVISIBLE);
                            if(isRevealComplete()){
                                brickWall.setVisibility(View.INVISIBLE);
                            }
                        }
                    }
                });

                row.addView(tile);
            }

            brickWall.addView(row);
        }
    }

所以我的问题是: 如何在子视图下面的父视图之外设置子视图的动画?

更新1

如果我移除瓷砖下面的瓷砖,我试图放下,然后我能够看到所需的效果,直到它下面有另一个瓷砖,此时下降的瓷砖将“仍然”在仍然存在的瓷砖“后面”。 那么如何让掉落的瓷砖移动到它下面的孩子身上呢?

更新2

我注意到的一件新事情是,如果我向左或向上移动按钮,它可以正常工作; 但是,如果将其向下或向右移动,它会落后于其他视图。 这让我相信在当前图块之后创建的按钮具有不同的效果。

虽然rupps提供的答案可以解决问题,但我个人不会使用这种方法,因为:

  • 它在主线程上分配Bitmap对象:除非真正需要,否则你应该努力不这样做。
  • 不必要地 *将样板添加到代码库中。

*不必要,因为框架提供了适当的API,如下所述。

因此,您要解决的问题是使View的父级界限动画化。 让我们熟悉ViewOverlay API:

叠加层是位于视图顶部的额外图层(“主视图”),该视图是在该视图中的所有其他内容之后绘制的(如果视图是ViewGroup,则包括子视图)。 通过添加和移除drawable来完成与覆盖层的交互。

正如以色列费雷尔卡马乔在他的“烟雾与镜子”中所提到的那样:

ViewOverlay永远是你最好的朋友......在动画中。

作为一个例子使用的情况下,你可以看到这个

在此输入图像描述 使用ViewOverlay API动画图标。 这看起来像共享元素转换 好吧,那是因为Transitions API内部使用了ViewOverlay

这也是Dave Smith的 一个很好的例子 ,展示了使用ViewOverlayAnimator之间的ViewOverlay

为了完成答案,我将从Dave Smith的例子中发布一大堆代码。 用法很简单:

container.getOverlay().add(button);

button“叠加”在容器的顶部,位于视图层次结构中的坐标中。 现在您可以在此button上执行动画,但关键是在不需要时删除叠加层:

@Override
public void onAnimationEnd(Animator arg0) {
    container.getOverlay().remove(button);
}

好吧,所以在我建议的替代方法中,我会使用一个帮助类FlyOverView ,它可以获取任何视图的“照片”,然后将其设置为任意所需的坐标。 一旦动画运行,您就可以隐藏/删除原始视图,因为在屏幕上移动的内容只是在画布上显示的图像。 您不必担心剪切其他视图等。

您需要在砖块系统的外部容器上声明此FlyOverView ,并且该布局必须覆盖您希望动画可见的整个区域。 因此我建议在root容器中使用以下内容,它只是一个FrameLayout ,您可以在其中绘制内容。

package whatever;

public class GameRootLayout extends FrameLayout {

    // the currently-animating effect, if any
    private FlyOverView mFlyOverView;

    public GameRootLayout(Context context, AttributeSet attrs) {
         super(context,attrs);
    }

    @Override
    public void dispatchDraw(Canvas canvas) {

        // draw the children            
        super.dispatchDraw(canvas);

        // draw our stuff
        if (mFlyOverView != null) {
            mFlyOverView.delegate_draw(canvas);
        }
    }


    /**
     * Starts a flyover animation for the specified view. 
     * It will "fly" to the desired position with an alpha / translation / rotation effect
     *
     * @param viewToFly The view to fly
     * @param targetX The target X coordinate
     * @param targetY The target Y coordinate
     */

    public void addFlyOver(View viewToFly, @Px int targetX, @Px int targetY) {
        if (mFlyOverView != null) mFlyOverView.cancel();
        mFlyOverView = new FlyOverView(this, viewToFly, targetX, targetY, new FlyOverView.OnFlyOverFinishedListener() {
            @Override
            public void onFlyOverFinishedListener() {
                mFlyOverView = null;
            }
        });
    }
}

你可以用作容器

  <whatever.GameRootLayout
          android:id="@+id/gameRootLayout"
          android:layout_width="match_parent"
          android:layout_height="match_parent">

          .
          .
    </whatever.GameRootLayout>

然后是FlyOverView本身:

package com.regaliz.gui.fx;

import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.util.Log;
import android.view.View;
import android.support.annotation.Px;
import android.view.animation.AccelerateDecelerateInterpolator;


/**
 * Neat animation that captures a layer bitmap and "flies" it to the corner of the screen, used to create
 * an "added to playlist" effect or something like that. The method delegate_draw() must be called from the
 * parent view to update the animation
 *
 * @author rupps'2014
 * license public domain, attribution appreciated
 */

@SuppressWarnings("unused")
public class FlyOverView {

    public  static final int DEFAULT_DURATION = 1000;
    private static final String TAG = "FlyOverView";
    private static final boolean LOG_ON = false;

    private ObjectAnimator mFlyoverAnimation;

    private float mCurrentX, mCurrentY;
    private Matrix mMatrix = new Matrix();

    private Bitmap mBitmap;
    private Paint mPaint = new Paint();

    private View mParentView = null;
    private OnFlyOverFinishedListener mOnFlyOverFinishedListener;

    public interface OnFlyOverFinishedListener {
        void onFlyOverFinishedListener();
    }

    /**
     * Creates the FlyOver effect
     *
     * @param parent    Container View to invalidate. That view has to call this class' delegate_draw() in its dispatchDraw().
     * @param viewToFly Target View to animate
     * @param finalX    Final X coordinate
     * @param finalY    Final Y coordinate
     */

    public FlyOverView(View parent, View viewToFly, @Px int finalX, @Px int finalY, OnFlyOverFinishedListener listener) {
        setupFlyOver(parent, viewToFly, finalX, finalY, DEFAULT_DURATION, listener);
    }

    /**
     * Creates the FlyOver effect
     *
     * @param parent    Container View to invalidate. That view has to call this class' delegate_draw() in its dispatchDraw().
     * @param viewToFly Target View to animate
     * @param finalX    Final X coordinate
     * @param finalY    Final Y coordinate
     * @param duration  Animation duration
     */

    public FlyOverView(View parent, View viewToFly, @Px int finalX, @Px int finalY, int duration, OnFlyOverFinishedListener listener) {
        setupFlyOver(parent, viewToFly, finalX, finalY, duration, listener);
    }

    /**
     * cancels current animation from the outside
     */
    public void cancel() {
        if (mFlyoverAnimation != null) mFlyoverAnimation.cancel();
    }

    private void setupFlyOver(View parentContainer, View viewToFly, @Px int finalX, @Px int finalY, int duration, OnFlyOverFinishedListener listener) {


        int[] location = new int[2];

        mParentView = parentContainer;
        mOnFlyOverFinishedListener = listener;
        viewToFly.getLocationInWindow(location);

        int 
            sourceX = location[0],
            sourceY = location[1];

        if (LOG_ON) Log.v(TAG, "FlyOverView, item " + viewToFly+", finals "+finalX+", "+finalY+", sources "+sourceX+", "+sourceY+ " duration "+duration);

         /* Animation definition table */

        mFlyoverAnimation = ObjectAnimator.ofPropertyValuesHolder(
            this,
            PropertyValuesHolder.ofFloat("translationX", sourceX, finalX),
            PropertyValuesHolder.ofFloat("translationY", sourceY, finalY),
            PropertyValuesHolder.ofFloat("scaleAlpha", 1, 0.2f) // not to 0 so we see the end of the effect in other properties
        );

        mFlyoverAnimation.setDuration(duration);
        mFlyoverAnimation.setRepeatCount(0);
        mFlyoverAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
        mFlyoverAnimation.addListener(new SimpleAnimationListener() {
            @Override
            public void onAnimationEnd(Animator animation) {
                if (LOG_ON) Log.v(TAG, "FlyOver: End");
                mParentView.invalidate();
                if (mBitmap != null) mBitmap.recycle(); // just for safety
                mBitmap = null;
                mOnFlyOverFinishedListener.onFlyOverFinishedListener();
            }
        });

        // take snapshot of viewToFly
        viewToFly.setDrawingCacheEnabled(true);
        mBitmap = Bitmap.createBitmap(viewToFly.getDrawingCache());
        viewToFly.setDrawingCacheEnabled(false);

        mFlyoverAnimation.start();

    }

    // ANIMATOR setter
    public void setTranslationX(float position) {
        mCurrentX = position;
    }

    // ANIMATOR setter
    public void setTranslationY(float position) {
        mCurrentY = position;
    }

    // ANIMATOR setter
    // as this will be called in every iteration, we set here all parameters at once then call invalidate,
    // rather than separately
    public void setScaleAlpha(float position) {

        mPaint.setAlpha((int) (100 * position));
        mMatrix.setScale(position, position);
        mMatrix.postRotate(360 * position); // asemos de to'
        mMatrix.postTranslate(mCurrentX, mCurrentY);

        mParentView.invalidate();
    }

    /**
     * This has to be called from the root container's dispatchDraw()
     * in order to update the animation.
     */

    public void delegate_draw(Canvas c) {
        if (LOG_ON) Log.v(TAG, "CX " + mCurrentX + ", CY " + mCurrentY);
        c.drawBitmap(mBitmap, mMatrix, mPaint);
    }

    private abstract class SimpleAnimationListener implements Animator.AnimatorListener {
        @Override public void onAnimationStart(Animator animation) {}
        @Override public void onAnimationRepeat(Animator animation) {}
        @Override public void onAnimationCancel(Animator animation) {}
    }
}

然后,当您想要为任何视图设置动画时,您只需在游戏根布局中调用该函数:

GameRootLayout rootLayout = (GameRootLayout)findViewById(...);
.
.
rootLayout.addFlyOver(yourBrick, targetX, targetY);

此示例还将alpha和旋转应用于视图,但您可以根据需要轻松调整它。

我希望这可以激励你,如果你有任何问题随时可以问!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM