简体   繁体   English

在父母之外动画儿童视图

[英]Animating child view outside of parent

I am trying to animate a view outside of its parent view and when I do the child view is not able to animate beyond its parent. 我试图在其父视图之外设置一个视图的动画,当我这样做时,子视图无法在其父视图之外进行动画处理。 I solved this by using the setClipChildren(false) and it worked... When the view is animated up . 我通过使用setClipChildren(false)解决了这个问题,并且它有效......当视图动画化 When I animate the view down the image is still hidden. 当我为视图设置动画 ,图像仍然是隐藏的。

Here is the code that works. 这是有效的代码。 This code will animate a tile button to the top of the screen: 此代码会将平铺按钮设置为屏幕顶部的动画:

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

But when I adjust where the view to go to the bottom of the screen, the view below it is "swallowed" and is hidden when it gets to the bottom of the parent view: 但是当我调整视图到屏幕底部的位置时,它下面的视图被“吞噬”并且当它到达父视图的底部时被隐藏:

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

So my question is: How can I animate a child view outside of a parent view below the child view? 所以我的问题是: 如何在子视图下面的父视图之外设置子视图的动画?

UPDATE 1 更新1

If I remove the tiles below the tile I am trying to drop, then I am able to see the desired effect until there is another tile below it, at which point the dropping tile will go "behind" the tile that is still there. 如果我移除瓷砖下面的瓷砖,我试图放下,然后我能够看到所需的效果,直到它下面有另一个瓷砖,此时下降的瓷砖将“仍然”在仍然存在的瓷砖“后面”。 So how do I get the dropping tile to move over the children below it? 那么如何让掉落的瓷砖移动到它下面的孩子身上呢?

UPDATE 2 更新2

One new things I've noticed is that if I move the button left or up, it works fine; 我注意到的一件新事情是,如果我向左或向上移动按钮,它可以正常工作; but, if move it down or to the right it goes behind the other views. 但是,如果将其向下或向右移动,它会落后于其他视图。 This leads me to believe that buttons created after the current tile have a different effect. 这让我相信在当前图块之后创建的按钮具有不同的效果。

Although the answer provided by rupps may solve the problem, but personally I would not use that approach, because: 虽然rupps提供的答案可以解决问题,但我个人不会使用这种方法,因为:

  • It allocates Bitmap object on the main thread: you should strive not to do that unless in a real need. 它在主线程上分配Bitmap对象:除非真正需要,否则你应该努力不这样做。
  • It unnecessarily * adds boilerplate to the codebase. 不必要地 *将样板添加到代码库中。

* Unnecessarily, because framework provides appropriate API, which is mentioned below. *不必要,因为框架提供了适当的API,如下所述。

So, the problem you are trying to solve is to animate a View out of the bounds of it's parent. 因此,您要解决的问题是使View的父级界限动画化。 Let's get acquainted with ViewOverlay API: 让我们熟悉ViewOverlay API:

An overlay is an extra layer that sits on top of a View (the "host view") which is drawn after all other content in that view (including children, if the view is a ViewGroup). 叠加层是位于视图顶部的额外图层(“主视图”),该视图是在该视图中的所有其他内容之后绘制的(如果视图是ViewGroup,则包括子视图)。 Interaction with the overlay layer is done by adding and removing drawables. 通过添加和移除drawable来完成与覆盖层的交互。

As mentioned by Israel Ferrer Camacho in his "Smoke & Mirrors" talk : 正如以色列费雷尔卡马乔在他的“烟雾与镜子”中所提到的那样:

ViewOverlay is gonna be your best friend forever ... in animations. ViewOverlay永远是你最好的朋友......在动画中。

As an example use cases you can see this : 作为一个例子使用的情况下,你可以看到这个

在此输入图像描述 Animating icons using ViewOverlay API. 使用ViewOverlay API动画图标。 This looks like shared element transition ?? 这看起来像共享元素转换 Well, that's because Transitions API internally uses ViewOverlay . 好吧,那是因为Transitions API内部使用了ViewOverlay

Also a nice example by Dave Smith , demonstrating difference between using ViewOverlay and Animator : 这也是Dave Smith的 一个很好的例子 ,展示了使用ViewOverlayAnimator之间的ViewOverlay

In order to complete the answer, I'll post a chunk of code from Dave Smith's example. 为了完成答案,我将从Dave Smith的例子中发布一大堆代码。 The usage is as simple as this: 用法很简单:

container.getOverlay().add(button);

The button would be "overlayed" atop of container right in the coordinates where it is in the view hierarchy. button“叠加”在容器的顶部,位于视图层次结构中的坐标中。 Now you can perform animations on this button , but the crucial point is to remove the overlay once you do not need it: 现在您可以在此button上执行动画,但关键是在不需要时删除叠加层:

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

Ok, so in the alternate approach I suggest, I'd use a helper class FlyOverView that takes a "photo" of any view then animates it to whichever desired coordinates. 好吧,所以在我建议的替代方法中,我会使用一个帮助类FlyOverView ,它可以获取任何视图的“照片”,然后将其设置为任意所需的坐标。 Once the animation is running, you then can hide / delete the original view, as what is moving around the screen is just an image blitted over the canvas. 一旦动画运行,您就可以隐藏/删除原始视图,因为在屏幕上移动的内容只是在画布上显示的图像。 You won't have to worry about clipping other views, etc. 您不必担心剪切其他视图等。

You'll need to declare this FlyOverView on an outer container of your brick system, and that layout has to cover the whole area where you intend the animation to be visible. 您需要在砖块系统的外部容器上声明此FlyOverView ,并且该布局必须覆盖您希望动画可见的整个区域。 So I'd suggest to use the following as you root container, it's just a FrameLayout where you'll draw stuff over its children. 因此我建议在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;
            }
        });
    }
}

that you can use as the container 你可以用作容器

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

          .
          .
    </whatever.GameRootLayout>

Then the FlyOverView itself: 然后是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) {}
    }
}

Then, when you want to animate any view, you just have to call the function in the game root layout: 然后,当您想要为任何视图设置动画时,您只需在游戏根布局中调用该函数:

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

This example also applies alpha and rotation to the view, but you can tune it easily to your needs. 此示例还将alpha和旋转应用于视图,但您可以根据需要轻松调整它。

I hope this can inspire you, if you have any question feel free to ask ! 我希望这可以激励你,如果你有任何问题随时可以问!

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

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