En este artículo, vamos a crear una lluvia de estrellas usando la animación de propiedades de Android. Crearemos una animación un poco más complicada, animando múltiples propiedades en múltiples objetos. Para este efecto, al hacer clic en un botón, se creará una estrella con un tamaño aleatorio, que se agregará al contenedor de fondo, justo fuera de la vista desde la parte superior de ese contenedor. La estrella procederá a caer hasta la parte inferior de la pantalla acelerando a medida que avanza. la estrella también girará cuando caiga. A continuación se proporciona un GIF de muestra para tener una idea de lo que vamos a hacer en este artículo. Tenga en cuenta que vamos a implementar este proyecto utilizando el lenguaje Kotlin .
Implementación paso a paso
Paso 1: Crear un nuevo proyecto
Para crear un nuevo proyecto en Android Studio, consulte Cómo crear/iniciar un nuevo proyecto en Android Studio . Tenga en cuenta que seleccione Kotlin como lenguaje de programación.
Paso 2: necesitaremos algunas variables locales para mantener el estado.
- una referencia al Starfield ViewGroup que es solo el padre de la vista de estrella actual.
- el ancho y la altura de ese contenedor que se utilizará para calcular los valores finales de traslación de las estrellas fugaces.
- el ancho y la altura predeterminados de la estrella que se cambiarán más tarde con un factor de escala para obtener estrellas de diferentes tamaños.
val container = star.parent como ViewGroup
val contenedorW = contenedor.ancho
val containerH = container.height
var estrellaW: Flotante = estrella.ancho.toFloat()
Cree una nueva Vista para contener la estrella, ya que es un activo VectorDrawable, use un AppCompatImageView , que tiene la capacidad de alojar ese tipo de recurso. Crea la estrella y agrégala al contenedor de fondo.
val newStar = AppCompatImageView(esto)
newStar.setImageResource(R.drawable.ic_star)
newStar.layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT)
Paso 3: Dimensionar y posicionar la estrella
No hemos definido la posición de la imagen en el contenedor, por lo que está posicionada en (0, 0) por defecto. Lo arreglaremos en este paso.
(i) Establezca el tamaño de la estrella. Modifique la estrella para que tenga un tamaño aleatorio, de .1x a 1.6x de su tamaño predeterminado. Utilice este factor de escala para cambiar los valores de ancho/alto almacenados en caché.
newStar.scaleX = Math.random().toFloat() * 1.5f + .1f
nuevaEstrella.escalaY = nuevaEstrella.escalaX
estrellaW *= nuevaEstrella.escalaX
estrellaH *= nuevaEstrella.escalaY
Ahora ha almacenado en caché el píxel H/W de la estrella almacenado en starW y starH:
(ii) Ahora coloque la nueva estrella. Debería aparecer aleatoriamente en algún lugar entre el borde izquierdo y el borde derecho horizontalmente. El siguiente código usa el ancho de la estrella para ubicarla desde la mitad de la pantalla a la izquierda (-starW / 2) hasta la mitad de la pantalla a la derecha (con la estrella posicionada en (containerW – starW / 2).
newStar.translationX = Math.random().toFloat() * containerW – starW / 2
Paso 4: Creación de animadores para rotación y caída de estrellas
Es hora de trabajar en la animación. La estrella debe girar a medida que cae hacia abajo. Podemos animar dos propiedades juntas. La rotación usará un movimiento lineal suave (moviéndose a una velocidad constante durante toda la animación de rotación), mientras que la animación de caída usará un movimiento acelerado (simulando la gravedad tirando de la estrella hacia abajo a una velocidad constantemente más rápida). Así que creará dos animadores y agregará un interpolador a cada uno.
(i) Primero, cree dos animadores, junto con sus interpoladores:
val mover = ObjectAnimator.ofFloat(newStar, View.TRANSLATION_Y, -starH, containerH + starH)
mover.interpolator = AccelerateInterpolator(1f) // provoca un suave movimiento de aceleración
val rotator = ObjectAnimator.ofFloat(newStar, View.ROTATION,
(Math.random() * 1080).toFloat()) // la estrella rota una cantidad aleatoria entre 0 y 1080 grados
rotator.interpolator = LinearInterpolator() //la rotación procederá a una velocidad constante a medida que cae la estrella
La animación del motor es responsable de hacer que la estrella «caiga». Anima la propiedad TRANSLATION_Y pero provoca un movimiento vertical en lugar de horizontal. El código se anima de -starH a (containerH + starH), lo que lo coloca justo fuera del contenedor en la parte superior y lo mueve hasta que está justo fuera del contenedor en la parte inferior, como se muestra aquí:
Paso 5: Ejecutar las animaciones en paralelo con AnimatorSet
Ahora es el momento de juntar estos dos animadores en un solo AnimatorSet . Es básicamente un grupo de animaciones, junto con instrucciones sobre cuándo ejecutar esas animaciones. Puede reproducir animaciones en paralelo.
(i) Cree el AnimatorSet y agréguele los animadores secundarios. El tiempo de animación predeterminado de 300 milisegundos es demasiado rápido para las estrellas fugaces, así que establezca la duración en un número aleatorio entre 500 y 2000 milisegundos, para que las estrellas puedan caer a diferentes velocidades.
conjunto de valores = AnimatorSet()
set.playTogether(motor, rotador)
set.duration = (Math.random() * 1500 + 500).toLong()
(ii) Una vez que newStar se haya caído por la parte inferior de la pantalla, debe retirarse del contenedor. Configure un oyente simple para que espere el final de la animación y elimínelo. Luego inicia la animación.
set.addListener(objeto: AnimatorListenerAdapter() {
anula la diversión onAnimationEnd (animación: ¿Animador?) {
container.removeView(nuevaEstrella)
}
})
seleccione arranque()
aplicación> res> diseño> actividad_principal.xml completar actividad_principal.xml
XML
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <Button android:id="@+id/showerButton" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="star shower" app:layout_constraintBottom_toTopOf="@+id/frameLayout" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <FrameLayout android:id="@+id/frameLayout" android:layout_width="match_parent" android:layout_height="0dp" android:background="@color/black" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/showerButton" app:layout_constraintVertical_bias="0.0"> <ImageView android:id="@+id/star" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:contentDescription="star image" android:visibility="invisible" app:srcCompat="@drawable/ic_star" tools:ignore="VectorDrawableCompat" /> </FrameLayout> </androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.kt completar MainActivity.kt
Kotlin
import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.AnimatorSet import android.animation.ObjectAnimator import android.os.Bundle import android.view.View import android.view.ViewGroup import android.view.animation.AccelerateInterpolator import android.view.animation.LinearInterpolator import android.widget.Button import android.widget.FrameLayout import android.widget.ImageView import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.AppCompatImageView class MainActivity : AppCompatActivity() { lateinit var showerButton: Button lateinit var star: ImageView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) showerButton = findViewById<Button>(R.id.showerButton) star = findViewById(R.id.star) showerButton.setOnClickListener { shower() } } private fun shower() { // Create a new star view in a random X position above the container. // Make it rotateButton about its center as it falls to the bottom. // Local variables val container = star.parent as ViewGroup val containerW = container.width val containerH = container.height var starW: Float = star.width.toFloat() var starH: Float = star.height.toFloat() // Create the new star (an ImageView in layout holding drawable star image) // and add it to the container val newStar = AppCompatImageView(this) newStar.setImageResource(R.drawable.ic_star) newStar.layoutParams = FrameLayout.LayoutParams( FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT ) container.addView(newStar) // Scale the view randomly between 10-160% of its default size newStar.scaleX = Math.random().toFloat() * 1.5f + .1f newStar.scaleY = newStar.scaleX starW *= newStar.scaleX starH *= newStar.scaleY // Position the view at a random place between // the left and right edges of the container newStar.translationX = Math.random().toFloat() * containerW - starW / 2 // Create an animator that moves the view from a starting position right about the container // to an ending position right below the container. Set an accelerate interpolator to give // it a gravity/falling feel val mover = ObjectAnimator.ofFloat(newStar, View.TRANSLATION_Y, -starH, containerH + starH) mover.interpolator = AccelerateInterpolator(1f) // Create an animator to rotateButton the // view around its center up to three times val rotator = ObjectAnimator.ofFloat( newStar, View.ROTATION, (Math.random() * 1080).toFloat() ) rotator.interpolator = LinearInterpolator() // Use an AnimatorSet to play the falling and // rotating animators in parallel for a duration // of a half-second to two seconds val set = AnimatorSet() set.playTogether(mover, rotator) set.duration = (Math.random() * 1500 + 500).toLong() // When the animation is done, remove the // created view from the container set.addListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator?) { container.removeView(newStar) } }) // Start the animation set.start() } private fun ObjectAnimator.disableViewDuringAnimation(view: View) { // This extension method listens for start/end // events on an animation and disables // the given view for the entirety of that animation. addListener(object : AnimatorListenerAdapter() { override fun onAnimationStart(animation: Animator?) { view.isEnabled = false } override fun onAnimationEnd(animation: Animator?) { view.isEnabled = true } }) } }
Ahora, ejecute su aplicación. Puede hacer clic en el botón » LLUVIA DE ESTRELLAS » varias veces, creando una nueva estrella y una nueva animación cada vez.
Producción:
Código fuente: Haga clic aquí