AS3 101: Eventos - Basix
() translation by (you can also view the original English article)
Para este capítulo de AS3 101, nos sumergiremos en la mecánica del sistema de eventos Flash. Si has estado siguiendo hasta ahora, habrás visto eventos en uso, que se remontan al primer episodio de la serie. El editor y yo sentimos que era hora de escribir algo para incluirlo formalmente en el plan de estudios, así que si alguna vez has visto esas líneas de código sobre agregar oyentes de eventos o enviar eventos, y no te has dado cuenta, entonces este es el tutorial para ti.
Ya existe un tutorial de Activetuts+ sobre los conceptos básicos de los eventos, por lo que el enfoque de este tutorial será el envío de eventos de tus propias clases, incluida la creación de tipos de eventos y objetos personalizados.
Para tener éxito con este tutorial, debes sentirse cómodo escribiendo y utilizando tus propias clases en ActionScript 3, así como sintiéndote seguro con el uso de los eventos existentes proporcionados por Flash, como MouseEvent.CLICK
o Event.ENTER_FRAME
. Nos centraremos principalmente en el envío de eventos personalizados de clases personalizadas.
Vista previa
Dedicaremos mucho tiempo a la teoría para este tutorial, pero al final construiremos un control deslizante simple que despacha sus propios eventos:
Paso 1: ¿Por qué usar el despacho de eventos?
Este ejemplo es en realidad un ejemplo bastante simple de por qué querrías enviar tus propios eventos. Cuando escribes tus propias clases, lo ideal es mantenerlas en sus propias cajas negras y encapsuladas. Pero aún necesitas que los diversos objetos inter operen para crear un programa útil.
El modelo de eventos proporcionado por ActionScript 3 es una forma bastante buena y cómoda de facilitar la comunicación entre las clases, manteniendo al mismo tiempo una separación de responsabilidades en las clases. Por lo tanto, si escribimos nuestra propia clase personalizada, como las muestras de clase ActiveSlider
anteriores, tenemos la necesidad de permitir que otros objetos sean conscientes de cuándo el control deslizante cambia su valor. Si el control deslizante puede enviar su propio evento de cambio, entonces otros objetos que necesitan saber esa información pueden suscribirse fácilmente y ser notificados.
Personalmente, encuentro la necesidad de enviar mis propios eventos en mis clases tan común que mi plantilla de clase configura cada nueva clase con la repetición que necesita para poder hacerlo. A medida que aprendas a mantener los objetos discretos, recurrirás al envío de eventos como la técnica más común para hacerlo.
Paso 2: Cómo enviar tus propios eventos
Tengo buenas noticias: enviar tus propios eventos es en realidad muy simple. Es uno de los principios básicos de ActionScript 3, integrado en Flash Player, y como tal, solo hay una cosa que debes hacer para obtener la capacidad de enviar eventos. Esta única cosa es:
Extender EventDispatcher
Eso es todo: al escribir tu clase, usa la línea:
1 |
public class MyClass extends EventDispatcher { |
Por supuesto, debes importar EventDispatcher
, que se encuentra en el paquete flash.events
. Lo más probable es que necesites otras clases en el paquete, por lo que podría ser más conveniente simplemente importar el paquete con un comodín.
1 |
import flash.events.*; |
Paso 3: Despacho
Ahora estás configurado para enviar un evento. Todo lo que necesitas hacer ahora es llamar a un método proporcionado por EventDispatcher
llamado dispatchEvent
. Fácil de entender, ¿no?
Al llamar a dispatchEvent
, debes proporcionar al menos un argumento, un objeto Event
. Todos los objetos Event
integrados están en el paquete flash.events
, así que aquí es donde esa importación de comodines es útil. Cada tipo de objeto Event
tendrá sus propios requisitos, pero la mayoría de las veces simplemente necesitas pasarle un solo argumento, también. Este argumento es el tipo de evento, que es una cadena
que nombra el evento, como "click"
o "complete"
. Estos se escriben más comúnmente como MouseEvent.CLICK
o Event.COMPLETE
, pero el resultado final es el mismo; es un identificador que separa un tipo de evento de otro y permite que un objeto Event
administre varios tipos de eventos.
Entonces, poniéndolo todo junto, si quisieras enviar un evento "complete"
, podrías hacerlo así:
1 |
dispatchEvent(new Event(Event.COMPLETE)); |
Simplemente suelta esa línea (o una similar) en cualquier método que sea apropiado en tu clase. Tu clase utilizará su sistema de envío de eventos heredado y cualquier oyente será notificado por ti. Hablando de oyentes, echemos un breve vistazo a ellos también.
Paso 4: Escucha
Cualquier otro objeto de la aplicación puede escuchar los eventos personalizados ahora. Mejores noticias: esto no es diferente a registrarse para eventos para las clases incorporadas. En el paso anterior, configuramos nuestra clase hipotética para enviar un evento COMPLETE
. Para escuchar ese evento, podríamos escribir esta línea en otro lugar de nuestro programa:
1 |
var myObject:MyClass = new MyClass(); |
2 |
myObject.addEventListener(Event.COMPLETE, myCompleteHandler); |
3 |
function myCompleteHandler(e:Event):void { |
4 |
trace("My object completes me."); |
5 |
}
|
Y eso es todo. Esto debería parecer familiar para cualquiera que haya conectado un oyente COMPLETE
a un Loader
, por ejemplo, así que no me detendré más en esto.
Paso 5: Dónde enviar
El lugar donde realmente colocas la línea de código dispatchEvent
requiere cierta consideración. Normalmente, debe ser la última línea de código del método en el que está escrito. Esto es para que cualquier otro código que también se ejecute en ese método pueda establecer propiedades o actualizar de otro modo el estado interno del objeto. Al distribuir después de que se complete esta actualización interna, el objeto se encuentra en un estado "limpio" en el momento del envío.
Consideremos, por ejemplo, nuestro ejemplo de trabajo. Digamos que el evento COMPLETE
se trata del procesamiento de algunos datos; un montón de datos tan grande que tardará varios segundos en procesarse por completo, por lo que el propósito del objeto es manejar el procesamiento de forma asincrónica para no bloquear la interfaz de usuario. Y estamos enviando el evento COMPLETE
como una forma de decir que los datos han sido procesados.
Ahora supongamos que el método principal en cuestión se parece a esto:
1 |
private function processDataChunk():void { |
2 |
_data += someDataProcess(); |
3 |
if (done()) { |
4 |
closeData(); |
5 |
}
|
6 |
}
|
De acuerdo, no es muy realista, pero ilustra el punto. Seguimos construyendo los datos internos hasta que alguna otra lógica interna determina que hemos terminado, momento en el que luego escribimos algunos bits finales de datos para cerrar la operación.
Ahora, agreguemos la llamada dispatchEvent
:
1 |
private function processDataChunk():void { |
2 |
_data += someDataProcess(); |
3 |
if (done()) { |
4 |
dispatchEvent(new Event(Event.COMPLETE)); |
5 |
closeData(); |
6 |
}
|
7 |
}
|
¿Cuál es el problema con este enfoque? Cualquier código que se ejecute dentro de los agentes de escucha para el evento COMPLETE
se ejecutará antes de que se llame al método closeData
. Por lo tanto, el estado del despachador cambia más de una vez dentro del intervalo del método processDataChunk
y no es "estable" hasta después de la llamada closeData
. Sin embargo, les decimos a todos nuestros oyentes que estamos completos antes de esa llamada. Esto podría conducir a algunos errores difíciles de rastrear donde un objeto afirma estar COMPLETE
, pero realmente no lo es. La solución obvia es cambiar algunas líneas:
1 |
private function processDataChunk():void { |
2 |
_data += someDataProcess(); |
3 |
if (done()) { |
4 |
closeData(); |
5 |
dispatchEvent(new Event(Event.COMPLETE)); |
6 |
}
|
7 |
}
|
Paso 6: Eventos personalizados
Todos están listos para enviar sus propios eventos. Ahora, ¿qué debes enviar? Hay algunas opciones a considerar:
- Simplemente reutiliza un objeto de evento y un tipo de evento ya proporcionados por Flash Player
- Reutilizar un objeto de evento existente, pero proporcionar un tipo de evento personalizado
- Volver a enviar un evento existente
- Crea un objeto de evento personalizado
- Empujar vs. tirar
Esta primera opción ya la hemos visto en nuestros ejemplos anteriores. Tenemos la necesidad de enviar un evento relacionado con la finalización de algún proceso, y como sucede, Flash proporciona un tipo de evento (COMPLETE
) asociado con un objeto de evento (Event
) que se ajusta a nuestros criterios. No tenemos necesidad de proporcionar datos adicionales con el evento. Enviar un evento Event.COMPLETE
es todo lo que necesitamos.
Exploraremos estas otras opciones en los próximos pasos.
Paso 7: Tipos de eventos personalizados
Como se insinúa en el paso "Enviar", los tipos de eventos son simplemente identificadores de String
. Técnicamente pueden ser cualquier String
que quieras. Por lo general, es suficiente convertirlo en una sola palabra (como "completo" o "clic") o una frase muy corta (como "ioError" o "keyFocusChange"); solo tienes que ser único dentro del ámbito de eventos disponibles de un despachador de eventos determinado.
Además, a los objetos Event
(incluidas las subclases, como MouseEvent
o ProgressEvent
) realmente no les importa qué tipo de evento se les da cuando se crea una instancia. Un EventDispatcher
con gusto enviará eventos de cualquier tipo de identificador y de cualquier clase (siempre que sea la clase Event
o una subclase).
El resultado de esto es que puedes crear tu propio tipo de evento String
, enviarlo y configurar oyentes con él, y todo estará bien. Esto es útil cuando deseas distribuir un evento, pero no necesariamente puedes encontrar una buena representación de la naturaleza del evento en las clases integradas.
Por ejemplo, es posible que tengas una clase que actúe como coordinador para varias cosas a la vez: cargar algún XML, cargar algunas imágenes basadas en los datos XML y crear un diseño basado en los tamaños de las imágenes cargadas, listo para una animación inicial, momento en el que deseas distribuir un evento. Si bien el evento COMPLETE
puede ser adecuado, puedes sentir que un evento "listo" encapsula el significado de manera más apropiada.
Esto es tan simple como decidir sobre la String
a usar y luego usarla. Úsalo tanto al agregar oyentes como al enviar el evento. Si la String
coincide, el evento llegará a donde debe ir. Por ejemplo, esta es una lista parcial de una clase hipotética:
1 |
public class MyClass extends EventDispatcher { |
2 |
|
3 |
// ... A bunch of other stuff not shown.
|
4 |
|
5 |
private function determineReadiness():void { |
6 |
if (everythingIsReady) { |
7 |
dispatchEvent(new Event("ready")); |
8 |
}
|
9 |
}
|
10 |
|
11 |
}
|
Y código de otra parte en el mismo programa:
1 |
var myObject:MyClass = new MyClass(); |
2 |
myObject.addEventListener("ready", onObjectReady); |
3 |
|
4 |
function onObjectReady(e:Event):void { |
5 |
// Do stuff now that it's ready.
|
6 |
}
|
Y eso funcionará
Sin embargo, vale la pena mencionar mientras estamos aquí que escribir String
coincidentes en todo el lugar no es una buena práctica. La posibilidad de error es alta, y el sistema de eventos no le dirá que ha escrito "raedy"
en lugar de "ready"
. El hecho de que el sistema de eventos sea flexible y fácil de usar, simplemente páselo cualquier string
antigua para el tipo de evento, también es una debilidad. Tu despachador aceptará con gusto a un oyente para cualquier cosa, incluso un evento "raedy"
. Realmente no reconcilia qué tipos de eventos están registrados con qué tipos de eventos se envían realmente.
Para ayudar a evitar esto, el enfoque estándar es simplemente poner la string
que deseas usar en una constante estática en algún lugar, y luego nunca volver a usar esa string
literal. Solo usa la constante. Por supuesto, la posibilidad de errores tipográficos es simplemente excelente como antes, pero si estás utilizando una constante READY
y no la sting
literal "ready"
, un error de tipo desencadenará una advertencia del compilador. Podrás corregir tu error de forma rápida y sencilla. Un error de tipo con el literal Strings
no produce ningún error del compilador, ni produce un error en tiempo de ejecución. Lo único que sucede es que el SWF no parece funcionar correctamente, porque el oyente de eventos no se dispara.
Con esto en mente, es más común almacenar estas constantes en la clase Event
relacionada. Llegaremos a clases de event
personalizadas en solo unos pocos pasos. Pero en la situación descrita en este paso (es decir, estamos reutilizando una clase Event
, pero no un tipo de evento), me parece más conveniente simplemente almacenar esa constante en la clase dispatcher. Así que podríamos optar por hacer esto:
1 |
public class MyClass extends EventDispatcher { |
2 |
|
3 |
public static const READY:String = "ready"; |
4 |
|
5 |
// etc.
|
6 |
|
7 |
private function determineReadiness():void { |
8 |
if (everythingIsReady) { |
9 |
dispatchEvent(new Event(READY)); |
10 |
}
|
11 |
}
|
12 |
|
13 |
}
|
Y:
1 |
var myObject:MyClass = new MyClass(); |
2 |
myObject.addEventListener(READY, onObjectReady); |
3 |
|
4 |
function onObjectReady(e:Event):void { |
5 |
// Do stuff now that it's ready.
|
6 |
}
|
Esto nos da la seguridad de almacenar tipos de eventos en constantes, sin forzar el inconveniente de crear una clase Event
completa que no necesitamos. Debo enfatizar que esta es una elección estilística, y puedes sentirse libre de usar esta técnica o no. Sentí que merecías una explicación, para que pudieras tomar tu propia decisión informada. En cualquier sentido, definitivamente debes almacenar tu tipo de evento personalizado Strings
en constantes estáticas. Dónde se definen esas constantes estáticas depende de ti.
Paso 8: Reenspachar eventos
Hay varias veces en que una clase (llamémosla ClassX
) posee una propiedad que se escribe como otra clase (la llamaremos ClassY
), mientras que ella misma es propiedad de una tercera clase (¿qué tal ClassZ
?). ClassX
está escuchando un evento de ClassY
, pero no solo queremos que ClassX
responda al evento, sino que también queremos considerar que ClassX
debe enviar un evento similar (o incluso el mismo) para que ClassZ
también pueda tomar medidas adicionales.
Como ejemplo más concreto, tenemos una clase (esta será "ClassX
") que es una especie de administrador de datos. Carga un documento XML, lo analiza y almacena datos del XML en sus propias propiedades. Por lo tanto, tiene un objeto URLLoader
(esto sería "ClassY
"), y escucha el evento Event.COMPLETE
para cuando se carga el documento XML.
Luego tenemos una clase de documento principal que posee el administrador de datos (la clase de documento es "ClassZ
"). Está coordinando la carga de datos con otros elementos de la interfaz de usuario, por lo que quiere saber cuándo se cargan y están listos los datos, para poder proceder a crear y diseñar elementos de la interfaz de usuario en función de los datos.
1 |
public class DataManager extends EventDispatcher { |
2 |
private var _xmlLoader:URLLoader; |
3 |
public function DataManager() { |
4 |
_xmlLoader = new URLLoader(new URLRequest("some.xml")); |
5 |
_xmlLoader.addEventListener(Event.COMPLETE, onLoadComplete); |
6 |
}
|
7 |
// ... A bunch of other stuff
|
8 |
private function onLoadComplete(e:Event):void { |
9 |
// ... XML parsing
|
10 |
// With the XML parsed, we'd like to dispatch another event to signal being done.
|
11 |
}
|
12 |
}
|
Podríamos hacer esto:
1 |
dispatchEvent(new Event(Event.COMPLETE)); |
Pero también podríamos hacer esto:
1 |
dispatchEvent(e); |
Aquí simplemente reenviamos el evento existente. No solo reutilizamos el tipo de evento y la clase Event
, sino que en realidad estamos reutilizando todo el objeto Event
a medida que se pasa a nuestro propio oyente.
No es ciencia espacial, pero es una pequeña técnica útil que sorprendentemente no es tan obvia.
"Pero espera", debes estar pensando, "si redisparáramos un evento que se originó a partir del objeto URLLoader
, ¿no sería el target
del evento aún _xmlLoader
cuando vuelva a la clase de documento?" Y tendrías un punto muy bueno y reflexivo, y estaría orgulloso de ti por pensar tan cuidadosamente, pero estarías equivocado.
Una cosa bastante mágica sucede cuando se reeparche los eventos. La propiedad de target
se establece en el despachador actual. Puedes encontrar un ejemplo práctico del código en este paso en el paquete de descarga, titulado reenspache.
En realidad, no es tan mágico. Al llamar a dispatchEvent
, si el objeto Event
que se pasa ya tiene un target
establecido, se llama al método clone
en el Event
, creando una copia idéntica pero discreta del Event
original, excepto por el valor contenido en el target
.
Paso 9: Objetos de evento personalizados
Todo lo mencionado hasta ahora es algo que querrás saber. Pero llegará un momento en que lo mejor que puedes hacer es enviar tu propio evento personalizado. No solo un tipo de evento personalizado, sino toda una clase de event
personalizada.
El proceso para hacer esto es sencillo, solo necesitas seguir algunos pasos. Discutiremos esto a su debido tiempo. Ten en cuenta que bastante de esto es código repetitivo, y podrías crear fácilmente una plantilla para una subclase event
y cambiar solo algunas piezas clave y estar apagado y en ejecución. Los pasos generales, en forma abreviada como si supieras de qué estaba hablando:
- Subclass
Event
- Llama a
super(...)
- Almacenar tipos de eventos en constantes estáticas públicas
- Declara propiedades privadas para contener datos personalizados
- Crear obtenedores públicos para proporcionar acceso de solo lectura a la información personalizada
- (Opcional) Invalidar el método de
clone
- (Opcional) Invalidar el método
toString
Para explicar estos procesos más profundamente, comenzaremos nuestro proyecto de control deslizante y crearemos el SliderEvent
que necesitaremos para eso. Por lo tanto, debemos comenzar nuestro proyecto antes de que podamos escribir algo de código, por lo que una desviación rápida en el siguiente paso, luego comenzaremos a escribir una clase de event
personalizada.
Paso 10: Crear la estructura del proyecto
Mantendremos las cosas bastante simples para este, sin embargo crearemos paquetes para nuestras clases.
Comienza creando una carpeta para todo el proyecto. El mío se llamará slider.
Dentro de esto, crea una carpeta com, y dentro de eso, una carpeta activetuts.
Ahora crea dos carpetas dentro de activetuts: eventos y ui. La estructura de carpetas final debería tener un aspecto similar al siguiente:
- slider
- com
- activetuts
- events
- slider
- activetuts
- com
Ahora volvamos a nuestra clase de Event
.
Paso 11: Event
de subclase
Primero, crea un nuevo archivo de texto en la carpeta slider/com/activetuts/events y llámalo SliderEvent.as. Iremos en el repetitivo para cualquier clase:
1 |
package com.activetuts.events { |
2 |
|
3 |
public class SliderEvent { |
4 |
|
5 |
public function SliderEvent() { |
6 |
|
7 |
}
|
8 |
|
9 |
}
|
10 |
|
11 |
}
|
No debería haber nada sorprendente aquí, y si tienes plantillas de ActionScript para tu editor de texto, ni siquiera deberías tener que escribir tanto.
Ahora, modificaremos esto para que extienda el event
.
1 |
package com.activetuts.events { |
2 |
|
3 |
import flash.events.Event; |
4 |
|
5 |
public class SliderEvent extends Event { |
6 |
|
7 |
public function SliderEvent() { |
8 |
|
9 |
}
|
10 |
|
11 |
}
|
12 |
|
13 |
}
|
Como puedes ver, simplemente importamos la clase Event
, agregamos extend Event
a la definición de clase.
Paso 12: Llama al super
Nuestra superclase puede manejar mucho por nosotros, lo cual es genial, pero debemos asegurarnos de inicializar la superclase correctamente cuando inicializamos nuestra subclase. Necesitamos configurar el constructor con argumentos que coincidan con los que se encuentran en el constructor de Event
, y pasarlos junto con una llamada a super
.
1 |
package com.activetuts.events { |
2 |
|
3 |
import flash.events.Event; |
4 |
|
5 |
public class SliderEvent extends Event { |
6 |
|
7 |
public function SliderEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=true) { |
8 |
super(type, bubbles, cancelable); |
9 |
}
|
10 |
|
11 |
}
|
12 |
|
13 |
}
|
Se trata, hasta ahora, de técnicas básicas de sub clasificación. De hecho, dependiendo de la destreza de tu editor con las plantillas, es posible que puedas especificar una superclase cuando crees el archivo y hacer todo esto por ti mismo. Flash Builder, por ejemplo, es capaz de hacer esto.
Paso 13: Almacenar tipos de eventos en constantes estáticas públicas
Presumiblemente, habrá uno o más tipos de eventos asociados con esta clase de evento. Al igual que el evento COMPLETE
está asociado con la clase Event
y el CLICK
incluso con la clase MouseEvent
, nuestra clase de evento personalizada probablemente tendrá tipos de eventos personalizados.
Esto es tan simple como escribir una línea como la siguiente para cada tipo de evento que desees agregar:
1 |
public static const EVENT_TYPE:String = "eventType"; |
Hagámoslo ahora para la clase SliderEvent
.
1 |
package com.activetuts.events { |
2 |
|
3 |
import flash.events.Event; |
4 |
|
5 |
public class SliderEvent extends Event { |
6 |
|
7 |
public static const CHANGE:String = "sliderChange"; |
8 |
|
9 |
public function SliderEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=true) { |
10 |
super(type, bubbles, cancelable); |
11 |
}
|
12 |
|
13 |
}
|
14 |
|
15 |
}
|
Teóricamente podríamos usar nuestra clase ahora. Podemos usar SliderEvent
en dispatchEvent
, y escuchar y crear eventos con el tipo de evento SliderEvent.CHANGE
.
Pero no nos detendremos ahí. Hay más que considerar. Pero antes de hacer más escritura de código, necesitamos tomar otro desvío hacia la teoría.
Paso 14: Empujar vs. Tirar
Cuando se envía un evento, a veces es suficiente simplemente saber que el evento ha ocurrido. Por ejemplo, la mayoría de las veces que está interesado en Event.ENTER_FRAME
eventos, Event.COMPLETE
o TimeEvent.TIMER,
probablemente solo desees saber que el evento ocurrió. Sin embargo, hay otros momentos en los que probablemente quieras saber más. Al escuchar MouseEvent.CLICK
, es posible que le interese saber si la tecla shift se mantuvo presionada o las coordenadas del ratón en el momento del clic. Si estás escuchando ProgressEvent.PROGRESS
, lo más probable es que desees saber el progreso real de la carga; es decir, cuántos bytes se han cargado y cuántos hay que cargar en total.
La diferencia entre estas dos metodologías a veces se conoce como "push" y "pull". Esos términos se refieren a cómo el agente de escucha de eventos obtiene datos relacionados con el evento. Si los datos se "empujan", entonces hay datos almacenados dentro del objeto de evento, y para obtener los datos, el oyente simplemente necesita usar propiedades en el objeto de evento. Sin embargo, si los datos se deben "extraer", generalmente el objeto de evento tiene muy poca información contenida en su interior, solo las necesidades: el tipo, el objetivo, etc. Este objetivo, sin embargo, es indispensable, ya que proporciona acceso al despachador de eventos al agente de escucha de eventos, lo que permite al oyente obtener los datos que necesita del despachador.
En otras palabras, puedes enviar un montón de datos al oyente dentro del objeto de evento, o puedes requerir que el oyente extraiga los datos del despachador según sea necesario.
Los pros y los contras de cada técnica son algo equilibrados, en mi opinión, y el camino que elija para tu objeto de evento depende de la situación en cuestión, y no un poco de la preferencia personal.
Anexo A:
Pros | Contras | |
---|---|---|
Empujar |
|
|
Tirar |
|
|
Esta podría ser una buena discusión para los comentarios; Estoy seguro de que muchos de ustedes que leen tienen sentimientos apasionados sobre qué metodología es mejor. Personalmente, trato de encontrar el método que mejor se adapte a la situación.
Dicho esto, vale la pena señalar que hasta este punto nuestra clase SliderEvent
es bastante "pull-ish". Por el bien de la ilustración, y porque no es una idea terrible (aunque se me ocurrieron varias de ellas), continuaremos haciendo de este un evento que empuje datos junto con él; es decir, el valor del control deslizante cuando se cambió.
Paso 15: Declarar propiedades privadas para mantener datos personalizados
Para implementar un evento push, necesitamos tener un lugar para almacenar los datos que se están empujando. Agregaremos una propiedad privada para ese propósito.
Aún deberías tener SliderEvent
abierto (si no... ¿A qué estás esperando?). Agrega la línea resaltada:
1 |
package com.activetuts.events { |
2 |
|
3 |
import flash.events.Event; |
4 |
|
5 |
public class SliderEvent extends Event { |
6 |
|
7 |
public static const CHANGE:String = "sliderChange"; |
8 |
|
9 |
private var _sliderValue:Number; |
10 |
|
11 |
public function SliderEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=true) { |
12 |
super(type, bubbles, cancelable); |
13 |
}
|
14 |
|
15 |
}
|
16 |
|
17 |
}
|
A continuación, modificaremos el constructor para que podamos aceptar un parámetro de valor y estableceremos la propiedad privada con eso:
1 |
public function SliderEvent(type:String, sliderValue:Number, bubbles:Boolean=false, cancelable:Boolean=true) { |
2 |
_sliderValue = sliderValue; |
3 |
super(type, bubbles, cancelable); |
4 |
}
|
De esta manera podemos crear fácilmente el SliderEvent
y configurar sus datos push en una línea.
¿Por qué usar propiedades privadas? En este caso, queremos proteger los datos. En mi opinión, los datos relacionados con un evento son inmutables, siempre y cuando estén asociados con el evento. Ser capaz de cambiar los datos de un objeto de evento es como editar un libro de texto de historia. Para ser justos, esta es mi opinión y el estándar utilizado por Adobe con sus clases incorporadas es usar propiedades grabables (técnicamente usan propiedades privadas y getters y setters públicos, pero el resultado final es el mismo).
Paso 16: Crea obtenedores públicos para los datos personalizados
Por lo tanto, el siguiente paso sería asegurarnos de que podemos acceder a los datos que se están empujando. Una propiedad privada por sí sola no es útil para este propósito. Por lo tanto, necesitamos escribir un getter público para el _sliderValue
propiedad. Elegiremos no escribir un setter, para que la propiedad se convierta en de solo lectura (como se discutió en el último paso).
Agrega este método a la clase:
1 |
public function get sliderValue():Number { |
2 |
return _sliderValue; |
3 |
}
|
Esto agrega un getter para que podamos acceder al sliderValue
de manera similar a una propiedad. Estoy eligiendo no agregar el setter coincidente. Puedes agregar uno si crees que vale la pena.
Paso 17: Invalidar el método de clone
Mencioné el método de clone
hace un tiempo. Probablemente no llamarás mucho al clon
tú mismo, pero no es una mala idea anular el método de clone
para que tu evento personalizado se juegue bien con el sistema de eventos.
Agrega este método a tu clase:
1 |
override public function clone():Event { |
2 |
return new SliderEvent(type, sliderValue, bubbles, cancelable); |
3 |
}
|
Primero, observa la firma de este método. Estamos usando override
porque este método se declara en Event
y lo estamos heredando. También devuelve un objeto de tipo Event
. Asegúrate, al escribir la anulación del clon
, de que has puesto el tipo de retorno correcto. Es fácil olvidar y poner el tipo de tu clase allí, pero eso causará un error de anulación incompatible, porque los tipos de retorno deben coincidir.
Todo lo que realmente estamos haciendo en la carne del evento es crear un nuevo SliderEvent
y pasar los mismos valores que hemos almacenado en el objeto de evento actual. Esto crea una copia idéntica pero discreta: un clon.
Este es un paso opcional, pero es una victoria rápida y garantiza que tu evento personalizado se juegue bien con el resto del sistema de eventos.
Paso 18: Invalidar el método toString
Una última cosa, y de nuevo esto es opcional. Pero también es muy útil como herramienta de depuración, por lo que generalmente se paga por sí misma en unos pocos usos.
En caso de que aún no se te ha dicho, el método toString
existe en todos los objetos (se declara y define en Object
, la clase über de la que heredan todas las demás clases, te guste o no). Se puede llamar explícitamente, pero lo útil es que se llama automáticamente en varios casos. Por ejemplo, cuando pase el objeto a la función de trace
, cualquier objeto que no sea ya una string
tendrás que toString
para asegurarte de que tienes un formato agradable para el panel Salida. Incluso se llama cuando se trabaja con object
junto con String
, como con la concatenación. Por ejemplo, si escribes esto:
1 |
"The answer to life, the universe, and everything is " + 42; |
ActionScript es lo suficientemente inteligente como para convertir 42
en una representación String
del Number
antes de concatenar el String
. Intenta agregar una string
y un Number
es una mala noticia, pero convertir un Number
en una string
y luego concatenarlo con otra string
está bien.
Por lo tanto, cuando escribes tus propias clases, puedes proporcionar un método toString
, que no toma argumentos y devuelve un String
, y devuelve el String
que desees.
En el caso de los objetos Event
, Adobe proporciona de forma útil un método formatToString
para ayudar a que todos los events
se vean similares cuando se rastrean. Lo usaremos en el método que estamos a punto de agregar a nuestra clase:
1 |
override public function toString():String { |
2 |
return formatToString("SliderValue", "type", "sliderValue"); |
3 |
}
|
Primero, anote la firma del método. Una vez más, es una override
, por lo que tenemos esa palabra clave. Es public
, no toma parámetros y devuelve un String
(que debería ser obvio).
A continuación, anota la línea única en el cuerpo del método. Llamamos formatToString
, que se define en Event
, por lo que es fácil de usar. El primer argumento que le pasamos es el nombre String
de la clase. Después de eso, los argumentos son abiertos. Puedes pasar en uno, 15 o ninguno. Estamos pasando en dos. No importa cuántos pases, todos deben ser Strings
y deben coincidir con los nombres de las propiedades de tu clase. "type"
está definido por Event
, pero "sliderValue"
está definido por nuestra propia clase. De cualquier manera, lo que sucede es que se imprime el nombre de la propiedad, seguido de un signo igual, que es seguido por el valor real de esa propiedad. En resumen, terminará viendo así:
1 |
[language="text"] |
2 |
[SliderValue type="sliderChange" sliderValue=0.34146341463414637] |
Esto es completamente no funcional pero muy útil. Puede proporcionar una visión rápida del evento cuando las cosas no están funcionando de la manera que crees que deberían.
Paso 19: Construyendo el control deslizante
En este punto, hemos pasado por el concepto clave de este tutorial: escribir una clase de event
personalizada. Pero realmente tenemos que ponerlo a prueba. Pasaremos el resto de nuestro tiempo creando la sencilla aplicación deslizante que se previsualizó al principio del tutorial.
Ya tenemos una estructura de carpetas de proyecto; solo necesitamos algunos archivos más. Comenzaremos con el archivo FLA.
Crea un nuevo archivo Flash (ActionScript 3.0, por supuesto) y guárdalo como ActiveSlider.fla en la raíz de la carpeta del proyecto. Voy a asumir que no necesitas detalles paso a paso sobre cómo armar este FLA simple, y en su lugar presentaré los elementos clave. También puedes usar el archivo FLA que se encuentra en la carpeta de inicio del paquete de descarga como referencia, o incluso simplemente copiar ese FLA a la carpeta de tu proyecto y llamar a este paso hecho.
Hay tres objetos principales en el escenario.
- La pista deslizante. Esta es una tira larga y estrecha que indica hacia dónde se puede mover el control deslizante. El control deslizante se mueve "en" la pista.
- Debe ser un clip de película
- Para facilitar las matemáticas, debes tener la obra de arte dispuesta de modo que el punto de registro esté en la esquina superior izquierda
- Nómbralo
track_mc
- Colócalo en el centro superior; debe ocupar la mayor parte del ancho del escenario y tener espacio debajo de él.
- El agarre deslizante. Este es un elemento del tamaño de un botón que indica la posición actual del control deslizante. Es la pieza que se mueve a lo largo de la pista y responde al ratón.
- Debe ser un clip de película.
- Una vez más, para las matemáticas, debes tener el punto de registro en la parte superior izquierda
- Nómbralo
grip_mc
- Colócalo sobre la pista, de modo que esté centrado verticalmente con la pista, y en algún lugar dentro de los extremos izquierdo y derecho de la pista.
- Debe apilarse en la parte superior de la pista, de modo que el agarre oscurezca la pista (colócala en una capa más alta)
- El campo de salida. Este es un campo de texto que, para nuestros propios fines de demostración, muestra el valor actual del control deslizante.
- Debe ser un campo de texto dinámico.
- Nómbralo
output_tf
- Las fuentes son intrascendentes; Configúralo en lo que desees e incrusta según sea necesario
- Colócalo en la parte inferior del escenario, para que no entre en conflicto con el espacio requerido por el control deslizante.




Además de conectar la clase de documento, que escribiremos en dos pasos, el FLA está listo para los negocios.
Paso 20: La clase ActiveSlider
La clase de interfaz de usuario principal con la que trabajaremos es la clase ActiveSlider
. Esto ampliará EventDispatcher
, apuntará a los dos clips de película en el escenario y configurará la interactividad del mouse para el comportamiento del control deslizante. Lo más emocionante de todo es que enviará un SliderEvent
.
Comienza creando un nuevo archivo de clase llamado ActiveSlider.as en la carpeta com/activetuts/slider de tu proyecto. Esta clase no es demasiado intensa (al menos, no para nuestros propósitos aquí. Una clase de control deslizante podría involucrarse mucho más), y solo presentaré el código completo y discutiré a medida que avanzamos:
1 |
package com.activetuts.slider { |
2 |
|
3 |
import flash.display.*; |
4 |
import flash.events.*; |
5 |
import flash.geom.*; |
Nada emocionante, solo configurar el paquete e importaciones.
1 |
public class ActiveSlider extends EventDispatcher { |
2 |
|
3 |
private var _track:Sprite; |
4 |
private var _grip:Sprite; |
5 |
private var _grabOffset:Point; |
Necesitaremos estas tres propiedades. Los dos primeros realizan un seguimiento de los Sprite
s (o MovieClips
) que componen el control deslizante. El tercero se utiliza mientras se arrastra la empuñadura deslizante; ayuda a mantener la posición del agarre desplazada del mouse en una cantidad relativa a dónde se hizo clic en el agarre en primer lugar.
1 |
public function ActiveSlider(track:Sprite, grip:Sprite) { |
2 |
_track = track; |
3 |
_grip = grip; |
4 |
if (_grip.parent != _track.parent) { |
5 |
throw new Error("The track and the grip Sprites are not in the same container.") |
6 |
}
|
7 |
|
8 |
_grip.addEventListener(MouseEvent.MOUSE_DOWN, onDown); |
9 |
|
10 |
if (_grip.stage) { |
11 |
addStageListener(); |
12 |
} else { |
13 |
_grip.addEventListener(Event.ADDED_TO_STAGE, onAddedToStage); |
14 |
}
|
15 |
}
|
Este es el constructor. Acepta dos argumentos sprite
, que se transmiten a las dos primeras de esas propiedades para el almacenamiento. A continuación, realiza una comprobación sencilla para asegurarte de que los dos Sprite
s están en el mismo espacio de coordenadas comprobando que sus parent
principales hacen referencia al mismo objeto. Si no lo hacen, entonces nuestras matemáticas para colocar el agarre pueden no ser confiables, por lo que lanzamos un error como una forma de alertar al desarrollador. El resto del constructor se dedica a agregar dos oyentes de eventos. El primero es un evento MOUSE_DOWN
y directo. Pero el segundo es tratar de agregar un evento MOUSE_UP
al Stage
, que podría o no existir dependiendo de si el Sprite
de grip
está en la lista de visualización o no. Los siguientes dos métodos podrían hacer esto un poco más claro:
1 |
private function onAddedToStage(e:Event):void { |
2 |
_grip.removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage); |
3 |
addStageListener(); |
4 |
}
|
5 |
|
6 |
private function addStageListener():void { |
7 |
_grip.stage.addEventListener(MouseEvent.MOUSE_UP, onUp); |
8 |
}
|
El método onAddedToStage
es un agente de escucha de eventos para el evento ADDED_TO_STAGE
, que se configuró en el constructor, pero solo si el Sprite
de agarre no tenía ya una referencia al stage
. El método addStageListener
simplemente agrega el MOUSE_UP
agente de escucha de eventos al strage
. Por lo tanto, si hay una referencia de etapa en el constructor, llamamos a addStageListener
directamente. Si no hay una referencia de stage
, configuramos el evento ADDED_TO_STAGE
, y cuando el agarre se agrega a la lista de visualización y, por lo tanto, tiene una referencia de stage
, se activa el método onAddedToStage
que, a su vez, llama a addStageListener
. También elimina el ADDED_TO_STAGE
oyente de eventos, porque solo necesitamos hacerlo una vez.
1 |
private function onDown(e:MouseEvent):void { |
2 |
_grabOffset = new Point(e.localX, e.localY); |
3 |
_grip.addEventListener(Event.ENTER_FRAME, onFrame); |
4 |
}
|
5 |
|
6 |
private function onUp(e:MouseEvent):void { |
7 |
_grip.removeEventListener(Event.ENTER_FRAME, onFrame); |
8 |
}
|
A continuación tenemos nuestros dos oyentes de eventos del mouse. En onDown
, la línea clave es agregar un ENTER_FRAME
el agente de escucha de eventos. En onUp
, eliminamos ese oyente. También en onDown
, tomamos nota de dónde en el agarre ocurrió realmente el clic del mouse, y lo almacenamos en _grabOffset
. Esto jugará en nuestro método onFrame
a continuación.
1 |
private function onFrame(e:Event):void { |
2 |
_grip.x = _grip.parent.mouseX - _grabOffset.x; |
3 |
|
4 |
var gripBounds = _grip.getBounds(_grip.parent); |
5 |
var trackBounds = _track.getBounds(_grip.parent); |
6 |
|
7 |
if (gripBounds.x < trackBounds.x) { |
8 |
_grip.x = _track.x; |
9 |
} else if (gripBounds.right > trackBounds.right) { |
10 |
_grip.x = trackBounds.right - gripBounds.width |
11 |
}
|
12 |
|
13 |
trace(this.value); |
14 |
}
|
Esta es la carne principal de nuestra lógica de control deslizante. Este método se dispara repetido en un evento ENTER_FRAME
, pero solo cuando el mouse ha sido presionado hacia abajo en el agarre, y solo durante el tiempo que el mouse permanezca presionado.
Primero, configuramos la propiedad x
del agarre para que coincida con la posición del mouse, solo necesitamos compensarlo en función de la posición original del mouse, para que se mueva suavemente y no salte para que su borde izquierdo esté en el mouse.
Las dos líneas siguientes calculan los rectángulos delimitadores de los Sprite
s de agarre y de pista. Usaremos estos rectángulos en las próximas matemáticas, por lo que mantendremos las cosas más ordenados calculando previamente los rectángulos y almacenándolos en variables.
Luego está el bloque if
. Esto solo restringe nuestro control deslizante dentro de la pista. Es una simple comprobación para ver si la x
del agarre, como se calcula en la primera línea del método, es más baja que (a la izquierda de) la x
de la pista. Si es así, sería demasiado lejos, por lo que debemos mover el agarre a ese valor mínimo. Del mismo modo, comprobamos si el borde derecho del agarre es mayor que (a la derecha de) el borde derecho de la pista, y si es así, debemos volver a enrollarlo a ese valor máximo.
Finalmente, tenemos una posición de agarre confiable, y por ahora solo rastreamos el valor actual del control deslizante, que se calcula en el bit final de código para la clase:
1 |
private function get value():Number { |
2 |
return (_grip.x - _track.x) / (_track.width - _grip.width); |
3 |
}
|
4 |
|
5 |
}
|
6 |
|
7 |
}
|
Este es un simple obtentor, aunque la matemática que se utiliza para el valor de retorno puede ser un poco confusa. Determina la posición de agarre actual como un porcentaje del rango de movimiento del agarre. Sería más obvio si fuera solo esto:
1 |
return _grip.x / _track.width; |
... Lo cual es razonable, pero no tiene en cuenta que el agarre en realidad no recorre todo el ancho de la pista. Va hasta el borde izquierdo, pero solo tan a la derecha como el borde derecho de la pista menos el ancho del agarre. Así que esto es más preciso:
1 |
return _grip.x / (_track.width - _grip.width); |
Esto hace que el ancho por el cual dividimos el rango de movimiento. Sin embargo, todavía puede haber un gotcha en el que la pista puede estar desplazada a la izquierda o a la derecha de su contenedor, lo que significa que los valores que obtenemos no son del todo correctos. Necesitamos neutralizar eso restando la posición x
de la pista de la x
del agarre, y terminamos con esto:
1 |
return (_grip.x - _track.x) / (_track.width - _grip.width); |
Como referencia, aquí está la clase completa, con mis divagaciones omitidas:
1 |
package com.activetuts.slider { |
2 |
|
3 |
import flash.display.*; |
4 |
import flash.events.*; |
5 |
import flash.geom.*; |
6 |
|
7 |
public class ActiveSlider extends EventDispatcher { |
8 |
|
9 |
private var _track:Sprite; |
10 |
private var _grip:Sprite; |
11 |
private var _grabOffset:Point; |
12 |
|
13 |
public function ActiveSlider(track:Sprite, grip:Sprite) { |
14 |
_track = track; |
15 |
_grip = grip; |
16 |
if (_grip.parent != _track.parent) { |
17 |
throw new Error("The track and the grip Sprites are not in the same container.") |
18 |
}
|
19 |
|
20 |
_grip.addEventListener(MouseEvent.MOUSE_DOWN, onDown); |
21 |
|
22 |
if (_grip.stage) { |
23 |
addStageListener(); |
24 |
} else { |
25 |
_grip.addEventListener(Event.ADDED_TO_STAGE, onAddedToStage); |
26 |
}
|
27 |
}
|
28 |
|
29 |
private function onAddedToStage(e:Event):void { |
30 |
_grip.removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage); |
31 |
addStageListener(); |
32 |
}
|
33 |
|
34 |
private function addStageListener():void { |
35 |
_grip.stage.addEventListener(MouseEvent.MOUSE_UP, onUp); |
36 |
}
|
37 |
|
38 |
private function onDown(e:MouseEvent):void { |
39 |
_grabOffset = new Point(e.localX, e.localY); |
40 |
_grip.addEventListener(Event.ENTER_FRAME, onFrame); |
41 |
}
|
42 |
|
43 |
private function onUp(e:MouseEvent):void { |
44 |
_grip.removeEventListener(Event.ENTER_FRAME, onFrame); |
45 |
}
|
46 |
|
47 |
private function onFrame(e:Event):void { |
48 |
_grip.x = _grip.parent.mouseX - _grabOffset.x; |
49 |
|
50 |
var gripBounds = _grip.getBounds(_grip.parent); |
51 |
var trackBounds = _track.getBounds(_grip.parent); |
52 |
|
53 |
if (gripBounds.x < trackBounds.x) { |
54 |
_grip.x = _track.x; |
55 |
} else if (gripBounds.right > trackBounds.right) { |
56 |
_grip.x = trackBounds.right - _grip.width |
57 |
}
|
58 |
|
59 |
trace(this.value); |
60 |
}
|
61 |
|
62 |
private function get value():Number { |
63 |
return (_grip.x - _track.x) / ((_track.width - _grip.width) - _track.x); |
64 |
}
|
65 |
|
66 |
}
|
67 |
|
68 |
}
|
Todavía no hemos agregado nuestra clase SliderEvent
; daremos un paso aparte para hacerlo. Pero primero, necesitamos nuestra clase de documento para poder usar ActiveSlider
.
Paso 21: La clase de documento
Necesitamos un archivo más para que funcione: nuestra clase de documento. Crea un nuevo archivo de clase denominado SliderDemo
en la carpeta raíz del proyecto. Agrega el código siguiente:
1 |
package { |
2 |
|
3 |
import flash.display.*; |
4 |
import flash.events.*; |
5 |
import com.activetuts.slider.ActiveSlider; |
6 |
|
7 |
public class SliderDemo extends Sprite { |
8 |
|
9 |
private var _slider:ActiveSlider; |
10 |
|
11 |
public function SliderDemo() { |
12 |
_slider = new ActiveSlider(track_mc, grip_mc); |
13 |
}
|
14 |
|
15 |
}
|
16 |
|
17 |
}
|
Esto es mucho más simple que nuestra clase ActiveSlider
. Realmente solo configura un ActiveSlider
en la propiedad denominada _slider
.
Vuelva al archivo FLA e ingrese SliderDemo en el campo de clase de documento, y deberías poder probarlo. El control deslizante debe poder moverse hacia adelante y hacia atrás, y restringirse al ancho de la pista.
Ahora para una última tarea. Necesitamos enviar y escuchar un evento SliderEvent.CHANGE
. Lo haremos a continuación.
Paso 22: Envío del SliderEvent
Vuelve a la clase ActiveSlider y realiza un cambio de una sola línea en el método onFrame
:
1 |
private function onFrame(e:Event):void { |
2 |
_grip.x = _grip.parent.mouseX - _grabOffset.x; |
3 |
|
4 |
var gripBounds = _grip.getBounds(_grip.parent); |
5 |
var trackBounds = _track.getBounds(_grip.parent); |
6 |
|
7 |
if (gripBounds.x < trackBounds.x) { |
8 |
_grip.x = _track.x; |
9 |
} else if (gripBounds.right > trackBounds.right) { |
10 |
_grip.x = trackBounds.right - _grip.width |
11 |
}
|
12 |
|
13 |
dispatchEvent(new SliderEvent(SliderEvent.CHANGE, this.value)); |
14 |
}
|
Hemos eliminado el rastro y lo hemos reemplazado con un envío de eventos real y en vivo. Sin embargo, para que esto funcione, necesitamos importar la clase SliderEvent
:
1 |
package com.activetuts.slider { |
2 |
|
3 |
import flash.display.*; |
4 |
import flash.events.*; |
5 |
import flash.geom.*; |
6 |
import com.activetuts.events.SliderEvent; |
Paso 23: Escuchar el SliderEvent
Finalmente, vuelve a la clase SliderDemo y cámbialo para que escuche y reaccione al SliderEvent
:
1 |
package { |
2 |
|
3 |
import flash.display.*; |
4 |
import flash.events.*; |
5 |
import com.activetuts.slider.ActiveSlider; |
6 |
import com.activetuts.events.SliderEvent; |
7 |
|
8 |
public class SliderDemo extends Sprite { |
9 |
|
10 |
private var _slider:ActiveSlider; |
11 |
|
12 |
public function SliderDemo() { |
13 |
_slider = new ActiveSlider(track_mc, grip_mc); |
14 |
_slider.addEventListener(SliderEvent.CHANGE, onSliderChange); |
15 |
}
|
16 |
|
17 |
private function onSliderChange(e:SliderEvent):void { |
18 |
trace(e); |
19 |
output_tf.text = e.sliderValue.toString(); |
20 |
}
|
21 |
|
22 |
}
|
23 |
|
24 |
}
|
Volvemos a importar la clase SliderEvent
y, después de crear ActiveSlider
, agregamos un agente de escucha llamado onSliderChange
al control deslizante. Ese método es la mayor adición, pero sigue siendo un oyente de eventos regular. Es muy parecido a cualquier otro oyente de eventos, solo que nos aseguramos de escribir el argumento del evento como SliderEvent
, porque eso es lo que estamos obteniendo.
La primera línea de código es un poco superflua, pero quería ver qué sucede cuando rastreas nuestro objeto SliderEvent
. Verás, cuando ejecute esto, un formato típico para Flash del objeto de evento.



La segunda línea hace lo que estábamos persiguiendo para empezar. Simplemente toma la propiedad sliderValue
, la conviertes en string
y luego pega ese string
en el textField
del escenario, para que podamos ver el valor del control deslizante en la película.
Paso 24: Terminando
Cuando empiezas a rodar tus propios eventos personalizados, se empieza a trabajar con ActionScript 3 de la forma en que estaba destinado a ser utilizado. Los eventos te ayudan a desacoplar las clases entre sí, y un flujo de eventos bien estructurado en tu aplicación realmente puede marcar la diferencia entre algo con lo que es fácil trabajar y algo que tiene errores y temperamental. Con esta (teóricamente) última entrega de AS3 101, deberías estar en camino de convertirte en un ninja.
¡Gracias por leer, y nos vemos aquí en Activetuts+ antes de que te des cuenta!