Ольга Корохина
Использование Google Protobuf: оберни и полетели.
Окт 14

Пост будет достаточно далёкий от Flex но зато на модную тему: будем использовать Google Protobuf.
На вопрос: “зачем?” ответ прост – мы хотим иметь возможность быстро подстраивать наше приложение (клиентскую часть сложной системы с бекендом на Java) под изменения протокола и не хотим при этом каждый раз плотно замарачиваться. Поэтому мы замарочимся один единственный раз – разберёмся что к чему, настроим всё, запустим, сгенерируем и научимся использовать.
Основная идея – иметь единый файл с описанием протокола (объектов передаваемых по нему) и генерироть согласно ему код и для бекенда и для фронтенда. Корпорация добра обещает, что если мы всё сделаем правильно то у нас магическим образом объекты будут передаваться по низкоуровнему протоколу (у меня их даже два – TCP и http/WebSockets) будучи обёрнутыми в наш кастомный протокол протобафом.
Для начала можно походить по интернету почитать что к чему, сходить на http://code.google.com/p/protobuf/downloads/list скачать оттуда, посмотреть версию на джаве. Понять что всё просто: создать файл с описанием объёктов которые пересылаем по нашему протоколу и сгенерировать согласно этому описанию код. Добавить его в джава-проект, немного уличной магии и всё работает. Проникнуться и захотеть уметь генерировать код на AS3 так же легко и просто как это делается для джавы. Всё что нам для этого нужно – сходить на http://code.google.com/p/protoc-gen-as3/downloads/detail?name=protoc-gen-as3-1.0.0-rc5-bin.tar.gz&can=2&q= и скачать архив. В нём всё что нам нужно для того чтобы сгенерировать код на AS3 кроме, естественно, файла с описанием объектов. Но раз мы уже генерироли код для джавы он у нас уже есть. Тут я приведу сильно упрощённую версию моего файла trans.proto:


package ru.codeflex.test.protobuf.protocol;

option java_package = "ru.codeflex.test.protobuf.protocol";
option java_outer_classname = "Message";

/**
   Frame - is a Protocol's transport packet, which is
   used by Server and Client for network communication.
   It consists of required Command code - command
   and optional payload (multiple fields).
   See available Commands description below for complete
   information about associated payload messages.

   Protobuf binary format does not provide build-in delimiters
   for detection of message boundaries. It is responsibility
   of target protocol - to delimit messages sent over the wire.

   In our protocol Frames are of variable size, so for determination
   of frame boundaries every Frame will have its length prepended
   as first uint32 value. So complete data structure will be:

   [uint32][Frame][uint32][Frame]...

   uint32 - is a base 128 Varint. Variable lenth integer.

   To learn more about Varint types visit official page
   http://code.google.com/apis/protocolbuffers/docs/encoding.html#varints

*/
message Frame {
   required Command command = 1;
   // payload
   optional Join join_message = 2;
   optional JoinAcknowledge join_acknowledge_message = 3;
   optional Error error_message = 7;
}

enum Command {

   JOIN = 5;                   // Send by the client who wants to join an existing (valid session_id) session.
                                  // If given session_id does not exist this message will be dropped silently.
                                  // The first joining client will become the host.
                                  // Payload: -> Join

   JOIN_ACK = 3;               // Send by the server to joining client in case of success.
                                  // If new client is being connected to an existing session
                                  // Payload: <- JoinAcknowledge
   ERROR = 99;                 // Send by the server in case of errors detected on server.
                                  // May be sent in response to any Client's command.
                                  // When triggered by the clients the message is dropped silently.
                                  // Payload: <- Error
}

// Client's role
enum Role {
   PLAYER = 92;
   // may be extended with additional roles in future
}

/**
 In order to successfully join the session, client must provide valid data:
 session_id  - id of existing (game) session provided by gface|engine
 gface_token - valid token, given by  gface|engine after successful authentication.
 Server will do additional check if provided token is enlisted as participant for given session_id
 and session has free slots. Otherwise it will reject Join request from client.
*/
message Join {
   optional Role role = 1 [default = PLAYER];
   required uint64 session_id = 2;
   required string gface_token = 3;
}

/**
 Send by the server after a player has joined.
 isHost is "true" if this player is a host, "false" otherwise.
 The slot is the player slot which is a zero based index
 and lower than the max number of players (0 <= slot < maxNoPlayers).
 */
message JoinAcknowledge {
   required uint32 slot = 1;
   optional bool is_host = 2 [default = false];
}
/**
  Error detected on server.
  code - see error codes list for possible error codes.
  msg - arbitrary message, describing occurred error.
*/
message Error {
   optional uint32 code = 1;
   optional string msg = 2;
}

uint32, uint64 – конвенции, при генерации по этому описанию кода в Джава uint64 например даёт Long, при генерации кода в AS3 почему-то мапится не на Number а на написанный разработчиками protoc-gen-as3 класс Uint64. Много странного вокруг нас :)
Для того чтобы получить по этому описанию код на Java нужно всего лишь запустить экзешник (для виндовой версии) или .sh файл скормив ему путь на наш trans.proto. Для того чтобы получить код на AS3 нужно запустить его же но с указанием дополнительных параметров плагина для генирации –plugin=protoc-gen-as3=protoc-gen-as3.bat и выходной папки для этого плагина –as3_out=as3. Я сделала generate_as3code.bat файл для того чтобы упростить всё это дело. К этому посту приаттачен архив со всем необходимым (generate_Javacode.bat для java) для генерации. Сорцы протобафа на Джаве можно взять со ссылки на гуглкод что я привела выше или позволить Мавену сделать это за вас, сорцы com.netease.protobuf.* я так же положила в архив к этому посту.
Итак, будем считать что у нас всё есть – как же нам добавить этот код в наш проект на AS3 и использовать его? Пример: имеем флеш-клиент общающийся с сервером по TCP. Есть некий кастомный сокет ProtoBufSocket extends Socket в конструкторе которого добавлен листенер addEventListener(ProgressEvent.SOCKET_DATA, onData); onData – функция в которой мы читаем из сокета, разбираем прочитанное и как-то реагируем – выглядит так:


private function onData(_event:ProgressEvent):void
    {
      
      var data:ByteArray = new ByteArray(); 
      if(this.bytesAvailable > 0)readBytes(data);
      
      var mess:Frame = new Frame();
      mess.mergeDelimitedFrom(data);//length included! 
      
      if (mess.command == Command.GF_JOIN_ACK)//joined ok, accept 
      {
        
        //process somehow mess.joinAcknowledgeMessage
        return;
      }
      
      else if (mess.command == Command.GF_ERROR)
      {
        //process somehow mess.errorMessage.msg
        return;
      }
      else{
        //throw new Error("Undefined protocol command:" + command);
        return;
      }
      
    }

В этой функции мы преобразовываем массив байтов в протобафовую “единицу” Frame. Для того чтобы записать в сокет нам нужно произвести обратную операцию – разобрать Frame на байты и послать по сокету. Это делаем в функции writeFrameToSocket:

public function writeFrameToSocket(frame:Frame):void{
      
      if(!this.connected) return;
      
      var buf:WritingBuffer = new WritingBuffer();  
      
      frame.writeToBuffer(buf);
      
      var t:ByteArray = new ByteArray();
      buf.toNormal(t)  
        
      flushLoggedWW(t);
      
    }

private function flushLoggedWW(_buf:ByteArray):void{
    
      var logBuff:WritingBuffer = new WritingBuffer();
      
      WriteUtils.write$TYPE_UINT32(logBuff, _buf.length); 
      
      logBuff.writeBytes(_buf);
      logBuff.position=0;
      
      writeBytes(logBuff);
      
      flush();
    }

Пример того как можно сформировать и послать по сокету сообщение в инстанце-родителе:


private var serverConnection:ProtoBufSocket;
...
serverConnection = new ProtoBufSocket(_serverAddress, _serverPort, this);
...
var mess:Frame = new Frame();
mess.command = Command.GF_JOIN;
mess.joinMessage = new Join();
        
mess.joinMessage.sessionId = UInt64.parseUInt64(gameSessionId.toString());
      
mess.joinMessage.gfaceToken = "here-some-token";
      
serverConnection.writeFrameToSocket(mess);

На примере приведённого тут очень прстого протокола сложно оценить все преимущества использования протофа но поверьте, в условиях сложного протокола в большом и сложном проекте протобаф оправдан целиком и полностью.

P.S. Огромное спасибо сеньёру Алексею Макаренко за то что правильно пнул меня в направлении Google Protobuf.

Обещанный архив можно скачать отсюда.

Ольга Корохина
Фронтальная камера на смартфонах и планшетах: немного уличной магии
Сен 12

Если вы занимаетесь разработкой на Flex под мобильные платформы вы уже знаете: если у смартофона/планшета 2 камеры то доступна из них одна, и как раз не фронтальная. Не могу даже предствить себе мощность потока космических поносных лучей полученных сотрудниками любимой нашей компании Adobe по этому поводу… лично я рассматривала массу вариантов как это обойти. А счастье было так рядом :) Итак, сегодня мы будем брать судьбу в свои руки – управлять камерами.
Первое что нам нужно – прочитать рецепт http://cookbooks.adobe.com/post_How_do_I_access_the_front_facing_camera_on_a_mobil-19312.html . Выглядит всё просто – думаешь а почему же у меня так не получилось? Ответ прост – потому что в рецепте используются классы которые “появятся” в 3-м эйре и 11-м плеере. Нас это не остановит, правда же? Мы идём в лабсы и качаем релиз кандидаты (это уже релиз кандидаты!) http://labs.adobe.com/technologies/flashplatformruntimes/air3/ – это эйр и http://labs.adobe.com/technologies/flashplatformruntimes/flashplayer11/ – плеер. Как их ставить не секрет: ставим плеер, ставим эйр, качаем эйровое сдк и разбаляем им свой 4.5.1 сдк на который настроен билдер (если ещё ни разу этого не делали – вот инструкция).
Не забываем в дескрипшене приложения поменять старую версию Эйра на 3.0 и в аргументах компилятора добвляем -swf-version=13. Стартуем. Ура! Всё собирается! Экспортируем в .apk и понимаем что что-то не так :) Например для Андроида последняя релизная версия эйра 2.7 а никак не желанная 3.0. Попытки системы обновить эйр из меркета или из броузера ни к чему не приводят – 3.0 рантайма для мобильных платформ просто пока нет. Не всё потерянно – нужно просто научиться упаковывать рантайм и нужный кусок sdk вместе с приложением (как это делается при экспорте под iOS).
Для того чтобы сделать это правильно я советую немного отвлечься от этой статьи и почитать “классиков”:
http://www.tricedesigns.com/2011/08/10/air-3-0-captive-runtime/
http://help.adobe.com/en_US/air/build/WS901d38e593cd1bac1e63e3d128cdca935b-8000.html
http://www.remotesynthesis.com/post.cfm/cool-stuff-with-the-flash-platform-8-19-2011

Итого: общая идея собирать и подписывать .apk не билдером а напрямую adt- сборщиком из sdk указав ему в качестве таргета при сборке параметр apk-captive-runtime. Учитывая сколько раз вам прийдётся запустить его пока наконец вы не достигнете 11-го дана советую сразу сделать .bat файл для винды или .sh для макоси. Сегодня я сидела на винде поэтому пока у меня есть только бат-файл, который лежит в корне моего проекта и у которого внутри следующее:


"C:Program Files (x86)AdobeAdobe Flash Builder 4.5sdks4.5.1binadt.bat" -package -target apk-captive-runtime -storetype pkcs12 -keystore ModularMVC.p12 bin-debug/nurse.apk VirtualNurse-pack.xml bin-debug/VirtualNurse.swf bin-debug/assets/* bin-debug/images/* data.db 

при выполнении запрашивается пароль сертификата- вводим его и нажимаем энтер.

Много приятных минут я получила пытаясь понять где и как должен лежать файл с базой data.db. Указывала ему путь и так и эдак, клала его в ассетс…в итоге плюнула и положила в корень проекта – там же где лежит батник для сборки. В коде он используется так:

static public var dbFileURL:String = "data.db";
....
var dbFile:File = File.applicationDirectory;          
dbFile = dbFile.resolvePath(dbFileURL);

на железке работает.
Так же вам потребуется немного изменить дескриптор приложения, поэтому советую скопировать текущий под другим именем и править в нём – дабы не поломать проект под билдер (у меня тут это файл VirtualNurse-pack.xml, лежит в корне как и батник для сборки и файл базы). Потребуется изменить:
1) Изменить значения тегов <id>, <filename> и <name> на более удобоваримые;
2) в блоке <initialWindow> в теге <content> приписать относительный путь к .swf файлу, у меня это bin-debug/VirtualNurse.swf
3) поприписывать аналогично пути для иконок в блоке <icon> (у меня

<image16×16>bin-debug/assets/nurseIcons/16.png</image16×16>

и т.д.)

Собираем проект батником, получаем файл примерно на 7 Mb тяжелее предыдущей версии (рантайм теперь включён в приложение) и тестируем его на железке. Вуаля! Фронтальная камера заработала.

P.S. Ждём когда эдобы зарелизят 3-й эйр для мобильных платформ и пересобираем приложение билдером без мерджа рантайма – лишние мегабайты должны уйти. Точной инсайдерской информации о том когда это случится у меня сейчас нет.

Ольга Корохина
NumericStapper на героине: гимми мо!
Авг 29

Продолжаем допиливать спарк-компоненты для использования в мобильный приложениях. Сегодня пилим NumericStapper.
Пилить его нужно в любом случае- кнопки “вверх-вниз” из коробки слишком маленькие для того чтобы тапать по ним пальцами, они отлично увеличиваются – но вот стрелочки-значки в них нет. Доктор прописывает кастомный скин.
Добавляем в файл стилей:

s|NumericStepper{

  skinClass: ClassReference("skins.CustomNumericStapperSkin");
}

CustomNumericStapperSkin.mxml выглядит так:


<?xml version="1.0" encoding="utf-8"?>
<s:SparkSkin xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark"
      xmlns:fb="http://ns.adobe.com/flashbuilder/2009" minHeight="92" minWidth="140" 
      alpha.disabled="0.5">

    <fx:Metadata>
    <![CDATA[ 
        /** 
         * @copy spark.skins.spark.ApplicationSkin#hostComponent
         */
        [HostComponent("spark.components.NumericStepper")]
    ]]>
    </fx:Metadata> 
    
    <fx:Script fb:purpose="styling">
        /* Define the skin elements that should not be colorized. 
           For numeric stepper, the skin itself is colorized but the individual parts are not. */
        static private const exclusions:Array = ["textDisplay", "decrementButton", "incrementButton"];

        /**
         * @private
         */  
        override public function get colorizeExclusions():Array {return exclusions;}
        
        /**
         * @private
         */
        override protected function initializationComplete():void
        {
            useChromeColor = true;
            super.initializationComplete();
        }
        
        private var cornerRadiusChanged:Boolean;
        private var borderStylesChanged:Boolean;
        
        /**
         *  @private
         */
        override protected function commitProperties():void
        {
            super.commitProperties();
            
            if (cornerRadiusChanged)
            {
                var cr:Number = getStyle("cornerRadius");
                if (incrementButton)
                    incrementButton.setStyle("cornerRadius", cr);
                if (decrementButton)
                    decrementButton.setStyle("cornerRadius", cr);
                cornerRadiusChanged = false;
            }
            
            if (borderStylesChanged)
            {
                textDisplay.setStyle("borderAlpha", getStyle("borderAlpha"));
                textDisplay.setStyle("borderColor", getStyle("borderColor"));
                textDisplay.setStyle("borderVisible", getStyle("borderVisible"));
                borderStylesChanged = false;
            }
        }
        
        /**
         *  @private
         */
        override public function styleChanged(styleProp:String):void
        {
            var allStyles:Boolean = !styleProp || styleProp == "styleName";

            super.styleChanged(styleProp);
            
            if (allStyles || styleProp == "cornerRadius")
            {
                cornerRadiusChanged = true;
                invalidateProperties();
            }
            
            if (allStyles || styleProp.indexOf("border") == 0)
            {
                borderStylesChanged = true;
                invalidateProperties();
            }
        }
    </fx:Script>
    
    <s:states>
        <s:State name="normal" />
        <s:State name="disabled" />
    </s:states>

    <s:Button id="incrementButton" right="0" top="0" height="50%" tabEnabled="false"  minWidth="46"
              skinClass="skins.NumericStepperIncrementButtonSkin" />
              
    <s:Button id="decrementButton" right="0" bottom="0" height="50%" tabEnabled="false"  minWidth="46" 
              skinClass="skins.CustomNumericStepperDecrementButtonSkin"  />
                           
    <s:TextInput id="textDisplay" left="0" top="0" right="46" bottom="0"
        skinClass="spark.skins.spark.NumericStepperTextInputSkin" />
        
</s:SparkSkin>

Стандартные скины для кнопок поражают вображение способом рисования стрелок, например:

<s:Path horizontalCenter="0" verticalCenter="0" id="arrow" 
      data="M 3.0 3.0 L 3.0 2.0 L 4.0 2.0 L 4.0 1.0 L 5.0 1.0 L 5.0 0.0 L 0.0 0.0 L 0.0 1.0 L 1.0 1.0 L 1.0 2.0 L 2.0 2.0 L 2.0 3.0 L 3.0 3.0">

Ноу комментс! Делаем свои:

CustomNumericStepperDecrementButtonSkin.mxml

<?xml version="1.0" encoding="utf-8"?>

<s:SparkSkin xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark"
       xmlns:fb="http://ns.adobe.com/flashbuilder/2009">
  
  <fx:Metadata>
    <![CDATA[ 
    /** 
    * @copy spark.skins.spark.ApplicationSkin#hostComponent
    */
    [HostComponent("spark.components.Button")]
    ]]>
  </fx:Metadata> 
  
  <fx:Script fb:purpose="styling">
    /* Define the skin elements that should not be colorized. 
    For NumericStepper buttons, the graphics are colorized but the arrow is not. */
    static private const exclusions:Array = ["arrow"];
    
    /**
     * @private
     */  
    override public function get colorizeExclusions():Array {return exclusions;}
    
    /* Define the symbol fill items that should be colored by the "symbolColor" style. */
    static private const symbols:Array = ["arrowFill"];
    
    /**
     * @private
     */
    override public function get symbolItems():Array {return symbols};
    
    /**
     * @private
     */
    override protected function initializationComplete():void
    {
      useChromeColor = true;
      super.initializationComplete();
    }
    
    /**
     *  @private
     */
    override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number) : void
    {
      var cr:Number = getStyle("cornerRadius");
      
      if (cornerRadius != cr)
      {
        cornerRadius = cr;
        fill.bottomRightRadiusX = cornerRadius;
        highlight.bottomRightRadiusX = cornerRadius;
      }
      
      super.updateDisplayList(unscaledWidth, unscaledHeight);
    }
    
    private var cornerRadius:Number = 2;
  </fx:Script>
  
  <s:states>
    <s:State name="up" />
    <s:State name="over"/>
    <s:State name="down" />
    <s:State name="disabled" />
  </s:states>
  
  <s:Group left="0" top="0" right="0" bottom="0">
    
    <!-- border/fill -->
    <!--- @private -->
    <s:Rect id="fill" left="0" top="0" right="0" bottom="0" width="18" height="16" bottomRightRadiusX="2">
      <s:stroke>
        <s:SolidColorStroke color="0x686868" weight="1"/>
      </s:stroke>
      <s:fill>
        <s:LinearGradient rotation="90">
          <s:GradientEntry color="0xE8E8E8"
                   color.over="0xC2C2C2"
                   color.down="0xAEB0B1" />
          <s:GradientEntry color="0xDFDFDF"
                   color.over="0xADAEAF"
                   color.down="0xA1A3A5" />
        </s:LinearGradient>
      </s:fill>
    </s:Rect>    
    
    <!-- highlight -->
    <!--- @private -->
    <s:Rect id="highlight" left="1" top="1" right="1" bottom="1" bottomRightRadiusX="2">
      <s:stroke>
        <s:LinearGradientStroke rotation="90" weight="1">
          <s:GradientEntry color="0xFFFFFF" 
                   color.down="0x000000" 
                   alpha="0.55"
                   alpha.over="0.55" 
                   alpha.down="0.15" />
          <s:GradientEntry color="0xFFFFFF" 
                   color.down="0x000000"
                   alpha="0.2475"
                   alpha.over="0.2475"
                   alpha.down="0" />
        </s:LinearGradientStroke>
      </s:stroke>
    </s:Rect>
    
    <!-- shadow -->
    <s:Rect left="1" top="2" right="1" height="1" includeIn="down">
      <s:fill>
        <s:SolidColor color="0x000000" alpha="0.07" />
      </s:fill>
    </s:Rect>
  </s:Group>
  
  <!-- arrow -->
  <!--- Defines the appearance of the down arrow. -->
  
  <s:Path horizontalCenter="0" verticalCenter="0" id="arrow"  scaleX="2" scaleY="2"
  data="M 0.0 0.0 L 5.0 5.0 L 10.0 0.0 L 0.0 0.0">
  
    <s:fill>
      <!--- @private
      Defines the down arrow's fill. The default color is 0x000000. The default alpha is .8. -->
      <s:SolidColor id="arrowFill" color="0" alpha="0.8" />
    </s:fill>
  </s:Path>            
  
</s:SparkSkin>

NumericStepperIncrementButtonSkin.mxml так:

<?xml version="1.0" encoding="utf-8"?>

<s:SparkSkin xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark"
       xmlns:fb="http://ns.adobe.com/flashbuilder/2009">
  
  <fx:Metadata>
    <![CDATA[ 
    /** 
    * @copy spark.skins.spark.ApplicationSkin#hostComponent
    */
    [HostComponent("spark.components.Button")]
    ]]>
  </fx:Metadata> 
  
  <fx:Script fb:purpose="styling">
    /* Define the skin elements that should not be colorized. 
    For NumericStepper buttons, the graphics are colorized but the arrow is not. */
    static private const exclusions:Array = ["arrow"];
    
    /**
     * @private
     */
    override public function get colorizeExcmW62'&&WGW&W6^usions;}
    
    /* Define the symbol fill items that should be colored by the "symbolColor" style. */
    static private const symbols:Array = ["arrowFill"];
    
    /**
     *  @private
     */
    override public function get symbolItems():Array {return symbols};
    
    /**
     * @private
     */
    override protected function initializationComplete():void
    {
      useChromeColor = true;
      super.initializationComplete();
    }
    
    /**
     *  @private
     */
    override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number) : void
    {
      var cr:Number = getStyle("cornerRadius");
      
      if (cornerRadius != cr)
      {
        cornerRadius = cr;
        fill.topRightRadiusX = cornerRadius;
        highlight.topRightRadiusX = cornerRadius;
      }
      
      super.updateDisplayList(unscaledWidth, unscaledHeight);
    }
    
    private var cornerRadius:Number = 2;
  </fx:Script>
  
  <s:states>
    <s:State name="up" />
    <s:State name="over"/>
    <s:State name="down" />
    <s:State name="disabled" />
  </s:states>
  
  <s:Group left="0" top="0" right="0" bottom="0">
    
    <!-- border/fill -->
    <!--- @private -->
    <s:Rect id="fill" left="0" top="0" right="0" bottom="0" width="18" height="16" topRightRadiusX="2">
      <s:stroke>
        <s:SolidColorStroke color="0x686868" weight="1"/>
      </s:stroke>
      <s:fill>
        <s:LinearGradient rotation="90">
          <s:GradientEntry color="0xFFFFFF"
                   color.over="0xC2C2C2"
                   color.down="0xAEB0B1" />
          <s:GradientEntry color="0xF0F0F0"
                   color.over="0xADAEAF"
                   color.down="0xA1A3A5" />
        </s:LinearGradient>
      </s:fill>
    </s:Rect>
    
    <!-- highlight -->
    <!--- @private -->
    <s:Rect id="highlight" left="1" top="1" right="1" bottom="1" topRightRadiusX="2">
      <s:stroke>
        <s:LinearGradientStroke rotation="90" weight="1">
          <s:GradientEntry color="0xFFFFFF" 
                   color.down="0x000000"
                   alpha.over="0.55" 
                   alpha.down="0.15" />
          <s:GradientEntry color="0xFFFFFF" 
                   color.down="0x000000"
                   alpha="0.45"
                   alpha.over="0.2475"
                   alpha.down="0" />
        </s:LinearGradientStroke>
      </s:stroke>
    </s:Rect>
    
    <!-- shadow -->
    <s:Rect left="1" top="2" right="1" height="1" incmVFT&Fv#Р3fР36ƖD6"6##"#r"Р3fР3&V7CР3w&WРР'&rРFVfW2FRV&6RbFRW'&rРР3F&F6VFW##"fW'F66VFW##"C&'&r"66U#""66U#" РFF$RRRR#Р3fР&fFPРFVfW2FRW'&rw2fFRFVfVB6"2FRFVfVB2Р36ƖD6"C&'&tf"6##"#"Р3fР3FРУ37&6

Выглядит получше и тоже работает :)

Ольга Корохина
Чего не хватает для полного мобильного счастья: ComboBox своими руками.
Авг 26

Как предупреждает горячо мною любимая компания Adobe в доках к спарк-компонентам, большинство из них не оптимизированно для использования в Hero. На свой страх и риск конечно можно их использовать – основательно обработав напильником. А можно “написать” свои. Сегодня мы будем делать второе – “писать” ComboBox.
Если вы уже пробовали использовать стандартные компоненты с выпадающими частями (DataField, ComboBox etc.), вы уже знаете что выпадающие запчасти выпадают где угодно – выше, ниже, левее-правее – но только не там где им положено. Можно пронаследоваться от этих компонентов и пытаться выяснить что в них не так, можно воспользоваться SkinnablePopUpContainer и собрать свой собственный компонент. Этим и займёмся.
Нам нужно текстовое поле, реагирующее на щелчёк мыши:


pillDropDown = new TextInput();
      pillDropDown.x = 120;
      pillDropDown.y = 10;
      pillDropDown.width = 300;
      pillDropDown.height = 50;
      
      addChild(pillDropDown);
      
      pillDropDown.addEventListener(MouseEvent.CLICK, onMedClick);

Хендлер:

private function onMedClick(evt:Event):void{
    
      comboPopUp = new ListPopupWindow();
      comboPopUp.dp = new ArrayList(DataBaseController.allDBData[DataBaseController.MEDS_TABLE_NAME]);
      comboPopUp.addEventListener(ComboEvent.COMBO_CLOSED,comboHandler);
      comboPopUp.open(this,true);
    
    }

Класс для события:

public class ComboEvent extends Event
  {
    public static const COMBO_CLOSED:String = "combo_closed";
    
    public var picked:Object;
    
    public function ComboEvent(type:String, picked:Object)
    {
      super(type);
      this.picked = picked;
    }
  }

Сам ListPopupView выглядит так:

<?xml version="1.0" encoding="utf-8"?>
<s:SkinnablePopUpContainer xmlns:fx="http://ns.adobe.com/mxml/2009"
               xmlns:s="library://ns.adobe.com/flex/spark"
               width="350" height="400" 
               creationComplete="skinnablepopupcontainer1_creationCompleteHandler(event)" xmlns:mx="library://ns.adobe.com/flex/mx">
  <fx:Script>
    <![CDATA[
      import utils.ComboEvent;
      
      import mx.collections.ArrayList;
      import mx.events.FlexEvent;
      import mx.managers.PopUpManager;
      import spark.events.IndexChangeEvent;
      
      [Bindable]
      public var dp:ArrayList;
      
      protected function skinnablepopupcontainer1_creationCompleteHandler(event:FlexEvent):void
      {
        PopUpManager.centerPopUp(this);
      }
      
      protected function list_changeHandler(event:IndexChangeEvent):void
      {
        dispatchEvent(new ComboEvent(ComboEvent.COMBO_CLOSED,list.selectedItem));
        this.close();
      }
      
      
    ]]>
  </fx:Script>
  <fx:Declarations>
  </fx:Declarations>
  <s:Group width="100%" height="100%" >
    
    <s:Rect id="rect2" radiusX="8" radiusY="8" top="0" right="0" bottom="0" left="0">
      <s:fill>
        <s:SolidColor color="0xeeeeee" />
      </s:fill>
      <s:stroke>
        <s:SolidColorStroke color="0x222222" weight="2"/>
      </s:stroke>
    </s:Rect>
    <s:HGroup width="100%" height="100%" paddingBottom="5" paddingLeft="5" paddingRight="5" paddingTop="5" >
    <s:VGroup width="300" height="400" id="mainGroup" >
    
      <s:List id="list" width="100%" dataProvider="{dp}"  labelField="title" change="list_changeHandler(event)" />
    
    </s:VGroup>
    
    <s:VScrollBar viewport="{mainGroup}" height="400"  visible="{mainGroup.contentHeight > mainGroup.height}" />
    </s:HGroup>        
  </s:Group>
</s:SkinnablePopUpContainer>

И это работает :)

Ольга Корохина
LCCS не для всех: VerifyError: Error #1014 и Flex SDK 4.5.1
июля 21

В продолжение прошлого поста: ипользуем Abode LiveCircle Collaboration Service, страдаем, не сдаёмся и побеждаем.
Допустим у нас есть свежепоставленный Flash Builder 4.5 и мы хотим сделать видеочат для Android 3.0. Казалось бы чего уж проще – интернеты пестрят скринкастами, step-by-step инструкциями, видео тьюториталами по этому поводу. Бери и повторяй.
Вот я беру и повторяю: http://tv.adobe.com/watch/adc-presents/video-chat-with-livecycle-collaboration-services-lccs/.
Итак я создаю мобильный проект на основе видов, только для Андроида.
Устанавливаю менеджер сдк (шаги для того чтобы завести этот зверинец подробно описаны в http://www.adobe.com/devnet/flashplatform/services/collaboration.html), в нём выбираю lccs.swc для 10.2 плеера, подключаю её в проект.
Довольно быстро оказывается, что для com.adobe.rtc.session.ConnectSessionContainer я не могу задать ширину и высоту да и вообще собрать не могу потому что билдер считает com.adobe.rtc.session.ConnectSessionContainer не визуальным копонентом. Смотрю в него – он наследник mx.containers.Canvas которого у меня, несмотря на подключенный неймспейс xmlns:mx=”library://ns.adobe.com/flex/mx”, нет. Сначала я в силу лени создаю инстанц ConnectSessionContainer в листенере события creationComplete моего компонента и вкладываю его в некий UIComponent. Собрать я теперь могу – но получаю ошибку 1014, в точности как автор крика души на http://forums.adobe.com/message/3664748 . Тут я наконец понимаю что всё плохо потому что у нас чего-то (почти всего) не хватает в пекадже mx в либах включённых в проект. Здорово что у меня 4.5 билдер, в Package Explorer открываю библиотечки SDK 4.5.1 и вижу, что в либе framework.swc в mx.containers имеем только errors и utilityClasses. Где же наш канвас, так нужный нам и SystemManager-у? Он в отдельной либе которая не включена в структуру мобильного проекта, sdks\4.5.1\frameworks\libs\mx\mx.swc . Подключаем её и всё чудестным образом заводится.
Чем думал тот разработчик который принимал решение не включать устаревшие визуальные компоненты в мобильный проект ясно. Как ясно и чем думал неведомый мне (вероятно индусский?) рзработчик систем менеджера ожидающего именно канвас и ничего кроме канваса в 284-й строке. Одно мы можем знать наверняка: он сидит на винде и флексокодит в папку E:\dev\hero_private\fr ameworks\projects\framework\src\mx\managers\ . То-есть он уже наказан, нам остаётся только простить его :)

« Previous Entries