На этой неделе я играл с мобильными рендерами для покупателей. Я хотел воспроизвести классический мобильный жест: проведите пальцем справа налево по элементу списка, чтобы отобразить пользовательские действия. Расширение средств визуализации мобильных элементов — это очень весело с Flex 4.5. При этом для обмена данными между списком и вашими средствами визуализации вам необходимо реализовать микроархитектуру на основе событий. С Flex 4.5 и новой парадигмой «View Navigator» это очень просто. Я выбрал глобальный диспетчер событий. Во-первых, позвольте мне показать вам окончательный результат на моих устройствах Android и на моих устройствах iOS.
Вот изображение, которое суммирует архитектуру этого приложения Flex 4.5:
<ViewNavigatorApplication>
Мое основное приложение использует новую архитектуру ViewNavigatorApplication. Он автоматизирует переходы между представлением, сохранением ваших данных, навигацией, компонентами actionBar и т. Д. Более подробная информация здесь: http://www.adobe.com/devnet/flex/articles/mobile-development-flex-flashbuilder.html.
Вместо того, чтобы автоматически выдвигать первое представление, я запускаю HTTPRequest, чтобы получить список сотрудников (файл XML, хранящийся на моем сервере). Кстати, я использую этот URL: http://www.riagora.com/sfdc/employees.xml , не стесняйтесь использовать его для своих собственных демонстраций. Когда я получаю ResultEvent, я выдвигаю первое представление, передающее event.result, для подачи объекта данных.
protected function employeeService_resultHandler(event:ResultEvent):void { navigator.pushView(views.sfdcempHomeView, event.result as ArrayCollection); }
Я также объявил объект события EventDispatcher. Это станет моим центром событий для всего приложения. Мне это нужно для отправки событий между средствами визуализации элементов и списком моего первого просмотра.
dispatcher = new EventDispatcher;
<Просмотры> и события
Мой первый вид отображает список сотрудников. DataProvider для моего <s: List> просто привязан к объекту {data}, поскольку он содержит ArrayCollection, выдвинутый моим основным приложением. Интересный код находится в настраиваемом визуализаторе элементов для моего списка.
Вот исходный код моего пользовательского средства визуализации элементов:
<?xml version="1.0" encoding="utf-8"?> <s:IconItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009" creationComplete="iconitemrenderer1_creationCompleteHandler(event)" xmlns:s="library://ns.adobe.com/flex/spark" height="100" labelField="firstName" messageField="title" iconField="picture" iconWidth="64" iconHeight="64" xmlns:views="views.*" > <fx:Script> <![CDATA[ import events.ScrollingEvent; import mx.events.EffectEvent; import mx.events.FlexEvent; import mx.events.MoveEvent; import spark.components.Button; import spark.components.List; import spark.events.ListEvent; private var FLAGSTATE:int = 2; protected function iconitemrenderer1_creationCompleteHandler(event:FlexEvent):void { Multitouch.inputMode = MultitouchInputMode.GESTURE; this.addEventListener(TransformGestureEvent.GESTURE_SWIPE, onSwipe); this.parentApplication.dispatcher.addEventListener(ScrollingEvent.SCROLLING_STARTED, onScrollAgain); wipeEffect.addEventListener(EffectEvent.EFFECT_END, onEffectEnd); wipeEffectOut.addEventListener(EffectEvent.EFFECT_END, onEffetEndOut); } protected function onSwipe(event:TransformGestureEvent):void { var myScrollEvent:ScrollingEvent = new ScrollingEvent(ScrollingEvent.SCROLLING_STARTED); if((event.offsetX == -1) && (FLAGSTATE == 2)){ this.addChild(actBar); actBar.width = this.width; actBar.height = this.height; actBar.visible = true; actBar.theData = data; actBar.addEventListener(FlexEvent.CREATION_COMPLETE, onItemComplete); FLAGSTATE = 0; this.parentApplication.dispatcher.dispatchEvent(myScrollEvent); }else{ if ((event.offsetX == -1)){ wipeEffect.play(); this.parentApplication.dispatcher.dispatchEvent(myScrollEvent); } } } protected function onItemComplete(event:FlexEvent):void { wipeEffect.play(); } private function onScrollAgain(event:ScrollingEvent):void { trace("scrolling"); if (FLAGSTATE == 1){ wipeEffectOut.play(); } } protected function onEffectEnd(event:EffectEvent):void { FLAGSTATE = 1; } protected function onEffetEndOut(event:EffectEvent):void { FLAGSTATE = 0; } ]]> </fx:Script> <fx:Declarations> <s:HGroup id="actBar2"> <s:Button id="btn1" label="action1"/> <s:Button label="action2"/> </s:HGroup> <s:Parallel id="wipeEffect" target="{actBar}"> <!--<s:Fade duration="800" alphaFrom="0.7" alphaTo="1" />--> <s:Move duration="300" xFrom="{this.width}" xTo="0"/> </s:Parallel> <s:Parallel id="wipeEffectOut" target="{actBar}"> <!--<s:Fade duration="800" alphaFrom="0.7" alphaTo="1" />--> <s:Move duration="150" xTo="{this.width}" xFrom="0"/> </s:Parallel> <views:ActionBG id="actBar" width="{this.width}" height="{this.height}"/> </fx:Declarations> </s:IconItemRenderer>
Во-первых, я добавляю слушателей событий, чтобы ловить события жестов «свайп». Цель состоит в том, чтобы отобразить «панель инструментов» с четырьмя потенциальными действиями (четыре кнопки). Если пользователь проводит пальцем по элементу, я проверяю направление жеста (если event.offsetX == -1, то смахивание было справа налево) и проверяю, отображается ли панель инструментов. Если нет, я создаю экземпляр нового объекта ActionBG (пользовательский компонент) и отображаю его поверх средства визуализации элементов с эффектом «Перемещение». По соображениям удобства использования мне пришлось обратить внимание на продолжительность этих эффектов Move и использовать переменную flag (FLAGSTATE) для описания текущего состояния моей панели инструментов.
Мой код средства визуализации элементов просто содержит функции для управления визуальными состояниями. Мой компонент actionBG (панель инструментов) отображает четыре кнопки действий. Давайте посмотрим на код этого компонента.
<?xml version="1.0" encoding="utf-8"?> <s:Group xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" width="400" height="150"> <fx:Script> <![CDATA[ import events.ScrollingEvent; import mx.events.FlexEvent; import valueObjects.Employee; public var theData:Object; protected function button1_clickHandler(event:MouseEvent):void { // DISPLAY the full name of the employee myTI.text = theData.id + ": " + theData.firstName + " " + theData.lastName; } protected function button2_clickHandler(event:MouseEvent):void { // TAP TO PUSH A NEW VIEW WITH Employee's details var tapEvent:ScrollingEvent = new ScrollingEvent(ScrollingEvent.TAP_ACTION); tapEvent.userObj = theData as Employee; this.parentApplication.dispatcher.dispatchEvent(tapEvent); } protected function button3_clickHandler(event:MouseEvent):void { // Remove the employee from the list var removeEvent:ScrollingEvent = new ScrollingEvent(ScrollingEvent.DELETE_ACTION); removeEvent.userId = int(theData.id); this.parentApplication.dispatcher.dispatchEvent(removeEvent); } ]]> </fx:Script> <fx:Declarations> <!-- Place non-visual elements (e.g., services, value objects) here --> <!--<s:DropShadowFilter inner="true" blurY="120" alpha="0.2" angle="90" distance="20" id="innerS"/> --> </fx:Declarations> <s:BitmapImage width="100%" height="100%" source="@Embed('assets/pattern.png')" fillMode="repeat" /> <s:HGroup verticalAlign="middle" horizontalAlign="center" width="100%" height="100%"> <s:Button icon="@Embed('assets/icon1.png')" click="button1_clickHandler(event)"/> <s:Button icon="@Embed('assets/icon3.png')" click="button2_clickHandler(event)"/> <s:Button icon="@Embed('assets/icon2.png')"/> <s:Button icon="@Embed('assets/icon4.png')" click="button3_clickHandler(event)"/> </s:HGroup> <s:Label id="myTI" text="" bottom="3" horizontalCenter="0" backgroundColor="0x444444" color="0xFFFFFF"/> </s:Group>
Этот компонент имеет открытую переменную с именем «theData». Когда я создаю экземпляр компонента, я могу передать некоторые данные о сотруднике. Если пользователь нажимает вторую кнопку, приложение должно выдвинуть новое представление, чтобы отобразить некоторые сведения о выбранном сотруднике. Чтобы информировать мое мнение, я использую объект диспетчера, созданный в основном документе приложения. Чтобы достичь этого, я просто использую свойство parentApplication. Я также создал пользовательское событие с именем ScrollingEvent. Я использую эту технику для второй кнопки, а также для последней кнопки, которая используется для удаления элемента из списка (посмотрите в коде функции обработчика нажатий button2 и button3).
На мой взгляд, мне просто нужно повторно использовать объект Dispatcher и прослушивать эти пользовательские события.
protected function myList_creationCompleteHandler(event:FlexEvent):void { // TODO Auto-generated method stub this.parentApplication.dispatcher.addEventListener(ScrollingEvent.TAP_ACTION, onTapItem); this.parentApplication.dispatcher.addEventListener(ScrollingEvent.DELETE_ACTION, onDeleteAction); } private function onTapItem(event:ScrollingEvent):void { // TODO Auto Generated method stub this.parentApplication.dispatcher.removeEventListener(ScrollingEvent.TAP_ACTION, onTapItem); this.parentApplication.dispatcher.removeEventListener(ScrollingEvent.DELETE_ACTION, onDeleteAction); navigator.pushView(views.EmployeeDetailsView, event.userObj); } private function getItemIndexByProperty(array:ArrayCollection, property:String, value:String):Number { for (var i:Number = 0; i < array.length; i++) { var obj:Object = Object(array[i]) if (obj[property] == value) return i; } return -1; } private function onDeleteAction(event:ScrollingEvent):void { // TODO Auto Generated method stub var userIndex:int = getItemIndexByProperty(myEmployees, "id", String(event.userId)); myEmployees.removeItemAt(userIndex); }
ScrollingEvent
Последняя проблема с удобством использования: мне нужно удалить панель инструментов в средстве визуализации элементов, когда пользователь снова прокручивает список, или если он проводит другим средством визуализации элементов. Для этого я использовал новое событие, доступное в компоненте List: события touchInteraction. Опять же, я использую свой глобальный диспетчер, чтобы сообщить средству визуализации, что оно должно скрывать панель инструментов.
На мой взгляд:
protected function myList_touchInteractionStartHandler(event:TouchInteractionEvent):void { var myScrollEvent:ScrollingEvent = new ScrollingEvent(ScrollingEvent.SCROLLING_STARTED); this.parentApplication.dispatcher.dispatchEvent(myScrollEvent); } (...) <s:List width="100%" useVirtualLayout="false" dataProvider="{myEmployees}" creationComplete="myList_creationCompleteHandler(event)" itemRenderer="views.MyIR" touchInteractionStart="myList_touchInteractionStartHandler(event)" height="100%" id="myList"/>
В моем элементе рендерера:
this.parentApplication.dispatcher.addEventListener(ScrollingEvent.SCROLLING_STARTED, onScrollAgain); (...) private function onScrollAgain(event:ScrollingEvent):void { trace("scrolling"); if (FLAGSTATE == 1){ wipeEffectOut.play(); } }
Исходный код
Проект Flash Builder доступен здесь: http://www.riagora.com/sfdc/sfdcemp.fxp.zip
В видео я использую «июньское обновление» Flex и AIR для мобильных приложений. Вот почему это работает очень хорошо.