Posts mit dem Label Gesten werden angezeigt. Alle Posts anzeigen
Posts mit dem Label Gesten werden angezeigt. Alle Posts anzeigen

8. Februar 2013

Android: Gestenerkennung mit GestureOverlayView

In einem vergangenen Post wurde bereits die Erkennung von einfachen Gesten in einer Android-App vorgestellt. Das Problem bei der dort vorgestellten Vorgehensweise, die auf der Verwendung der Klasse "GestureDetector" basierte, war, dass nur diejenigen Gesten erkannt wurden, die direkt auf der entsprechenden Activity, bzw. View ausgeführt wurden. Wenn diese  von anderen Komponenten, wie z.B. Buttons verdeckt wurde, schlug die Erkennung fehl. Diese Problematik lässt sich durch die Komponente "GestureOverlayView" umgehen. Diese Möglichkeit der Gestenerkennung  soll in diesem Post vorgestellt werden. Der ursprüngliche Post findet sich hier. Das Beispiel in diesem Post basiert auf dessen Original-Beispiel.

Zunächst einmal muss eine GestureOverlayKomponente in das entsprechende XML Layout File integriert werden. In diesem Beispiel soll die Komponente über allen anderen Komponenten und über die komplette Bildschirmfläche gelegt werden. Der Aufbau der resultierenden XML-Datei sieht hierbei wie folgt aus:
 <?xml version="1.0" encoding="utf-8"?>  
 <android.gesture.GestureOverlayView xmlns:android="http://schemas.android.com/apk/res/android"  
   android:id="@+id/gestureOverlayView"  
   android:layout_width="fill_parent"  
   android:layout_height="fill_parent"  
   android:eventsInterceptionEnabled="true"  
   android:fadeEnabled="false"  
   android:fadeOffset="0"  
   android:gestureColor="#00000000"  
   android:gestureStrokeType="multiple"  
   android:orientation="vertical"  
   android:uncertainGestureColor="#00000000" >  
   
   <LinearLayout  
     android:layout_width="match_parent"  
     android:layout_height="match_parent"  
     android:orientation="vertical" >  
   
     <TextView  
       android:id="@+id/textView"  
       android:layout_width="wrap_content"  
       android:layout_height="wrap_content"  
       android:text="no gesture detected" />  
   </LinearLayout>  
   
 </android.gesture.GestureOverlayView>  

Standardmäßig werden die ausgeführten Gesten auf der Komponente visuell dargestellt, sollen diese jedoch ausschließlich erkannt werden, so ist diese Darstellung unerwünscht und kann vermieden werden, in dem man die zur Darstellung benutzen Farben über die Attribute android:gestureColor="#00000000" und android:uncertainGestureColor="#00000000" auf transparent stellt. Über die Attribute android:fadeOffset="0" und android:fadeEnabled="false" lässt sich die störende Verzögerung zwischen Gestenerkennung und daraus resultierender Aktion elimieren, die dadurch entsteht, das die Visualisierung, wenn vorhanden, zunächst langsam ausgeblendet wird, bevor die Aktion ausgeführt wird.

Im Gegensatz zur Verwendung der Klasse "GestureDetector", bietet die Verwendung einer GestureOverlayView weitreichendere Möglichkeiten, da beliebige Gesten über eine sogenannte Gesture-Library definiert werden können. Dazu bietet das Android-SDK ein Tool namens "GestureBuilder", das im Android-Emulator bereits als App vorhanden ist.Mithilfe dieser App lassen sich beliebige Gesten durch Zeichnen definieren, mit einem Namen versehen und in einer Datei exportieren. Dazu muss der Emulator so konfiguriert sein, dass er eine virtuelle SD-Karte benutzt (dies wurde bereits in diesem Post beschrieben). Die dadurch erzeugte Datei lässt sich beispielsweise über die Eclipse-View "File Explorer" aus dem Emulator in das reguläre Dateisystem übertragen (die Datei ist auf diese Weise unter dem Pfad mnt/sdcard/gesture zu finden). Die so erhaltene Gesture-Library muss imjeweiligen Projekt im Unterverzeichnis res/raw abgelegt werden.

Im GestureBuilder erstellte Gesten (Klicken zum Vergrößern)

Um die im XML-Layout definierte GestureOverlayView-Komponente zu benutzen um, in der Gesture-Library abgelegte, Gesten zu erkennen, muss anschließen der Code der entsprechenden Activity angepasst werden. Diese muss dazu das Interface android.gesture.GestureOverlayView.OnGesturePerformedListener implementieren. Der resultierende Code könnte etwa wie folgt aussehen:
 public class DashboardActivity extends Activity implements  
         OnGesturePerformedListener {  
   
     private GestureLibrary gestureLibrary;  
   
     @Override  
     public void onCreate(Bundle savedInstanceState) {  
         super.onCreate(savedInstanceState);  
         setContentView(R.layout.dashboard_activity);  
   
         gestureLibrary = GestureLibraries.fromRawResource(this, R.raw.gestures);  
   
         if (!gestureLibrary.load()) {  
             finish();  
         }  
   
         GestureOverlayView gestures = (GestureOverlayView) findViewById(R.id.gestureOverlayView);  
         gestures.addOnGesturePerformedListener(this);  
     }  
   
     public void onGesturePerformed(GestureOverlayView overlay, Gesture gesture) {  
         ArrayList<Prediction> predictions = gestureLibrary.recognize(gesture);  
   
         if (predictions.size() > 0) {  
             Prediction prediction = predictions.get(0);  
   
             if (prediction.score > 1.0) {  
                 if (prediction.name.equalsIgnoreCase("swipe_right")) {  
                     TextView textView = (TextView) findById(R.id.textView);  
                     textView.setText("detected swipe right gesture");  
                 } else if (prediction.name.equalsIgnoreCase("swipe_left")) {  
                     TextView textView = (TextView) findById(R.id.textView);  
                     textView.setText("detected swipe left gesture");  
                 }  
             }  
         }  
     }  
   
 }  
Die Activity erhält hierbei eine Instanz der Klasse android.gesture.GestureLibrary, in die, beim Aufruft der onCreate-Methode, die zuvor erstellte Gesture-Library aus der entsprechenden Datei geladen wird. Außerdem wird die in der XML-Datei definierte GestureOverlayView über ihre ID geladen und die Activity wird als Listener an ihr angemeldet.

Die eigentliche Erkennung findet nun in der onGesturePerformed-Methode statt, die automatisch bei der Erkennung einer definierten Geste aufgerufen wird. Es gilt dabei, zu überprüfen, welche Geste erkannt wurde, was über den, im GestureBuilder angegebenen, Namen möglich ist. In diesem Fall wurden zwei Swipe-Gesten definiert, deren Erkennung in einer TextView ausgegeben wird.

4. Februar 2013

Android: Gestenerkennung (Swipe)

Die Bedienung eines Smartphones oder Tablets fühlt sich besonders elegant an, wenn die jeweilige App Gesten zur Bedienung anbietet. Dieser Post soll die Gestenerkennung von Wischgesten unter Android rudimentär erklären. Eine Übersicht über alle, von den Android-Entwicklern offiziell vorgesehenen Gesten, findet man unter http://developer.android.com/design/patterns/gestures.html. Dieses Beispiel soll sich dabei um Swipe-Gesten drehen, also eine Wischbewegung in eine bestimmte Richtung, wie sie etwa genutzt werden kann, um zwischen mehreren Seiten zu blättern oder eine Sidebar einzublenden.

Die Gestenerkennung ist innerhalb einer Activity oder einer View möglich. Für die Erkennung bietet die Android-API die Klasse android.view.GestureDetector an. Außerdem muss das Interface android.view.GestureDetector.OnGestureListener implementiert werden, um auf erkannte Gesten reagieren zu können. Für eine beispielhafte Activity könnte die Erkennung einer Swipe-Geste (in diesem Fall von links nach rechts) wie folgt aussehen:
 public class MainActivity extends Activity implements OnGestureListener {  
     
   private GestureDetector gestureDetector;   
   
   @Override  
   public void onCreate(Bundle savedInstanceState) {  
     super.onCreate(savedInstanceState);  
     setContentView(R.layout.activity_main);  
       
     gestureDetector = new GestureDetector(this,this);  
   }  
   
   @Override   
   public boolean onTouchEvent (MotionEvent event) {   
     this.gestureDetector.onTouchEvent(event);  
     return super.onTouchEvent(event);  
   }  
   
   @Override  
   public boolean onDown(MotionEvent event) {   
     return false;  
   }  
   
   @Override  
   public boolean onFling(MotionEvent event1, MotionEvent event2,   
       float velocityX, float velocityY) {  
     if (event1.getX() < event2.getX()) {  
       TextView textView = (TextView) findById(R.id.textView);  
       textView.setText("detected swipe gesture");  
       return true;
     }  
     return false;  
   }  
   
   @Override  
   public void onLongPress(MotionEvent event) {  
        
   }  
   
   @Override  
   public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,  
       float distanceY) {  
     return false;  
   }  
   
   @Override  
   public void onShowPress(MotionEvent event) {  
       
   }  
   
   @Override  
   public boolean onSingleTapUp(MotionEvent event) {  
     return false;  
   }  
   
   @Override  
   public boolean onDoubleTap(MotionEvent event) {  
     return false;  
   }  
   
   @Override  
   public boolean onDoubleTapEvent(MotionEvent event) {  
     return false;  
   }  
   
   @Override  
   public boolean onSingleTapConfirmed(MotionEvent event) {  
     return false;  
   }  
   
 }  
Die Instanz des GestureDetectors wird hierbei in der onCreate-Methode der Activity erzeugt, indem der Context, in diesem Fall die Activity selbst, sowie die entsprechende Listener-Implementation, in diesem Fall auch die Activity selbst, übergeben wird.

Die Activity, die in diesem Fall die Schnittstelle OnGestureListener implementiert, wird dadurch um einige Methoden erweitert. In diesem Beispiel ist nur die onFling-Methode, die für die Erkennung von Wischbewegungen gedacht ist, relevant. Der Methode werden, neben zwei Werten für die horizontale und vertikale Wischgeschwindigkeit, auch jeweils ein MotionEvent für den Start und das Ende der Wischbewegung als Parameter übergeben. Über diese MotionEvents lässt sich die Wischrichtung ermitteln. Handelt es sich um eine horizontale Swipe-Geste von links nach rechts, so wird in diesem Beispiel der Text einer TextView geändert.

Außerdem ist zu beachten, dass die Activity die onTouchEvent-Methode überschreiben muss, um das jeweilige TouchEvent an den GestureDetector und die Superklasse weiterzuleiten.

Die zugehörige XML Layout Datei zu diesem Beispiel sieht wie folgt aus:
 <?xml version="1.0" encoding="utf-8"?>  
 <LinearLayout  
   android:layout_width="match_parent"  
   android:layout_height="match_parent"  
   android:orientation="vertical" >  
   
   <TextView  
     android:id="@+id/textView"  
     android:layout_width="wrap_content"  
     android:layout_height="wrap_content"  
     android:text="no gesture detected" />  
   
 </LinearLayout>  
Ein Problem der hier beschriebenen Vorgehensweise kann sein, dass Gesten nur erkannt werden, wenn sie auf dem Hintergrund der Activity ausgeführt werden. Beinhaltet die Activity viele Komponenten wie beispielsweise Buttons, so werden darauf ausgeführte Gesten nicht erkannt. Die Problematik kann durch die Verwendung einer so genannten GestureOverlayView gelöst werden, die transparent über alle anderen Views gelegt wird um die Eingaben abzufangen. Auf diese Möglichkeit soll in einem weiterführenden Post eingegangen werden.

Nachtrag vom 8. Februar 2013: Der Post über oben erwähnte Möglichkeit zur Verwendung einer "GestureOverlayView" ist von nun an hier zu finden.