Recrear el efecto de flujo de cobertura con Flash y AS3
() translation by (you can also view the original English article)
Sin dudas, has visto la vista "Flujo de cobertura" en efecto. Está por todas partes en las cosas de Apple. Y probablemente también haya visto una serie de implementaciones de Cover Flow en Flash. En este tutorial, obtendrá uno más. Aprovecharemos las capacidades 3D integradas de Flash Player 10 (anterior a Stage3D) y crearemos nuestra propia versión de Cover Flow basada en XML.
Nota: Este tutorial se publicó originalmente en abril de 2011, antes de que se lanzara Stage3D, como regalo de promoción para los suscriptores de nuestro boletín. Dado que Activetuts + ahora se ha cerrado, lo hacemos libre para todos los lectores.
Vista previa del resultado final
Mira la demostración para ver para qué estamos trabajando.
Paso 1: crea un archivo Flash ActionScript 3
Lo primero que debemos hacer es crear un archivo Flash en el que trabajemos. Abra Flash CS4 o CS5 y elija Archivo> Nuevo y seleccione Archivo Flash (ActionScript 3.0) y presione OK. Guarde este archivo en una carpeta que estará dedicada a este proyecto.



Estableceré el tamaño del escenario a 600 x 400. Siéntase libre de usar el tamaño que desee, pero recomendaría 600 x 400 como mínimo, teniendo en cuenta que el efecto Coverflow es mejor cuando tiene suficiente espacio para mostrarse. Además, elija una velocidad de fotogramas bastante alta, como la predeterminada 24. Esto hará que las animaciones sean más suaves.



Guarde este archivo como CoverFlow.fla en una carpeta que pueda dedicar a este proyecto.
Paso 2: crea la clase de documento de prueba
Nuestro objetivo será crear una clase CoverFlow reutilizable, pero para desarrollarla necesitamos un lugar donde vivir. Utilizaremos el archivo Flash que acabamos de crear para que sirva de campo de pruebas para la clase CoverFlow a medida que lo desarrollemos, por lo que necesitaremos una clase de documento para ir con el archivo Flash. Esta clase funcionará para proporcionar un lugar para instanciar y probar la clase CoverFlow. Como tal, proporcionará un ejemplo de cómo usar la clase CoverFlow una vez que esté completa.
En el editor de texto de su elección, cree un nuevo archivo y guárdelo como CoverFlowTest.as en la misma carpeta que su archivo Flash.

Apagar una clase simple:
1 |
|
2 |
package { |
3 |
|
4 |
import flash.display.*; |
5 |
import flash.events.*; |
6 |
import flash.net.*; |
7 |
import flash.utils.*; |
8 |
|
9 |
public class CoverFlowTest extends Sprite { |
10 |
|
11 |
public function CoverFlowTest() { |
12 |
trace("CoverFlowTest"); |
13 |
}
|
14 |
|
15 |
|
16 |
}
|
17 |
|
18 |
}
|
Esto no hace mucho, pero rastreará un mensaje si está conectado correctamente, lo cual haremos a continuación.
Paso 3: conecte la clase de documento a la FLA
En el archivo Flash, haga clic en algún lugar en el escenario vacío y abra el panel Propiedades (Comando / Control-F3, o Ventana> Propiedades). En el campo Clase, escriba CoverFlowTest
para asignar la clase del documento.
Pruebe su película ahora, y debería ver el rastro antes mencionado.



Paso 4: Crea la clase CoverFlow
A continuación crearemos el archivo para la clase CoverFlow. Esto vivirá en un paquete, así que primero crea la estructura de la carpeta. Comenzando en la carpeta del proyecto (al mismo nivel que su FLA), cree una carpeta llamada com. Dentro de eso, crea otra carpeta llamada tutsplus. Dentro de esto, crea una carpeta más llamada Coverflow.
Ahora crea otro archivo de texto llamado CoverFlow.as dentro de la carpeta coverflow.



Agregue la siguiente plantilla repetitiva:
1 |
|
2 |
package com.tutsplus.coverflow { |
3 |
|
4 |
import flash.display.*; |
5 |
import flash.geom.*; |
6 |
import flash.events.*; |
7 |
import flash.net.*; |
8 |
import flash.text.*; |
9 |
import flash.utils.*; |
10 |
|
11 |
public class CoverFlow extends Sprite { |
12 |
|
13 |
public function CoverFlow() { |
14 |
trace("CoverFlow"); |
15 |
}
|
16 |
|
17 |
}
|
18 |
|
19 |
}
|
Esto es muy similar a la clase de documento, solo anticipamos la necesidad de más clases, por lo que hay más instrucciones de importación. Esta clase también extenderá Sprite, para que podamos tratar CoverFlow como un objeto de visualización.
Paso 5: crea una instancia de CoverFlow
Ahora, para asegurarnos de que podemos crear y trabajar con CoverFlow en nuestra clase de documento. De vuelta en CoverFlowTest.as, importe la clase CoverFlow. Después de las otras importaciones:
1 |
|
2 |
import com.tutsplus.coverflow.CoverFlow; |
Ahora necesitamos una propiedad para almacenar la instancia de CoverFlow. Antes del constructor:
1 |
|
2 |
private var coverFlow:CoverFlow; |
Y ahora para crear la instancia y agregarla a la lista de visualización. En el constructor, elimine la traza y reemplácela por:
1 |
|
2 |
coverFlow = new CoverFlow(); |
3 |
addChild(coverFlow); |
Pruébelo ahora, y ahora debería ver el trazo "CoverFlow" en el panel de Salida. Si es así, todo está bien. Ahora podemos comenzar a construir CoverFlow.
Aquí está el código de clase del documento completo, para referencia:
1 |
|
2 |
package { |
3 |
|
4 |
import flash.display.*; |
5 |
import flash.events.*; |
6 |
import flash.net.*; |
7 |
import flash.utils.*; |
8 |
|
9 |
import com.tutsplus.coverflow.CoverFlow; |
10 |
|
11 |
public class CoverFlowTest extends Sprite { |
12 |
|
13 |
private var coverFlow:CoverFlow; |
14 |
|
15 |
public function CoverFlowTest() { |
16 |
coverFlow = new CoverFlow(); |
17 |
addChild(coverFlow); |
18 |
}
|
19 |
|
20 |
}
|
21 |
|
22 |
}
|
Paso 6: crea una clase Cover
Una clase Cover será un elemento único en todo el "flujo". Comenzaremos por perfeccionar la clase en un solo elemento, luego nos preocuparemos de cargar los datos y generar el "flujo" una vez que se haya solucionado.
De nuevo, en su editor de texto, cree un nuevo archivo de clase. Guárdelo como Cover.as en la misma carpeta com / tutsplus / coverflow que la clase CoverFlow (a diferencia de la clase CoverFlowTest ... sí, tenemos algunos nombres potencialmente confusos. Trataré de ayudarte a mantener las cosas en orden a lo largo de este tutorial).



Agregue el siguiente texto repetitivo (ves un patrón?):
1 |
|
2 |
package com.tutsplus.coverflow { |
3 |
|
4 |
import flash.display.*; |
5 |
import flash.geom.*; |
6 |
import flash.events.*; |
7 |
import flash.net.*; |
8 |
import flash.utils.*; |
9 |
|
10 |
public class Cover extends Sprite { |
11 |
|
12 |
public function Cover() { |
13 |
trace("Cover"); |
14 |
}
|
15 |
|
16 |
}
|
17 |
|
18 |
}
|
Esto es en realidad idéntico a la repetición de CoverFlow, excepto por el uso de la palabra "Cover" en lugar de "CoverFlow". Esto terminará siendo bastante diferente, no te preocupes, solo me estoy asegurando de tener los archivos en su lugar antes de ir demasiado lejos en el agujero de conejo de codificación.
Paso 7: crea una instancia de portada
Entonces, haremos que el objeto CoverFlow cree y use un objeto Cover. Al final, CoverFlow será responsable de crear y administrar varios objetos de portada, pero por ahora, a medida que construimos la clase Cover, haremos que cree y muestre un solo objeto Cover.
En CoverFlow, en lugar de rastrear "CoverFlow", hagamos que haga esta creación de objetos.
1 |
|
2 |
public function CoverFlow() { |
3 |
var test:Cover = new Cover(); |
4 |
addChild(test); |
5 |
}
|
Si lo prueba ahora, debería ver que se trazó "Cover" (recuerde, ese es un único objeto de portada, no los rastros de prueba previos que hemos estado usando).



Sin embargo, demos un paso más y tratamos de mostrar algo en la pantalla. En el archivo Cover, elimine la traza y agregue esto:
1 |
|
2 |
public function Cover() { |
3 |
graphics.beginFill(0xff0000); |
4 |
graphics.drawRect(0, 0, 200, 200); |
5 |
}
|
Ahora, en lugar de buscar en el panel de Salida, debería ver aparecer un cuadrado rojo en la esquina superior izquierda de su película. Si eso sucede, estamos listos para seguir trabajando. Lo que esto significa es que no solo el código se está ejecutando hasta llegar al objeto de portada individual, sino que también hemos agregado elementos al escenario para que podamos crear objetos visuales.



Paso 8: poner cubiertas en un contenedor
Acabamos de agregar nuestro objeto Cover directamente a la lista de visualización de CoverFlow. Por razones que pueden no estar claras en este momento, eventualmente las necesitaremos en un contenedor adicional; es decir, CoverFlow contendrá un Sprite, que contendrá todas las instancias Cover. A medida que avanzamos en este tutorial, agregaremos otros objetos de visualización que no sean Cover, y será muy conveniente asegurarse de que todas las Covers se administren fácilmente.
Primero, en CoverFlow, declare una propiedad de Sprite que hará referencia a nuestro contenedor.
1 |
|
2 |
private var _coversContainer:Sprite; |
Y en el constructor, cree ese Sprite y agréguelo a la lista de visualización. Además, en lugar de agregar el objeto Cover de prueba a la lista de visualización de CoverFlow, agréguelo al _coversContainer
Sprite:
1 |
|
2 |
public function CoverFlow() { |
3 |
_coversContainer = new Sprite(); |
4 |
addChild(_coversContainer); |
5 |
var test:Cover = new Cover(); |
6 |
_coversContainer.addChild(test); |
7 |
}
|
Si prueba ahora, debería ver exactamente lo mismo que antes, lo cual es bueno. No queremos que se vea diferente, pero queremos una funcionalidad diferente bajo el capó.
Paso 9: establece el tamaño de CoverFlow
Hay algunas cosas de las que debemos ocuparnos ahora. El tamaño del contenedor que contiene la pantalla CoverFlow es uno de ellos, ya que el ancho y la altura se usarán en cualquier otro lugar. Esto será tan simple como unas pocas propiedades y setters y getters coincidentes.
Primero, en CoverFlow.as, agregue dos propiedades al comienzo de la clase:
1 |
|
2 |
private var _width:Number; |
3 |
private var _height:Number; |
Y después del constructor, agregue los siguientes setters y getters:
1 |
|
2 |
override public function set width(num:Number):void { |
3 |
_width = num; |
4 |
}
|
5 |
override public function get width():Number { |
6 |
return _width; |
7 |
}
|
8 |
override public function set height(num:Number):void { |
9 |
_height = num; |
10 |
}
|
11 |
override public function get height():Number { |
12 |
return _height; |
13 |
}
|
Como CoverFlow es una subclase de Sprite, ya hay definidores de ancho y alto y getters. Entonces, debemos asegurarnos de anularlos. No queremos el comportamiento predeterminado de estiramiento, por lo que querremos controlar eso por nuestra cuenta. Volveremos a estos setters en unos pocos pasos.
Sin embargo, tener un tamaño para el objeto CoverFlow es lo suficientemente importante como para requerir estos parámetros en el constructor. El ancho y la altura determinan gran parte del diseño final, por lo que agregaremos algunos parámetros al constructor y luego estableceremos de inmediato nuestras propiedades con ellos:
1 |
|
2 |
public function CoverFlow(w:Number, h:Number) { |
3 |
_width = w; |
4 |
_height = h; |
5 |
|
6 |
_coversContainer = new Sprite(); |
7 |
addChild(_coversContainer); |
8 |
var test:Cover = new Cover(); |
9 |
_coversContainer.addChild(test); |
10 |
}
|
Y, por supuesto, necesitamos proporcionar algunos argumentos a esto de CoverFlowTest. En ese archivo, actualice la línea que crea un nuevo CoverFlow a esto:
1 |
|
2 |
coverFlow = new CoverFlow(stage.stageWidth, stage.stageHeight); |
No hay mucho que probar ahora, pero si lo desea, puede publicar la película y ver si aparece algún error de compilación. Si hay algún error, cuídalos ahora. Sabrá que los errores se relacionan de alguna manera con el código que acaba de tipear, así que comience allí.
Paso 10: establece el color de fondo
Otra propiedad que se usará más tarde será el color de fondo de todo el "flujo". Apple hace el suyo negro, pero no hay razón para quedarse con eso, ya que será bastante simple cambiarlo. Para que esto suceda, sin embargo, necesitaremos un objeto Shape que se encuentra en la parte inferior de la pila de visualización del objeto CoverFlow, y tendremos que dibujar un rectángulo del color elegido en esa forma.
Primero, agregue dos propiedades a CoverFlow, una para contener la instancia de Shape y otra para almacenar el color de fondo:
1 |
|
2 |
private var _backgroundColor:uint = 0; |
3 |
private var _background:Shape; |
Tenga en cuenta que le damos a _backgroundColor
un valor predeterminado, de modo que si nunca lo establece el usuario de esta clase, de todos modos tendremos un fondo negro. El número 0
es el código de color para negro ...
A continuación, escriba en setter y getter para backgroundColor
(no necesitamos que Shape sea accesible fuera de esta clase, solo el color):
1 |
|
2 |
public function set backgroundColor(val:uint):void { |
3 |
_backgroundColor = val; |
4 |
drawBackground(); |
5 |
}
|
6 |
public function get backgroundColor():uint { |
7 |
return _backgroundColor; |
8 |
}
|
Notarás que estamos llamando a un método todavía inexistente llamado drawBackground
. Esto hará lo que dice en la etiqueta. Vamos a escribirlo ahora:
1 |
|
2 |
private function drawBackground():void { |
3 |
_background.graphics.clear(); |
4 |
_background.graphics.beginFill(_backgroundColor, 1); |
5 |
_background.graphics.drawRect(0, 0, _width, _height); |
6 |
}
|
Esto simplemente borra cualquier dibujo gráfico anterior en la forma de fondo, establece el color de relleno al valor actual de la propiedad y luego dibuja un rectángulo.
Finalmente, tenemos que configurar la forma inicialmente. En el constructor, agregue esto al final:
1 |
|
2 |
_background = new Shape(); |
3 |
addChildAt(_background, 0); |
4 |
drawBackground(); |
¡Y adelante, pruébalo! Debería ver un fondo negro detrás de nuestro cuadrado rojo.



Si lo desea, puede cambiar el tamaño de la ventana del reproductor, y verá que efectivamente tenemos un rectángulo negro sobre la base blanca de la película.
Como referencia, aquí está la clase completa de CoverFlow en este punto. Los cambios realizados en este paso están resaltados.
1 |
|
2 |
package com.tutsplus.coverflow { |
3 |
|
4 |
import flash.display.*; |
5 |
import flash.geom.*; |
6 |
import flash.events.*; |
7 |
import flash.net.*; |
8 |
import flash.text.*; |
9 |
import flash.utils.*; |
10 |
|
11 |
public class CoverFlow extends Sprite { |
12 |
|
13 |
private var _coversContainer:Sprite; |
14 |
private var _width:Number; |
15 |
private var _height:Number; |
16 |
private var _backgroundColor:uint = 0; |
17 |
private var _background:Shape; |
18 |
|
19 |
public function CoverFlow(w:Number, h:Number) { |
20 |
_width = w; |
21 |
_height = h; |
22 |
|
23 |
_coversContainer = new Sprite(); |
24 |
addChild(_coversContainer); |
25 |
var test:Cover = new Cover(); |
26 |
_coversContainer.addChild(test); |
27 |
|
28 |
_background = new Shape(); |
29 |
addChildAt(_background, 0); |
30 |
drawBackground(); |
31 |
}
|
32 |
|
33 |
override public function set width(num:Number):void { |
34 |
_width = num; |
35 |
}
|
36 |
override public function get width():Number { |
37 |
return _width; |
38 |
}
|
39 |
override public function set height(num:Number):void { |
40 |
_height = num; |
41 |
}
|
42 |
override public function get height():Number { |
43 |
return _height; |
44 |
}
|
45 |
|
46 |
public function set backgroundColor(val:uint):void { |
47 |
_backgroundColor = val; |
48 |
drawBackground(); |
49 |
}
|
50 |
public function get backgroundColor():uint { |
51 |
return _backgroundColor; |
52 |
}
|
53 |
|
54 |
private function drawBackground():void { |
55 |
_background.graphics.clear(); |
56 |
_background.graphics.beginFill(_backgroundColor, 1); |
57 |
_background.graphics.drawRect(0, 0, _width, _height); |
58 |
}
|
59 |
|
60 |
}
|
61 |
|
62 |
}
|
Paso 11: Crea la máscara
Es posible que hayamos creado una forma de fondo que responda al tamaño de CoverFlow, pero cualquier otra cosa que agreguemos al objeto, como Covers individuales, puede no respetar el tamaño deseado. Lo que necesitamos es una máscara para todo el objeto de visualización CoverFlow que se establece en el mismo tamaño.
Podríamos usar una vieja máscara normal para esta tarea, pero debido a que estamos esperando una máscara rectangular, tenemos un enfoque aún más fácil. La propiedad scrollRect
de DisplayObjects proporciona una funcionalidad similar a la de las máscaras, aunque existen diferencias. Una de las ventajas que tenemos con scrollRect
es una optimización del rendimiento. No sé detalles, pero al utilizar scrollRect le dice a Flash que represente solo los píxeles contenidos dentro del rectángulo, a diferencia de las máscaras regulares, que aún representan todos los píxeles involucrados en el contenido enmascarado.
Configurarlo es tan simple como esto, en el constructor de CoverFlow:
1 |
|
2 |
public function CoverFlow(w:Number, h:Number) { |
3 |
// ...
|
4 |
|
5 |
drawBackground(); |
6 |
|
7 |
scrollRect = new Rectangle(0, 0, _width, _height); |
8 |
|
9 |
// ...
|
No hay mucho que probar ahora, pero puedes compilar para asegurarte de que no cometiste ningún error tipográfico.
Paso 12: Ajuste el ancho y la altura
Ahora, necesitamos implementar nuestra propia lógica de tamaño. En los ajustadores de width
y height
de CoverFlow, agregue estas líneas:
1 |
|
2 |
override public function set width(num:Number):void { |
3 |
_width = num; |
4 |
_background.width = _width; |
5 |
scrollRect = new Rectangle(0, 0, _width, _height); |
6 |
}
|
7 |
override public function get width():Number { |
8 |
return _width; |
9 |
}
|
10 |
override public function set height(num:Number):void { |
11 |
_height = num; |
12 |
_background.height = _height; |
13 |
scrollRect = new Rectangle(0, 0, _width, _height); |
14 |
}
|
15 |
override public function get height():Number { |
16 |
return _height; |
17 |
}
|
Podemos probar esto hasta cierto punto añadiendo un cambio de tamaño al objeto coverFlow en CoverFlowTest:
1 |
|
2 |
public function CoverFlowTest() { |
3 |
coverFlow = new CoverFlow(stage.stageWidth, stage.stageHeight); |
4 |
addChild(coverFlow); |
5 |
coverFlow.width = stage.stageWidth / 2; |
6 |
coverFlow.height = stage.stageHeight / 3; |
7 |
}
|
Deberías ver lo que has estado viendo, solo enmascarado.



Es difícil decir que scrollRect
está funcionando en este momento, pero al menos espera resultados por ahora. Elimina las dos líneas que acabas de agregar; querremos un CoverFlow de tamaño completo para continuar con el desarrollo.
Paso 13: agregue propiedades para cubrir
Volveremos a CoverFlow.as en un futuro cercano, pero por ahora nos enfocaremos en hacer que una instancia individual de Cover sea más rápida.
Pensemos en lo que la Cover deberá hacer. Tendrá que cargar y mostrar una imagen. Tendrá que mostrar un título. Necesitará ser posicionado. También deberá mostrar un reflejo debajo de la imagen. Cuando lleguemos a los datos XML, haremos que cada tienda de portada sea el nodo XML relacionado con esa instancia, de modo que podamos almacenar información adicional relacionada con la Cover asociada. Y deberá enviar algunos eventos, para el progreso de la carga, la carga completa, seleccionar (para cuando la cubierta llegue a la posición central) y hacer clic. La Cover sin duda podría hacer más, pero por ahora, estas capacidades coinciden bastante con lo que iTunes implementa con Cover Flow, y ayudarán a mantener nuestro tutorial a un alcance razonable. Para el código, este conjunto de características significa:
Cargar imagen Necesitaremos un cargador para cargar la imagen, junto con la especificación de la URL de la imagen para cargar.
Mostrar imagen Tendremos que agregar el cargador a la lista de visualización.
Mostrar título Tendremos que poder establecer el título, colocarlo en un campo de texto y mostrar el campo de texto. Tenga en cuenta que esto abre la lata de gusanos que es el estilo de texto, y la cuestión de cuánto control del estilo para abrir fuera de la clase. Para nuestros propósitos, nos quedaremos con un estilo estándar. Si desea una implementación que permita estilos definibles por el usuario, es un ejercicio para más adelante.
Sin embargo, tenga en cuenta que en la implementación de referencia de Cover Flow, el título no está adjunto a la imagen de portada, es un área fija en el centro de la parte inferior de toda el área de visualización. Lo que necesitaremos no es un TextField para cada objeto Cover, sino un solo TextField para todo el sistema CoverFlow. Todo lo que el objeto Cover necesita hacer es almacenar su título. Tendremos CoverFlow y luego sacaremos esa información de cada portada a medida que esté enfocada y manejemos su pantalla. Entonces, todo lo que realmente necesitamos en este momento es un mecanismo de almacenamiento para un título.
Mostrar reflejo de imagen Esto requerirá un objeto de mapa de bits y cierta sofisticación de BitmapData, pero no es difícil. Sin embargo, requiere que conozcamos el color de fondo general de toda la pantalla de Cover Flow, porque la manera más fácil de manejar la transparencia de los reflejos es no ser transparente en realidad (si lo fueran, tendríamos reflejos superpuestos a través de cada otro). Por lo tanto, solicitaremos que el color de fondo se transfiera a nuestro constructor desde CoverFlow.
Además, querremos colgar en el mapa de bits, pero BitmapData puede ser un objeto de una sola vez usado para crear el reflejo en primer lugar.
Posicionamiento Dado que estamos subclasificando Sprite, todo lo que mostramos dentro de ese Sprite se posiciona automáticamente como una unidad por las propiedades de posición de Sprite. No tendremos que hacer nada para obtener esta funcionalidad, salvo asegurarnos de agregar los objetos de visualización apropiados como elementos secundarios de la instancia de Cover.
Datos XML Solo necesitaremos una propiedad para almacenar un nodo XML arbitrario y una manera de volver a salir del objeto.
Eventos De nuevo, dado que somos una subclase de Sprite, también tenemos automáticamente la capacidad de enviar eventos. De hecho, el evento click ya está definido por el Sprite. El progreso de carga y finalización solo se reenviará a eventos desde LoaderInfo del Loader que usamos para cargar la imagen. Y seleccionar realmente será manejado por CoverFlow, ya que sabe cómo administrar una colección de Covers. ¡Entonces, hemos terminado con esto también!
Agreguemos las propiedades que necesitamos, junto con los adaptadores y captadores cuando corresponda. Seguiremos la convención de hacer que las propiedades reales sean privadas, y proporcionar acceso público a través de instaladores y captadores.
En Cover.as, agregue algunas propiedades al archivo de clase. Me gusta mantenerlos agrupados, en la parte superior de la definición de la clase:
1 |
|
2 |
private var _src:String; |
3 |
private var _caption:String; |
4 |
private var _data:XML; |
5 |
private var _loader:Loader; |
6 |
private var _reflection:Bitmap; |
7 |
private var _backgroundColor:uint; |
Escribe los setters y getters:
1 |
|
2 |
public function get caption():String { |
3 |
return _caption; |
4 |
}
|
5 |
|
6 |
public function get data():XML { |
7 |
return _data; |
8 |
}
|
9 |
|
10 |
public function set backgroundColor(num:Number):void { |
11 |
_backgroundColor = num; |
12 |
}
|
13 |
public function get backgroundColor():Number { |
14 |
return _backgroundColor; |
15 |
}
|
¿Por qué solo tener estos? Bueno, otros objetos realmente no necesitarán acceso al Bitmap o al Loader, y para los subtítulos y los datos, operaremos bajo la suposición de que una vez que se establezcan esos valores, no necesitarán cambiar. Nos ocuparemos de eso en el siguiente paso.
Adelante, prueba esto. No habrá cambios para tomar nota; aún deberías ver un cuadrado rojo. Pero al probar la película, ejecutamos las cosas a través del compilador, lo que nos ayuda a encontrar errores en caso de que se introduzca alguno. Si todo fue bien, la película se ejecutará y verá el cuadrado rojo. Aquí está la clase Cover completa a partir de ahora:
1 |
|
2 |
package com.tutsplus.coverflow { |
3 |
|
4 |
import flash.display.*; |
5 |
import flash.geom.*; |
6 |
import flash.events.*; |
7 |
import flash.net.*; |
8 |
import flash.utils.*; |
9 |
|
10 |
public class Cover extends Sprite { |
11 |
|
12 |
private var _src:String; |
13 |
private var _caption:String; |
14 |
private var _data:XML; |
15 |
private var _loader:Loader; |
16 |
private var _reflection:Bitmap; |
17 |
private var _backgroundColor:uint; |
18 |
|
19 |
public function Cover() { |
20 |
graphics.beginFill(0xff0000); |
21 |
graphics.drawRect(0, 0, 200, 200); |
22 |
}
|
23 |
|
24 |
public function get caption():String { |
25 |
return _caption; |
26 |
}
|
27 |
|
28 |
public function get data():XML { |
29 |
return _data; |
30 |
}
|
31 |
|
32 |
public function set backgroundColor(num:Number):void { |
33 |
_backgroundColor = num; |
34 |
}
|
35 |
public function get backgroundColor():Number { |
36 |
return _backgroundColor; |
37 |
}
|
38 |
|
39 |
}
|
40 |
|
41 |
}
|
Paso 14: establece las propiedades
Como se mencionó, estableceremos las propiedades como leyenda, datos y color de fondo a través del constructor. Modifique el constructor de Cover.as para que tome esos tres valores y luego transfiera esos valores a las propiedades apropiadas. Los cambios al constructor a continuación están resaltados:
1 |
|
2 |
public function Cover(caption:String, data:XML, backgroundColor:Number) { |
3 |
_caption = caption; |
4 |
_data = data; |
5 |
_backgroundColor = backgroundColor; |
6 |
|
7 |
graphics.beginFill(0xff0000); |
8 |
graphics.drawRect(0, 0, 200, 200); |
9 |
}
|
Ahora, de nuevo en CoverFlow.as, necesitamos proporcionar valores cuando creamos nuestra prueba Cover o de lo contrario obtendremos errores. En el constructor:
1 |
|
2 |
public function CoverFlow(w:Number, h:Number) { |
3 |
_width = w; |
4 |
_height = h; |
5 |
|
6 |
_coversContainer = new Sprite(); |
7 |
addChild(_coversContainer); |
8 |
var test:Cover = new Cover("I am a caption", <data />, _backgroundColor); |
9 |
_coversContainer.addChild(test); |
10 |
|
11 |
_background = new Shape(); |
12 |
addChildAt(_background, 0); |
13 |
drawBackground(); |
14 |
|
15 |
scrollRect = new Rectangle(0, 0, _width, _height); |
16 |
}
|
Obviamente estamos usando datos ficticios en este momento, pero esto debería compilarse de manera segura. Nuevamente, no veremos ningún cambio, pero pruebe la película para asegurarse de que no ha introducido errores. Sin embargo, podemos escribir una prueba rápida de lo que escribimos al rastrear los valores de los captadores. Aún en el constructor CoverFlow:
1 |
|
2 |
public function CoverFlow(w:Number, h:Number) { |
3 |
_width = w; |
4 |
_height = h; |
5 |
|
6 |
_coversContainer = new Sprite(); |
7 |
addChild(_coversContainer); |
8 |
var test:Cover = new Cover("I am a caption", <data />, _backgroundColor); |
9 |
_coversContainer.addChild(test); |
10 |
|
11 |
_background = new Shape(); |
12 |
addChildAt(_background, 0); |
13 |
drawBackground(); |
14 |
|
15 |
scrollRect = new Rectangle(0, 0, _width, _height); |
16 |
|
17 |
trace(test.caption); |
18 |
trace(test.data.toXMLString()); |
19 |
trace(test.backgroundColor); |
20 |
}
|
Debería ver lo siguiente en su panel de Salida:



Esto verifica que estamos configurando correctamente y obteniendo al menos las propiedades caption, data XML y backgroundColor.
Paso 15: carga una imagen
Para este paso, necesitaremos una imagen para cargar. Hay varios en el paquete de descarga para este tutorial, ya recortados y clasificados como cuadrados. Vamos a elegir uno para cargar. Usaré "best.jpg".
Primero, coloque la carpeta de imágenes en la misma carpeta de proyecto que ha estado usando. Debería estar en el mismo lugar que su CoverFlowTest.swf. Deje caer la carpeta de imágenes de la descarga aquí, o cree su propia carpeta de imágenes aquí y coloque las imágenes que desea cargar en esa carpeta.



A continuación, en el constructor de Cover.as, elimine las líneas que dibujan el cuadrado rojo.
1 |
|
2 |
public function Cover(caption:String, data:XML, backgroundColor:Number) { |
3 |
_caption = caption; |
4 |
_data = data; |
5 |
_backgroundColor = backgroundColor; |
6 |
|
7 |
//graphics.beginFill(0xff0000);
|
8 |
//graphics.drawRect(0, 0, 200, 200);
|
9 |
}
|
Ahora, crea un método público llamado load. Recibirá un URL de cadena como parámetro, lo almacena en la propiedad _src
y luego carga la imagen desde esa URL.
1 |
|
2 |
public function load(src:String):void { |
3 |
_src = src; |
4 |
_loader = new Loader(); |
5 |
_loader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, onLoadProgress); |
6 |
_loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadComplete); |
7 |
_loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onLoadError); |
8 |
addChild(_loader); |
9 |
_loader.load(new URLRequest(_src)); |
10 |
}
|
Esto es material estándar de Loader. En última instancia, dependerá de otro objeto (como CoverFlow) alimentar este valor al Cover.
Hemos agregado tres oyentes de eventos, todos los precios estándar para la carga. Vamos a escribir algunas funciones de oyente de stub para que podamos probar. Agrega este código a tu clase. Estos son métodos nuevos, no código dentro de otro método:
1 |
|
2 |
private function onLoadProgress(e:ProgressEvent):void { |
3 |
trace("progress: " + e.bytesLoaded + " of " + e.bytesTotal); |
4 |
}
|
5 |
|
6 |
private function onLoadComplete(e:Event):void { |
7 |
trace("loaded"); |
8 |
}
|
9 |
|
10 |
private function onLoadError(e:IOErrorEvent):void { |
11 |
trace("error: " + e.text) |
12 |
}
|
Terminaremos haciendo algo más útil con estos, pero por ahora dispararemos un rastro solo para asegurarnos de que las funciones se llamen en respuesta a los eventos. Como referencia, toda la clase debería tener un aspecto similar (las adiciones de este paso están resaltadas):
1 |
|
2 |
package com.tutsplus.coverflow { |
3 |
|
4 |
import flash.display.*; |
5 |
import flash.geom.*; |
6 |
import flash.events.*; |
7 |
import flash.net.*; |
8 |
import flash.utils.*; |
9 |
|
10 |
public class Cover extends Sprite { |
11 |
|
12 |
private var _src:String; |
13 |
private var _caption:String; |
14 |
private var _data:XML; |
15 |
private var _loader:Loader; |
16 |
private var _reflection:Bitmap; |
17 |
private var _backgroundColor:uint; |
18 |
|
19 |
public function Cover(caption:String, data:XML, backgroundColor:Number) { |
20 |
_caption = caption; |
21 |
_data = data; |
22 |
_backgroundColor = backgroundColor; |
23 |
}
|
24 |
|
25 |
public function get caption():String { |
26 |
return _caption; |
27 |
}
|
28 |
|
29 |
public function get data():XML { |
30 |
return _data; |
31 |
}
|
32 |
|
33 |
public function set backgroundColor(num:Number):void { |
34 |
_backgroundColor = num; |
35 |
}
|
36 |
public function get backgroundColor():Number { |
37 |
return _backgroundColor; |
38 |
}
|
39 |
|
40 |
public function load(src:String):void { |
41 |
_src = src; |
42 |
_loader = new Loader(); |
43 |
_loader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, onLoadProgress); |
44 |
_loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadComplete); |
45 |
_loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onLoadError); |
46 |
addChild(_loader); |
47 |
_loader.load(new URLRequest(_src)); |
48 |
}
|
49 |
|
50 |
private function onLoadProgress(e:ProgressEvent):void { |
51 |
trace("progress: " + e.bytesLoaded + " of " + e.bytesTotal); |
52 |
}
|
53 |
|
54 |
private function onLoadComplete(e:Event):void { |
55 |
trace("loaded"); |
56 |
}
|
57 |
|
58 |
private function onLoadError(e:IOErrorEvent):void { |
59 |
trace("error: " + e.text) |
60 |
}
|
61 |
|
62 |
}
|
63 |
|
64 |
}
|
Ahora, para probar esto, tenemos que volver a CoverFlow.as y en algún punto del constructor, llamar al método load
y pasar una ruta válida a una imagen:
1 |
|
2 |
public function CoverFlow(w:Number, h:Number) { |
3 |
_width = w; |
4 |
_height = h; |
5 |
|
6 |
_coversContainer = new Sprite(); |
7 |
addChild(_coversContainer); |
8 |
var test:Cover = new Cover("I am a caption", <data />, _backgroundColor); |
9 |
_coversContainer.addChild(test); |
10 |
|
11 |
_background = new Shape(); |
12 |
addChildAt(_background, 0); |
13 |
drawBackground(); |
14 |
|
15 |
scrollRect = new Rectangle(0, 0, _width, _height); |
16 |
|
17 |
trace(test.caption); |
18 |
trace(test.data.toXMLString()); |
19 |
trace(test.backgroundColor); |
20 |
|
21 |
test.load("images/best.jpg"); |
22 |
}
|
Si prueba la película ahora, no solo verá una imagen en lugar del cuadrado rojo:



... pero también debería ver algunos rastros en el panel de Salida que confirman que el progreso y los eventos completos se dispararon (los eventos de progreso reales pueden variar):



Obviamente, la carga se completó porque apareció la imagen, pero es bueno asegurarse de que conectamos el evento correctamente. Si desea probar el evento de error, simplemente cambie el valor de la ruta pasada al constructor del Cover a algo que no funcionará, y debería ver un rastro diferente.
Paso 16: Centre la imagen
El movimiento dentro del "flujo" tiene dos requisitos. En primer lugar, los elementos deben girar alrededor de un eje vertical centrado horizontalmente en la imagen. En segundo lugar, la parte inferior de la imagen debe estar alineada con un "suelo" para que las imágenes de diferentes alturas estén todas "sentadas" en el mismo plano.
Para facilitar la rotación vertical, podemos centrar la imagen horizontalmente en relación con el Sprite que lo contiene. Del mismo modo, para facilitar la alineación del fondo, podemos colocar la imagen de modo que su borde inferior esté en el eje x del Sprite que lo contiene. Para lograr esto, necesitamos saber el tamaño de la imagen que acabamos de cargar. Para determinar eso, debemos asegurarnos de que la imagen esté cargada antes de intentar trabajar con su tamaño. Entonces, todo nuestro código de centrado debe estar en el controlador completo.
Agregue esto al método onLoadComplete
de Cover.as (y elimine el rastreo que está actualmente allí):
1 |
|
2 |
private function onLoadComplete(e:Event):void { |
3 |
_loader.x = -Math.round(_loader.width / 2); |
4 |
_loader.y = -_loader.height; |
5 |
}
|
Si prueba esto ahora, probablemente no verá ninguna imagen, porque el sprite Cover se posiciona en (0, 0) en el escenario, pero todo su contenido visible está por encima del punto de registro del sprite Cover. Por lo tanto, antes de realizar pruebas, agregue algunas líneas de código para colocar el objeto Cover en CoverFlow.as, justo después de crear el objeto Cover de prueba:
1 |
|
2 |
test.x = _width / 2; |
3 |
test.y = _height / 2; |
Pruébalo ahora, y deberías ver la imagen en algún lugar en el medio del escenario.
Si lo desea, puede volver atrás y volver a probar la configuración del ancho y la altura de CoverFlow (consulte el Paso 12) para ver si el scrollRect funciona para enmascarar el contenido de CoverFlow. Debería ver un objeto Cover parcial.
Paso 17: Refleja la imagen
Ahora entramos en cosas más desafiantes. Vamos a tomar la imagen que acabamos de cargar y crear una copia que se voltee verticalmente (es decir, se giró alrededor del borde inferior). Usaremos BitmapData para clonar el aspecto de la imagen. Al igual que antes, porque necesitamos la imagen real antes de que podamos hacer esto, iniciaremos el dibujo de la reflexión en el método onLoadComplete
:
1 |
|
2 |
private function onLoadComplete(e:Event):void { |
3 |
_loader.x = -Math.round(_loader.width / 2); |
4 |
_loader.y = -_loader.height; |
5 |
drawReflection(); |
6 |
}
|
7 |
|
8 |
private function drawReflection():void { |
9 |
var clone:BitmapData = new BitmapData(_loader.width, _loader.height, false, 0x000000); |
10 |
var flip:Matrix = new Matrix(); |
11 |
flip.scale(1, -1); |
12 |
flip.translate(0, _loader.height); |
13 |
clone.draw(_loader, flip); |
14 |
_reflection = new Bitmap(clone); |
15 |
addChild(_reflection); |
16 |
_reflection.x = _loader.x; |
17 |
}
|
Es un montón de código que puede no ser familiar para usted, según cuánto haya trabajado con BitmapData.
BitmapData, en primer lugar, es una clase que le permite trabajar con los píxeles de un mapa de bits. La primera línea crea una, especificando un ancho y alto, transparencia y un color de relleno predeterminado.
Lo siguiente que haremos es generalmente una operación directa, donde dibujamos los gráficos de otro objeto de visualización en una representación de mapa de bits renderizado. Sin embargo, no queremos un clon directo, queremos voltear la imagen. Podemos hacer eso pasando un objeto Matrix al segundo parámetro del método draw. Un objeto Matrix representa una transformación geométrica, bidimensional, que incluye escala, rotación y traducción (o posición). Entonces, antes de hacer la operación de dibujar, creamos un nuevo objeto Matrix. Luego usamos el método de la escala en la Matriz para voltearlo verticalmente (los dos argumentos son escala horizontal y escala vertical; 1 no es cambio y -1 es inversión). A continuación, como la operación de escala funciona de forma similar a cómo funcionan los objetos de visualización, de hecho escalamos la imagen para que esté "apuntando" en la otra dirección. Entonces tenemos que reposicionarlo para volver a colocarlo en el lienzo del mapa de bits. El método translate lo hace, reposicionando la imagen escalada por la altura de la imagen.
Con la imagen volteada representada en un objeto BitmapData, necesitamos mostrarla realmente en un objeto Bitmap. BitmapData es una representación pura de datos y no es, en sí misma, visualizable. Sin embargo, el objeto Bitmap se puede visualizar y, con bastante facilidad, toma un objeto BitmapData como parámetro para su constructor, por lo que creamos un mapa de bits, lo agregamos a la lista de visualización y finalmente lo posicionamos horizontalmente para que esté alineado con la imagen original.
Prueba esto, y deberías ver doble.



Las operaciones de BitmapData pueden ser confusas, así que si no están claras para usted en este punto, lo aliento a que busque más información al respecto. BitmapData abre algunas posibilidades muy interesantes, por lo que vale la pena aprender más sobre ello.
Paso 18: atenúa la reflexión
Ahora, para hacer que el reflejo se desvanezca, necesitamos atenuar ese color de fondo y mantener el reflejo opaco. Dará la apariencia de ser transparente, pero si lo mantenemos opaco, un reflejo puede superponerse sobre otro sin que se vea el más bajo. Continúa y mira la vista previa de nuevo si necesitas visualizar esto:



Agregaremos un montón de código de dibujo para dibujar Reflection (en Cover.as). Aquí está todo el método, con los cambios resaltados:
1 |
|
2 |
private function drawReflection():void { |
3 |
var clone:BitmapData = new BitmapData(_loader.width, _loader.height, false, 0x000000); |
4 |
var flip:Matrix = new Matrix(); |
5 |
flip.scale(1, -1); |
6 |
flip.translate(0, _loader.height); |
7 |
clone.draw(_loader, flip); |
8 |
_reflection = new Bitmap(clone); |
9 |
addChild(_reflection); |
10 |
_reflection.x = _loader.x; |
11 |
|
12 |
var gradient:Shape = new Shape(); |
13 |
var gradientMatrix:Matrix = new Matrix(); |
14 |
gradientMatrix.createGradientBox(_reflection.width, _reflection.height, Math.PI / 2); |
15 |
var gradientFill:GraphicsGradientFill = new GraphicsGradientFill(GradientType.LINEAR, [_backgroundColor, _backgroundColor], [.5, 1], [0, 255], gradientMatrix); |
16 |
var gradientRect:GraphicsPath = new GraphicsPath(); |
17 |
gradientRect.moveTo(0, 0); |
18 |
gradientRect.lineTo(_reflection.width, 0); |
19 |
gradientRect.lineTo(_reflection.width, _reflection.height); |
20 |
gradientRect.lineTo(0, _reflection.height); |
21 |
gradientRect.lineTo(0, 0); |
22 |
var graphicsData:Vector.<IGraphicsData> = new Vector.<IGraphicsData>(); |
23 |
graphicsData.push(gradientFill, gradientRect); |
24 |
gradient.graphics.drawGraphicsData(graphicsData); |
25 |
|
26 |
_reflection.bitmapData.draw(gradient); |
27 |
}
|
Sí, eso es un montón de código para masticar. En resumen, crea otro objeto Shape, en el que dibujamos un degradado lineal. El gradiente se establece para ser del mismo color que el fondo principal, con cambios alfa que van de la mitad de transparente a completamente opaco, de arriba a abajo. Este gradiente luego se fusiona en el BitmapData que ya contiene el reflejo volteado de la imagen, de modo que el efecto final es un reflejo que se desvanece al color de fondo.
Adelante, y prueba esto, debes sentirte bien al obtener estos resultados:



Sin embargo, si prueba más esto e intenta configurar la instancia de coverFlow en CoverFlowTest con un color de fondo que no sea negro, verá algunos resultados impredecibles:



Nos ocuparemos de esto a continuación.
Paso 19: realizar un seguimiento de las instancias de cobertura
Actualmente estamos utilizando la instancia de CoverFlow como un lugar para probar una sola instancia de portada. Y a pesar de que eventualmente nos desharemos de esa instancia de prueba, ahora debemos almacenarla en una lista oficial de todas las instancias. Crearemos una matriz de instancias de Cobertura, y todos los que obtengan creaciones quedarán escondidos en la Matriz. En realidad, dado que estamos apuntando a Flash 10, podemos convertirlo en un Vector, que ofrecerá un pequeño aumento en el rendimiento.
Primero, en CoverFlow.as, declare Vector, junto con el resto de las propiedades:
1 |
|
2 |
private var _covers:Vector.<Cover>; |
Luego, cree una instancia de ese Vector casi de inmediato. En el constructor, coloca esta línea justo después de configurar _width
y _height
:
1 |
|
2 |
_covers = new Vector.<Cover>(); |
Y después de haber creado el objeto de portada "prueba" (aún en el constructor), agréguelo al Vector:
1 |
|
2 |
_covers.push(test); |
La parte superior de su clase CoverFlow debe verse así (las adiciones resaltadas):
1 |
|
2 |
public class CoverFlow extends Sprite { |
3 |
|
4 |
private var _coversContainer:Sprite; |
5 |
private var _width:Number; |
6 |
private var _height:Number; |
7 |
private var _backgroundColor:uint = 0; |
8 |
private var _background:Shape; |
9 |
private var _covers:Vector.<Cover>; |
10 |
|
11 |
public function CoverFlow(w:Number, h:Number) { |
12 |
_width = w; |
13 |
_height = h; |
14 |
_covers = new Vector.<Cover>(); |
15 |
|
16 |
_coversContainer = new Sprite(); |
17 |
addChild(_coversContainer); |
18 |
var test:Cover = new Cover("I am a caption", <data />, _backgroundColor); |
19 |
_coversContainer.addChild(test); |
20 |
test.x = _width / 2; |
21 |
test.y = _height / 2; |
22 |
_covers.push(test); |
23 |
|
24 |
trace(test.caption); |
25 |
trace(test.data.toXMLString()); |
26 |
trace(test.backgroundColor); |
27 |
|
28 |
test.load("images/best.jpg"); |
29 |
|
30 |
_background = new Shape(); |
31 |
addChildAt(_background, 0); |
32 |
drawBackground(); |
33 |
}
|
34 |
// ...
|
Paso 20: establece el color de fondo de las cubiertas
Ahora, en el backgroundColor
setter, no solo necesitamos dibujar el fondo principal, sino que debemos informar a todas nuestras instancias de Cover que el color de fondo ha cambiado. El método completo se verá así (el nuevo código está resaltado):
1 |
|
2 |
public function set backgroundColor(val:uint):void { |
3 |
_backgroundColor = val; |
4 |
drawBackground(); |
5 |
for each (var cover:Cover in _covers) { |
6 |
cover.backgroundColor = val; |
7 |
}
|
8 |
}
|
Luego, en la clase Cover, actualice su setter de BackgroundColor
para que vuelva a dibujar el reflejo:
1 |
|
2 |
public function set backgroundColor(num:Number):void { |
3 |
_backgroundColor = num; |
4 |
drawReflection(); |
5 |
}
|
Por último, tenemos que hacer algunas comprobaciones de errores. En el caso de prueba que hemos configurado en este momento, el backgroundColor
se configura antes de que se cargue la imagen (y esta es una acción razonable, normalmente configurará CoverFlow y le dará un backgroundColor de inmediato, a medida que se carguen las imágenes). Debido a esto, el cargador tiene un ancho y una altura de 0, lo que hace que la primera línea de drawReflection produzca un error. Si configura el backgroundColor
del objeto CoverFlow en este momento y prueba la película, verá esto:



Esto es bastante fácil de manejar. Si el cargador tiene un ancho y / o alto de 0, podemos suponer con seguridad que el cargador no ha terminado de cargarse todavía. Entonces, las primeras líneas de drawReflection
pueden verificar esto:
1 |
|
2 |
private function drawReflection():void { |
3 |
if (_loader.width == 0 || _loader.height == 0) { |
4 |
return; |
5 |
}
|
6 |
var clone:BitmapData = new BitmapData(_loader.width, _loader.height, false, 0x000000); |
7 |
var flip:Matrix = new Matrix(); |
8 |
// ...
|
Simplemente salga del método, y todo estará bien. No se preocupe, también llamamos a drawReflection
desde onLoadComplete
, por lo tanto, en ese punto, la propiedad _backgroundColor
se establecerá con el valor correcto y el cargador se cargará para que podamos dibujar. En el caso de que deseemos cambiar el color de fondo después de que se carguen las imágenes, esto todavía funciona, porque el Loader tendrá dimensiones que no sean cero, y drawReflection
se ejecutará desde el backgroundColor
setter.
Pruébelo: vuelva a CoverFlow Test y configure el color de fondo de la instancia de CoverFlow:
1 |
|
2 |
public function CoverFlowTest() { |
3 |
coverFlow = new CoverFlow(stage.stageWidth, stage.stageHeight); |
4 |
addChild(coverFlow); |
5 |
coverFlow.backgroundColor = 0xC14216; |
6 |
}
|
Y tendrás una gloriosa cosa naranja pasando:



Siéntase libre de eliminar esa línea una vez que esté satisfecho de que el color de fondo y el reflejo funcionan (¿a qué no le gusta el naranja?).
Paso 21: Escribe un origen de datos XML
Pasaremos a proporcionar un conjunto real de datos para conducir esta pieza. Nuestra imagen de prueba puede desaparecer y comenzaremos a mostrar un conjunto de imágenes.
Supondremos que para proporcionar datos al objeto CoverFlow, lo más probable es que se proporcione como un archivo XML externo. Esto facilita los cambios en el contenido en vivo, pero también nos permite cargar más de un archivo XML para reutilizar el mismo módulo CoverFlow con contenido diferente en la misma película.
Antes de escribir el código ActionScript para manejar el XML, escribamos nuestro archivo XML. Cree un nuevo archivo de texto llamado coverFlowImages.xml en la misma carpeta que su archivo CoverFlow.fla. Habrá un nodo raíz, por supuesto, y dentro de eso, simplemente habrá una lista de nodos de imagen, uno para que cada elemento aparezca en el flujo. El formato general para un nodo de imagen se verá así:
1 |
|
2 |
<image src="path/to/image.jpg" title="A Titillating Title"> |
3 |
whatever data we want to associate with the image |
4 |
</image>
|
Se supone que la imagen tendrá una ruta de imagen, y probablemente un título. Sin embargo, es posible que deseemos asociar más datos con cada imagen, que podría ser solo texto simple, o incluso podríamos proporcionar una estructura XML anidada con datos complejos dentro de, por ejemplo:
1 |
|
2 |
<image src="people/DruKepple.jpg" title="Dru Kepple, ActionScript Implementor"> |
3 |
<name>
|
4 |
<first>Dru</first> |
5 |
<last>Kepple</last> |
6 |
</name>
|
7 |
<employment>
|
8 |
<company name="Summit Projects" url="http://www.summitprojects.com" /> |
9 |
<company name="Art Institute of Portland" url="http://www.aidepartments.com" /> |
10 |
</employment>
|
11 |
<websites>
|
12 |
<site url="summitprojectsflashblog.wordpress.com" /> |
13 |
<site url="www.thekeppleeffect.com" /> |
14 |
<site url="active.tutsplus.com/author/dru-kepple" /> |
15 |
<site url="www.linkedin.com/drukepple" /> |
16 |
</websites>
|
17 |
</image>
|
El punto aquí es que dentro del nodo de imagen, podemos poner lo que queramos (desde datos XML extensos y complejos hasta nada). Esta información estará disponible a través de la clase CoverFlow, como el nodo XML que es.
Aquí está nuestro documento final completo (utilicé otras imágenes y enlaces durante la construcción), una lista de imágenes y datos seleccionados de active.tutsplus.com:
1 |
|
2 |
<coverflow>
|
3 |
<image src="images/megaphone.jpg" title="HTML5, Flash and RIAs: 18 Industry Experts Have Their Say"> |
4 |
<link>https://active.tutsplus.com/articles/roundups/html5-and-flash-17-industry-experts-have-their-say/</link> |
5 |
</image>
|
6 |
<image src="images/magnifyer.jpg" title="Create an Impressive Magnifying Effect with ActionScript 3.0"> |
7 |
<link>https://active.tutsplus.com/tutorials/effects/create-an-impressive-magnifying-effect-with-actionscript-30/</link> |
8 |
</image>
|
9 |
<image src="images/as3101.jpg" title="AS3 101: Five Reasons to use Setters and Getters"> |
10 |
<link>https://active.tutsplus.com/tutorials/actionscript/as3-101-five-reasons-to-use-setters-and-getters/</link> |
11 |
</image>
|
12 |
<image src="images/montypython.jpg" title="10 Flash Things You Can’t Do With HTML5"> |
13 |
<link>https://active.tutsplus.com/articles/roundups/10-flash-things-you-can’t-do-with-html5/</link> |
14 |
</image>
|
15 |
<image src="images/bad.jpg" title="Blog Action Day: Clean up With a Beautiful Watery Game"> |
16 |
<link>https://active.tutsplus.com/tutorials/games/blog-action-day-clean-up-with-a-beautiful-watery-game/</link> |
17 |
</image>
|
18 |
<image src="images/hype.jpg" title="Create a Mesmeric Music Visualizer with HYPE"> |
19 |
<link>https://active.tutsplus.com/tutorials/effects/create-a-mesmeric-music-visualizer-with-hype/</link> |
20 |
</image>
|
21 |
<image src="images/max.jpg" title="Smart AS3 Video Loading with GreenSock LoaderMax – Free Active Premium!"> |
22 |
<link>https://active.tutsplus.com/tutorials/actionscript/smart-as3-video-loading-with-greensock-loadermax-free-active-premium/</link> |
23 |
</image>
|
24 |
<image src="images/50twitterers.jpg" title="50 More Flash Twitterers Worth Following"> |
25 |
<link>https://active.tutsplus.com/articles/roundups/50-more-flash-twitterers-worth-following/</link> |
26 |
</image>
|
27 |
<image src="images/open_mic_prefixes.jpg" title="Open Mike: Prefixes"> |
28 |
<link>https://active.tutsplus.com/articles/open-mike/open-mike-prefixes/</link> |
29 |
</image>
|
30 |
<image src="images/fullscreen_website.jpg" title="Create a Full Screen, Scalable Flash Website: Part 1"> |
31 |
<link>https://active.tutsplus.com/tutorials/web-design/create-a-full-screen-scalable-flash-website-part-1/</link> |
32 |
</image>
|
33 |
</coverflow>
|
Parece mucho, pero en realidad es solo una lista de nodos de imágenes con dos atributos cada uno, el src
y el title
, y un nodo < link>
dentro del nodo < image>
, que alberga la URL del artículo asociado con la imagen.
Paso 22: carga un archivo XML
Para cargar XML, necesitaremos una propiedad para almacenar el XML y una propiedad para que un URLLoader cargue el XML. Necesitaremos un método público para iniciar una carga con una URL de cadena, algunos manejadores de eventos internos para que el URLLoader maneje los eventos de carga XML. Los elementos de este paso son una tarifa bastante estándar para cargar XML, así que simplemente apilaré todo en un solo paso y no pasaré mucho tiempo explicando las cosas.
Comience agregando dos propiedades, una para el urlLoader y otra para el xml, en la parte superior de la clase CoverFlow, con el resto de las propiedades.
1 |
|
2 |
private var _urlLoader:URLLoader; |
3 |
private var _xml:XML; |
En el constructor de CoverFlow, cree y configure el URLLoader (en realidad no importa en qué parte del constructor, pero estoy optando por el final):
1 |
|
2 |
_urlLoader = new URLLoader(); |
3 |
_urlLoader.addEventListener(Event.COMPLETE, onXMLLoad); |
4 |
_urlLoader.addEventListener(IOErrorEvent.IO_ERROR, onXMLLoadError); |
Escriba las funciones de manejo de eventos de URLLoader, en algún lugar del cuerpo principal de su clase:
1 |
|
2 |
private function onXMLLoad(e:Event):void { |
3 |
_xml = new XML(_urlLoader.data); |
4 |
trace("XML Loaded:\n" + _xml); |
5 |
}
|
6 |
|
7 |
private function onXMLLoadError(e:IOErrorEvent):void { |
8 |
trace("There was an error loading the XML document: " + e.text); |
9 |
}
|
En este momento solo estamos rastreando cosas; nos aseguramos de convertir los datos de URLLoader a XML y luego escupirlos como están. También estamos solucionando un error en caso de que obtengamos una URL incorrecta para cargar. Podríamos hacer algo más elegante aquí, pero por ahora solo estamos evitando que el error detenga todo lo demás, mientras seguimos rastreando un mensaje.
Necesitamos un método de carga pública para iniciar la carga de XML. Podemos suponer que si ya hay algo cargado, primero debemos despejarlo y luego comenzar la nueva carga. Con ese fin, no solo iniciaremos la carga en el URLLoader, sino que también llamaremos a un método llamado clearContents para eliminar todo lo creado previamente en CoverFlow. Lo completaremos más tarde, pero lo planearemos y lo llamaremos, y crearemos un método vacío para albergarlo.
1 |
|
2 |
public function load(url:String):void { |
3 |
clearContents(); |
4 |
_urlLoader.load(new URLRequest(url)); |
5 |
}
|
6 |
|
7 |
private function clearContents():void { |
8 |
|
9 |
}
|
Por último, tenemos que actualizar nuestras pruebas. Necesitamos eliminar las líneas que crean una Cover de prueba del constructor de CoverFlow (las he comentado aquí para que pueda identificarlas, pero continúe y elimínelas). El próximo fragmento de código de esta función los eliminará):
1 |
|
2 |
public function CoverFlow(w:Number, h:Number) { |
3 |
_width = w; |
4 |
_height = h; |
5 |
_covers = new Vector.<Cover>(); |
6 |
|
7 |
_coversContainer = new Sprite(); |
8 |
addChild(_coversContainer); |
9 |
|
10 |
//var test:Cover = new Cover("I am a caption", <data />, _backgroundColor);
|
11 |
//_coversContainer.addChild(test);
|
12 |
//test.x = _width / 2;
|
13 |
//test.y = _height / 2;
|
14 |
//_covers.push(test);
|
15 |
|
16 |
//trace(test.caption);
|
17 |
//trace(test.data.toXMLString());
|
18 |
//trace(test.backgroundColor);
|
19 |
|
20 |
//test.load("images/best.jpg");
|
21 |
|
22 |
_background = new Shape(); |
23 |
addChildAt(_background, 0); |
24 |
drawBackground(); |
25 |
|
26 |
scrollRect = new Rectangle(0, 0, _width, _height); |
27 |
|
28 |
_urlLoader = new URLLoader(); |
29 |
_urlLoader.addEventListener(Event.COMPLETE, onXMLLoad); |
30 |
_urlLoader.addEventListener(IOErrorEvent.IO_ERROR, onXMLLoadError); |
31 |
}
|
Y luego tenemos que volver a CoverFlowTest.as y agregar una llamada para load
. Pase el archivo coverFlowImages.xml que creamos en el último paso.
1 |
|
2 |
public function CoverFlowTest() { |
3 |
coverFlow = new CoverFlow(stage.stageWidth, stage.stageHeight); |
4 |
addChild(coverFlow); |
5 |
coverFlow.backgroundColor = 0x000000; |
6 |
coverFlow.load("coverFlowImages.xml"); |
7 |
}
|
Debería ver nuestro documento XML trazado en el panel de Salida.
Paso 23: analizar el XML
A continuación, debemos analizar el XML y eventualmente hacer algo con los datos. Lo analizaremos en este paso y comenzaremos a hacer algo con él en el siguiente paso.
En el método onXMLLoad
de CoverFlow, elimine el rastreo y reemplácelo con un bucle XML básico. Por ahora, rastrearemos los valores para asegurarnos de que los estamos analizando correctamente.
1 |
|
2 |
private function onXMLLoad(e:Event):void { |
3 |
_xml = new XML(_urlLoader.data); |
4 |
|
5 |
var imageList:XMLList = _xml.image; |
6 |
var iLen:uint = imageList.length(); |
7 |
var imageNode:XML; |
8 |
for (var i:uint = 0; i < iLen; i++) { |
9 |
imageNode = imageList[i]; |
10 |
var src:String = imageNode.@src; |
11 |
var title:String = imageNode.@title; |
12 |
trace(src); |
13 |
trace(title); |
14 |
trace(""); |
15 |
}
|
16 |
}
|
No hay nada demasiado especial pasando. Solo estamos seleccionando todos los nodos de imagen, lo repetimos y luego extraemos los atributos de ellos. Ahora, haremos algo con ellos.
Paso 24: Creación de objetos de portada
Tendremos el potencial de cargar bastantes imágenes. Será mejor controlar la carga cargando de a uno por vez. Esto maximizará el ancho de banda para cada imagen, permitiendo que la primera imagen aparezca lo más pronto posible, de modo que haya algo que ver en la pantalla antes de que pase demasiado tiempo. Además, la mayoría de los navegadores tienden a reducir el número de solicitudes simultáneas de todos modos, por lo que, en lugar de dejar que el navegador lo controle, podemos controlarlo en Flash.
Para trabajar en esto, tendremos que cargar algunas imágenes en varios objetos Cover, así que comenzaremos creando los objetos Cover e iniciando la carga. Analizaremos el proceso en los próximos pasos.
En el método CoverFlow onXMLLoad
, eliminaremos los rastros y en su lugar crearemos objetos de portada:
1 |
|
2 |
private function onXMLLoad(e:Event):void { |
3 |
_xml = new XML(_urlLoader.data); |
4 |
|
5 |
var imageList:XMLList = _xml.image; |
6 |
var iLen:uint = imageList.length(); |
7 |
var imageNode:XML; |
8 |
var cover:Cover; |
9 |
for (var i:uint = 0; i < iLen; i++) { |
10 |
imageNode = imageList[i]; |
11 |
var src:String = imageNode.@src; |
12 |
var title:String = imageNode.@title; |
13 |
cover = new Cover(title, imageNode, _backgroundColor); |
14 |
_coversContainer.addChild(cover); |
15 |
cover.x = i * 200 + 100; |
16 |
cover.y = 250; |
17 |
cover.load(src); |
18 |
_covers.push(cover); |
19 |
}
|
20 |
}
|
Notarás que estamos haciendo lo que esperas: primero, creamos un nuevo objeto Cover, pasando los datos seleccionados del XML. Luego lo agregamos a la lista de visualización. Luego lo posicionamos; esto es temporal, pero nos permitirá ver resultados por ahora. El valor de x se basa en el número de iteraciones, de modo que obtenemos una ubicación de izquierda a derecha. Una vez más, revisaremos la lógica del diseño, esto es solo para ver algo en este momento.
Luego le decimos a la portada que cargue con el origen de la imagen que se encuentra en el XML, y finalmente almacenamos el objeto Cover en nuestro _covers
Vector.
Esta llamada a cargar también es temporal; en este momento solo nos estamos asegurando de que estamos creando con éxito objetos Cover a partir de los datos XML. Pasaremos a cargar imágenes secuencialmente a continuación.
Si publica ahora, debería ver algo al menos un poco interesante:



No hay 3D todavía, pero eso viene. ¡Se paciente!
Paso 25: carga de imágenes en secuencia
Vamos a cambiar la lógica de cargar cada cubierta de inmediato para cargarlas progresivamente. Esto implicará hacer un seguimiento de un contador que apunta al Cover que se está cargando actualmente, y una función que carga el siguiente Cover. Tenemos algunas paradas en el camino, así que síguelo.
Primero, en Cover, queremos asegurarnos de volver a enviar el evento COMPLETE
una vez que se cargue la imagen. En onLoadComplete
, agregue esta línea al final:
1 |
|
2 |
private function onLoadComplete(e:Event):void { |
3 |
_loader.x = -Math.round(_loader.width / 2); |
4 |
_loader.y = -_loader.height; |
5 |
drawReflection(); |
6 |
dispatchEvent(e); |
7 |
}
|
El resto de nuestros cambios ocurrirá en CoverFlow. Primero, agregue una propiedad para rastrear la carga actual por índice:
1 |
|
2 |
private var _loadCounter:uint; |
Luego, en onXMLLoad
, eliminaremos var src: String = ...
y cover.load (src)
líneas, y establecer la propiedad _loadCounter
en 0, así como llamar a un método que aún no hemos escrito:
1 |
|
2 |
private function onXMLLoad(e:Event):void { |
3 |
_xml = new XML(_urlLoader.data); |
4 |
|
5 |
var imageList:XMLList = _xml.image; |
6 |
var iLen:uint = imageList.length(); |
7 |
var imageNode:XML; |
8 |
var cover:Cover; |
9 |
for (var i:uint = 0; i < iLen; i++) { |
10 |
imageNode = imageList[i]; |
11 |
//var src:String = imageNode.@src;
|
12 |
var title:String = imageNode.@title; |
13 |
cover = new Cover(title, imageNode, _backgroundColor); |
14 |
_coversContainer.addChild(cover); |
15 |
cover.x = i * 200 + 100; |
16 |
cover.y = 250; |
17 |
//cover.load(src);
|
18 |
_covers.push(cover); |
19 |
}
|
20 |
_loadCounter = 0; |
21 |
loadNextCover(); |
22 |
}
|
Ahora vamos a escribir ese método loadNextCover
. La idea es tomar el valor actual de _loadCounter
y usarlo para apuntar a una portada y una imagen de origen.
1 |
|
2 |
private function loadNextCover():void { |
3 |
var cover:Cover = _covers[_loadCounter]; |
4 |
var src:String = _xml.image[_loadCounter].@src; |
5 |
cover.load(src); |
6 |
cover.addEventListener(Event.COMPLETE, onCoverLoad); |
7 |
}
|
Primero, obtenemos el objeto de portada creado previamente del _covers
Vector. Luego obtenemos la URL de la imagen correspondiente volviendo a los datos XML y encontrando el nodo de imagen usando el mismo valor de _loadCounter
. Luego simplemente le pedimos a la cubierta que cargue esa URL. Finalmente, agregamos un oyente COMPLETE
, que debemos escribir a continuación:
1 |
|
2 |
private function onCoverLoad(e:Event):void { |
3 |
e.target.removeEventListener(Event.COMPLETE, onCoverLoad); |
4 |
_loadCounter++; |
5 |
if (_loadCounter < _covers.length) { |
6 |
loadNextCover(); |
7 |
} else { |
8 |
dispatchEvent(new Event(Event.COMPLETE)); |
9 |
}
|
10 |
}
|
Cada vez que una cubierta termina de cargarse, primero limpiemos y eliminemos el detector de eventos COMPLETE
. Luego incrementará _loadCounter
y luego verificará si todavía podemos tener Cover en el _covers Vector. Si lo hacemos, llamamos a loadNextCover ()
de nuevo, lo que vuelve a iniciar el proceso con una nueva portada e imagen. Si no, entonces debemos estar al final, para poder enviar un evento COMPLETE
.
Para probar este evento, regrese a CoverFlowTest y agregue el siguiente código:
1 |
|
2 |
public function CoverFlowTest() { |
3 |
coverFlow = new CoverFlow(stage.stageWidth, stage.stageHeight); |
4 |
addChild(coverFlow); |
5 |
coverFlow.backgroundColor = 0x000000; |
6 |
coverFlow.load("coverFlowImages.xml"); |
7 |
coverFlow.addEventListener(Event.COMPLETE, onCoverFlowLoaded); |
8 |
}
|
9 |
|
10 |
private function onCoverFlowLoaded(e:Event):void { |
11 |
trace("Coverflow loaded and ready to go."); |
12 |
}
|
Esto debería ser sencillo: simplemente estamos agregando un oyente a ese evento COMPLETE
que solo rastrea. Adelante, ejecuta la película ahora. Debería ver las imágenes aparecer secuencialmente y, una vez que todas las imágenes estén cargadas, debería ver el "Coverflow cargado y listo para funcionar". mensaje en el panel de Salida.



Paso 26: determinando el progreso
Para poder enviar eventos PROGRESS
a medida que se cargan las imágenes, vamos a fingir un poco las cosas. Vamos a suponer que la primera imagen para cargar es, en promedio, representativa de todas las imágenes. Ciertamente habrá casos en que esto no sea cierto, pero para la mayoría de las aplicaciones trataremos con imágenes de dimensiones, calidad y contenido similares, y por lo tanto, la mayoría de las imágenes tendrán un tamaño de archivo similar.
Primero, necesitamos cambiar el método onLoadProgress
en Cover para que podamos deshacernos de la traza y volver a enviar el evento PROGRESS
:
1 |
|
2 |
private function onLoadProgress(e:ProgressEvent):void { |
3 |
dispatchEvent(e); |
4 |
}
|
Entonces necesitaremos dos propiedades más en CoverFlow para ayudar a rastrear el progreso general:
1 |
|
2 |
private var _bytesPerImage:int; |
3 |
private var _bytesTotal:int; |
Ahora, lo siguiente es obtener el tamaño de la primera imagen una vez que comience a cargarse. Necesitamos asegurarnos de que estamos agregando un oyente de eventos PROGRESS
cuando comenzamos la carga. En el método loadNextCover
de CoverFlow:
1 |
|
2 |
private function loadNextCover():void { |
3 |
var cover:Cover = _covers[_loadCounter]; |
4 |
var src:String = _xml.image[_loadCounter].@src; |
5 |
cover.load(src); |
6 |
cover.addEventListener(Event.COMPLETE, onCoverLoad); |
7 |
cover.addEventListener(ProgressEvent.PROGRESS, onCoverProgress); |
8 |
}
|
Y agregue el método onCoverProgress
:
1 |
|
2 |
private function onCoverProgress(e:ProgressEvent):void { |
3 |
if (_bytesPerImage == 0) { |
4 |
_bytesPerImage = e.bytesTotal; |
5 |
_bytesTotal = _bytesPerImage * _covers.length; |
6 |
}
|
7 |
var adjustedBytesLoaded:uint = e.bytesLoaded * (_bytesPerImage / e.bytesTotal); |
8 |
var cumulativeBytesLoaded:uint = (_loadCounter * _bytesPerImage) + adjustedBytesLoaded; |
9 |
dispatchEvent(new ProgressEvent(ProgressEvent.PROGRESS, false, false, cumulativeBytesLoaded, _bytesTotal)); |
10 |
}
|
Ahora, para probarlo, regrese a CoverFlowTest y agregue un oyente para el evento PROGRESS
:
1 |
|
2 |
public function CoverFlowTest() { |
3 |
coverFlow = new CoverFlow(stage.stageWidth, stage.stageHeight); |
4 |
addChild(coverFlow); |
5 |
coverFlow.backgroundColor = 0x000000; |
6 |
coverFlow.load("coverFlowImages.xml"); |
7 |
coverFlow.addEventListener(Event.COMPLETE, onCoverFlowLoaded); |
8 |
coverFlow.addEventListener(ProgressEvent.PROGRESS, onCoverFlowProgress); |
9 |
}
|
10 |
|
11 |
private function onCoverFlowProgress(e:ProgressEvent):void { |
12 |
trace("Coverflow progress: " + e.bytesLoaded + " / " + e.bytesTotal); |
13 |
}
|
Y prueba la película. Debería ver una larga cadena de trazas de progreso, que termina en el mensaje "cargado", algo como lo siguiente:



Por supuesto, querrás mostrar algo informativo en el escenario, no trazar números. Sin embargo, esta tarea particular no corresponde realmente a CoverFlow; es suficiente que esté enviando los eventos apropiados. No profundizaré en la creación de una barra de progreso, y en su lugar, me referiré al trabajo más interesante de mostrar Covers en 3D.
Paso 27: Refactorizar la lógica
Veremos ese bucle que analiza el XML, crea los objetos Cover y los establece. Vamos a descargar la lógica de diseño a otro método. Elimine las líneas en el bucle que colocan el objeto Cover (es posible que desee copiarlas) y agregue una llamada a un método pendiente de escritura después del bucle.
1 |
|
2 |
private function onXMLLoad(e:Event):void { |
3 |
_xml = new XML(_urlLoader.data); |
4 |
|
5 |
var imageList:XMLList = _xml.image; |
6 |
var iLen:uint = imageList.length(); |
7 |
var imageNode:XML; |
8 |
var cover:Cover; |
9 |
for (var i:uint = 0; i < iLen; i++) { |
10 |
imageNode = imageList[i]; |
11 |
var src:String = imageNode.@src; |
12 |
var title:String = imageNode.@title; |
13 |
cover = new Cover(title, imageNode, _backgroundColor); |
14 |
_coversContainer.addChild(cover); |
15 |
//cover.x = i * 200 + 100;
|
16 |
//cover.y = 250;
|
17 |
_covers.push(cover); |
18 |
}
|
19 |
layout(); |
20 |
_loadCounter = 0; |
21 |
loadNextCover(); |
22 |
}
|
Luego, escribe ese método de layout
. Es otro ciclo, este sobre el vector _covers
, e internamente hace la misma lógica de posicionamiento:
1 |
|
2 |
private function layout():void { |
3 |
var len:uint = _covers.length; |
4 |
var cover:Cover; |
5 |
for (var i:uint = 0; i < len; i++) { |
6 |
cover = _covers[i]; |
7 |
cover.x = i * 200 + 100; |
8 |
cover.y = 250; |
9 |
}
|
10 |
}
|
Adelante, pruébalo ahora; no deberías ver absolutamente ningún cambio. La única diferencia es la lógica puramente detrás de escena. La ventaja que hemos agregado es que podremos llamar a layout () en cualquier momento, independientemente de onXMLLoad ().
Paso 28: Diseño inicial
Vamos por una gran recompensa en este momento. Reescribiremos la lógica del layout
para que eso comience a suceder en 3D. Retire la línea cover.x = ...
y agregue las líneas resaltadas a continuación (aún en CoverFlow):
1 |
|
2 |
private function layout():void { |
3 |
var len:uint = _covers.length; |
4 |
var cover:Cover; |
5 |
for (var i:uint = 0; i < len; i++) { |
6 |
cover = _covers[i]; |
7 |
if (i == 0) { |
8 |
cover.rotationY = 0; |
9 |
cover.x = _background.width / 2; |
10 |
cover.z = 0; |
11 |
} else { |
12 |
cover.rotationY = 45; |
13 |
cover.x = ((_background.width / 2) + 60) + (i * 30); |
14 |
cover.z = 150; |
15 |
}
|
16 |
cover.y = 250; |
17 |
}
|
18 |
}
|
Esta lógica necesita mucho amor, pero sigue adelante y hazlo ahora, y deberías ver ... algo que no está bien.



Debería ver el potencial allí, pero obviamente necesitamos gestionar la profundidad. Esto es una desventaja al uso de las capacidades 3D integradas de Flash: representa cada objeto DisplayObject correctamente, pero no una "escena" de múltiples DisplayObjects. Se aplican las reglas normales de apilamiento de profundidad 2D, incluso si la "z" de un objeto dado debe colocarlo de otro modo.
Sin embargo, este poco de lógica le da una idea de lo que vamos a hacer: Primero, determine en qué parte del "flujo" se encuentra una Cubierta determinada: centrada, a la derecha o (eventualmente) a la izquierda. Luego configure apropiadamente las propiedades x, zy rotationY de la Portada. La matemática involucrada en la posición x del lado derecho es un bocado, pero se puede leer así:
Comience en el centro (_background.width / 2
), mueva 60 píxeles hacia la derecha para dejar espacio alrededor del elemento centrado (... + 60
), luego, dependiendo de dónde estemos en el bucle, mueva la tapa más hacia la derecha (… + (i + 30)
)
Paso 29: Administrar la profundidad
Vamos a abordar el problema de profundidad. Esto es lo que debe suceder: el artículo centrado debe ser el que más adelante. Después de eso, los elementos de cada lado deben disminuir en el índice de profundidad cuanto más lejos estén del centro. Afortunadamente, esto es relativamente simple de lograr. Ajuste la lógica en el método de layout
de CoverFlow:
1 |
|
2 |
private function layout():void { |
3 |
var len:uint = _covers.length; |
4 |
var cover:Cover; |
5 |
for (var i:uint = 0; i < len; i++) { |
6 |
cover = _covers[i]; |
7 |
if (i == 0) { |
8 |
cover.rotationY = 0; |
9 |
cover.x = _background.width / 2; |
10 |
cover.z = 0; |
11 |
_coversContainer.setChildIndex(cover, _coversContainer.numChildren-1); |
12 |
} else { |
13 |
cover.rotationY = 45; |
14 |
cover.x = ((_background.width / 2) + 60) + (i * 30); |
15 |
cover.z = 150; |
16 |
_coversContainer.setChildIndex(cover, _coversContainer.numChildren - (i + 1)); |
17 |
}
|
18 |
cover.y = 250; |
19 |
}
|
20 |
}
|
En el primer bloque if, estamos en la portada centrada, por lo que establecemos el índice de la portada en el índice más alto disponible. En el bloque else, cuanto mayor es i
, menor es el número de índice que obtenemos. Eso funciona en relación con otros Covers en el lado derecho, pero tenemos que bajar el índice una más para acomodar el Cover central (más alto).
Pruébelo ahora ... ¡debería ver algunas imágenes prometedoras!



Paso 30: el lado izquierdo
OK, hasta ahora hemos estado haciendo suposiciones que hacen que sea muy difícil establecer las cosas de verdad. La gran suposición es que estamos centrando la primera Cobertura (índice 0). Si intentas centrar, digamos, el cuarto Cover, obtendrás resultados que no se esperan del todo:



No se ve mal a primera vista, pero piénselo. Si centramos la cuarta Cobertura, esperaríamos ver tres Cubiertas a la izquierda, la cuarta en el centro y el resto a la derecha. Hagamos que eso suceda.
Primero, creemos una propiedad nueva (aún en CoverFlow) que contendrá nuestro valor de índice actual. Llamémoslo _selectedIndex
.
1 |
|
2 |
private var _selectedIndex:uint; |
Y, solo para fines de prueba, estableceremos ese valor en 4 en la parte superior del método de diseño. Esto se eliminará en el siguiente paso. Luego, volveremos a trabajar la lógica dentro del ciclo de diseño para ser un poco más dinámico.
1 |
|
2 |
private function layout():void { |
3 |
_selectedIndex = 4; |
4 |
var len:uint = _covers.length; |
5 |
var cover:Cover; |
6 |
var distanceFromCenter:uint; |
7 |
for (var i:uint = 0; i < len; i++) { |
8 |
cover = _covers[i]; |
9 |
if (i == _selectedIndex) { |
10 |
cover.rotationY = 0; |
11 |
cover.x = _background.width / 2; |
12 |
cover.z = 0; |
13 |
_coversContainer.setChildIndex(cover, _coversContainer.numChildren-1); |
14 |
} else if (i < _selectedIndex) { |
15 |
distanceFromCenter = _selectedIndex - i; |
16 |
cover.rotationY = -45; |
17 |
cover.x = ((_background.width / 2) - 60) - (distanceFromCenter * 30); |
18 |
cover.z = 150; |
19 |
_coversContainer.setChildIndex(cover, _coversContainer.numChildren - (distanceFromCenter + 1)); |
20 |
} else if (i > _selectedIndex) { |
21 |
distanceFromCenter = i - _selectedIndex; |
22 |
cover.rotationY = 45; |
23 |
cover.x = ((_background.width / 2) + 60) + (distanceFromCenter * 30); |
24 |
cover.z = 150; |
25 |
_coversContainer.setChildIndex(cover, _coversContainer.numChildren - (distanceFromCenter + 1)); |
26 |
}
|
27 |
cover.y = 250; |
28 |
}
|
29 |
}
|
Es mucha lógica y matemática, pero en realidad es bastante repetitiva. El nuevo bloque está en el medio, pero es casi idéntico al otro bloque else if
, solo unas pocas cosas se invierten. Pero adelante y prueba la película, debería verse más o menos así:



El gran cambio fue el alejamiento de i
como un factor directo en la colocación de Covers, pero en cambio determina la distancia de i
del índice actual. Cuanto más lejos esté, más lejos estará la x
del centro, y también la más atrás en profundidad tiene que ser. La matemática es solo una traducción de lo que estábamos haciendo antes a un enfoque más dinámico. Si lo desea, siga adelante e intente configurar _selectedIndex
en otros valores, y asegúrese de obtener los resultados que espera.
Paso 31: Personalizando la Apariencia
Puede sentir que la distribución de Covers es un poco pequeña (sé que lo hago, pero tengo que trabajar dentro del límite de 600 píxeles que Activetuts + pone en mí). De hecho, hay bastantes parámetros de posicionamiento que podrían ajustarse para ajustar el aspecto general. Vamos a seguir adelante y crear un conjunto de propiedades, junto con los adaptadores y ejecutores asociados, para manejar esta personalización. Y por supuesto usaremos esas propiedades en lugar de números codificados en nuestro método de diseño.
Este será un paso largo, pero no temas, es bastante básico.
Primero, las propiedades:
1 |
|
2 |
private var _centerMargin:Number; |
3 |
private var _horizontalSpacing:Number; |
4 |
private var _backRowDepth:Number; |
5 |
private var _backRowAngle:Number; |
6 |
private var _verticalOffset:Number; |
Estas propiedades controlarán lo siguiente:
- centerMargin controla la cantidad de espacio en cada lado de la cubierta central y las primeras cubiertas laterales. Debe ser un valor positivo.
- horizontalSpacing controla la cantidad de espacio entre las Cubiertas en la "parte posterior". Es decir, toda el cover a la izquierda y a la derecha (pero sin contar el centro) tendrá la misma cantidad de espacio entre los covers adyacentes. la cantidad de espacio entre los Covers en la "parte posterior". Debe ser un valor positivo.
- backRowDepth controla qué tan atrás está la última fila. Esto será en forma de un desplazamiento, a fin de evitar que la fila de atrás se encuentre frente al Cover central. Debe ser un valor positivo.
- backRowAngle controla el ángulo al que girarán los Covers en la última fila. Esto es una especie de valor absoluto, y el valor es "reflejado" para el otro lado. Debe estar limitado entre 0 y 90.
- verticalOffset controla la cantidad por la cual mover todo el conjunto de Covers hacia arriba o hacia abajo, compensar el valor y determinado automáticamente. Este puede ser cualquier número. Volveremos a este poco de lógica, pero también podemos escribir la propiedad, el setter y el getter mientras hacemos los otros cuatro.
Configuraremos constantes para contener los valores predeterminados de cada una de estas propiedades. Estos pueden ir con el resto de sus propiedades:
1 |
|
2 |
private static const DEFAULT_CENTER_MARGIN:Number = 60; |
3 |
private static const DEFAULT_HORIZONTAL_SPACING:Number = 30; |
4 |
private static const DEFAULT_BACK_ROW_DEPTH:Number = 150; |
5 |
private static const DEFAULT_BACK_ROW_ANGLE:Number = 45; |
6 |
private static const DEFAULT_VERTICAL_OFFSET:Number = 0; |
En el constructor, establezca cada una de las propiedades a estos valores predeterminados (si no le gustan los valores predeterminados, puede cambiarlos, esa es parte de la razón para juntarlos en un lugar fácil de encontrar). De esta forma, nos aseguraremos de que cada una de estas variables se configure con un valor adecuado, incluso si el usuario nunca especificó una.
1 |
|
2 |
public function CoverFlow(w:Number, h:Number) { |
3 |
_width = w; |
4 |
_height = h; |
5 |
_covers = new Vector.<Cover>(); |
6 |
|
7 |
_centerMargin = DEFAULT_CENTER_MARGIN; |
8 |
_horizontalSpacing = DEFAULT_HORIZONTAL_SPACING; |
9 |
_backRowDepth = DEFAULT_BACK_ROW_DEPTH; |
10 |
_backRowAngle = DEFAULT_BACK_ROW_ANGLE; |
11 |
_verticalOffset = DEFAULT_VERTICAL_OFFSET; |
12 |
|
13 |
_coversContainer = new Sprite(); |
14 |
addChild(_coversContainer); |
15 |
|
16 |
_background = new Shape(); |
17 |
addChildAt(_background, 0); |
18 |
drawBackground(); |
19 |
|
20 |
scrollRect = new Rectangle(0, 0, _width, _height); |
21 |
|
22 |
_urlLoader = new URLLoader(); |
23 |
_urlLoader.addEventListener(Event.COMPLETE, onXMLLoad); |
24 |
_urlLoader.addEventListener(IOErrorEvent.IO_ERROR, onXMLLoadError); |
25 |
}
|
Y, con los valores válidos en mente, escriba los setters y getters (voy a poner estos correctos después de nuestros setters y getters actuales, alrededor de la línea 89):
1 |
|
2 |
public function set centerMargin(num:Number):void { |
3 |
if (isNaN(num)) num = DEFAULT_CENTER_MARGIN; |
4 |
_centerMargin = Math.max(0, num); |
5 |
}
|
6 |
public function get centerMargin():Number { |
7 |
return _centerMargin; |
8 |
}
|
9 |
|
10 |
public function set horizontalSpacing(num:Number):void { |
11 |
if (isNaN(num)) num = DEFAULT_HORIZONTAL_SPACING; |
12 |
_horizontalSpacing = Math.max(0, num); |
13 |
}
|
14 |
public function get horizontalSpacing():Number { |
15 |
return _horizontalSpacing; |
16 |
}
|
17 |
|
18 |
public function set backRowDepth(num:Number):void { |
19 |
if (isNaN(num)) num = DEFAULT_BACK_ROW_DEPTH; |
20 |
_backRowDepth = Math.max(0, num); |
21 |
}
|
22 |
public function get backRowDepth():Number { |
23 |
return _backRowDepth; |
24 |
}
|
25 |
|
26 |
public function set backRowAngle(num:Number):void { |
27 |
if (isNaN(num)) num = DEFAULT_BACK_ROW_ANGLE; |
28 |
_backRowAngle = Math.min(90, Math.abs(num)); |
29 |
}
|
30 |
public function get backRowAngle():Number { |
31 |
return _backRowAngle; |
32 |
}
|
33 |
|
34 |
public function set verticalOffset(num:Number):void { |
35 |
if (isNaN(num)) num = DEFAULT_VERTICAL_OFFSET; |
36 |
_verticalOffset = num; |
37 |
}
|
38 |
public function get verticalOffset():Number { |
39 |
return _verticalOffset; |
40 |
}
|
Y, por último, necesitamos reemplazar los números integrados en nuestra lógica de diseño con estas propiedades. En layout
:
1 |
|
2 |
for (var i:uint = 0; i < len; i++) { |
3 |
cover = _covers[i]; |
4 |
if (i == _selectedIndex) { |
5 |
cover.rotationY = 0; |
6 |
cover.x = _background.width / 2; |
7 |
cover.z = 0; |
8 |
_coversContainer.setChildIndex(cover, _coversContainer.numChildren-1); |
9 |
} else if (i < _selectedIndex) { |
10 |
distanceFromCenter = _selectedIndex - i; |
11 |
cover.rotationY = -_backRowAngle; |
12 |
cover.x = ((_background.width / 2) - _centerMargin) - (distanceFromCenter * _horizontalSpacing); |
13 |
cover.z = _backRowDepth; |
14 |
_coversContainer.setChildIndex(cover, _coversContainer.numChildren - (distanceFromCenter + 1)); |
15 |
} else if (i > _selectedIndex) { |
16 |
distanceFromCenter = i - _selectedIndex; |
17 |
cover.rotationY = _backRowAngle; |
18 |
cover.x = ((_background.width / 2) + _centerMargin) + (distanceFromCenter * _horizontalSpacing); |
19 |
cover.z = _backRowDepth; |
20 |
_coversContainer.setChildIndex(cover, _coversContainer.numChildren - (distanceFromCenter + 1)); |
21 |
}
|
22 |
cover.y = 250; |
23 |
}
|
Puedes probar la película tal como está, y las cosas deberían funcionar exactamente como antes. Sin embargo, también puede probar estas propiedades configurándolas desde CoverFlowTest. Configúrelos como desee (de hecho, asegúrese de probar los valores ilegales, como -200 para centerMargin), por ejemplo:
1 |
|
2 |
public function CoverFlowTest() { |
3 |
coverFlow = new CoverFlow(stage.stageWidth, stage.stageHeight); |
4 |
addChild(coverFlow); |
5 |
coverFlow.backgroundColor = 0x000000; |
6 |
coverFlow.horizontalSpacing = 60; |
7 |
coverFlow.centerMargin = 100; |
8 |
coverFlow.load("coverFlowImages.xml"); |
9 |
coverFlow.addEventListener(Event.COMPLETE, onCoverFlowLoaded); |
10 |
coverFlow.addEventListener(ProgressEvent.PROGRESS, onCoverFlowProgress); |
11 |
}
|



Paso 32: diseño eficiente
En este momento, nuestro CoverFlow funciona y es algo personalizable, pero solo si establecemos los valores antes de que se cargue el archivo XML. Como está ahora, establecer las propiedades en cualquier punto después de eso dará como resultado ningún cambio.
Esto podría ser tan fácil como simplemente llamar a layout ()
desde cada uno de los setters. Sin embargo, esto es propenso a un uso ineficiente de los ciclos de CPU. Si necesita establecer las cinco propiedades de una sola vez, terminaría ejecutando la lógica de diseño 5 veces seguidas, las 4 primeras son inútiles ya que no terminó de configurar los valores para lo que necesitaba.
Debido a que Flash opera en el modelo de trama de renderizado, donde el código se ejecuta entre cada cuadro, y luego se actualiza la pantalla, necesitamos una forma de ejecutar solo ese método de diseño cuando el escenario esté por renderizarse para el siguiente cuadro. Esto podría permitirnos establecer propiedades tanto como nos plazca, pero solo redibujar CoverFlow una vez por cuadro.
Afortunadamente, hay una manera fácil de hacerlo, pero requiere algunos medios. Necesitamos utilizar el evento RENDER
. El evento RENDER
será enviado por un DisplayObject cuando el escenario esté a punto de ser renderizado, pero antes de que ocurra el renderizado. Como beneficio adicional, el evento no se despacha cuando se minimiza Flash Player, lo que significa que no deberíamos estar desperdiciando ciclos si ni siquiera puedes ver la película.
Implementar esto es un proceso de tres pasos: primero, tenemos que escuchar el evento. En el constructor de CoverFlow, agregue esta línea:
1 |
|
2 |
this.addEventListener(Event.RENDER, onRender); |
A continuación, agregue el método de escucha:
1 |
|
2 |
private function onRender(e:Event):void { |
3 |
layout(); |
4 |
}
|
Sí, eso es todo ... llamaremos diseño cada vez que recibamos el evento.
Por último, necesitamos llamar a invalidate
en el escenario cada vez que queremos que el evento RENDER
se presente en nuestro camino. Esto significa que en cada uno de los setters, necesitamos agregar esta línea al final:
1 |
|
2 |
if (stage) stage.invalidate(); |
Si no hay etapa, no queremos llamar a un método y causar un error de referencia de objeto nulo. De todos modos, si no hay una etapa, entonces el objeto CoverFlow no está en la lista de visualización de todos modos, por lo que no tiene sentido pedir el evento de renderizado.
La llamada para invalidate
, sin embargo, es cómo hacemos que el evento RENDER
se active. Sin esa llamada, no obtendremos el evento, incluso con un oyente de eventos. Entonces, necesitamos esto en cada setter:
1 |
|
2 |
public function set centerMargin(num:Number):void { |
3 |
if (isNaN(num)) num = DEFAULT_CENTER_MARGIN; |
4 |
_centerMargin = Math.max(0, num); |
5 |
if (stage) stage.invalidate(); |
6 |
}
|
7 |
public function get centerMargin():Number { |
8 |
return _centerMargin; |
9 |
}
|
10 |
|
11 |
public function set horizontalSpacing(num:Number):void { |
12 |
if (isNaN(num)) num = DEFAULT_HORIZONTAL_SPACING; |
13 |
_horizontalSpacing = Math.max(0, num); |
14 |
if (stage) stage.invalidate(); |
15 |
}
|
16 |
public function get horizontalSpacing():Number { |
17 |
return _horizontalSpacing; |
18 |
}
|
19 |
|
20 |
public function set backRowDepth(num:Number):void { |
21 |
if (isNaN(num)) num = DEFAULT_BACK_ROW_DEPTH; |
22 |
_backRowDepth = Math.max(0, num); |
23 |
if (stage) stage.invalidate(); |
24 |
}
|
25 |
public function get backRowDepth():Number { |
26 |
return _backRowDepth; |
27 |
}
|
28 |
|
29 |
public function set backRowAngle(num:Number):void { |
30 |
if (isNaN(num)) num = DEFAULT_BACK_ROW_ANGLE; |
31 |
_backRowAngle = Math.min(90, Math.abs(num)); |
32 |
if (stage) stage.invalidate(); |
33 |
}
|
34 |
public function get backRowAngle():Number { |
35 |
return _backRowAngle; |
36 |
}
|
37 |
|
38 |
public function set verticalOffset(num:Number):void { |
39 |
if (isNaN(num)) num = DEFAULT_VERTICAL_OFFSET; |
40 |
_verticalOffset = num; |
41 |
if (stage) stage.invalidate(); |
42 |
}
|
43 |
public function get verticalOffset():Number { |
44 |
return _verticalOffset; |
45 |
}
|
Entonces, si configuramos las 5 propiedades al mismo tiempo, podemos llamar invalidate
al escenario más de una vez, pero está bien, eso no va a causar problemas. Deberíamos obtener un único evento RENDER
como resultado, lo que significa que tendremos CoverFlow solo una vez por cuadro.
Para probar esto, podemos poner algunos rastros en nuestros métodos, y luego establecer estas propiedades después de que el XML se haya cargado. Primero, agregue un mensaje de seguimiento a onRender:
1 |
|
2 |
private function onRender(e:Event):void { |
3 |
trace("render") |
4 |
layout(); |
5 |
}
|
Luego, en CoverFlowTest, configure las 5 propiedades en onCoverFlowLoaded:
1 |
|
2 |
private function onCoverFlowLoaded(e:Event):void { |
3 |
trace("Coverflow loaded and ready to go."); |
4 |
coverFlow.horizontalSpacing = 100; |
5 |
coverFlow.centerMargin = 75; |
6 |
coverFlow.backRowDepth = 300; |
7 |
coverFlow.backRowAngle = 75; |
8 |
coverFlow.verticalOffset = 50; |
9 |
}
|
Debería ver que la película carga imágenes como antes, sin embargo, esta vez, las imágenes deberían saltar a un diseño ligeramente diferente una vez que todas las imágenes se hayan cargado. Esto demuestra que las cosas funcionan visualmente, pero más importante aún, verifica el panel de Salida. Debería ver un solo mensaje de "render". Llamamos a stage.invalidate de los 5 setters, pero eso resultó en un solo evento RENDER
, que es lo que buscamos.
Siéntase libre de eliminar la traza y el código de diseño que acabamos de agregar.
Paso 33: Reagrupar
Ha pasado un tiempo desde que he enumerado el código de clase completo. Nuestro trabajo ha sido principalmente en CoverFlow. Aquí está el estado actual de las cosas con esa clase:
1 |
|
2 |
package com.tutsplus.coverflow { |
3 |
|
4 |
import flash.display.*; |
5 |
import flash.geom.*; |
6 |
import flash.events.*; |
7 |
import flash.net.*; |
8 |
import flash.text.*; |
9 |
import flash.utils.*; |
10 |
|
11 |
public class CoverFlow extends Sprite { |
12 |
|
13 |
private var _coversContainer:Sprite; |
14 |
private var _width:Number; |
15 |
private var _height:Number; |
16 |
private var _backgroundColor:uint = 0; |
17 |
private var _background:Shape; |
18 |
private var _covers:Vector.<Cover>; |
19 |
private var _urlLoader:URLLoader; |
20 |
private var _xml:XML; |
21 |
private var _loadCounter:uint; |
22 |
private var _bytesPerImage:int; |
23 |
private var _bytesTotal:int; |
24 |
private var _selectedIndex:uint; |
25 |
private var _centerMargin:Number; |
26 |
private var _horizontalSpacing:Number; |
27 |
private var _backRowDepth:Number; |
28 |
private var _backRowAngle:Number; |
29 |
private var _verticalOffset:Number; |
30 |
|
31 |
private static const DEFAULT_CENTER_MARGIN:Number = 60; |
32 |
private static const DEFAULT_HORIZONTAL_SPACING:Number = 30; |
33 |
private static const DEFAULT_BACK_ROW_DEPTH:Number = 150; |
34 |
private static const DEFAULT_BACK_ROW_ANGLE:Number = 45; |
35 |
private static const DEFAULT_VERTICAL_OFFSET:Number = 0; |
36 |
|
37 |
public function CoverFlow(w:Number, h:Number) { |
38 |
_width = w; |
39 |
_height = h; |
40 |
_covers = new Vector.<Cover>(); |
41 |
|
42 |
_centerMargin = DEFAULT_CENTER_MARGIN; |
43 |
_horizontalSpacing = DEFAULT_HORIZONTAL_SPACING; |
44 |
_backRowDepth = DEFAULT_BACK_ROW_DEPTH; |
45 |
_backRowAngle = DEFAULT_BACK_ROW_ANGLE; |
46 |
_verticalOffset = DEFAULT_VERTICAL_OFFSET; |
47 |
|
48 |
_coversContainer = new Sprite(); |
49 |
addChild(_coversContainer); |
50 |
|
51 |
_background = new Shape(); |
52 |
addChildAt(_background, 0); |
53 |
drawBackground(); |
54 |
|
55 |
scrollRect = new Rectangle(0, 0, _width, _height); |
56 |
|
57 |
_urlLoader = new URLLoader(); |
58 |
_urlLoader.addEventListener(Event.COMPLETE, onXMLLoad); |
59 |
_urlLoader.addEventListener(IOErrorEvent.IO_ERROR, onXMLLoadError); |
60 |
|
61 |
this.addEventListener(Event.RENDER, onRender); |
62 |
}
|
63 |
|
64 |
override public function set width(num:Number):void { |
65 |
_width = num; |
66 |
_background.width = _width; |
67 |
scrollRect = new Rectangle(0, 0, _width, _height); |
68 |
}
|
69 |
override public function get width():Number { |
70 |
return _width; |
71 |
}
|
72 |
override public function set height(num:Number):void { |
73 |
_height = num; |
74 |
_background.height = _height; |
75 |
scrollRect = new Rectangle(0, 0, _width, _height); |
76 |
}
|
77 |
override public function get height():Number { |
78 |
return _height; |
79 |
}
|
80 |
|
81 |
public function set backgroundColor(val:uint):void { |
82 |
_backgroundColor = val; |
83 |
drawBackground(); |
84 |
for each (var cover:Cover in _covers) { |
85 |
cover.backgroundColor = val; |
86 |
}
|
87 |
}
|
88 |
public function get backgroundColor():uint { |
89 |
return _backgroundColor; |
90 |
}
|
91 |
|
92 |
public function set centerMargin(num:Number):void { |
93 |
if (isNaN(num)) num = DEFAULT_CENTER_MARGIN; |
94 |
_centerMargin = Math.max(0, num); |
95 |
if (stage) stage.invalidate(); |
96 |
}
|
97 |
public function get centerMargin():Number { |
98 |
return _centerMargin; |
99 |
}
|
100 |
|
101 |
public function set horizontalSpacing(num:Number):void { |
102 |
if (isNaN(num)) num = DEFAULT_HORIZONTAL_SPACING; |
103 |
_horizontalSpacing = Math.max(0, num); |
104 |
if (stage) stage.invalidate(); |
105 |
}
|
106 |
public function get horizontalSpacing():Number { |
107 |
return _horizontalSpacing; |
108 |
}
|
109 |
|
110 |
public function set backRowDepth(num:Number):void { |
111 |
if (isNaN(num)) num = DEFAULT_BACK_ROW_DEPTH; |
112 |
_backRowDepth = Math.max(0, num); |
113 |
if (stage) stage.invalidate(); |
114 |
}
|
115 |
public function get backRowDepth():Number { |
116 |
return _backRowDepth; |
117 |
}
|
118 |
|
119 |
public function set backRowAngle(num:Number):void { |
120 |
if (isNaN(num)) num = DEFAULT_BACK_ROW_ANGLE; |
121 |
_backRowAngle = Math.min(90, Math.abs(num)); |
122 |
if (stage) stage.invalidate(); |
123 |
}
|
124 |
public function get backRowAngle():Number { |
125 |
return _backRowAngle; |
126 |
}
|
127 |
|
128 |
public function set verticalOffset(num:Number):void { |
129 |
if (isNaN(num)) num = DEFAULT_VERTICAL_OFFSET; |
130 |
_verticalOffset = num; |
131 |
if (stage) stage.invalidate(); |
132 |
}
|
133 |
public function get verticalOffset():Number { |
134 |
return _verticalOffset; |
135 |
}
|
136 |
|
137 |
private function drawBackground():void { |
138 |
_background.graphics.clear(); |
139 |
_background.graphics.beginFill(_backgroundColor, 1); |
140 |
_background.graphics.drawRect(0, 0, _width, _height); |
141 |
}
|
142 |
|
143 |
private function layout():void { |
144 |
_selectedIndex = 4; |
145 |
var len:uint = _covers.length; |
146 |
var cover:Cover; |
147 |
var distanceFromCenter:uint; |
148 |
for (var i:uint = 0; i < len; i++) { |
149 |
cover = _covers[i]; |
150 |
if (i == _selectedIndex) { |
151 |
cover.rotationY = 0; |
152 |
cover.x = _background.width / 2; |
153 |
cover.z = 0; |
154 |
_coversContainer.setChildIndex(cover, _coversContainer.numChildren-1); |
155 |
} else if (i < _selectedIndex) { |
156 |
distanceFromCenter = _selectedIndex - i; |
157 |
cover.rotationY = -_backRowAngle; |
158 |
cover.x = ((_background.width / 2) - _centerMargin) - (distanceFromCenter * _horizontalSpacing); |
159 |
cover.z = _backRowDepth; |
160 |
_coversContainer.setChildIndex(cover, _coversContainer.numChildren - ( |