在父级之外对子视图进行动画处理 [英] Animating child view outside of parent

查看:76
本文介绍了在父级之外对子视图进行动画处理的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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. 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?

更新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?

更新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.

推荐答案

好,所以在我建议的另一种方法中,我将使用辅助类 FlyOverView 拍摄任何视图的照片,然后将其设置为所需坐标的动画。动画运行后,您可以隐藏/删除原始视图,因为在屏幕上移动的只是在画布上变白的图像。您不必担心会剪切其他视图,等等。

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

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

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

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本身:

Then the FlyOverView itself:

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

此示例还对视图应用了alpha和旋转,但是您可以轻松地对其进行调整。

This example also applies alpha and rotation to the view, but you can tune it easily to your needs.

如果您有任何疑问,我希望这可以激发您的灵感!

I hope this can inspire you, if you have any question feel free to ask !

这篇关于在父级之外对子视图进行动画处理的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆