├── .gitignore
├── README
├── src
└── com
│ └── freshplanet
│ └── lib
│ ├── util
│ └── pool
│ │ └── IPool.as
│ └── ui
│ └── scroll
│ ├── mobile
│ ├── ScrollListEvent.as
│ ├── ScrollList.as
│ ├── AlphabetizedScrollList.as
│ ├── HorizontalScrollController.as
│ └── ScrollController.as
│ └── web
│ └── ScrollController.as
├── NOTICE
├── example
├── src
│ ├── ScrollControllerExampleApp.as
│ ├── com
│ │ └── freshplanet
│ │ │ └── lib
│ │ │ └── ui
│ │ │ ├── example
│ │ │ └── util
│ │ │ │ └── RectangleSprite.as
│ │ │ └── scroll
│ │ │ └── mobile
│ │ │ └── example
│ │ │ └── ScrollControllerExample.as
│ └── ScrollControllerExampleApp-app.xml
├── .project
└── .actionScriptProperties
└── LICENSE
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | # Air Mobile Scroll Controller
2 |
3 | Everything you need to have scrolling in your Adobe Air application that feels native on mobile.
4 |
5 |
6 | # How to use
7 |
8 | Add ScrollController.as, create a new ScrollController object, and call its addScrollControll() method.
9 | See the documentation for the details.
10 |
11 |
12 | # Note
13 |
14 | Don't hesitate to play around with the scrolling parameters (static constants) and suggest values that feel
15 | more native to you.
16 |
--------------------------------------------------------------------------------
/src/com/freshplanet/lib/util/pool/IPool.as:
--------------------------------------------------------------------------------
1 | package com.freshplanet.lib.util.pool
2 | {
3 |
4 | /**
5 | * @author Renaud Bardet
6 | *
7 | * this is the prototype of a Pool, for storing reusable elements
8 | * you can either use the DynamicPool implementation or implement this Interface for better performances on a specific case
9 | *
10 | */
11 | public interface IPool
12 | {
13 |
14 | function alloc(size:int):void ;
15 |
16 | function pop():* ;
17 |
18 | function push(element:*):void ;
19 |
20 | function dealloc():void ;
21 |
22 | function close():void ;
23 |
24 | }
25 |
26 | }
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | Copyright 2012 Freshplanet (http://freshplanet.com | opensource@freshplanet.com)
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/src/com/freshplanet/lib/ui/scroll/mobile/ScrollListEvent.as:
--------------------------------------------------------------------------------
1 | package com.freshplanet.lib.ui.scroll.mobile
2 | {
3 | import flash.display.DisplayObject;
4 | import flash.events.Event;
5 |
6 | public class ScrollListEvent extends Event
7 | {
8 |
9 | public static const CLICK : String = "ui.scroll.ScrollListEvent.CLICK" ;
10 |
11 | public var listElement : DisplayObject ;
12 | public var data : * ;
13 |
14 | public function ScrollListEvent(type:String, listElement:DisplayObject, data:*, bubbles:Boolean=false, cancelable:Boolean=false)
15 | {
16 |
17 | super(type, bubbles, cancelable);
18 |
19 | this.listElement = listElement ;
20 | this.data = data ;
21 |
22 | }
23 |
24 | }
25 | }
--------------------------------------------------------------------------------
/example/src/ScrollControllerExampleApp.as:
--------------------------------------------------------------------------------
1 | package
2 | {
3 | import com.freshplanet.lib.ui.scroll.mobile.example.ScrollControllerExample;
4 |
5 | import flash.display.Sprite;
6 | import flash.display.StageAlign;
7 | import flash.display.StageScaleMode;
8 | import flash.utils.setTimeout;
9 |
10 | public class ScrollControllerExampleApp extends Sprite
11 | {
12 | public function ScrollControllerExampleApp()
13 | {
14 | super();
15 |
16 | // support autoOrients
17 | stage.align = StageAlign.TOP_LEFT;
18 | stage.scaleMode = StageScaleMode.NO_SCALE;
19 |
20 | setTimeout(showScrollControllerExample, 100);
21 | }
22 |
23 | private function showScrollControllerExample():void
24 | {
25 | this.removeChildren();
26 | this.addChild(new ScrollControllerExample());
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/example/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | ScrollController
4 |
5 |
6 |
7 |
8 |
9 | com.adobe.flexbuilder.project.flexbuilder
10 |
11 |
12 |
13 |
14 | com.adobe.flexbuilder.project.apollobuilder
15 |
16 |
17 |
18 |
19 |
20 | com.adobe.flexide.project.multiplatform.multiplatformasnature
21 | com.adobe.flexide.project.multiplatform.multiplatformnature
22 | com.adobe.flexbuilder.project.apollonature
23 | com.adobe.flexbuilder.project.actionscriptnature
24 |
25 |
26 |
27 | [source path] src
28 | 2
29 | /Users/arno/Projects/Freshplanet/Air-Mobile-ScrollController/src
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/example/src/com/freshplanet/lib/ui/example/util/RectangleSprite.as:
--------------------------------------------------------------------------------
1 | package com.freshplanet.lib.ui.example.util
2 | {
3 | import flash.display.Shape;
4 | import flash.display.Sprite;
5 | import flash.text.TextField;
6 |
7 | public class RectangleSprite extends Sprite
8 | {
9 | public function RectangleSprite(color:uint, rsX:int, rsY:int, rsWidth:int, rsHeight:int, segmentHeight:int = -1)
10 | {
11 | super();
12 |
13 | this.x = rsX;
14 | this.y = rsY;
15 |
16 | var shape:Shape;
17 | if(segmentHeight > 0)
18 | {
19 | var segmentCount:int = rsHeight / segmentHeight;
20 | segmentHeight = rsHeight / segmentCount;
21 | for (var i:int = 0; i < segmentCount; i++)
22 | {
23 | shape = new Shape();
24 | shape.y = i*segmentHeight;
25 | shape.graphics.beginFill(i % 2 == 0 ? color : color * 2);
26 | shape.graphics.drawRect(0, 0, rsWidth, segmentHeight);
27 | shape.graphics.endFill();
28 | this.addChild(shape);
29 |
30 | var textfield:TextField = new TextField();
31 | textfield.textColor = 0xffffff;
32 | textfield.y = i*segmentHeight;
33 | textfield.height = segmentHeight - 1;
34 | textfield.text = String(i);
35 | this.addChild(textfield);
36 | }
37 | }
38 | else
39 | {
40 | shape = new Shape();
41 | shape.graphics.beginFill(color);
42 | shape.graphics.drawRect(0, 0, rsWidth, rsHeight);
43 | shape.graphics.endFill();
44 | this.addChild(shape);
45 | }
46 |
47 | }
48 | }
49 | }
--------------------------------------------------------------------------------
/example/src/com/freshplanet/lib/ui/scroll/mobile/example/ScrollControllerExample.as:
--------------------------------------------------------------------------------
1 | package com.freshplanet.lib.ui.scroll.mobile.example
2 | {
3 | import com.freshplanet.lib.ui.example.util.RectangleSprite;
4 | import com.freshplanet.lib.ui.scroll.mobile.ScrollController;
5 |
6 | import flash.display.Sprite;
7 | import flash.events.Event;
8 | import flash.geom.Rectangle;
9 |
10 |
11 | public class ScrollControllerExample extends Sprite
12 | {
13 | private var _scroll:ScrollController;
14 |
15 | public function ScrollControllerExample()
16 | {
17 | super();
18 |
19 | this.addEventListener(Event.ADDED_TO_STAGE, this.onAddedToStage);
20 | this.addEventListener(Event.REMOVED_FROM_STAGE, this.onRemovedFromStage);
21 | }
22 |
23 | private function onAddedToStage(e:Event):void
24 | {
25 | this.removeEventListener(Event.ADDED_TO_STAGE, this.onAddedToStage);
26 |
27 | var container:RectangleSprite = new RectangleSprite(0x440000, 50, 50, this.stage.stageWidth - 100, this.stage.stageHeight - 100);//red background
28 | this.addChild(container);
29 |
30 | var content:RectangleSprite = new RectangleSprite(0x444477, 0, 0, this.stage.stageWidth - 100, this.stage.stageHeight * 2, 30);//blue foreground
31 | container.addChild(content);
32 |
33 | var containerViewport:Rectangle = new Rectangle(0, 0, this.stage.stageWidth - 100, this.stage.stageHeight - 100);
34 |
35 | this._scroll = new ScrollController();
36 | this._scroll.horizontalScrollingEnabled = false;
37 | this._scroll.addScrollControll(content, container, containerViewport);
38 | }
39 |
40 | private function onRemovedFromStage(e:Event):void
41 | {
42 | this.removeEventListener(Event.REMOVED_FROM_STAGE, this.onRemovedFromStage);
43 |
44 | this._scroll.removeScrollControll();
45 | }
46 | }
47 | }
--------------------------------------------------------------------------------
/example/.actionScriptProperties:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | pache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
--------------------------------------------------------------------------------
/src/com/freshplanet/lib/ui/scroll/web/ScrollController.as:
--------------------------------------------------------------------------------
1 | package com.freshplanet.lib.ui.scroll.web
2 | {
3 | import flash.display.DisplayObject;
4 | import flash.display.DisplayObjectContainer;
5 | import flash.display.InteractiveObject;
6 | import flash.events.MouseEvent;
7 | import flash.geom.Point;
8 | import flash.geom.Rectangle;
9 |
10 | public class ScrollController
11 | {
12 | private var _content:DisplayObject;
13 | private var _container:DisplayObjectContainer;
14 | /** ViewPort in coordinates of the container. */
15 | private var _containerViewport:Rectangle;
16 | private var _scrollBarThumb:InteractiveObject;
17 | private var _scrollBarViewPort:Rectangle;
18 |
19 | /** ViewPort in coordinates of the content. */
20 | private var _contentViewport:Rectangle;
21 |
22 | /** Level in the screen stack. */
23 | private static var level:int = 0;
24 |
25 | public static const JS_SCROLLING_EVENT:String = "JSScrollingEvent";
26 |
27 | public function ScrollController():void{};
28 |
29 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
30 | // INTERFACE
31 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
32 | /**
33 | * Add a scroll controller
34 | * @param content the display object that will be scrolled
35 | * @param container the object containing the content
36 | * @param containerViewPort mask for the content
37 | * @param scrollBarThumb the thumb (clickable moving part) of the scrollbar
38 | * @param scrollBarViewPort mask for the scrollbar
39 | */
40 | public function addScrollControll(content:DisplayObject, container:DisplayObjectContainer, containerViewport:Rectangle, scrollBarThumb:InteractiveObject, scrollBarViewPort:Rectangle = null):void
41 | {
42 | _content = content;
43 | _container = container;
44 | _containerViewport = containerViewport.clone();
45 | _scrollBarThumb = scrollBarThumb;
46 | if(scrollBarViewPort == null)
47 | _scrollBarViewPort = containerViewport.clone();
48 | else
49 | _scrollBarViewPort = scrollBarViewPort.clone();
50 |
51 | // compute the viewport in the content coordinates
52 | var viewportTopLeft:Point = _content.globalToLocal( _container.localToGlobal( _containerViewport.topLeft ));
53 | var viewportBottomRight:Point = _content.globalToLocal( _container.localToGlobal( _containerViewport.bottomRight ));
54 |
55 | _contentViewport = new Rectangle( 0, 0, viewportBottomRight.x - viewportTopLeft.x, viewportBottomRight.y - viewportTopLeft.y );
56 | if(_contentViewport.height >= getContentHeight())
57 | {
58 | scrollBarThumb.visible = false;
59 | _content = null;
60 | _container = null;
61 | _containerViewport = null;
62 | _scrollBarThumb = null;
63 | _scrollBarViewPort = null;
64 | return;
65 | }
66 | scrollBarThumb.visible = true;
67 | scrollBarThumb.y = _scrollBarViewPort.y;
68 | _content.scrollRect = _contentViewport.clone();
69 |
70 | setupListeners();
71 | level++;
72 | }
73 |
74 | public function removeScrollControll():void
75 | {
76 | if(_content != null)
77 | {
78 | removeListeners();
79 | _content = null;
80 | _container = null;
81 | _containerViewport = null;
82 | _scrollBarThumb = null;
83 | _scrollBarViewPort = null;
84 | level--;
85 | }
86 | }
87 |
88 | /** Scroll to a specific position. Fraction should be between 0.0 and 1.0. */
89 | public function scrollTo(fraction:Number):void
90 | {
91 | if(_content != null)
92 | {
93 | fraction = fraction > 1.0 ? 1.0 : (fraction < 0.0 ? 0.0 : fraction);
94 |
95 | // bounds of the scrollbar
96 | var minY:Number = _scrollBarViewPort.y;
97 | var maxY:Number = _scrollBarViewPort.height - _scrollBarThumb.height + _scrollBarViewPort.y;
98 |
99 | // update scrollbar position
100 | _scrollBarThumb.y = maxY + (minY - maxY) * (1.0 - fraction);
101 |
102 | updateContentPositionFromScrollBar();
103 | }
104 | }
105 |
106 | public function getCurrentScrollPosition():Number
107 | {
108 | return _content != null ? _content.scrollRect.y : 0;
109 | }
110 |
111 | public function setCurrentScrollPosition(position:Number):void
112 | {
113 | if(_content == null)
114 | return;
115 | moveContentTo(position);
116 | updateScrollBarPositionFromContent();
117 | }
118 |
119 | public function getReversedScrollPosition():Number
120 | {
121 | return getContentHeight() - _content.scrollRect.y;
122 | }
123 |
124 | public function setReversedScrollPosition(position:Number):void
125 | {
126 | moveContentTo(getContentHeight() - position);
127 | updateScrollBarPositionFromContent();
128 | }
129 |
130 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
131 | // LISTENERS MANAGEMENT
132 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
133 | private function setupListeners():void
134 | {
135 | if (_scrollBarThumb) _scrollBarThumb.addEventListener(MouseEvent.MOUSE_DOWN, onScrollBarThumbMouseDown);
136 | _container.stage.addEventListener(JS_SCROLLING_EVENT, handleMouseWheel, false, level);
137 | }
138 |
139 | private function removeListeners():void
140 | {
141 | _scrollBarThumb.removeEventListener(MouseEvent.MOUSE_DOWN, onScrollBarThumbMouseDown);
142 | _scrollBarThumb.removeEventListener(MouseEvent.MOUSE_UP, onScrollBarThumbMouseUp);
143 | _container.stage.removeEventListener(MouseEvent.MOUSE_MOVE, onDragThumb);
144 | _container.stage.removeEventListener(JS_SCROLLING_EVENT, handleMouseWheel);
145 | }
146 |
147 | private function onScrollBarThumbMouseDown(event:MouseEvent):void
148 | {
149 | _scrollBarThumb.removeEventListener(MouseEvent.MOUSE_DOWN, onScrollBarThumbMouseDown);
150 | _container.stage.addEventListener(MouseEvent.MOUSE_UP, onScrollBarThumbMouseUp);
151 | _container.stage.addEventListener(MouseEvent.MOUSE_MOVE, onDragThumb);
152 |
153 | // stores the initial positions
154 | _firstTouchY = event.stageY;
155 | _initialScrollY = _scrollBarThumb.y;
156 | }
157 |
158 | private function onScrollBarThumbMouseUp(event:MouseEvent):void
159 | {
160 | _scrollBarThumb.addEventListener(MouseEvent.MOUSE_DOWN, onScrollBarThumbMouseDown);
161 |
162 | if (_container && _container.stage)
163 | {
164 | _container.stage.removeEventListener(MouseEvent.MOUSE_UP, onScrollBarThumbMouseUp);
165 | _container.stage.removeEventListener(MouseEvent.MOUSE_MOVE, onDragThumb);
166 | }
167 | }
168 |
169 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
170 | // UPDATE HANDLERS
171 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
172 | private var _firstTouchY:Number;
173 | private var _initialScrollY:Number;
174 | private function onDragThumb(event:MouseEvent):void
175 | {
176 | var newTouchY:Number = event.stageY;
177 | var stageDeltaY:Number = newTouchY - _firstTouchY;
178 |
179 | // convert to scrollbar coordinates
180 | var deltaY:Number = _scrollBarThumb.globalToLocal( new Point( 0, stageDeltaY )).y - _scrollBarThumb.globalToLocal( new Point( 0, 0 )).y;
181 |
182 | moveScrollBarTo(_initialScrollY + deltaY);
183 | updateContentPositionFromScrollBar();
184 | }
185 |
186 | private function handleMouseWheel(event:MouseEvent):void {
187 | // arbitrary conversion of delta
188 | var atBound:Boolean = moveContentBy(-event.delta*6);
189 | updateScrollBarPositionFromContent();
190 | event.stopImmediatePropagation();
191 | if(!atBound)
192 | event.preventDefault();
193 | }
194 |
195 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
196 | // POSITION UPDATE
197 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
198 | // Scrollbar
199 | private function moveScrollBarTo(posY:Number):void
200 | {
201 | // bounds of the scrollbar
202 | var minY:Number = _scrollBarViewPort.y;
203 | var maxY:Number = _scrollBarViewPort.height - _scrollBarThumb.height + _scrollBarViewPort.y;
204 |
205 | // update scrollbar position
206 | _scrollBarThumb.y = posY > maxY ? maxY : (posY < minY ? minY:posY);
207 | }
208 |
209 | private function updateContentPositionFromScrollBar():void
210 | {
211 | // get the percent from the scrollbar position
212 | // percent = (val - min)/(max - min)
213 | var percent:Number = (_scrollBarThumb.y - _scrollBarViewPort.y) / ( _scrollBarViewPort.height - _scrollBarThumb.height );
214 |
215 | // bounds of the content
216 | var minY:Number = _contentViewport.y;
217 | var maxY:Number = _contentViewport.y - _contentViewport.height + getContentHeight();
218 |
219 | var newViewPort:Rectangle = _content.scrollRect.clone();
220 | newViewPort.y = maxY + (minY - maxY) * (1.0 - percent);
221 |
222 | // update content position
223 | _content.scrollRect = newViewPort;
224 | }
225 |
226 | private function moveContentTo(position:Number):void
227 | {
228 | // bounds of the content
229 | var minY:Number = _contentViewport.y;
230 | var maxY:Number = _contentViewport.y - _contentViewport.height + getContentHeight();
231 |
232 | var newViewPort:Rectangle = _content.scrollRect.clone();
233 | newViewPort.y = position > maxY ? maxY : (position < minY ? minY:position);
234 |
235 | // update content position
236 | _content.scrollRect = newViewPort;
237 | }
238 |
239 | // Mouse wheel
240 | private function moveContentBy(deltaY:Number):Boolean
241 | {
242 | // bounds of the content
243 | var minY:Number = _contentViewport.y;
244 | var maxY:Number = _contentViewport.y - _contentViewport.height + getContentHeight();
245 |
246 | var newViewPort:Rectangle = _content.scrollRect.clone();
247 |
248 | var atBound:Boolean = newViewPort.y == maxY || newViewPort.y == minY;
249 |
250 | var newY:Number = newViewPort.y + deltaY;
251 | newY = newY > maxY ? maxY : (newY < minY ? minY:newY);
252 | newViewPort.y = newY;
253 |
254 | // update content position
255 | _content.scrollRect = newViewPort;
256 |
257 | atBound = atBound && (newY == maxY || newY == minY);
258 |
259 | return atBound;
260 | }
261 |
262 | private function updateScrollBarPositionFromContent():void
263 | {
264 | // get the percent from the content position
265 | // percent = (val - min)/(max - min)
266 | var percent:Number = (_content.scrollRect.y - _contentViewport.y) / ( getContentHeight() - _contentViewport.height );
267 |
268 | // bounds of the scrollbar
269 | var minY:Number = _scrollBarViewPort.y;
270 | var maxY:Number = _scrollBarViewPort.height - _scrollBarThumb.height + _scrollBarViewPort.y;
271 |
272 | // update scrollbar position
273 | _scrollBarThumb.y = maxY + (minY - maxY) * (1.0 - percent);
274 | }
275 |
276 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
277 | // UTIL
278 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
279 | private function getContentHeight():Number
280 | {
281 | if (!_content.stage) return 0;
282 |
283 | var originalHeightOnStage:Number = _content.transform.pixelBounds.height;
284 | var originalHeightOnContent:Number = _content.globalToLocal( new Point( 0, originalHeightOnStage )).y - _content.globalToLocal( new Point( 0, 0 )).y;
285 | originalHeightOnContent += 20;
286 | // handle the browser zoom out which scales the pixelBounds
287 | originalHeightOnContent *= _content.stage.getChildAt(0).height/_content.stage.getChildAt(0).transform.pixelBounds.height;
288 | return originalHeightOnContent;
289 | }
290 | }
291 | }
--------------------------------------------------------------------------------
/src/com/freshplanet/lib/ui/scroll/mobile/ScrollList.as:
--------------------------------------------------------------------------------
1 | package com.freshplanet.lib.ui.scroll.mobile
2 | {
3 |
4 | import flash.display.DisplayObject;
5 | import flash.display.Sprite;
6 | import flash.events.Event;
7 | import flash.events.MouseEvent;
8 | import flash.geom.Point;
9 | import flash.geom.Rectangle;
10 |
11 |
12 | /**
13 | * @author Renaud Bardet
14 | *
15 | * This Class handles a scrollable list of any displayable items
16 | * it is optimised for long lists
17 | */
18 | public class ScrollList extends Sprite
19 | {
20 |
21 | public static const SCROLL_LIST_ORIENTATION_HORIZONTAL:String = "horizontal" ;
22 | public static const SCROLL_LIST_ORIENTATION_VERTICAL:String = "vertical" ;
23 |
24 | private var _orientation:String ;
25 |
26 | private var _dataProvider : Vector.<*>;
27 |
28 | private var _content : Sprite;
29 | private var _extraContent:Sprite;
30 | private var _listContent:Sprite;
31 |
32 | private var _scrollController : ScrollController;
33 |
34 | private var _getElementBounds : Function ;
35 |
36 | private var _createElement : Function ; // function(elementData):DisplayObject
37 |
38 | private var _releaseElement : Function ; // function(DisplayObject):void
39 |
40 | private var _upperIndex : int ; // index of data wich is currently represented by the upper effective element
41 |
42 | private var _cacheElementsBounds : Vector. ;
43 | private var _cacheContentBounds : Rectangle ;
44 |
45 | private var _currentElements : Array ; // used as a int-hash
46 |
47 | // ---------------------------------------
48 | // CONSTRUCTOR
49 | // ---------------------------------------
50 |
51 | public function ScrollList(
52 | orientation:String,
53 | dataProvider:Vector.<*>, // elementData
54 | boundsRect:Rectangle,
55 | getElementBoundsFct:Function, // function(elementData):Rectangle
56 | createElementFct:Function, // function(elemenetData):DisplayObject
57 | releaseElementFct:Function = null // function(DisplayObject):void
58 | )
59 | {
60 |
61 | if ( orientation != SCROLL_LIST_ORIENTATION_HORIZONTAL && orientation != SCROLL_LIST_ORIENTATION_VERTICAL )
62 | throw new ArgumentError( "Orientation should be one of refered Strings SCROLL_LIST_ORIENTATION_HORIZONTAL or SCROLL_LIST_ORIENTATION_VERTICAL" ) ;
63 |
64 | _orientation = orientation ;
65 |
66 | if ( !dataProvider )
67 | throw new ArgumentError( "You should provide a dataProvider even if it is empty" ) ;
68 |
69 | this._getElementBounds = getElementBoundsFct ;
70 | this._createElement = createElementFct ;
71 | this._releaseElement = releaseElementFct ;
72 |
73 | this._content = new Sprite();
74 | _extraContent = new Sprite();
75 | _listContent = new Sprite();
76 |
77 | addChild( this._content );
78 | _content.addChild(_extraContent);
79 | _content.addChild(_listContent);
80 |
81 | _scrollController = new ScrollController() ;
82 | _scrollController.horizontalScrollingEnabled = _orientation == SCROLL_LIST_ORIENTATION_HORIZONTAL ;
83 | _scrollController.verticalScrollingEnabled = _orientation == SCROLL_LIST_ORIENTATION_VERTICAL ;
84 | _scrollController.displayVerticalScrollbar = true;
85 | _scrollController.addScrollControll( _content, this, boundsRect, null, 1 ) ;
86 |
87 | this.dataProvider = dataProvider ;
88 |
89 | _scrollController.addEventListener( ScrollController.SCROLL_POSITION_CHANGE, onScrollChanged, false, 0, true ) ;
90 |
91 | addEventListener( Event.ADDED_TO_STAGE, onAddedToStage, false, 0, true ) ;
92 |
93 | }
94 |
95 | // ---------------------------------------
96 | // PUBLIC
97 | // ---------------------------------------
98 |
99 | public function addExtraContent(object:DisplayObject):void
100 | {
101 | _extraContent.addChild(object);
102 | }
103 |
104 | public function setListMask(mask:DisplayObject):void
105 | {
106 | _extraContent.addChild(mask);
107 | _listContent.mask = mask;
108 | }
109 | /**
110 | * scroll to a specified element in the list
111 | * if the element is duplicated, the first element from the top will be considered
112 | * @param data an element present in dataProvider
113 | */
114 | public function scrollTo( data:*, animated:Boolean = false ):void
115 | {
116 |
117 | for( var i:int = 0 ; i < _dataProvider.length ; ++i )
118 | {
119 |
120 | if ( _dataProvider[i] == data )
121 | {
122 |
123 | var elBounds:Rectangle = _cacheElementsBounds[i] ;
124 | var to:Point = elBounds.topLeft.clone();
125 |
126 | if ( _orientation == SCROLL_LIST_ORIENTATION_VERTICAL )
127 | to.y = Math.min( to.y, _cacheContentBounds.height - bounds.height ) ;
128 | else
129 | to.x = Math.min( to.x, _cacheContentBounds.width - bounds.width ) ;
130 |
131 | _scrollController.scrollTo( to, animated ) ;
132 | redraw() ;
133 |
134 | break;
135 | }
136 |
137 | }
138 |
139 | }
140 |
141 | public function dispose():void
142 | {
143 |
144 | _scrollController.removeScrollControll() ;
145 |
146 | for each ( var el:DisplayObject in _currentElements )
147 | {
148 |
149 | _listContent.removeChild( el ) ;
150 | el.removeEventListener( MouseEvent.MOUSE_DOWN, onElementMouseDown ) ;
151 | el.removeEventListener( MouseEvent.MOUSE_UP, onElementMouseUp ) ;
152 | _releaseElement( el ) ;
153 |
154 | }
155 |
156 | _currentElements = null ;
157 |
158 | }
159 |
160 | // ---------------------------------------
161 | // PRIVATE
162 | // ---------------------------------------
163 |
164 | private function onScrollChanged( e: Event ):void
165 | {
166 |
167 | redraw() ;
168 |
169 | }
170 |
171 | private function onAddedToStage( e : Event ):void
172 | {
173 |
174 | redraw() ;
175 |
176 | }
177 |
178 | private function redraw():void
179 | {
180 |
181 | var displayedBounds:Rectangle = bounds.clone() ;
182 | if ( _orientation == SCROLL_LIST_ORIENTATION_VERTICAL )
183 | displayedBounds.y += _scrollController.scrollPosition.y ;
184 | else
185 | displayedBounds.x += _scrollController.scrollPosition.x ;
186 |
187 | for ( var i:int = 0 ; i < _currentElements.length ; ++i )
188 | {
189 |
190 | if ( _currentElements[i] == undefined )
191 | continue ;
192 |
193 | var elBounds:Rectangle = _currentElements[i].getBounds( _listContent ) ;
194 |
195 | // if the element is not visible anymore
196 | if (
197 | _orientation == SCROLL_LIST_ORIENTATION_VERTICAL && ( elBounds.bottom < displayedBounds.top || elBounds.top > displayedBounds.bottom )
198 | ||
199 | _orientation == SCROLL_LIST_ORIENTATION_HORIZONTAL && ( elBounds.right < displayedBounds.left || elBounds.left > displayedBounds.right )
200 | )
201 | {
202 |
203 | _listContent.removeChild( _currentElements[i] ) ;
204 | _currentElements[i].removeEventListener( MouseEvent.MOUSE_DOWN, onElementMouseDown ) ;
205 | _currentElements[i].removeEventListener( MouseEvent.MOUSE_UP, onElementMouseUp ) ;
206 | _releaseElement( _currentElements[i] ) ;
207 | delete _currentElements[i] ; // delete the reference in the array but keep the indexes of other elements intact
208 |
209 | }
210 |
211 | }
212 |
213 | for ( i = 0 ; i < _cacheElementsBounds.length ; ++i )
214 | {
215 |
216 | if ( _currentElements[i] == undefined ) // if it's not currently displayed
217 | {
218 |
219 | elBounds = _cacheElementsBounds[i] ;
220 |
221 | // check if it's visible
222 | if ( _orientation == SCROLL_LIST_ORIENTATION_VERTICAL )
223 | {
224 | if ( elBounds.bottom < displayedBounds.top ) // + _scrollController.speed
225 | continue ; // too high, skip to the next
226 | else if ( elBounds.top > displayedBounds.bottom )
227 | break ; // too low, next are not relevant
228 | }
229 | else
230 | {
231 | if ( elBounds.right < displayedBounds.left )
232 | continue ; // the el is left of the viewport, skip to the next
233 | else if ( elBounds.left > displayedBounds.right )
234 | break ; // the el is right of the viewport, next els are not relevant
235 | }
236 |
237 | var el:DisplayObject = _createElement( _dataProvider[i] ) ;
238 | el.addEventListener( MouseEvent.MOUSE_DOWN, onElementMouseDown ) ;
239 | el.addEventListener( MouseEvent.MOUSE_UP, onElementMouseUp ) ;
240 | el.y = elBounds.y ;
241 | el.x = elBounds.x ;
242 | _listContent.addChild( el ) ;
243 | _currentElements[i] = el ;
244 |
245 | }
246 |
247 | }
248 |
249 | }
250 |
251 | private function estimateContentBounds():Rectangle
252 | {
253 |
254 | var estBounds:Rectangle = new Rectangle( 0, 0, 0, 0 ) ;
255 |
256 | for ( var i:int = 0 ; i < _dataProvider.length ; ++i )
257 | {
258 |
259 | var elBounds:Rectangle = _getElementBounds( _dataProvider[i] ) ;
260 | if ( _orientation == SCROLL_LIST_ORIENTATION_VERTICAL )
261 | {
262 | estBounds.width = Math.max( elBounds.width, estBounds.width ) ;
263 | elBounds.y += estBounds.height ;
264 | estBounds.height += elBounds.height ;
265 | }
266 | else
267 | {
268 | estBounds.height = Math.max( elBounds.height, estBounds.height ) ;
269 | elBounds.x += estBounds.width ;
270 | estBounds.width += elBounds.width ;
271 | }
272 | _cacheElementsBounds.push( elBounds ) ;
273 |
274 | }
275 |
276 | return estBounds ;
277 |
278 | }
279 |
280 | private var _lastDownMousePos:Point = new Point(0, 0);
281 | private function onElementMouseDown(e:Event):void
282 | {
283 |
284 | _lastDownMousePos = new Point( this.mouseX, this.mouseY ) ;
285 |
286 | }
287 |
288 | private function onElementMouseUp(e:MouseEvent):void
289 | {
290 |
291 | // check if there was no significant delta Y between the down and the up
292 | // if so it's a click
293 | if (
294 | _orientation == SCROLL_LIST_ORIENTATION_VERTICAL && Math.abs(this.mouseY - _lastDownMousePos.y) < 10
295 | || _orientation == SCROLL_LIST_ORIENTATION_HORIZONTAL && Math.abs(this.mouseX - _lastDownMousePos.x) < 10
296 | )
297 | {
298 |
299 | var element:DisplayObject = DisplayObject(e.currentTarget) ;
300 | var data:* = _currentElements.indexOf(element) > -1 ? _dataProvider[ _currentElements.indexOf(element) ] : null ;
301 | dispatchEvent( new ScrollListEvent( ScrollListEvent.CLICK, element, data ) ) ;
302 |
303 | }
304 |
305 | }
306 |
307 | // ---------------------------------------
308 | // GETTERS AND SETTERS
309 | // ---------------------------------------
310 |
311 | public function get bounds():Rectangle
312 | {
313 | return _scrollController.containerViewport ;
314 | }
315 |
316 | public function set bounds(value:Rectangle):void
317 | {
318 | _scrollController.containerViewport = value ;
319 | redraw() ;
320 | }
321 |
322 | public function get dataProvider():Vector.<*>
323 | {
324 | return _dataProvider;
325 | }
326 |
327 | public function set dataProvider(value:Vector.<*>):void
328 | {
329 |
330 | for each ( var el:DisplayObject in _currentElements )
331 | {
332 |
333 | _listContent.removeChild( el ) ;
334 | _releaseElement( el ) ;
335 |
336 | }
337 |
338 | _dataProvider = value;
339 |
340 | _cacheElementsBounds = new [] ;
341 | _currentElements = [] ;
342 |
343 | _cacheContentBounds = estimateContentBounds() ;
344 |
345 | _scrollController.setContentRect( _cacheContentBounds ) ;
346 |
347 | // if scroll is out of new bounds go to end
348 | if ( _orientation == SCROLL_LIST_ORIENTATION_VERTICAL )
349 | {
350 | if ( _scrollController.scrollPosition.y > _cacheContentBounds.height - _scrollController.containerViewport.height )
351 | _scrollController.scrollToBottom() ;
352 | }
353 | else
354 | {
355 | if ( _scrollController.scrollPosition.x > _cacheContentBounds.width - _scrollController.containerViewport.width )
356 | _scrollController.scrollToRight() ;
357 | }
358 |
359 | redraw() ;
360 |
361 | }
362 |
363 | public function get scrollController():ScrollController
364 | {
365 |
366 | return _scrollController ;
367 |
368 | }
369 |
370 | }
371 |
372 | }
--------------------------------------------------------------------------------
/src/com/freshplanet/lib/ui/scroll/mobile/AlphabetizedScrollList.as:
--------------------------------------------------------------------------------
1 | package com.freshplanet.lib.ui.scroll.mobile
2 | {
3 | import com.freshplanet.lib.util.pool.IPool;
4 |
5 | import flash.display.DisplayObject;
6 | import flash.display.Shape;
7 | import flash.display.Sprite;
8 | import flash.events.MouseEvent;
9 | import flash.geom.Rectangle;
10 | import flash.text.TextField;
11 | import flash.text.TextFormat;
12 | import flash.text.TextFormatAlign;
13 |
14 | public class AlphabetizedScrollList extends Sprite
15 | {
16 |
17 | private static var SYMBOLS : String = 'AlphabetizedScrollList.SYMBOLS' ;
18 |
19 | private static var ALPHABET : Array = [ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', { label : '1', data : '123456789' } ];
20 |
21 | // this array of Strings determine how keys will be grouped,
22 | // by default it's letter by letter
23 | // but you could also define it as ABC - DEF - HIJ ...
24 | // it also defines in wich order the keys will be presented, so you can define numbers first or last
25 | // any element that cannot be affiliated to one of those key will be displayed at the end
26 | // it is case insensitive
27 | private var _groupBy:Array = ALPHABET;
28 |
29 | private var _dataProvider:Vector.<*> ;
30 |
31 | private var _alphaIndex:Function ; // returns a string on wich the alphabetical sort will be based
32 |
33 | private var _alphaHash:Object ;
34 |
35 | private var _scrollList:ScrollList ;
36 | private var _scrollListData:Vector.<*> ;
37 |
38 | private var _anchorPool:IPool ;
39 | private var _initAnchor:Function ; // function(DisplayObject, String):void
40 | private var _elementPool:IPool ;
41 | private var _initElement:Function ; // function(DisplayObject, elementData):void
42 |
43 | private var _anchors:Vector. ;
44 |
45 | private var _alphabetSelector:Sprite ;
46 | private var _alphabetSelectorBackground:Shape ;
47 | private var _alphabetSelectorScrollController:ScrollController ;
48 |
49 | private var _getListItemBounds:Function ;
50 |
51 | public function AlphabetizedScrollList(
52 | dataProvider:Vector.<*>,
53 | viewport:Rectangle,
54 | alphaIndex:Function, // function(elementData):String
55 | elementPool:IPool,
56 | initElementFct:Function, // function(DisplayObject, elementData):void
57 | anchorPool:IPool,
58 | initAnchorFct:Function // function(DisplayObject, String):void
59 | )
60 | {
61 |
62 | super() ;
63 |
64 | _alphaIndex = alphaIndex ;
65 |
66 | _elementPool = elementPool ;
67 | _initElement = initElementFct ;
68 |
69 | _anchorPool = anchorPool ;
70 | _initAnchor = initAnchorFct ;
71 |
72 | _getListItemBounds = defaultGetBounds ;
73 | _anchors = new Vector.() ;
74 |
75 | _scrollList = new ScrollList(
76 | ScrollList.SCROLL_LIST_ORIENTATION_VERTICAL,
77 | new <*>[],
78 | viewport,
79 | getElementBounds,
80 | createListElement,
81 | disposeListElement
82 | ) ;
83 |
84 | _scrollList.addEventListener( ScrollListEvent.CLICK, onElementClicked ) ;
85 |
86 | addChild( _scrollList ) ;
87 |
88 | this.dataProvider = dataProvider ;
89 |
90 | }
91 |
92 | // ---------------------------------------
93 | // PUBLIC
94 | // ---------------------------------------
95 |
96 | public function refresh():void
97 | {
98 |
99 | // reinit current list with the same data
100 | dataProvider = dataProvider ;
101 |
102 | }
103 |
104 | public function dispose():void
105 | {
106 |
107 | _scrollList.dispose() ;
108 | this.removeChild( _alphabetSelector ) ;
109 |
110 | }
111 |
112 | // ---------------------------------------
113 | // PRIVATE
114 | // ---------------------------------------
115 |
116 | private function onElementClicked( e:ScrollListEvent ):void
117 | {
118 |
119 | dispatchEvent( new ScrollListEvent( e.type, e.listElement, e.data ) ) ;
120 |
121 | }
122 |
123 | private function getElementBounds( data:* ):Rectangle
124 | {
125 |
126 | var el:DisplayObject = createListElement( data ) ;
127 | var bounds:Rectangle = _getListItemBounds(el) ;
128 | disposeListElement( el ) ;
129 | return bounds ;
130 |
131 | }
132 |
133 | private function defaultGetBounds(el:DisplayObject):Rectangle
134 | {
135 |
136 | return el.getBounds( el ) ;
137 |
138 | }
139 |
140 | private function createListElement( data:* ):DisplayObject
141 | {
142 |
143 | if( !data.hasOwnProperty( "type" ) )
144 | throw "unexpected data" ;
145 |
146 | if( data['type'] == "anchor" )
147 | {
148 | var a:DisplayObject = _anchorPool.pop() ;
149 | _anchors.push(a) ;
150 | var anchorName:String = data.data ;
151 | if ( anchorName == SYMBOLS )
152 | anchorName = '' ;
153 | _initAnchor( a, anchorName ) ;
154 | return a ;
155 | }else{
156 | var el:DisplayObject = _elementPool.pop() ;
157 | _initElement( el, data.data ) ;
158 | return el ;
159 | }
160 |
161 | }
162 |
163 | private function disposeListElement( element:DisplayObject ):void
164 | {
165 |
166 | var anchorIndex:int = _anchors.indexOf( element )
167 | if ( anchorIndex > -1 )
168 | {
169 |
170 | _anchors.splice( anchorIndex, 1 ) ;
171 | _anchorPool.push( element ) ;
172 |
173 | }
174 | else
175 | {
176 |
177 | _elementPool.push( element ) ;
178 |
179 | }
180 |
181 | }
182 |
183 | private function createAlphabetSelector( _bounds:Rectangle ):void
184 | {
185 |
186 | if ( _alphabetSelector )
187 | this.removeChild( _alphabetSelector ) ;
188 |
189 | _alphabetSelector = new Sprite();
190 | _alphabetSelectorBackground = new Shape();
191 | _alphabetSelectorBackground.graphics.beginFill(0x000000, 1.0);
192 | _alphabetSelectorBackground.graphics.drawRoundRect(0, 0, 37, _bounds.height - 16, 37);
193 | _alphabetSelectorBackground.graphics.endFill();
194 | _alphabetSelectorBackground.y = 8 ;
195 | _alphabetSelector.addChild(_alphabetSelectorBackground);
196 | _alphabetSelectorBackground.alpha = 0.1;
197 |
198 | _alphabetSelector.x = _bounds.width - _alphabetSelector.width - 5 ;
199 |
200 | var letterHeight:Number = (_bounds.height - 32) / _groupBy.length ;
201 | var letterContainer:Sprite = new Sprite() ;
202 | var textfield:TextField;
203 | var currentY:Number = 16;
204 | var defaultFormat:TextFormat = new TextFormat();
205 | defaultFormat.align = flash.text.TextFormatAlign.CENTER;
206 | defaultFormat.size = 20;
207 | defaultFormat.font = "Futura Medium";
208 | defaultFormat.color = 0x4c626d;
209 | for each (var key:* in _groupBy)
210 | {
211 | var letter:String = ''
212 | if ( key is String )
213 | letter = key ;
214 | else
215 | letter = key.label ;
216 |
217 | textfield = new TextField;
218 | textfield.defaultTextFormat = defaultFormat;
219 | textfield.text = letter ;
220 | textfield.height = 25 ;
221 | textfield.width = 37;
222 | textfield.y = currentY;
223 | textfield.x = 0;
224 | textfield.selectable = false;
225 | letterContainer.addChild(textfield);
226 | textfield.addEventListener(MouseEvent.ROLL_OVER, onLetterOver, false, 0, true);
227 | textfield.addEventListener(MouseEvent.ROLL_OUT, onLetterOut, false, 0, true);
228 | currentY += letterHeight ;
229 | }
230 | _alphabetSelector.addChild( letterContainer ) ;
231 |
232 | _alphabetSelector.addEventListener(MouseEvent.ROLL_OVER, onAlphabetOver, false, 0, true);
233 | _alphabetSelector.addEventListener(MouseEvent.ROLL_OUT, onAlphabetOut, false, 0, true);
234 |
235 | this.addChild(_alphabetSelector);
236 |
237 | }
238 |
239 | private function onAlphabetOver(event:MouseEvent):void
240 | {
241 | _alphabetSelectorBackground.alpha = 0.3;
242 | }
243 |
244 | private function onAlphabetOut(event:MouseEvent):void
245 | {
246 | _alphabetSelectorBackground.alpha = 0.1;
247 | }
248 |
249 | private function onLetterClicked(event:MouseEvent):void
250 | {
251 | var textfield:TextField = event.target as TextField;
252 | gotoAnchor(textfield.text);
253 | }
254 |
255 | private function onLetterOver(event:MouseEvent):void
256 | {
257 | var textfield:TextField = event.target as TextField;
258 | textfield.textColor = 0xffffff;
259 | gotoAnchor(textfield.text);
260 | }
261 |
262 | private function onLetterOut(event:MouseEvent):void
263 | {
264 | var textfield:TextField = event.target as TextField;
265 | textfield.textColor = 0x4c626d ;
266 | }
267 |
268 | private function gotoAnchor( anchorName:String ):void
269 | {
270 |
271 | _scrollList.scrollTo( _alphaHash[ anchorName ].anchor ) ;
272 |
273 | }
274 |
275 | // ---------------------------------------
276 | // GETTERS AND SETTERS
277 | // ---------------------------------------
278 |
279 | public function get dataProvider():Vector.<*>
280 | {
281 |
282 | return _dataProvider ;
283 |
284 | }
285 |
286 | public function set dataProvider(value:Vector.<*>):void
287 | {
288 |
289 | _dataProvider = value ;
290 |
291 | _alphaHash = new Object() ;
292 |
293 | // construct the hash wich consist in a dictionary of
294 | // groupingKey -> {
295 | // data : dictionary of
296 | // elementHash -> element
297 | // anchor : reference to the scrollList data associated with this grouping Key
298 | // }
299 | for ( var i:int=0 ; i<_dataProvider.length ; ++i )
300 | {
301 |
302 | var elementHash:String = _alphaIndex(_dataProvider[i]) ;
303 |
304 | var hashKey:String = SYMBOLS ; // default key, means the element will be displayed at the end if no other key can be found in the grouping funciton
305 |
306 | // look for the first letter of the hash in the grouping function and determine the hashKey for that element
307 | for ( var j:int=0 ; j < _groupBy.length ; ++j )
308 | {
309 |
310 | var keyLabel : String = '' ;
311 | var keyData : String = '' ;
312 | if ( _groupBy[j] is String )
313 | {
314 | keyLabel = _groupBy[j] ;
315 | keyData = _groupBy[j] ;
316 | } else {
317 | keyLabel = _groupBy[j].label ;
318 | keyData = _groupBy[j].data ;
319 | }
320 |
321 | // use case incensitive keys
322 | if ( keyData.toUpperCase().indexOf( elementHash.substr(0,1).toUpperCase() ) > -1 )
323 | {
324 |
325 | hashKey = keyLabel ;
326 |
327 | break ;
328 | }
329 | }
330 |
331 | if ( !_alphaHash.hasOwnProperty( hashKey ) )
332 | {
333 | _alphaHash[ hashKey ] = {
334 | data : new Object(),
335 | anchor : { type : "anchor", data : hashKey }
336 | } ;
337 | }
338 |
339 | _alphaHash[ hashKey ].data[ elementHash ] = _dataProvider[i] ;
340 |
341 | }
342 |
343 | // construct the dataProvider that will be passed to the scrollList
344 | _scrollListData = new Vector.<*>() ;
345 | var keys:Array = _groupBy.concat( [ SYMBOLS ] ) ;
346 | for ( j=0 ; j < keys.length ; ++j )
347 | {
348 |
349 | var label:String = '';
350 | if ( keys[j] is String )
351 | label = keys[j] ;
352 | else
353 | label = keys[j].label ;
354 |
355 | if ( !_alphaHash.hasOwnProperty( label ) )
356 | {
357 | _alphaHash[ label ] = {
358 | data : new Object(),
359 | anchor : { type : "anchor", data : label }
360 | } ;
361 | }
362 |
363 | _scrollListData.push( _alphaHash[label].anchor ) ;
364 |
365 | var sortedElements : Vector.<*> = new <*>[] ;
366 | for ( var key : * in _alphaHash[label].data )
367 | sortedElements.push(key) ;
368 | sortedElements.sort( Array.CASEINSENSITIVE ) ;
369 |
370 | for ( var k:int = 0 ; k < sortedElements.length ; ++k )
371 | {
372 |
373 | _scrollListData.push(
374 | {
375 | type : "element",
376 | data : _alphaHash[label].data[ sortedElements[k] ]
377 | } ) ;
378 |
379 | }
380 |
381 | }
382 |
383 | if( _alphaHash[ SYMBOLS ].data.length == 0 )
384 | _scrollListData.splice( _alphaHash[SYMBOLS].anchor, 1 ) ;
385 |
386 | _scrollList.dataProvider = _scrollListData ;
387 |
388 | createAlphabetSelector( _scrollList.bounds ) ;
389 |
390 | }
391 |
392 | public function get groupBy():Array
393 | {
394 | return _groupBy;
395 | }
396 |
397 | public function set groupBy(value:Array):void
398 | {
399 | _groupBy = value;
400 | dataProvider = dataProvider ; // reset the content
401 | }
402 |
403 | public function set getListItemBounds(value:Function):void
404 | {
405 | _getListItemBounds = value;
406 | dataProvider = dataProvider ;
407 | }
408 |
409 |
410 | }
411 | }
--------------------------------------------------------------------------------
/example/src/ScrollControllerExampleApp-app.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
15 |
16 |
18 | ScrollController
19 |
20 |
21 | ScrollController
22 |
23 |
25 | ScrollController
26 |
27 |
30 | 0.0.0
31 |
32 |
33 |
34 |
35 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | [This value will be overwritten by Flash Builder in the output app.xml]
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 | true
115 | false
116 | true
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
138 |
162 |
163 |
166 |
167 |
168 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
217 |
218 |
227 |
228 |
229 |
230 |
231 |
234 |
235 |
236 |
237 |
238 |
239 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
267 |
268 |
270 |
271 |
272 |
273 |
274 |
276 |
277 |
278 |
279 |
280 |
282 |
283 |
284 |
285 |
286 | ]]>
287 |
288 |
289 | UIDeviceFamily
291 |
292 | 1
293 | 2
294 |
295 | ]]>
296 | high
297 |
298 |
299 |
--------------------------------------------------------------------------------
/src/com/freshplanet/lib/ui/scroll/mobile/HorizontalScrollController.as:
--------------------------------------------------------------------------------
1 | package com.freshplanet.lib.ui.scroll.mobile
2 | {
3 | import flash.display.DisplayObject;
4 | import flash.display.DisplayObjectContainer;
5 | import flash.display.Shape;
6 | import flash.events.Event;
7 | import flash.events.MouseEvent;
8 | import flash.geom.Point;
9 | import flash.geom.Rectangle;
10 | import flash.utils.getTimer;
11 |
12 | public class HorizontalScrollController
13 | {
14 |
15 |
16 | // ------------------------------------------------------------------------------------------
17 | //
18 | // Static Vars
19 | //
20 | // ------------------------------------------------------------------------------------------
21 |
22 | public static var FRICTION_COEFFICIENT:Number = 0.9;
23 | public static var MAX_SPEED_ALLOWED_WHEN_BOUNCING:Number = 0.5;
24 | public static var MAX_SPEED_ALLOWED_WHEN_RUNNING_FREE:Number = 2.0;
25 | public static var SPEED_REDUCTION_WHEN_BOUNCING:Number = 0.3;
26 | public static var SCROLLBAR_FIRST_POSITION:Number = 0;
27 |
28 | // ------------------------------------------------------------------------------------------
29 | //
30 | // Private Vars
31 | //
32 | // ------------------------------------------------------------------------------------------
33 |
34 | // state
35 | private var _paused : Boolean = true;
36 |
37 | // config scroll
38 | private var _content : DisplayObject;
39 | private var _container : DisplayObjectContainer;
40 |
41 | private var _contentViewport : Rectangle;
42 | private var _containerViewport : Rectangle;
43 |
44 | private var _contentInitialBounds : Rectangle; // full size of the content before we put it in the scroll Rect, don't seem to be able to find it after so cache it here
45 |
46 | // scroll movement cache
47 | private var _lastTouch : Object;
48 | private var _touches : Vector.