Как использовать несколько TouchDelegate - PullRequest
10 голосов
/ 23 июля 2011

У меня есть две кнопки ImageButton, каждая внутри RelativeLayout, и эти две RelativeLayout находятся в другой RelativeLayout, я хочу установить TouchDelegate для каждой ImageButton. Если обычно я добавляю TouchDelegate к каждому ImageButton и его родительскому RelativeLayout, тогда только одна ImageButton работает правильно, другая не расширяет область щелчка. Поэтому, пожалуйста, помогите мне узнать, как использовать TouchDelegate в обеих кнопках ImageButton. Если это невозможно, то каким может быть эффективный способ расширить область щелчка в представлении? Заранее спасибо ........

Вот мой код xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout android:id="@+id/FrameContainer"
android:layout_width="fill_parent" android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
<RelativeLayout android:layout_alignParentLeft="true"
    android:id="@+id/relativeLayout3" android:layout_width="wrap_content"
    android:layout_height="wrap_content">
    <RelativeLayout android:layout_alignParentLeft="true"
        android:id="@+id/relativeLayout1" android:layout_width="113dip"
        android:layout_height="25dip">
        <ImageButton android:id="@+id/tutorial1"
            android:layout_width="wrap_content" android:layout_height="wrap_content"
            android:background="@null" android:src="@drawable/tutorial" />
    </RelativeLayout>
    <RelativeLayout android:layout_alignParentLeft="true"
        android:id="@+id/relativeLayout2" android:layout_width="113dip"
        android:layout_height="25dip" android:layout_marginLeft="100dip">
        <ImageButton android:id="@+id/tutorial2"
            android:layout_width="wrap_content" android:layout_height="wrap_content"
            android:background="@null" android:src="@drawable/tutorial"
            android:layout_marginLeft="50dip" />
    </RelativeLayout>
</RelativeLayout>

Мой класс занятий:

import android.app.Activity;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.TouchDelegate;
import android.view.View;
import android.widget.ImageButton;
import android.widget.Toast;

public class TestTouchDelegate extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    View mParent1 = findViewById(R.id.relativeLayout1);
    mParent1.post(new Runnable() {
        @Override
        public void run() {
            Rect bounds1 = new Rect();
            ImageButton mTutorialButton1 = (ImageButton) findViewById(R.id.tutorial1);
            mTutorialButton1.setEnabled(true);
            mTutorialButton1.setOnClickListener(new View.OnClickListener() {
                public void onClick(View view) {
                    Toast.makeText(TestTouchDelegate.this, "Test TouchDelegate 1", Toast.LENGTH_SHORT).show();
                }
            });

            mTutorialButton1.getHitRect(bounds1);
            bounds1.right += 50;
            TouchDelegate touchDelegate1 = new TouchDelegate(bounds1, mTutorialButton1);

            if (View.class.isInstance(mTutorialButton1.getParent())) {
                ((View) mTutorialButton1.getParent()).setTouchDelegate(touchDelegate1);
            }
        }
    });

    //View mParent = findViewById(R.id.FrameContainer);
    View mParent2 = findViewById(R.id.relativeLayout2);
    mParent2.post(new Runnable() {
        @Override
        public void run() {
            Rect bounds2 = new Rect();
            ImageButton mTutorialButton2 = (ImageButton) findViewById(R.id.tutorial2);
            mTutorialButton2.setEnabled(true);
            mTutorialButton2.setOnClickListener(new View.OnClickListener() {
                public void onClick(View view) {
                    Toast.makeText(TestTouchDelegate.this, "Test TouchDelegate 2", Toast.LENGTH_SHORT).show();
                }
            });

            mTutorialButton2.getHitRect(bounds2);
            bounds2.left += 50;
            TouchDelegate touchDelegate2 = new TouchDelegate(bounds2, mTutorialButton2);

            if (View.class.isInstance(mTutorialButton2.getParent())) {
                ((View) mTutorialButton2.getParent()).setTouchDelegate(touchDelegate2);
            }
        }
    });

}

}

Ответы [ 7 ]

21 голосов
/ 18 ноября 2013

Вы можете использовать составной шаблон, чтобы иметь возможность добавить более одного TouchDelegate к View.Шаги:

  1. Создать TouchDelegateComposite (независимо от того, какое представление вы передадите в качестве аргумента, он используется только для получения контекста)
  2. Создайте необходимые TouchDelegates и добавьте ихв композит
  3. Добавить композит для просмотра, как они рекомендуют здесь (через view.post(new Runnable))

    public class TouchDelegateComposite extends TouchDelegate {
    
        private final List<TouchDelegate> delegates = new ArrayList<TouchDelegate>();
        private static final Rect emptyRect = new Rect();
    
        public TouchDelegateComposite(View view) {
            super(emptyRect, view);
        }
    
        public void addDelegate(TouchDelegate delegate) {
            if (delegate != null) {
                delegates.add(delegate);
            }
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            boolean res = false;
            float x = event.getX();
            float y = event.getY();
            for (TouchDelegate delegate : delegates) {
                event.setLocation(x, y);
                res = delegate.onTouchEvent(event) || res;
            }
            return res;
        }
    
    }
    
3 голосов
/ 24 февраля 2012

Предполагается, что для каждого просмотра должен быть только один сенсорный делегат. Документация для getTouchDelegate () в классе View гласит:

"Получает TouchDelegate для этого просмотра."

Должен быть только один TouchDelegate. Чтобы использовать только один TouchDelegate для каждого вида, вы можете обернуть каждый сенсорный вид в вид с размерами, отражающими то, что вы хотели бы сделать сенсорным. Разработчик Android в квадрате дает пример того, как вы можете сделать это для нескольких видов, используя только один статический метод (http://www.youtube.com/watch?v=jF6Ad4GYjRU&t=37m4s):

  public static void expandTouchArea(final View bigView, final View smallView, final int extraPadding) {
bigView.post(new Runnable() {
    @Override
    public void run() {
        Rect rect = new Rect();
        smallView.getHitRect(rect);
        rect.top -= extraPadding;
        rect.left -= extraPadding;
        rect.right += extraPadding;
        rect.bottom += extraPadding;
        bigView.setTouchDelegate(new TouchDelegate(rect, smallView));
    }
});

}

Допустим, вы не хотите загромождать иерархию представлений. Есть еще два варианта, которые я могу придумать. Вы можете определить границы того, что является сенсорным внутри сенсорного представления, и убедиться, что все промежуточные события переданы этому дочернему виду из соответствующих родительских представлений. Или вы можете переопределить getHitRect () для сенсорного представления. Первое быстро загромождает ваш код и затрудняет его понимание, поэтому второе - лучший путь вперед. Вы хотите перейти с переопределением getHitRect.

Если mPadding - это количество дополнительной области, которую вы хотите сделать доступной для просмотра, вы можете использовать что-то вроде следующего:

    @Override
public void getHitRect(Rect outRect) {
    outRect.set(getLeft() - mPadding, getTop() - mPadding, getRight() + mPadding, getTop() + mPadding);
}

Если вы используете код, подобный приведенному выше, вам нужно будет рассмотреть, какие сенсорные виды находятся поблизости. Сенсорная область представления, которая является самой высокой в ​​стеке, может перекрывать поверх другого представления.

Другим аналогичным вариантом было бы просто изменить отступ сенсорного вида. Мне не нравится это как решение, потому что может быть трудно отслеживать, как изменяются размеры Представлений.

1 голос
/ 04 ноября 2014

Чтобы ваш код работал, вам нужно уменьшить левую границу bounds2, а не увеличивать ее.

bounds2.left -= 50;

После игры с TouchDelegate я пришел к приведенному ниже коду, который работаетдля меня все время на любой версии Android.Хитрость заключается в том, чтобы расширить область, гарантированную после вызова макета.

public class ViewUtils {

    public static final void extendTouchArea(final View view, 
            final int padding) {

        view.getViewTreeObserver().addOnGlobalLayoutListener(
                new OnGlobalLayoutListener() {

            @Override
            @SuppressWarnings("deprecation")
            public void onGlobalLayout() {
                final Rect touchArea = new Rect();
                view.getHitRect(touchArea);
                touchArea.top -= padding;
                touchArea.bottom += padding;
                touchArea.left -= padding;
                touchArea.right += padding;

                final TouchDelegate touchDelegate = 
                    new TouchDelegate(touchArea, view);
                final View parent = (View) view.getParent();
                parent.setTouchDelegate(touchDelegate);

                view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
            }
        });
    }

}
0 голосов
/ 30 мая 2018

Я скопировал код TouchDelegate и сделал некоторые изменения. Теперь он может поддерживать множественные представления независимо от того, имеют ли эти представления общих родителей

class MyTouchDelegate: TouchDelegate {
private var mDelegateViews = ArrayList<View>()

private var mBoundses = ArrayList<Rect>()

private var mSlopBoundses = ArrayList<Rect>()

private var mDelegateTargeted: Boolean = false

val ABOVE = 1
val BELOW = 2
val TO_LEFT = 4
val TO_RIGHT = 8

private var mSlop: Int = 0

constructor(context: Context): super(Rect(), View(context)) {
    mSlop = ViewConfiguration.get(context).scaledTouchSlop
}

fun addTouchDelegate(delegateView: View, bounds: Rect) {
    val slopBounds = Rect(bounds)
    slopBounds.inset(-mSlop, -mSlop)
    mDelegateViews.add(delegateView)
    mSlopBoundses.add(slopBounds)
    mBoundses.add(Rect(bounds))
}

var targetIndex = -1

override fun onTouchEvent(event: MotionEvent): Boolean {
    val x = event.x.toInt()
    val y = event.y.toInt()
    var sendToDelegate = false
    var hit = true
    var handled = false


    when (event.action) {
        MotionEvent.ACTION_DOWN -> {
            targetIndex = -1

            for ((index, item) in mBoundses.withIndex()) {
                if (item.contains(x, y)) {
                    mDelegateTargeted = true
                    targetIndex = index
                    sendToDelegate = true
                }
            }
        }
        MotionEvent.ACTION_UP, MotionEvent.ACTION_MOVE -> {
            sendToDelegate = mDelegateTargeted
            if (sendToDelegate) {
                val slopBounds = mSlopBoundses[targetIndex]
                if (!slopBounds.contains(x, y)) {
                    hit = false
                }
            }
        }
        MotionEvent.ACTION_CANCEL -> {
            sendToDelegate = mDelegateTargeted
            mDelegateTargeted = false
        }
    }
    if (sendToDelegate) {
        val delegateView = mDelegateViews[targetIndex]

        if (hit) {
            // Offset event coordinates to be inside the target view
            event.setLocation((delegateView.width / 2).toFloat(), (delegateView.height / 2).toFloat())
        } else {
            // Offset event coordinates to be outside the target view (in case it does
            // something like tracking pressed state)
            val slop = mSlop
            event.setLocation((-(slop * 2)).toFloat(), (-(slop * 2)).toFloat())
        }
        handled = delegateView.dispatchTouchEvent(event)
    }
    return handled
}

}

используйте это так

  fun expandTouchArea(viewList: List<View>, touchSlop: Int) {


    val rect = Rect()

    viewList.forEach {
        it.getHitRect(rect)
        if (rect.left == rect.right && rect.top == rect.bottom) {
            postDelay(Runnable { expandTouchArea(viewList, touchSlop) }, 200)
            return
        }
        rect.top -= touchSlop
        rect.left -= touchSlop
        rect.right += touchSlop
        rect.bottom += touchSlop

        val parent = it.parent as? View
        if (parent != null) {
            val parentDelegate = parent.touchDelegate
            if (parentDelegate != null) {
                (parentDelegate as? MyTouchDelegate)?.addTouchDelegate(it, rect)
            } else {
                val touchDelegate = MyTouchDelegate(this)
                touchDelegate.addTouchDelegate(it, rect)
                parent.touchDelegate = touchDelegate
            }
        }
    }
}
0 голосов
/ 04 сентября 2017

У меня была та же проблема: Попытка добавить несколько TouchDelegates для разных LinearLayouts, которые маршрутизируют события касания для разделения Switches соответственно, в одном макете.

Подробнее см. этот вопрос, заданный мной и мой ответ .

Я обнаружил следующее: как только я заключаю LinearLayouts каждый в другой LinearLayout соответственно, второй (конец каждого последующего) TouchDelegate начинает работать, как и ожидалось.

Так что это может помочь ОП создать рабочее решение его проблемы. У меня нет удовлетворительного объяснения, почему он так себя ведет.

0 голосов
/ 20 марта 2015

Хорошо, я думаю, что никто не дает реального ответа, чтобы решить его и упростить его. В последнее время у меня была та же проблема, и я читал все выше, я просто понятия не имел, как просто заставить ее работать. Но, наконец, я это сделал. чтобы иметь в виду! Давайте представим, что у вас есть одна целая раскладка, которая содержит ваши две маленькие кнопки, область которых должна быть расширена, поэтому вы ДОЛЖНЫ создать другую раскладку внутри вашей основной раскладки и поместить другую кнопку в только что созданную раскладку, так что в этом случае с Статическим методом вы можете дать сенсорный делегат двум кнопкам одновременно. Теперь глубже и шаг за шагом в код! сначала вы наверняка просто найдете вид вашего MAINLAYOUT и кнопки вот так (этот макет будет удерживать нашу первую кнопку)

RelativeLayout mymainlayout = (RelativeLayout)findViewById(R.id.mymainlayout)
Button mybutoninthislayout = (Button)findViewById(R.id.mybutton)

хорошо, мы закончили нахождение основного макета и его вида кнопки, который будет содержать все, только наш onCreate() отображающий макет, но вы должны найти его на случай, если вы захотите использовать его позже. какая ширина и высота на ваш вкус (этот новый созданный макет будет удерживать нашу вторую кнопку)

RelativeLayout myinnerlayout = (RelativeLayout)findViewById(R.id.myinnerlayout)
Button mybuttoninsideinnerlayout = (Button)findViewById(R.id.mysecondbutton)

хорошо, мы закончили поиск представлений, так что теперь мы можем просто скопировать и вставить код нашего парня, который первым ответил на ваш вопрос. Просто скопируйте этот код в вашу основную деятельность.

 public static void expandTouchArea(final View bigView, final View smallView, final int extraPadding) {
    bigView.post(new Runnable() {
        @Override
        public void run() {
            Rect rect = new Rect();
            smallView.getHitRect(rect);
            rect.top -= extraPadding;
            rect.left -= extraPadding;
            rect.right += extraPadding;
            rect.bottom += extraPadding;
            bigView.setTouchDelegate(new TouchDelegate(rect, smallView));
        }
    });
}

Теперь, как использовать этот метод и заставить его работать? Вот как! в вашем методе onCreate() вставьте следующий фрагмент кода

  expandTouchArea(mymainlayout,mybutoninthislayout,60);
  expandTouchArea(myinnerlayout, mybuttoninsideinnerlayout,60);

Объяснение того, что мы сделали в этом фрагменте. Мы взяли созданный нами статический метод с именем expandTouchArea() и дали 3 аргумента. 1-й аргумент - макет, который содержит кнопку, область которой должна быть расширена 2-й аргумент - актуальная кнопка для расширения области 3-й аргумент - область в пикселях того, насколько мы хотим увеличить область кнопки! НАСЛАЖДАЙТЕСЬ!

0 голосов
/ 21 июля 2013

мне показалось, что это работает http://cyrilmottier.com/2012/02/16/listview-tips-tricks-5-enlarged-touchable-areas/

...