Soru Android: Kullanıcının düğme bölgesinin dışına dokunduğunda ve sürüklendiğinde algılanır mı?


Android'de, bir kullanıcının bu düğmeye dokunup bu düğmenin dışına çıkıp çıkmadığını nasıl algılayabiliriz?


44
2018-06-20 11:03


Menşei




Cevaplar:


MotionEvent.MOVE_OUTSIDE öğesini kontrol edin: MotionEvent.MOVE kontrol edin:

private Rect rect;    // Variable rect to hold the bounds of the view

public boolean onTouch(View v, MotionEvent event) {
    if(event.getAction() == MotionEvent.ACTION_DOWN){
        // Construct a rect of the view's bounds
        rect = new Rect(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());

    }
    if(event.getAction() == MotionEvent.ACTION_MOVE){
        if(!rect.contains(v.getLeft() + (int) event.getX(), v.getTop() + (int) event.getY())){
            // User moved outside bounds
        }
    }
    return false;
}

NOT: Android 4.0'ı hedeflemek istiyorsanız, tümüyle yeni olasılıklar dünyası açılır: http://developer.android.com/reference/android/view/MotionEvent.html#ACTION_HOVER_ENTER


81
2017-11-09 18:42



Üzgünüm ama işe yaramıyor. Sadece ACTION_UP, ACTION_DOWN ve ACTION_MOVE, ancak ACTION_OUTSIDE değerini yakalayamıyorum. - anticafe
API Seviye 3'ten itibaren kullanılabilir: developer.android.com/reference/android/view/... - Entreco
Bu cevaba göre (stackoverflow.com/questions/6162497/...) Sadece bir dokunuşun bir Etkinlik değil, bir görünüm olup olmadığını algılamak için ACTION_OUTSIDE özelliğini kullanabileceğimize inanıyorum. - anticafe
Haklısın! Cevabımı değiştirdim - Entreco
Kökte konumlandırılmayan bireysel görünümler için bu çalışmayı yapmak için rect.contains testinin güncellenmesi için FrostRocket'ın yanıtına bakın. - Dan J


Entreco'nun gönderdiği cevap, benim durumumda biraz hafifçe düzeltmeye ihtiyaç duyuyordu. Yedeklemek zorundaydım:

if(!rect.contains((int)event.getX(), (int)event.getY()))

için

if(!rect.contains(v.getLeft() + (int) event.getX(), v.getTop() + (int) event.getY()))

Çünkü event.getX() ve event.getY() Sadece ekranın tamamını değil, sadece ImageView'a uygulanır.


21
2018-01-04 05:46



+1 teşekkürler, bu daha evrensel yaklaşım ve özel görünüm uygulamalarında da çalışıyor - Knickedi


OnTouch'ımda biraz giriş ekledim ve öğrendim. MotionEvent.ACTION_CANCEL vuruluyordu. Bu benim için yeterince iyi ...


5
2018-05-25 14:49



Sen bir MotionEvent.ACTION_CANCEL eğer geri çağırma View bir ebeveynin içinde bulunur ViewGroup sevmek ScrollView Çocuklarının dokunuşlarını çalmakla ilgileniyor ancak başka bir şekilde almanıza gerek yok. MotionEvent.ACTION_CANCEL içinde geri arama View. - Adil Hussain


En iyi 2 cevap, görünümde bir kaydırma görünümü dışındadır: parmağınızı hareket ettirdiğinizde kaydırma işlemi gerçekleştiğinde, hala bir hareket olayı olarak kaydedilir, ancak bir MotionEvent.ACTION_MOVE olayı olarak değil. Bu nedenle, cevabı iyileştirmek için (yalnızca görünümünüz kaydırma öğesi içinde ise gereklidir):

private Rect rect;    // Variable rect to hold the bounds of the view

public boolean onTouch(View v, MotionEvent event) {
    if(event.getAction() == MotionEvent.ACTION_DOWN){
        // Construct a rect of the view's bounds
        rect = new Rect(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());

    } else if(rect != null && !rect.contains(v.getLeft() + (int) event.getX(), v.getTop() + (int) event.getY())){
        // User moved outside bounds
    }
    return false;
}

Bunu Android 4.3 ve Android 4.4 sürümlerinde test ettim

Moritz'in cevabı ile ilk 2 arasında herhangi bir fark görmedim ama bu aynı zamanda cevabı için de geçerli:

private Rect rect;    // Variable rect to hold the bounds of the view

public boolean onTouch(View v, MotionEvent event) {
    if(event.getAction() == MotionEvent.ACTION_DOWN){
        // Construct a rect of the view's bounds
        rect = new Rect(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());

    } else if (rect != null){
        v.getHitRect(rect);
        if(rect.contains(
                Math.round(v.getX() + event.getX()),
                Math.round(v.getY() + event.getY()))) {
            // inside
        } else {
            // outside
        }
    }
    return false;
}

2
2018-04-14 08:05



Bu kodu bir RecyclerView öğesinde yer alan ve mükemmel şekilde çalışan bir düğmeyle uyguladım. Sadece yeni Rect'ten kurtulabileceğinizi söylemek istedim. ACTION_DOWN Eğer getHitRect'ten önce eklerseniz. Rect rect = yeni Rect (); getHitRect (diag); - Jose M Lechon


OP ile aynı problemi yaşadım, çünkü ne zaman bilmek istedim (1) View aşağı dokunduğunda aşağıya dokunduğunda (2) View veya (3) aşağı dokunuşun View. Basit bir uzantı oluşturmak için bu konudaki çeşitli cevapları bir araya getirdim. View.OnTouchListener (adlandırılmış SimpleTouchListener) böylece başkaları ile uğraşmak zorunda değilsiniz MotionEvent nesne. Sınıfın kaynağı bulunabilir İşte veya bu cevabın alt kısmında.

Bu sınıfı kullanmak için, sadece tek bir parametre olarak ayarlayın. View.setOnTouchListener(View.OnTouchListener) yöntem şu şekildedir:

myView.setOnTouchListener(new SimpleTouchListener() {

    @Override
    public void onDownTouchAction() {
        // do something when the View is touched down
    }

    @Override
    public void onUpTouchAction() {
        // do something when the down touch is released on the View
    }

    @Override
    public void onCancelTouchAction() {
        // do something when the down touch is canceled
        // (e.g. because the down touch moved outside the bounds of the View
    }
});

İşte projenize ekleyebileceğiniz sınıfın kaynağı:

public abstract class SimpleTouchListener implements View.OnTouchListener {

    /**
     * Flag determining whether the down touch has stayed with the bounds of the view.
     */
    private boolean touchStayedWithinViewBounds;

    @Override
    public boolean onTouch(View view, MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                touchStayedWithinViewBounds = true;
                onDownTouchAction();
                return true;

            case MotionEvent.ACTION_UP:
                if (touchStayedWithinViewBounds) {
                    onUpTouchAction();
                }
                return true;

            case MotionEvent.ACTION_MOVE:
                if (touchStayedWithinViewBounds
                        && !isMotionEventInsideView(view, event)) {
                    onCancelTouchAction();
                    touchStayedWithinViewBounds = false;
                }
                return true;

            case MotionEvent.ACTION_CANCEL:
                onCancelTouchAction();
                return true;

            default:
                return false;
        }
    }

    /**
     * Method which is called when the {@link View} is touched down.
     */
    public abstract void onDownTouchAction();

    /**
     * Method which is called when the down touch is released on the {@link View}.
     */
    public abstract void onUpTouchAction();

    /**
     * Method which is called when the down touch is canceled,
     * e.g. because the down touch moved outside the bounds of the {@link View}.
     */
    public abstract void onCancelTouchAction();

    /**
     * Determines whether the provided {@link MotionEvent} represents a touch event
     * that occurred within the bounds of the provided {@link View}.
     *
     * @param view  the {@link View} to which the {@link MotionEvent} has been dispatched.
     * @param event the {@link MotionEvent} of interest.
     * @return true iff the provided {@link MotionEvent} represents a touch event
     * that occurred within the bounds of the provided {@link View}.
     */
    private boolean isMotionEventInsideView(View view, MotionEvent event) {
        Rect viewRect = new Rect(
                view.getLeft(),
                view.getTop(),
                view.getRight(),
                view.getBottom()
        );

        return viewRect.contains(
                view.getLeft() + (int) event.getX(),
                view.getTop() + (int) event.getY()
        );
    }
}

2
2018-03-26 16:09



Bu özellikle ACTION_CANCEL olması gerektiği gibi tetiklemediğinde sorunla çalışan kişiler için harika bir yanıttır. - New Guy


@FrostRocket'ın cevabı doğruysa da, çevirilerdeki değişiklikleri hesaba katmak için view.getX () ve Y'yi kullanmalısınız:

 view.getHitRect(viewRect);
 if(viewRect.contains(
         Math.round(view.getX() + event.getX()),
         Math.round(view.getY() + event.getY()))) {
   // inside
 } else {
   // outside
 }

1
2017-10-17 10:37





view.setClickable(true);
view.setOnTouchListener(new OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (!v.isPressed()) {
            Log.e("onTouch", "Moved outside view!");
        }
        return false;
    }
});

view.isPressed kullanımları view.pointInView ve bazı dokunma eğimlerini içerir. Eğim istemezseniz, mantığı dahili olarak kopyalayın. view.pointInView (kamuya açık, ancak gizli olduğu için resmi API'nın bir parçası değildir ve herhangi bir zamanda kaybolabilir).

view.setClickable(true);
view.setOnTouchListener(new OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            v.setTag(true);
        } else {
            boolean pointInView = event.getX() >= 0 && event.getY() >= 0
                    && event.getX() < (getRight() - getLeft())
                    && event.getY() < (getBottom() - getTop());
            boolean eventInView = ((boolean) v.getTag()) && pointInView;
            Log.e("onTouch", String.format("Dragging currently in view? %b", pointInView));
            Log.e("onTouch", String.format("Dragging always in view? %b", eventInView));
            v.setTag(eventInView);
        }
        return false;
    }
});

1
2017-10-06 22:17