Animación de la vista de un niño fuera del padre
Estoy intentando animar una visión fuera de su opinión del padre y cuando hago la visión del niño no puedo animar más allá de su padre. He resuelto esto usando el setClipChildren(false)
y funcionó … Cuando la vista está animada. Cuando animo la vista hacia abajo, la imagen todavía está oculta.
Aquí está el código que funciona. Este código animará un botón de mosaico en la parte superior de la pantalla:
- Ver animación de derecha a izquierda android
- Cómo cambiar las animaciones utilizadas por el mecanismo de animateLayoutChanges?
- Fragmento de superposición de animación de transacciones
- Android: animación en el diseño después de hacer clic en el botón para una versión mínima de SDK de 14
- ¿Cómo traducir la animación en una imagen diagonalmente?
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); } }
Pero cuando ajusto la vista para ir a la parte inferior de la pantalla, la vista debajo de ella se "traga" y se oculta cuando llega a la parte inferior de la vista principal:
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); } }
Así que mi pregunta es: ¿Cómo puedo animar una vista de niño fuera de una vista de padre debajo de la vista de niño?
ACTUALIZACIÓN 1
Si quito los azulejos debajo del azulejo que estoy tratando de dejar caer, entonces puedo ver el efecto deseado hasta que haya otro azulejo debajo de él, en cuyo punto el azulejo que cae irá "detrás" del azulejo que todavía está allí. Entonces, ¿cómo puedo obtener el azulejo de caída para moverse sobre los niños por debajo de él?
ACTUALIZACIÓN 2
Una cosa nueva que he notado es que si muevo el botón hacia la izquierda o hacia arriba, funciona bien; pero, si se mueve hacia abajo o hacia la derecha va detrás de las otras vistas. Esto me lleva a creer que los botones creados después de la baldosa actual tienen un efecto diferente.
- Diseño de materiales
- Cómo hacer una animación fluida líquido en Android
- Onlclick oyente no está funcionando correctamente ..?
- Cómo posicionar una vista fuera de la pantalla para que pueda ser animado para moverse en pantalla?
- ¿Cómo crear una vista similar al área de notificación del sistema en android?
- Aplicación de animaciones al cambiar la visibilidad de una vista
- Comportamiento extraño de View.getHitRect ()
- Animación de radar android
Ok, por lo que en el enfoque alternativo que sugiero, usaría una clase de ayuda FlyOverView
que toma una "foto" de cualquier vista, luego la anima a las coordenadas que desee. Una vez que la animación se está ejecutando, entonces puede ocultar / borrar la vista original, como lo que se mueve alrededor de la pantalla es sólo una imagen blitted sobre el lienzo. No tendrá que preocuparse por recortar otras vistas, etc.
Tendrá que declarar este FlyOverView
en un contenedor externo de su sistema de ladrillos, y ese diseño tiene que cubrir todo el área donde desea que la animación sea visible. Por lo tanto, sugiero que utilice lo siguiente como contenedor de raíz, es sólo un FrameLayout
donde dibujarás cosas sobre sus hijos.
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; } }); } }
que puede utilizar como contenedor
<whatever.GameRootLayout android:id="@+id/gameRootLayout" android:layout_width="match_parent" android:layout_height="match_parent"> . . </whatever.GameRootLayout>
Luego, el propio 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) {} } }
A continuación, cuando desea animar cualquier vista, sólo tiene que llamar a la función en el diseño de la raíz del juego:
GameRootLayout rootLayout = (GameRootLayout)findViewById(...); . . rootLayout.addFlyOver(yourBrick, targetX, targetY);
Este ejemplo también se aplica alfa y rotación a la vista, pero se puede sintonizar fácilmente a sus necesidades.
¡Espero que esto pueda inspirarle, si usted tiene cualquier pregunta siente libre de pedir!
Aunque la respuesta proporcionada por rupps puede resolver el problema, pero personalmente no usaría ese enfoque, porque:
-
Bitmap
objeto deBitmap
en el hilo principal: debe esforzarse no hacer eso a menos que en una necesidad real. - Innecesariamente * agrega boilerplate a la base de código.
* Innecesariamente, porque el marco proporciona el API apropiado, que se menciona a continuación.
Por lo tanto, el problema que está tratando de resolver es animar una View
fuera de los límites de su padre. Vamos a familiarizarse con ViewOverlay
API:
Una superposición es una capa adicional que se sitúa encima de una vista (la "vista de host") que se dibuja después de todo el resto del contenido de esa vista (incluidos los niños, si la vista es un grupo de vistas). La interacción con la capa de superposición se realiza mediante la adición y extracción de elementos estirables.
Como lo mencionó Israel Ferrer Camacho en su charla "Smoke & Mirrors" :
ViewOverlay
va a ser tu mejor amigo para siempre … en animaciones.
Como ejemplos de casos de uso puede ver esto :
Animación de iconos mediante la API de ViewOverlay
. Esto se parece a la transición de elementos compartidos ?? Bueno, eso es porque la API de Transitions utiliza internamente ViewOverlay
.
También un buen ejemplo de Dave Smith , que demuestra la diferencia entre usar ViewOverlay
y Animator
:
Para completar la respuesta, publicaré un fragmento de código del ejemplo de Dave Smith. El uso es tan simple como esto:
container.getOverlay().add(button);
El button
se "superpuesta" encima del contenedor derecho en las coordenadas donde está en la jerarquía de vista. Ahora puede realizar animaciones en este button
, pero el punto crucial es quitar la superposición una vez que no lo necesite:
@Override public void onAnimationEnd(Animator arg0) { container.getOverlay().remove(button); }
- Cómo dibujar el marcador en el centro de la polilínea
- El servicio no se detiene incluso después de que el método onDestroy se llama en Android