Interactive Designer and Developer
Collider Exhibit Series

Sliding Lists ~ TUIO Flash App Widget

screenshot1So I am trying to build out this proof of concept application, its a demo of a conference's floor plan with a list of all the attendee on the side, you'll scroll through the list, pick one and the app will show you the location on the map and give more information about the attendee. Only one problem, I'm using the TUIO, so there isn't a component for a scollable list of ...anything. So looks like I just have to build one.

I took a couple tries before I stopped coding myself into a corner. The layering of the component had to be just right. Plus, it needed to the usable at any angle. It is intended for a table top / multi-user display. And to make that happen, I needed quite a bit of layering, on the app and in the code itself.

I also wanted this to start off all code based, so I only used FlashDevelop. So moving forward, if you have Flash you can build a MovieClip and attach it to the List Item Object and customize the look of the selectable items.

Download Slide List Widget

And now I'm trying to figure out the best way to show this all, there are a lot of layers, so lets start at the beginning I supposed

The Layering

layers2

Here is the long and short of it, all of the items [ListItemObj.as] are built out and put into the list container movie clip [ListItemObj.as]. Still inside the list container is another layer, which is invisible, to preform the touch interaction. One solid movie clip is needed to get the relative Y position of the movie clip. the touch layer's height is adjusted once the list is generated. This whole movie clip is then masked to a specific size.

The selection of a list item is done by taking the spot on the touch layer and calculating what item is positioned relative to that touch with this equation: (evt.localY - evt.localY % itemFullHeight)/itemFullHeight, now thats some serious voodoo. I'm not even sure how it all works, but it does. After that, its a combo of using Booleans and a Timer to determine if you really intended on touching that item or scrolling the list. I've added in comments to the code that will helpfully explain better.

Main.as

This is the starting point.

package
{
        import flash.display.Sprite;
        import flash.events.Event;
        import flash.events.TUIO;
        import flash.events.TouchEvent;
        import flash.utils.Timer;
        import TouchListObject;
        import RotatableScalableList;
        import flash.text.TextField;
        import flash.text.AntiAliasType;
        import flash.text.TextFieldAutoSize;
        import flash.text.TextFormat;
 
        /**
         * ...
         * @author cyancdesign
         */
        public class Main extends Sprite
        {
                [Embed(source="ARIAL.TTF", fontFamily="Arial")]
                public var bar:String;
 
                public var newList:TouchListObject;                                                                                                                        // for non-rotatablescalable implimentation
                public var newRotatableScalableList:RotatableScalableList = new RotatableScalableList(); // for rotatablescalable implimentation
 
                public var displayText:TextField = new TextField();                                                                                    // And TextField for Interactivity Demo
 
                public function Main():void
                {
                        if (stage) init();
                        else addEventListener(Event.ADDED_TO_STAGE, init);
                }
 
                private function init(e:Event = null):void
                {
                        removeEventListener(Event.ADDED_TO_STAGE, init);
                        // entry point
 
                        //activate TUIO
                        TUIO.init(this,'localhost',3000,'',true);
 
                        newList = new TouchListObject();
                        addChild(newList);
                        newList.name = "List 1";
                        newList.x = 200;
                        newList.y = 50;
                        newList.rotation = -10;
                        newList.addEventListener("ItemSelected", itemSelection);          // non-rotatablescalable listener
 
                        /*newList = new TouchListObject();
                        addChild(newList);
                        newList.name = "List 2";
                        newList.x = 950;
                        newList.y = 200;
                        newList.rotation = -250;
                        newList.addEventListener("ItemSelected", itemSelection);*/
 
                        newRotatableScalableList = new RotatableScalableList();
                        addChild(newRotatableScalableList);
                        newRotatableScalableList.x = 600;
                        newRotatableScalableList.y = 50;                      
 
                        // Build TextField and format it for interactivity demo
                        var format:TextFormat = new TextFormat();
                        format.font        = "Arial";
                        format.color = 0x000000;
                        format.size = 23;
 
                        displayText.y = 700;
                        displayText.x = 10;
                        displayText.autoSize = TextFieldAutoSize.LEFT;
                        displayText.selectable = false;
                        displayText.embedFonts = true;
                        displayText.antiAliasType = AntiAliasType.ADVANCED;
                        displayText.defaultTextFormat = format;
                        displayText.text = "select item";
                        addChild(displayText);                        
 
                }
 
                public function itemSelection(evt:Event):void {
                        var thisObj:Object = evt.currentTarget as TouchListObject;
                        trace("Item Has Been Selected from: " + thisObj.name + " | item: " + thisObj.selectedObj.thisId);
                        displayText.text = "You Selected: " + thisObj.selectedObj.textVal;
                }
 
        }
 
}

This is showing an example of using a generix TouchListObject and a movable RotatableScalableList. In the end you'd usually only need to use one version. but its nice to know both. the event listener is only listening for TouchListObject interaction. The listener from the movable list is in RotatableScalableList. displayText is the text object that the list item changes to show that its working and how to hook in an event listener.

TouchListObject.as

This builds the list of items and contains the scrolling/selection events

package
{
        import flash.display.MovieClip;
        import flash.display.Shape;
        import flash.events.ContextMenuEvent;
        import flash.events.Event;
        import flash.events.TimerEvent;
        import flash.events.TUIO;
        import flash.events.TouchEvent;
        import flash.utils.describeType;
        import flash.utils.Timer;
        import ListItemObj;
        import app.core.action.RotatableScalable;
        /**
         * ...
         * @author cyancdesign
         * This is what builds the List of Items to display and scroll - current dimensions are set to: 137 x 577
         */
        public class TouchListObject extends MovieClip
        {
 
                public var triggerShape:Shape = new Shape();
                public var ListContainer:MovieClip = new MovieClip();
                public var newListItem:ListItemObj;
                public var maskList:Shape = new Shape();
                public var maskTrigger:Shape = new Shape();
                public var bkgShape:Shape = new Shape();
                public var itemHeight:Number = 25;
                public var itemPadding:Number = 0;
                public var itemFullHeight:Number;
                public var triggerDelay:Number = 150;                            // Timer Delay for Activating Item for Selection
                public var fauxNumberofItems:Number = 40;
 
                public var listWidth:Number = 137;                                       // Set Height
                public var listHeight:Number = 577;                                      // Set Height
                public var itemArray:Array = new Array();
 
                // Values for Easing and Dragging and Clicking Items
                private var initLocalY:Number;
                private var moveToY:Number;
                private var newLocation:Number = 0;
                private var speed:Number  =  1.1;
                private var isDragging:Boolean = false;
                private var waitTimer:Timer;
                public var selectedObj:ListItemObj;
                private var isActivated:Boolean = false;
                public var yCompare:Number = 0;
 
                public function TouchListObject():void {
 
                        itemFullHeight = itemHeight + itemPadding;
 
                        bkgShape.graphics.beginFill(0x333333);
                        bkgShape.graphics.drawRect(0, 0, listWidth, listHeight);
                        addChild(bkgShape);
 
                        addChild(ListContainer);
 
                        for (var t:int = 0; t < fauxNumberofItems; ++t) {
                                newListItem = new ListItemObj(listWidth, itemHeight);
                                newListItem.thisId = t;
                                newListItem.y = itemFullHeight * t;
                                newListItem.textVal = "Item #"+t.toString();
                                newListItem.itemText.text = newListItem.textVal;
                                itemArray.push(newListItem);
                                ListContainer.addChild(newListItem);
                        }
 
                        maskList.graphics.beginFill(0x4e2f34);
                        maskList.graphics.drawRect(0, 0, listWidth, listHeight);
                        addChild(maskList);
 
                        maskTrigger.graphics.beginFill(0x333333);
                        maskTrigger.graphics.drawRect(0, 0, listWidth, listHeight);
                        addChild(maskTrigger);                        
 
                        triggerShape.graphics.beginFill(0x333333, .55);
                        triggerShape.graphics.drawRect(0, 0, listWidth, ListContainer.height);
                        ListContainer.addChild(triggerShape);
 
                        ListContainer.addEventListener(TouchEvent.MOUSE_DOWN, ListDown);
                        ListContainer.addEventListener(TouchEvent.MOUSE_MOVE, ListMove);
                        ListContainer.addEventListener(TouchEvent.MOUSE_UP, ListUp);
                        ListContainer.addEventListener(TouchEvent.MOUSE_OUT, ListUp);
 
                        ListContainer.addEventListener(Event.ENTER_FRAME, animateList);
 
                        ListContainer.mask = maskList;
                        triggerShape.mask = maskTrigger;
 
                        waitTimer = new Timer(triggerDelay, 1);
 
                }
 
                public function ListDown(evt:TouchEvent):void {
                        isActivated = false;
                        isDragging = true;
                        yCompare = evt.localY;
                        initLocalY = evt.localY - ListContainer.y;
                        var selectedItem:Number = (evt.localY - evt.localY % itemFullHeight)/itemFullHeight;
 
                        waitTimer.addEventListener(TimerEvent.TIMER_COMPLETE, completeHandler);
                        waitTimer.start();
                        selectedObj = itemArray[selectedItem];
                }
 
                public function completeHandler(evt:TimerEvent):void {
                        isActivated = true;
                        trace("activated: " + selectedObj);
                        ListContainer.addEventListener(TouchEvent.MOUSE_UP, TriggerItem);
                        selectedObj.switchOn();
                        //selectedObj.switchBkg();
                }
 
                public function TriggerItem(evt:TouchEvent):void {
                        trace("select: " + selectedObj);                                          // this is needed if you intend on using both ways in the end...
                        var stageObj:Object = parent;                                                      // if you intent on ONLY using RotatableScalableList, use the equation in the if statement instead
                        if (stageObj is RotatableScalableList) {                               // if you intent on not using RotatableScalableList, remove this if statement
                                stageObj = parent.parent;
                        }
 
                        dispatchEvent(new Event("ItemSelected"));
 
                        isActivated = false;
                        waitTimer.stop();
                        waitTimer = new Timer(triggerDelay, 1);
                        selectedObj.switchOff();
                        ListContainer.removeEventListener(TouchEvent.MOUSE_UP, TriggerItem);
                        evt.stopPropagation();
                }
 
                public function ListMove(evt:TouchEvent):void {
                        if (isDragging) {
                                newLocation = evt.localY - initLocalY;
                                if (Math.abs(yCompare - evt.localY) > 0) {
                                        if (isActivated) {
                                                ListContainer.removeEventListener(TouchEvent.MOUSE_UP, TriggerItem);
                                        }
                                        waitTimer.stop();
                                        waitTimer = new Timer(triggerDelay, 1);
                                        trace("deactivate")
                                        selectedObj.switchOff();
                                        isActivated = false;
                                }
                                yCompare = evt.localY;
                        }
                        evt.stopPropagation();
                }
 
                public function ListUp(evt:TouchEvent):void {
                        isDragging = false;
                        initLocalY = evt.localY;
                        waitTimer.stop();
                        waitTimer = new Timer(triggerDelay, 1);
                        if(selectedObj != null){
                                selectedObj.switchOff();
                        }
                        isActivated = false;
                        evt.stopPropagation();
                }
 
                public function animateList(evt:Event):void {
                        if (ListContainer.height > maskList.height) {
                                ListContainer.y = newLocation - (newLocation - ListContainer.y) / speed;
 
                                if (ListContainer.y >= 0) {
                                        ListContainer.y = 0;
                                }else if (ListContainer.y <= maskList.height - ListContainer.height) {
                                        ListContainer.y = maskList.height - ListContainer.height;
                                }
                        }
                }
 
        }
 
}

This only makes a generic list of items through a for loop for example purposes. There is no reason it cannot bring in information from an XML feed or external txt file. I tried to pull all the variables into the top of the code to make it easier to edit overall. The timer delay is a fraction of a second, i tried to mimic my phone and how it handles touch/sliding intentions.

The actual sliding uses some old school easing calculations, no tweening API, though they could be used if you wanted to, I wanted to keep this with as few APIs as possible at first.

ListItemObj.as

This is the individual list item. Changes background color when selectable and returns to normal after selection.

package
{
        import flash.display.ColorCorrection;
        import flash.display.MovieClip;
        import flash.display.Shape;
        import flash.text.TextField;
        import flash.text.AntiAliasType;
        import flash.text.TextFieldAutoSize;
        import flash.text.TextFormat;
 
        /**
         * ...
         * @author cyancdesign
         * This is one of the items in the list.
         * Basic color transform for changing the background
         * This could also be attached to a MovieClip in the Flash GUI
         */
 
        public class ListItemObj extends MovieClip
        {
                [Embed(source="ARIAL.TTF", fontFamily="Arial")]
                public var bar:String;
 
                public var thisId:Number;                                                                             // The ID of the List Item
                public var textVal:String = "";                                                                        // Value to Put into TextField
                public var itemText:TextField = new TextField();                               // Display Text
 
                private var _itemWidth:Number;
                private var _itemHeight:Number;
 
                private var fauxItem:Shape;
                private var isHighlit:Boolean = false;
                private var heiY:Number;
                public var itemBorder:Number = 1;
                public var itemBorderColor:Number = 0x333333;
 
                private var regColor:uint = 0x000000;
                private var higColor:uint = 0xFE5900;
 
                public function ListItemObj(itemWidth:Number = 37, itemHeight:Number = 0):void {
 
                        _itemWidth = itemWidth;
                        _itemHeight = itemHeight;
 
                        heiY = _itemHeight;
                        fauxItem = new Shape();
                        fauxItem.graphics.beginFill(regColor);
                        fauxItem.graphics.lineStyle(itemBorder, itemBorderColor);
                        fauxItem.graphics.drawRect(0, 0, _itemWidth - itemBorder, _itemHeight);
                        addChild(fauxItem);
 
                        // Text Format of Items
                        var format:TextFormat = new TextFormat();
                        format.font        = "Arial";
                        format.color = 0xFFFFFF;
                        format.size = 10;
 
                        // TextField Values
                        itemText.x = 5;
                        itemText.y = 5;
                        itemText.autoSize = TextFieldAutoSize.LEFT;
                        itemText.selectable = false;
                        itemText.textColor = 0xffffff;
                        itemText.embedFonts = true;
                        itemText.antiAliasType = AntiAliasType.ADVANCED; // Needs AntiAliasing to remain viewable in rotated views
                        itemText.defaultTextFormat = format;
                        addChild(itemText);
                }
 
                public function switchOn():void {
                        fauxItem.graphics.clear();
                        fauxItem.graphics.beginFill(higColor);
                        fauxItem.graphics.lineStyle(itemBorder, itemBorderColor);
                        fauxItem.graphics.drawRect(0, 0, _itemWidth - itemBorder, _itemHeight);
                        isHighlit = true;
                }
                public function switchOff():void {
                        fauxItem.graphics.clear();
                        fauxItem.graphics.beginFill(regColor);
                        fauxItem.graphics.lineStyle(itemBorder, itemBorderColor);
                        fauxItem.graphics.drawRect(0, 0, _itemWidth - itemBorder, _itemHeight);
                        isHighlit = false;
                }
 
        }
 
}

This one is pretty simple. i makes a box, and a textfield, changes color of the background box, and contains values that it needs to know and be able to give to the event listener when envoked.

RotatableScalableList.as

This pull a scrollable list into a RotatableScalable object to make list movable. Needed a place to add in stopPropagation() so the list wouldn't move when users tried to use it.

package
{
        import app.core.action.RotatableScalable;
        import flash.display.Shape;
        import TouchListObject;
        import flash.events.TouchEvent;
        import flash.events.Event;
 
        /**
         * ...
         * @author cyancdesign
         * Another layer is needed to be able to make RotatableScalable not conflict with TouchListObject.
         * Use this as an example to attach a TouchListObject to a RotatableScalable Object.
         */
        public class RotatableScalableList extends RotatableScalable
        {
                public var newList:TouchListObject = new TouchListObject();
                public var bkgShape:Shape = new Shape();
                public var interactivePadding:Number = 25;                                               // This is the padding around the list for RotatableScalable
 
                public function RotatableScalableList():void {
 
                        bkgShape.graphics.beginFill(0x333333);                                              // Background of TouchListObject, RotatableScalable Area
                        bkgShape.graphics.drawRect(0 - interactivePadding, 0 - interactivePadding, newList.listWidth + interactivePadding * 2, newList.listHeight + interactivePadding * 2);
 
                        addChild(bkgShape);
 
                        addChild(newList);
                        newList.name = "List RotatableScalable";
                        newList.addEventListener(TouchEvent.MOUSE_DOWN, stopProp);  // stop RotatableScalable motion when trying to interact with list
                        newList.addEventListener(TouchEvent.MOUSE_UP, stopProp);    // stop RotatableScalable motion when trying to interact with list
                        newList.addEventListener(TouchEvent.MOUSE_MOVE, stopProp);  // stop RotatableScalable motion when trying to interact with list
 
                        newList.addEventListener("ItemSelected", itemSelection);
                }
 
                public function stopProp(evt:TouchEvent):void {
                        evt.stopPropagation();
                }
 
                public function itemSelection(evt:Event):void {
                        var thisObj:Object = evt.currentTarget as TouchListObject;
                        trace("Item Has Been Selected from: " + thisObj.name + " | item: " + thisObj.selectedObj.thisId);
                        //trace(thisObj.parent.parent)
                        thisObj.parent.parent.displayText.text = "You Selected: " + thisObj.selectedObj.textVal; // this paths backward relatively to Main.as and connects to the displayText
                }
 
        }
 
}

So this should be a good start. I've already been implimenting it in a project I'm working on now. More on that later, hopefully.

Note to Flash GUI users, look out for:
[Embed(source="ARIAL.TTF", fontFamily="Arial")]
public var bar:String;

I have had nothing but trouble when it comes to embedding fonts to use in apps...they can be a real pain.
I've added the Zip to my Google Code Page (download section)

Download Slide List Widget

Tags: , , , , , ,

2 Responses to “Sliding Lists ~ TUIO Flash App Widget”

  1. Amit says:

    Please write an API or something so that I can use this in my application just by using

    addChild(new TouchList(myListItemObjects))

    I can read it and modify myself, But I want people to use this as I know how much it can be used in Flash touch Apps. So i thought the author would get credit . ;)

    I dont think it should take much time for you as you are quite experienced.

    And ya cool app ;)

  2. Chris Yanc says:

    funny you should mention. i have been toying with that idea. and i just began looking into to it. do you have any other components you’d like to see? i’m open to suggestions.

Leave a Reply

Downloads

What do you all think?

What developer tool[s] do you use to compile your SWF files?

View Results

Loading ... Loading ...

What kind of tutorial do you prefer?

View Results

Loading ... Loading ...

What is your prefered language for developing multi-touch apps?

View Results

Loading ... Loading ...

Shameless Attempts to Pay the Bills