Montag, 12. September 2011

Flex: SnapScroller

Da ich derzeit an meiner Master Thesis sitze und via Flex eine Mobil App erstellen möchte, ist mir folgendes aufgefallen. Flex 4.5 ist zwar echt nett was die Veröffentlichung von Apps auf mehreren Plattformen angeht, hat aber leider recht eingeschränkte Komponenten zum zusammenbauen. So fehlte mir dringend die Paging Funktion die man aus iOS kennt. Sprich man hat eine Scrollview, die seitenweise Inhalte präsentiert. Da ich auch nach längeren Suchen keine Möglichkeit gefunden habe die Scroller Komponente zu parametrisieren, habe ich sie einfach mal erweitert.

Beispiel (rote Fläche nach links wischen):




Dazu folgenden Quelltext

MXML:

<local:SnapScroller  x="0" y="0" width="320" height="460">
<s:HGroup gap="0" width="100%" height="100%" >
<s:Image source="assets/Startscreen_Portrait.png"/><s:Image x="320" source="assets/Startscreen_Portrait.png"/><s:Image x="640" source="assets/Startscreen_Portrait.png"/>
</s:HGroup>
</local:SnapScroller>
Wichtig ist, das die Komponente vom Typ SnapScroller- und der Container eine HGroup ist. Gap sollte auf 0 stehen damit die Elemente direkt nebeneinander liegen und nicht unnötig mit Spacern getrennt sind.

Die AS3 Komponente (derzeit nur horizontales snapping):

package
{
 package
{
 import flash.events.MouseEvent;
 import flash.events.TimerEvent;
 import flash.events.TouchEvent;
 import flash.ui.Mouse;
 import flash.ui.Multitouch;
 import flash.ui.MultitouchInputMode;
 import flash.utils.Timer;
 
 import flashx.textLayout.formats.Float;
 
 import mx.core.InteractionMode;
 import mx.core.ScrollPolicy;
 
 import spark.components.Group;
 import spark.components.Scroller;
 
 
 public class SnapScroller extends Scroller
 {
  private var isMouseDown:Boolean;
  private var xStart:Number;
  private var mouseUpDownXDistance : Number;
  private var t:Timer;
  private var nextHorizontalPosition: Number; 
  private var nextHorizontalPositionDistance : Number;
  
  public function SnapScroller()
  {
   super();
   
   this.setStyle('horizontalScrollPolicy',ScrollPolicy.OFF);
   this.addEventListener(MouseEvent.MOUSE_DOWN,onMouseDown);
   this.addEventListener(MouseEvent.MOUSE_MOVE,onMouseMove);
   
  }
  
  protected function onMouseDown(event:MouseEvent):void
  {
   this.addEventListener(MouseEvent.MOUSE_UP,onMouseUp);
   stage.addEventListener(MouseEvent.MOUSE_UP,onMouseUp);
   
   if (t)
    t.stop();
   
   isMouseDown = true;
   xStart = mouseX;
  }
  
  protected function onMouseMove(event:MouseEvent):void
  {
   if(isMouseDown)
   {
    mouseUpDownXDistance = mouseX-xStart;
    this.viewport.horizontalScrollPosition -= mouseUpDownXDistance;
    xStart = mouseX;
   }
  }
  
  protected function onMouseUp(event:MouseEvent):void
  {
   isMouseDown = false;
   
   if(mouseUpDownXDistance > 0)
    nextHorizontalPosition = (int(this.viewport.horizontalScrollPosition/this.width))*this.width;  
   else
    nextHorizontalPosition = (int(this.viewport.horizontalScrollPosition/this.width)+1)*this.width;
   
   if (nextHorizontalPosition > ((this.getElementAt(0) as Group).contentWidth)-this.width)
    nextHorizontalPosition = ((this.getElementAt(0) as Group).contentWidth)-this.width;
   
   t = new Timer(1,0);
   t.addEventListener(TimerEvent.TIMER,timerEnds);
   t.start();
   
   stage.removeEventListener(MouseEvent.MOUSE_UP,onMouseUp);
   this.removeEventListener(MouseEvent.MOUSE_UP,onMouseUp);
  }
  
  protected function timerEnds(event:TimerEvent):void
  {
   var distanceToTarget:Number = Math.abs(this.viewport.horizontalScrollPosition - nextHorizontalPosition);
   
   if(this.viewport.horizontalScrollPosition > nextHorizontalPosition)
    this.viewport.horizontalScrollPosition -= Math.sqrt(distanceToTarget);
    
   else
    this.viewport.horizontalScrollPosition += Math.sqrt(distanceToTarget);
   
   if (distanceToTarget < 0.01)    
   {     
    this.viewport.horizontalScrollPosition = nextHorizontalPosition;          
    t.stop();     
    t.removeEventListener(TimerEvent.TIMER,timerEnds);    
   }   
  }  
 } 
}

Tutorial Video

4 Kommentare:

  1. Gute idee!

    Mit diesen fix funktioniert es für auch DataGroup:

    if (nextHorizontalPosition > ((this.getElementAt(0) as IViewport).contentWidth)-this.width)
    nextHorizontalPosition = ((this.getElementAt(0) as IViewport).contentWidth)-this.width;

    (contentWidth ist von den IViewport interface)

    AntwortenLöschen
  2. Seit Flex 4.6 gibt es die Eigenschaft pageScrollingEnabled die meine Lösung nicht mehr notwendig macht... :-)

    AntwortenLöschen
  3. Very nice results ... thank you so much

    AntwortenLöschen
  4. Thanks for your sharing..It is so nice..

    I watched video and make a little changes..Put an image controls instead of buttons..I have a dynamic data from xml..I need a dataprovider but snapscroller, hgroup and image control doesnt have attribute..How can ı did it?

    AntwortenLöschen