27/11/2024
Forstå Bevægelsesbaserede Gestus i Android
I moderne mobilapplikationer er brugerinteraktion kernen i en god oplevelse. Android tilbyder en rig vifte af måder, hvorpå brugere kan interagere med deres enheder, og en af de mest dynamiske er gennem bevægelsesbaserede gestus. Disse gestus, som at stryge, trække eller rotere, giver en intuitiv og naturlig måde at styre apps på. Denne artikel vil uddybe, hvordan Android håndterer disse bevægelser, og hvordan udviklere kan implementere dem effektivt.

Hvad er Bevægelsesbaserede Gestus?
En bevægelsesbaseret gestus er enhver brugerinput, der involverer en ændring i position, tryk eller størrelse af en berøringskontakt på skærmen. I modsætning til en simpel trykhandling, som primært handler om det præcise punkt på skærmen, involverer bevægelsesbaserede gestus en sekvens af begivenheder, der sporer en bevægelse over tid. Android registrerer disse ændringer gennem MotionEvent-objekter, som indeholder detaljeret information om hver berøringsinteraktion.
MotionEvent og Spore Bevægelse
Når en bruger rører ved skærmen, sender Android systemet MotionEvent-objekter til din app. Hvert MotionEvent har en handlingstype, der angiver, hvad der sker. For bevægelsesbaserede gestus er ACTION_MOVE-handlingen central. Hver gang brugerens finger ændrer position, tryk eller størrelse, mens den forbliver på skærmen, udløses en ny ACTION_MOVE-begivenhed.
MotionEvent-objektet indeholder værdifuld information som:
- X og Y koordinater: Den aktuelle position for berøringspunktet.
- Pointer ID: Unikt ID for hver finger eller pointer, hvilket er essentielt for multi-touch-gestus.
- Historisk data: Mulighed for at tilgå tidligere positioner, tider og trykniveauer for en gestus. Dette er især nyttigt til at tegne spor eller analysere præcise bevægelsesmønstre.
'Slop' – Afstanden for Acceptabel Bevægelse
En af de udfordringer, når man registrerer bevægelsesbaserede gestus, er at skelne mellem en utilsigtet lille bevægelse og en bevidst gestus som et stryg. Android introducerer begrebet 'slop' (eller 'touch slop'). Dette er en defineret afstand i pixels. Hvis brugerens berøring bevæger sig inden for denne 'slop'-afstand fra det oprindelige berøringspunkt, betragtes det stadig som et enkelt tryk. Først når bevægelsen overskrider denne grænse, fortolkes den som en bevægelsesbaseret gestus.
Dette 'slop'-koncept sikrer, at små, ufrivillige bevægelser ikke fejlagtigt udløser gestus, hvilket forbedrer brugeroplevelsen.
Sporing af Bevægelsesdetaljer
Der er flere måder at spore bevægelsen af en gestus på, afhængigt af appens behov:
- Start- og slutposition: Simpelthen at registrere, hvor gestussen startede og sluttede, kan være nok til grundlæggende bevægelser.
- Retning: Ved at analysere ændringer i X og Y koordinater kan man bestemme den retning, en finger bevæger sig i.
- Historik: Ved at kalde
getHistorySize()og derefter bruge metoder somgetHistoricalX()oggetHistoricalY()kan man hente en liste over tidligere positioner. Dette er ideelt til at tegne en glat linje, der følger brugerens finger, som i en tegne-app. - Hastighed: Hastigheden af bevægelsen er ofte afgørende for at identificere specifikke gestus, såsom et 'swipe' eller et 'fling'.
VelocityTracker – Måling af Hastighed
For at præcist måle hastigheden af en bevægelse tilbyder Android VelocityTracker-klassen. Denne klasse hjælper med at spore hastigheden af MotionEvent-objekter. Den er især nyttig, når hastighed er en del af kriterierne for en gestus, for eksempel ved et 'fling' (et hurtigt stryg, der kan fortsætte efter brugeren slipper).
Her er et kig på, hvordan VelocityTracker bruges:
Kotlin Eksempel:
private const val DEBUG_TAG = "Velocity" class MainActivity: Activity() { private var mVelocityTracker: VelocityTracker? = null override fun onTouchEvent(event: MotionEvent): Boolean { when (event.actionMasked) { MotionEvent.ACTION_DOWN -> { // Reset the velocity tracker back to its initial state. mVelocityTracker?.clear() // If necessary, retrieve a new VelocityTracker object to watch // the velocity of a motion. mVelocityTracker = mVelocityTracker ?: VelocityTracker.obtain() // Add a user's movement to the tracker. mVelocityTracker?.addMovement(event) } MotionEvent.ACTION_MOVE -> { mVelocityTracker?.apply { val pointerId: Int = event.getPointerId(event.actionIndex) addMovement(event) // When you want to determine the velocity, call // computeCurrentVelocity(). Then, call getXVelocity() and // getYVelocity() to retrieve the velocity for each pointer // ID. computeCurrentVelocity(1000) // Units: 1000 is pixels/second // Log velocity of pixels per second. Log.d("", "X velocity: ${getXVelocity(pointerId)}") Log.d("", "Y velocity: ${getYVelocity(pointerId)}") } } MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { // Return a VelocityTracker object back to be re-used by others. mVelocityTracker?.recycle() mVelocityTracker = null } } return true } }Java Eksempel:
public class MainActivity extends Activity { private static final String DEBUG_TAG = "Velocity"; private VelocityTracker mVelocityTracker = null; @Override public boolean onTouchEvent(MotionEvent event) { int index = event.getActionIndex(); int action = event.getActionMasked(); int pointerId = event.getPointerId(index); switch (action) { case MotionEvent.ACTION_DOWN: if (mVelocityTracker == null) { // Retrieve a new VelocityTracker object to watch the // velocity of a motion. mVelocityTracker = VelocityTracker.obtain(); } else { // Reset the velocity tracker back to its initial state. mVelocityTracker.clear(); } // Add a user's movement to the tracker. mVelocityTracker.addMovement(event); break; case MotionEvent.ACTION_MOVE: mVelocityTracker.addMovement(event); // When you want to determine the velocity, call // computeCurrentVelocity(). Then call getXVelocity() and // getYVelocity() to retrieve the velocity for each pointer ID. mVelocityTracker.computeCurrentVelocity(1000); // Units: 1000 is pixels/second // Log velocity of pixels per second. Log.d(DEBUG_TAG, "X velocity: " + mVelocityTracker.getXVelocity(pointerId)); Log.d(DEBUG_TAG, "Y velocity: " + mVelocityTracker.getYVelocity(pointerId)); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: // Return a VelocityTracker object back to be re-used by others. mVelocityTracker.recycle(); mVelocityTracker = null; break; } return true; } }Vigtigt: Beregn hastigheden efter ACTION_MOVE, ikke efter ACTION_UP, da hastigheden vil være nul ved ACTION_UP.
Pointer Capture – Fuld Kontrol over Pointers
For visse typer apps, især spil, fjernskrivebordsapplikationer eller virtualiseringsklienter, kan det være nødvendigt med mere præcis kontrol over muse- eller pointer-input. Android 8.0 (API-niveau 26) introducerede Pointer Capture-funktionen, som giver en vis i din app mulighed for at modtage alle museevents, når den har fokus. Dette er især nyttigt, når du skal simulere en mus eller et trackpad.
Anmodning om Pointer Capture
En vis kan anmode om pointer capture, når den har fokus inden for sin synshierarki. Det er god praksis at anmode om capture under specifikke brugerhandlinger, f.eks. ved et klik på en knap eller i onWindowFocusChanged()-metoden.

Du anmoder om capture ved at kalde requestPointerCapture() på din vis:
Kotlin:
view.requestPointerCapture()Java:
view.requestPointerCapture();Når capture er succesfuld, kaldes onPointerCaptureChange(true) på din vis. Systemet sender herefter alle museevents til den fokuserede vis, så længe den forbliver inden for samme hierarki. Andre apps holder op med at modtage museevents, indtil capture frigives. Bemærk, at events fra andre kilder end musen stadig leveres normalt.
Håndtering af Catturede Pointer Events
Når din vis har pointer capture, modtager den museevents. Du kan håndtere disse events på to måder:
- Overskriv
onCapturedPointerEvent(MotionEvent): Hvis du bruger en brugerdefineret vis, kan du overskrive denne metode. - Registrer en
OnCapturedPointerListener: Du kan registrere en lytter for at håndtere events.
Eksempel med onCapturedPointerEvent (Kotlin):
override fun onCapturedPointerEvent(motionEvent: MotionEvent): Boolean { // Hent de nødvendige koordinater val verticalOffset: Float = motionEvent.y // Brug koordinaterne til at opdatere din vis og returner true, hvis eventen blev behandlet. return true }Eksempel med OnCapturedPointerListener (Kotlin):
myView.setOnCapturedPointerListener { view, motionEvent -> // Hent de nødvendige koordinater val horizontalOffset: Float = motionEvent.x // Brug koordinaterne til at opdatere din vis og returner true, hvis eventen blev behandlet. true }I begge tilfælde modtager din vis et MotionEvent med koordinater, der angiver relative bevægelser (X- eller Y-deltaer), ligesom fra et trackpad. Du kan hente disse koordinater med getX() og getY().
Frigivelse af Pointer Capture
Din vis kan frigive pointer capture ved at kalde releasePointerCapture():
Kotlin:
view.releasePointerCapture()Java:
view.releasePointerCapture();Opsummering
At forstå og implementere bevægelsesbaserede gestus er afgørende for at skabe engagerende og intuitive brugeroplevelser på Android. Ved at udnytte MotionEvent, VelocityTracker og Pointer Capture kan udviklere skabe alt fra simple stryge-menuer til komplekse interaktive elementer, der reagerer præcist på brugerens input. Husk at tage højde for 'slop' for at forhindre utilsigtede udløsninger og brug historisk data for at opnå glattere animationer og mere præcis sporing.
Ofte Stillede Spørgsmål
Q1: Hvad er forskellen mellem et tryk og en bevægelsesbaseret gestus?
Et tryk er en statisk handling, hvor brugeren rører et punkt på skærmen. En bevægelsesbaseret gestus involverer en ændring i position over tid, f.eks. et stryg eller træk.
Q2: Hvornår skal jeg bruge VelocityTracker?
Brug VelocityTracker, når hastigheden af brugerens bevægelse er vigtig for at identificere en gestus, som f.eks. et 'fling' eller et hurtigt stryg.
Q3: Hvad er 'slop' i Android gestus-detektion?
'Slop' er en lille afstand i pixels, der tillader små, utilsigtet bevægelser uden at afbryde en potentiel gestus. Først når bevægelsen overskrider denne grænse, fortolkes den som en bevægelsesbaseret gestus.
Q4: Hvad er fordelen ved Pointer Capture?
Pointer Capture giver din app fuld kontrol over muse- eller pointer-input, hvilket er nyttigt for spil eller apps, der simulerer desktop-lignende interaktioner.
Q5: Hvordan sporer jeg præcist en brugers tegnebevægelse?
Brug MotionEvent.getHistoricalSize() og tilhørende metoder som getHistoricalX() og getHistoricalY() til at hente en sekvens af tidligere pointer-positioner og tegne en glat linje.
Hvis du vil læse andre artikler, der ligner Forstå bevægelsesbaserede gestus i Android, kan du besøge kategorien Teknologi.
