Umgang mit Touch-Ereignissen in einem
ViewGroup
ist besonders vorsichtig
denn es ist üblich, dass eine ViewGroup
untergeordnete Elemente hat,
Touch-Ereignisse als das ViewGroup
selbst. Um sicherzustellen, dass jede Ansicht den
Touch-Events, die dafür vorgesehen sind, überschreiben
onInterceptTouchEvent()
.
Weitere Informationen finden Sie in den folgenden verwandten Ressourcen:
Berührungsereignisse in einer ViewGroup abfangen
Die Methode onInterceptTouchEvent()
wird immer dann aufgerufen, wenn ein Touch-Ereignis auf
die Oberfläche eines ViewGroup
, auch auf der Oberfläche seiner untergeordneten Elemente. Wenn
onInterceptTouchEvent()
gibt true
zurück.
MotionEvent
abgefangen, d. h. nicht an das untergeordnete Element, sondern an das
onTouchEvent()
-Methode der übergeordneten Ebene.
Die Methode onInterceptTouchEvent()
gibt einem Elternteil die Möglichkeit, Touch-Ereignisse zu sehen
bevor die untergeordneten Elemente dies tun. Wenn Sie true
über onInterceptTouchEvent()
zurückgeben,
erhält die untergeordnete Ansicht, die zuvor Touch-Ereignisse verarbeitet hat, ein
ACTION_CANCEL
,
Die Ereignisse werden ab diesem Zeitpunkt an die onTouchEvent()
-Methode des übergeordneten Elements gesendet.
für die übliche Verarbeitung. onInterceptTouchEvent()
kann auch false
und
Ereignisse ausspionieren, während sie sich in der Ansichtshierarchie zu ihren üblichen Zielen bewegen,
Events mit ihren eigenen onTouchEvent()
.
Im folgenden Snippet erweitert die Klasse MyViewGroup
die ViewGroup
.
MyViewGroup
enthält mehrere untergeordnete Ansichten. Wenn Sie Ihren Finger über eine untergeordnete Ansicht ziehen
horizontal, werden in der Kinderansicht keine Touch-Events mehr angezeigt und MyViewGroup
verarbeitet Berührungen
indem Sie im Inhalt scrollen. Wenn du jedoch in der Kinderansicht auf Schaltflächen tippst oder in der Kinderansicht scrollst,
vertikal ansehen, fängt das übergeordnete Element diese Berührungsereignisse nicht ab, da das untergeordnete Element
Ziel. In diesen Fällen gibt onInterceptTouchEvent()
false
zurück und der
onTouchEvent()
des Kurses „MyViewGroup
“ wurde nicht angerufen.
Kotlin
class MyViewGroup @JvmOverloads constructor( context: Context, private val mTouchSlop: Int = ViewConfiguration.get(context).scaledTouchSlop ) : ViewGroup(context) { ... override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { // This method only determines whether you want to intercept the motion. // If this method returns true, onTouchEvent is called and you can do // the actual scrolling there. return when (ev.actionMasked) { // Always handle the case of the touch gesture being complete. MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> { // Release the scroll. mIsScrolling = false false // Don't intercept the touch event. Let the child handle it. } MotionEvent.ACTION_MOVE -> { if (mIsScrolling) { // You're currently scrolling, so intercept the touch event. true } else { // If the user drags their finger horizontally more than the // touch slop, start the scroll. // Left as an exercise for the reader. val xDiff: Int = calculateDistanceX(ev) // Touch slop is calculated using ViewConfiguration constants. if (xDiff > mTouchSlop) { // Start scrolling! mIsScrolling = true true } else { false } } } ... else -> { // In general, don't intercept touch events. The child view // handles them. false } } } override fun onTouchEvent(event: MotionEvent): Boolean { // Here, you actually handle the touch event. For example, if the action // is ACTION_MOVE, scroll this container. This method is only called if // the touch event is intercepted in onInterceptTouchEvent. ... } }
Java
public class MyViewGroup extends ViewGroup { private int mTouchSlop; ... ViewConfiguration vc = ViewConfiguration.get(view.getContext()); mTouchSlop = vc.getScaledTouchSlop(); ... @Override public boolean onInterceptTouchEvent(MotionEvent ev) { // This method only determines whether you want to intercept the motion. // If this method returns true, onTouchEvent is called and you can do // the actual scrolling there. final int action = MotionEventCompat.getActionMasked(ev); // Always handle the case of the touch gesture being complete. if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { // Release the scroll. mIsScrolling = false; return false; // Don't intercept touch event. Let the child handle it. } switch (action) { case MotionEvent.ACTION_MOVE: { if (mIsScrolling) { // You're currently scrolling, so intercept the touch event. return true; } // If the user drags their finger horizontally more than the // touch slop, start the scroll. // Left as an exercise for the reader. final int xDiff = calculateDistanceX(ev); // Touch slop is calculated using ViewConfiguration constants. if (xDiff > mTouchSlop) { // Start scrolling. mIsScrolling = true; return true; } break; } ... } // In general, don't intercept touch events. The child view handles them. return false; } @Override public boolean onTouchEvent(MotionEvent ev) { // Here, you actually handle the touch event. For example, if the // action is ACTION_MOVE, scroll this container. This method is only // called if the touch event is intercepted in onInterceptTouchEvent. ... } }
Beachten Sie, dass ViewGroup
auch ein
requestDisallowInterceptTouchEvent()
. ViewGroup
ruft diese Methode auf, wenn ein untergeordnetes Element das übergeordnete Element und seine
Ancestors, um Touch-Events mit onInterceptTouchEvent()
abzufangen.
ACTION_OUTSIDE-Ereignisse verarbeiten
Wenn ein ViewGroup
ein MotionEvent
mit einer
ACTION_OUTSIDE
,
wird das Ereignis nicht standardmäßig an seine untergeordneten Elemente gesendet. So verarbeiten Sie MotionEvent
mit
ACTION_OUTSIDE
, entweder überschreiben
dispatchTouchEvent(MotionEvent event)
an die entsprechende View
weiterleiten oder
in den relevanten
Window.Callback
– für
Beispiel: Activity
.
ViewConfiguration-Konstanten verwenden
Im vorherigen Snippet wird der aktuelle ViewConfiguration
verwendet, um eine Variable zu initialisieren
namens mTouchSlop
. Sie können die Klasse ViewConfiguration
verwenden, um auf
Entfernungen, Geschwindigkeiten und Zeiten, die das Android-System verwendet.
„Touch Slop“ bezieht sich auf die Entfernung in Pixeln, die ein Nutzer zurücklegen kann, bevor die Geste ausgeführt wird. als Scrollen interpretiert wird. Touch Slop wird normalerweise verwendet, um versehentliches Scrollen zu verhindern, wenn Nutzer eine andere Touchfunktion ausführt, z. B. das Berühren von Bildschirmelementen.
Zwei weitere häufig verwendete ViewConfiguration
-Methoden sind
getScaledMinimumFlingVelocity()
und
getScaledMaximumFlingVelocity()
Diese Methoden geben die minimale bzw. maximale Geschwindigkeit zurück, um einen
in Pixeln pro Sekunde. Beispiel:
Kotlin
private val vc: ViewConfiguration = ViewConfiguration.get(context) private val mSlop: Int = vc.scaledTouchSlop private val mMinFlingVelocity: Int = vc.scaledMinimumFlingVelocity private val mMaxFlingVelocity: Int = vc.scaledMaximumFlingVelocity ... MotionEvent.ACTION_MOVE -> { ... val deltaX: Float = motionEvent.rawX - mDownX if (Math.abs(deltaX) > mSlop) { // A swipe occurs, do something. } return false } ... MotionEvent.ACTION_UP -> { ... if (velocityX in mMinFlingVelocity..mMaxFlingVelocity && velocityY < velocityX) { // The criteria are satisfied, do something. } }
Java
ViewConfiguration vc = ViewConfiguration.get(view.getContext()); private int mSlop = vc.getScaledTouchSlop(); private int mMinFlingVelocity = vc.getScaledMinimumFlingVelocity(); private int mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity(); ... case MotionEvent.ACTION_MOVE: { ... float deltaX = motionEvent.getRawX() - mDownX; if (Math.abs(deltaX) > mSlop) { // A swipe occurs, do something. } ... case MotionEvent.ACTION_UP: { ... } if (mMinFlingVelocity <= velocityX && velocityX <= mMaxFlingVelocity && velocityY < velocityX) { // The criteria are satisfied, do something. } }
Den berührbaren Bereich der Kinderansicht vergrößern
Android bietet
Noch TouchDelegate
Kurs
kann ein Elternteil den antippbaren Bereich einer untergeordneten Ansicht über die Grenzen des Kindes hinaus erweitern. Dieses
ist nützlich, wenn das Kind klein sein muss, aber einen größeren Touchbereich benötigt. Sie können auch diese
den Touchbereich des Kindes verkleinern.
Im folgenden Beispiel wird ein
ImageButton
ist _delegate
view_, d. h. das untergeordnete Element, dessen Berührungsbereich das übergeordnete Element erweitert. So sieht die Layoutdatei aus:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/parent_layout" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" > <ImageButton android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@null" android:src="@drawable/icon" /> </RelativeLayout>
Das folgende Snippet erledigt diese Aufgaben:
- Ruft die Elternansicht auf und postet ein
Runnable
im UI-Thread. Dadurch wird sichergestellt, dass das übergeordnete Element seine untergeordneten Elemente vor dem Aufrufen der MethodegetHitRect()
. Die MethodegetHitRect()
ruft das Trefferrechteck des untergeordneten Publishers ab (oder antippbarer Bereich) in den Koordinaten des übergeordneten Elements. - Findet die untergeordnete Ansicht
ImageButton
und ruftgetHitRect()
auf, um die des berührbaren Bereichs des Kindes definiert. - Erweitert die Grenzen des Trefferrechtecks der untergeordneten
ImageButton
-Ansicht. - Instanziiert ein
TouchDelegate
und übergibt das maximierte Trefferrechteck und den Untergeordnete AnsichtImageButton
als Parameter. - Legt das
TouchDelegate
in der übergeordneten Ansicht so fest, dass Berührungen innerhalb des Touch-Bevollmächtigten erfolgen Grenzen an das untergeordnete Element geleitet.
Da es sich bei der untergeordneten Ansicht von ImageButton
als Touch-Bevollmächtigter fühlt, ist die übergeordnete Ansicht
erhält alle Touch-Ereignisse. Wenn das Touch-Ereignis im Trefferrechteck des untergeordneten Publishers eintritt, wird das übergeordnete Element
übergibt das Touch-Ereignis zur Verarbeitung an das untergeordnete Element.
Kotlin
public class MainActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // Post in the parent's message queue to make sure the parent lays out // its children before you call getHitRect(). findViewById<View>(R.id.parent_layout).post { // The bounds for the delegate view, which is an ImageButton in this // example. val delegateArea = Rect() val myButton = findViewById<ImageButton>(R.id.button).apply { isEnabled = true setOnClickListener { Toast.makeText( this@MainActivity, "Touch occurred within ImageButton touch region.", Toast.LENGTH_SHORT ).show() } // The hit rectangle for the ImageButton. getHitRect(delegateArea) } // Extend the touch area of the ImageButton beyond its bounds on the // right and bottom. delegateArea.right += 100 delegateArea.bottom += 100 // Set the TouchDelegate on the parent view so that touches within // the touch delegate bounds are routed to the child. (myButton.parent as? View)?.apply { // Instantiate a TouchDelegate. "delegateArea" is the bounds in // local coordinates of the containing view to be mapped to the // delegate view. "myButton" is the child view that receives // motion events. touchDelegate = TouchDelegate(delegateArea, myButton) } } } }
Java
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Get the parent view. View parentView = findViewById(R.id.parent_layout); parentView.post(new Runnable() { // Post in the parent's message queue to make sure the parent lays // out its children before you call getHitRect(). @Override public void run() { // The bounds for the delegate view, which is an ImageButton in // this example. Rect delegateArea = new Rect(); ImageButton myButton = (ImageButton) findViewById(R.id.button); myButton.setEnabled(true); myButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(MainActivity.this, "Touch occurred within ImageButton touch region.", Toast.LENGTH_SHORT).show(); } }); // The hit rectangle for the ImageButton. myButton.getHitRect(delegateArea); // Extend the touch area of the ImageButton beyond its bounds on // the right and bottom. delegateArea.right += 100; delegateArea.bottom += 100; // Instantiate a TouchDelegate. "delegateArea" is the bounds in // local coordinates of the containing view to be mapped to the // delegate view. "myButton" is the child view that receives // motion events. TouchDelegate touchDelegate = new TouchDelegate(delegateArea, myButton); // Set the TouchDelegate on the parent view so that touches // within the touch delegate bounds are routed to the child. if (View.class.isInstance(myButton.getParent())) { ((View) myButton.getParent()).setTouchDelegate(touchDelegate); } } }); } }