ViewGroup
içinde dokunma etkinliklerinin ele alınması özel bir özen gösterir. Çünkü ViewGroup
için, ViewGroup
öğesinin kendisinden farklı dokunma etkinliklerinin hedefi olan alt öğeler olması sık karşılaşılan bir durumdur. Her görünümün ilgili dokunma etkinliklerini doğru şekilde aldığından emin olmak için onInterceptTouchEvent()
yöntemini geçersiz kılın.
Aşağıdaki ilgili kaynaklara bakın:
ViewGroup'taki dokunma etkinliklerine müdahale et
onInterceptTouchEvent()
yöntemi, alt yüzeyler de dahil olmak üzere ViewGroup
yüzeyinde bir dokunma etkinliği algılandığında çağrılır. onInterceptTouchEvent()
, true
değerini döndürürse MotionEvent
yeterli olur. Diğer bir deyişle, alt öğeye değil,
üst öğenin onTouchEvent()
yöntemine iletilir.
onInterceptTouchEvent()
yöntemi, ebeveyne dokunma etkinliklerini alt öğelerinden önce görme fırsatı sağlar. onInterceptTouchEvent()
ürününden true
öğesini döndürürseniz daha önce dokunma etkinliklerini işleyen alt görünüm ACTION_CANCEL
alır ve bu noktadan itibaren etkinlikler, normal işleme için üst öğenin onTouchEvent()
yöntemine gönderilir. onInterceptTouchEvent()
ayrıca, görünüm hiyerarşisinde aşağı doğru inen ve etkinlikleri kendi onTouchEvent()
değerleriyle işleyen normal hedeflerine doğru ilerlerken false
değerini döndürebilir ve etkinlikleri gizlice izleyebilir.
Aşağıdaki snippet'te MyViewGroup
sınıfı ViewGroup
kapsamını genişletir.
MyViewGroup
birden çok alt görünüm içeriyor. Parmağınızı bir çocuk görünümünde yatay olarak sürüklerseniz alt görünüm bundan böyle dokunma etkinliklerini almaz ve MyViewGroup
, içeriğini kaydırarak dokunma etkinliklerini işler. Bununla birlikte, alt öğe görünümündeki düğmelere dokunur veya alt görünümü dikey olarak kaydırırsanız üst öğe, amaçlanan hedef çocuk olduğundan bu dokunma etkinliklerine müdahale etmez. Bu durumlarda, onInterceptTouchEvent()
false
değerini döndürür ve MyViewGroup
sınıfının onTouchEvent()
çağrılmaz.
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. ... } }
ViewGroup
öğesinin bir requestDisallowInterceptTouchEvent()
yöntemi de sağladığını unutmayın. Çocuk, üst veya üst öğelerinin onInterceptTouchEvent()
ile dokunma etkinliklerine müdahale etmesini istemediğinde ViewGroup
bu yöntemi çağırır.
ACTION_OUTSIDE etkinliklerini işle
Bir ViewGroup
, ACTION_OUTSIDE
içeren bir MotionEvent
alırsa etkinlik varsayılan olarak alt öğelerine gönderilmez. MotionEvent
öğesini ACTION_OUTSIDE
ile işlemek için dispatchTouchEvent(MotionEvent event)
öğesini geçersiz kılarak uygun View
öğesine gönderin veya ilgili Window.Callback
öğesinde (örneğin, Activity
) işleyin.
ViewConfiguration sabitlerini kullan
Önceki snippet, mTouchSlop
adlı değişkeni başlatmak için geçerli ViewConfiguration
öğesini kullanır. Android sistemi tarafından kullanılan ortak mesafeler, hızlar ve sürelere erişmek için ViewConfiguration
sınıfını kullanabilirsiniz.
"Dokunma eğimi", hareket kaydırma olarak yorumlanmadan önce kullanıcının dokunuşuyla gezinebileceği olan mesafeyi piksel cinsinden belirtir. Dokunma eğimi, genellikle kullanıcı ekrandaki öğelere dokunma gibi başka bir dokunma işlemi gerçekleştirirken yanlışlıkla kaydırma yapılmasını önlemek için kullanılır.
Yaygın olarak kullanılan diğer iki ViewConfiguration
yöntemi
getScaledMinimumFlingVelocity()
ve
getScaledMaximumFlingVelocity()
yöntemleridir.
Bu yöntemler, saniyede piksel cinsinden ölçülen bir hızlı kaydırma başlatmak için sırasıyla minimum ve maksimum hızı döndürür. Örneğin:
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. } }
Çocuk görünümünün dokunulabilir alanını genişletme
Android, ebeveynin çocuk görünümünün dokunulabilir alanını çocuğun sınırlarını aşmasına olanak tanımak için TouchDelegate
sınıfını sağlar. Bu, çocuğun küçük olmasına rağmen daha geniş bir temas alanına ihtiyacı olduğunda faydalıdır. Bu yaklaşımı, çocuğun dokunulabileceği bölgeyi
küçültmek için de kullanabilirsiniz.
Aşağıdaki örnekte ImageButton
, _yetki verilmiş görünüm_, yani dokunma alanını genişlettiği alt öğedir. Düzen dosyasını aşağıda görebilirsiniz:
<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>
Aşağıdaki snippet şu görevleri tamamlar:
- Üst görünümü alır ve kullanıcı arayüzü iş parçacığında bir
Runnable
yayınlar. Bu, üst yayıncınıngetHitRect()
yöntemini çağırmadan önce alt öğelerini ortaya koyduğundan emin olur.getHitRect()
yöntemi, alt öğenin isabet dikdörtgenini (veya dokunulabilir alanını) üst öğenin koordinatlarında alır. ImageButton
alt görünümünü bulur ve alt öğenin dokunulabilir alanının sınırlarını almak içingetHitRect()
öğesini çağırır.ImageButton
alt görünümünün isabet dikdörtgeninin sınırlarını genişletir.- Genişletilmiş isabet dikdörtgeni ve
ImageButton
alt görünümünü parametre olarak geçirerek birTouchDelegate
örneğini somutlaştırır. - Üst görünümde
TouchDelegate
öğesini, dokunma yetkisi sınırları içindeki dokunmaların alt öğeye yönlendirileceği şekilde ayarlar.
ImageButton
alt görünümü için dokunma yetkisi verme yetkisi bulunan üst görünüm, tüm dokunma etkinliklerini alır. Dokunma etkinliği, alt yayıncının isabet dikdörtgeni içinde gerçekleşirse üst düzey, dokunma etkinliğini işlenmesi için alt tabloya iletir.
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); } } }); } }