Есть ли пример того, как использовать TouchDelegate в Android для увеличения размера цели щелчка представления?

82

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

Однако поиск примеров использования в Google приводит к тому, что несколько человек задают вопрос, но мало ответов.

Кто-нибудь знает, как правильно настроить сенсорный делегат для представления, чтобы, скажем, увеличить его интерактивную область на 4 пикселя во всех направлениях?

Emmby
источник
3
Вот еще один хороший пост о том, как использовать TouchDelegate: groups.google.com/group/android-developers/browse_thread/thread/…
Мейсон Ли,
Хорошее руководство: увеличенные области касания
Тьерри-Димитри Рой

Ответы:

100

Я спросил друга из Google, и они помогли мне разобраться, как использовать TouchDelegate. Вот что у нас получилось:

final View parent = (View) delegate.getParent();
parent.post( new Runnable() {
    // Post in the parent's message queue to make sure the parent
    // lays out its children before we call getHitRect()
    public void run() {
        final Rect r = new Rect();
        delegate.getHitRect(r);
        r.top -= 4;
        r.bottom += 4;
        parent.setTouchDelegate( new TouchDelegate( r , delegate));
    }
});
Emmby
источник
16
Что, если на одном экране есть две кнопки, для которых вы хотите это сделать? Если вы снова установите TouchDelegate, он удалит предыдущий делегат касания.
cottonBallPaws
2
Это не сработало для меня ... Я предполагаю, что делегат - это представление, в которое я пытаюсь добавить TouchDelegate.
Кевин Паркер
20
@AmokraneChentir У меня была такая же проблема. Вместо использования TouchDelegates у меня есть класс, расширяющий кнопку. В этом классе я переопределяю getHitRect. Вы можете расширить прямоугольник попадания до размера родительского представления. public void getHitRect (Rect outRect) {outRect.set (getLeft () - mPadding, getTop () - mPadding, getRight () + mPadding, getTop () + mPadding); }
Брендан Вайнштейн,
5
@Amokrane: Оказывается, предложенное мной решение не работает в ICS. Я написал сообщение в блоге о двух разных стратегиях Touch Delegate brendanweinstein.me/2012/06/26/…, и вы можете получить образец кода для обеих стратегий на github.com/brendanw/Touch-Delegates/blob/master/src/com/ brendan /…
Брендан Вайнштейн
8
Согласно @ZsoltSafrany, более безопасный способ сделать это - использовать OnGlobalLayoutListener и назначить делегату onGlobalLayout ().
greg7gkb 01
20

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

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));
        }
   });
}

В моем случае у меня была сетка просмотра изображений с наложенными сверху флажками, и я вызвал метод следующим образом:

CheckBox mCheckBox = (CheckBox) convertView.findViewById(R.id.checkBox1);
final ImageView imageView = (ImageView) convertView.findViewById(R.id.imageView1);

// Increase checkbox clickable area
expandTouchArea(imageView, mCheckBox, 100);

Отлично работает для меня.

Кайл Клегг
источник
11

Это решение было опубликовано @BrendanWeinstein в комментариях.

Вместо отправки TouchDelegateвы можете переопределить свой getHitRect(Rect)метод View(в случае, если вы его расширяете).

public class MyView extends View {  //NOTE: any other View can be used here

    /* a lot of required methods */

    @Override
    public void getHitRect(Rect r) {
        super.getHitRect(r); //get hit Rect of current View

        if(r == null) {
            return;
        }

        /* Manipulate with rect as you wish */
        r.top -= 10;
    }
}
Дмитрий Зайцев
источник
Интересная идея. Но чем не будет означать, что представление не может быть создано из макета? Нет, подождите - если я спроектирую новый класс представления и буду использовать его в макете. Это могло сработать. Стоит попробовать.
Мартин
10
@ Мартин, это правильная идея. К сожалению, getHitRect больше не вызывается из ICS с помощью dispatchTouchEvent grepcode.com/file/repository.grepcode.com/java/ext/… Переопределение getHitRect будет работать только для имбирных пряников и ниже.
Брендан Вайнштейн
6

Подход emmby не сработал для меня, но после небольших изменений он сработал:

private void initApplyButtonOnClick() {
    mApplyButton.setOnClickListener(onApplyClickListener);
    final View parent = (View)mApplyButton.getParent();
    parent.post(new Runnable() {

        @Override
        public void run() {
            final Rect hitRect = new Rect();
            parent.getHitRect(hitRect);
            hitRect.right = hitRect.right - hitRect.left;
            hitRect.bottom = hitRect.bottom - hitRect.top;
            hitRect.top = 0;
            hitRect.left = 0;
            parent.setTouchDelegate(new TouchDelegate(hitRect , mApplyButton));         
        }
    });
}

Может, это сэкономит чье-то время

вирус
источник
это то, что сработало для меня. фактически он заставляет всего родителя отправлять события щелчка дочернему элементу. он даже не обязательно должен быть прямым родителем (может быть родителем родителя).
разработчик Android
Это, вероятно, сработает, если у вас есть одно представление для делегирования. что, если у вас есть, скажем, 50 просмотров для улучшения сенсорной области? - Как на этом снимке экрана: plus.google.com/u/0/b/112127498414857833804/… - Тогда TouchDelegate все еще является подходящим решением?
Мартин
2

Согласно комментарию @Mason Lee, это решило мою проблему. В моем проекте была относительная раскладка и одна кнопка. Итак, родительский элемент -> макет, а дочерний элемент -> кнопка.

Вот пример ссылки Google код Google

В случае удаления его очень ценного ответа я помещаю здесь его ответ.

Меня недавно спросили, как использовать TouchDelegate. Я сам немного заржавел и не смог найти по нему никакой хорошей документации. Вот код, который я написал после небольших проб и ошибок. touch_delegate_view - это простой RelativeLayout с идентификатором touch_delegate_root. Я определил с одним дочерним элементом макета кнопку delegated_button. В этом примере я увеличиваю интерактивную область кнопки до 200 пикселей над верхней частью моей кнопки.

public class TouchDelegateSample extends Activity {

  Button mButton;   
  @Override   
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.touch_delegate_view);
    mButton = (Button)findViewById(R.id.delegated_button);
    View parent = findViewById(R.id.touch_delegate_root);

    // post a runnable to the parent view's message queue so its run after
    // the view is drawn
    parent.post(new Runnable() {
      @Override
      public void run() {
        Rect delegateArea = new Rect();
        Button delegate = TouchDelegateSample.this.mButton;
        delegate.getHitRect(delegateArea);
        delegateArea.top -= 200;
        TouchDelegate expandedArea = new TouchDelegate(delegateArea, delegate);
        // give the delegate to an ancestor of the view we're delegating the
        // area to
        if (View.class.isInstance(delegate.getParent())) {
          ((View)delegate.getParent()).setTouchDelegate(expandedArea);
        }
      }
    });   
  } 
}

С уважением, Justin Android Team @ Google

Несколько слов от меня: если вы хотите развернуть левую часть, вы даете значение с минусом, а если вы хотите расширить правую часть объекта, вы даете значение с плюсом. То же самое с верхом и низом.

мертвая рыба
источник
Магическая подсказка здесь, кажется, одна кнопка - почему-то у меня такое чувство, что TouchDelegate непригоден для использования с несколькими кнопками. Вы можете это подтвердить?
Мартин
1

Разве это не лучшая идея дать Padding этому конкретному компоненту (Button).

user2454519
источник
1
Это сделало бы вид больше. Что делать, если у вас есть текст без щелчка над кнопкой, и вы хотите, чтобы область этого текста использовалась для повышения точности нажатия для кнопки. - Как на этом снимке
Мартин
1

Немного поздно на вечеринку, но после долгих исследований я теперь использую:

/**
 * Expand the given child View's touchable area by the given padding, by
 * setting a TouchDelegate on the given ancestor View whenever its layout
 * changes.
 */*emphasized text*
public static void expandTouchArea(final View ancestorView,
    final View childView, final Rect padding) {

    ancestorView.getViewTreeObserver().addOnGlobalLayoutListener(
        new OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                TouchDelegate delegate = null;

                if (childView.isShown()) {
                    // Get hitRect in parent's coordinates
                    Rect hitRect = new Rect();
                    childView.getHitRect(hitRect);

                    // Translate to ancestor's coordinates
                    int ancestorLoc[] = new int[2];
                    ancestorView.getLocationInWindow(ancestorLoc);

                    int parentLoc[] = new int[2];
                    ((View)childView.getParent()).getLocationInWindow(
                        parentLoc);

                    int xOffset = parentLoc[0] - ancestorLoc[0];
                    hitRect.left += xOffset;
                    hitRect.right += xOffset;

                    int yOffset = parentLoc[1] - ancestorLoc[1];
                    hitRect.top += yOffset;
                    hitRect.bottom += yOffset;

                    // Add padding
                    hitRect.top -= padding.top;
                    hitRect.bottom += padding.bottom;
                    hitRect.left -= padding.left;
                    hitRect.right += padding.right;

                    delegate = new TouchDelegate(hitRect, childView);
                }

                ancestorView.setTouchDelegate(delegate);
            }
        });
}

Это лучше, чем принятое решение, потому что оно также позволяет установить TouchDelegate на любом предковом представлении, а не только на родительском представлении.

В отличие от принятого решения, он также обновляет TouchDelegate всякий раз, когда происходит изменение макета предка View.

Стивен Талли
источник
0

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

Серая область может быть прозрачной для увеличения сенсорной области.

введите описание изображения здесь

Винаяк Бевинакатти
источник
1
Разве это не создаст вокруг вида пустое пространство, которое нельзя использовать ни для чего другого? Что, если вы хотите отобразить информационный текст, закройте кнопку и используйте текстовую область для увеличения области нажатия кнопки?
Мартин
Я не говорю, что это лучший и последний подход. Но он подойдет для многих сценариев, когда вы хотите показать крошечное изображение кнопки, а сенсорную область нужно расширить. В вашем случае трудно отдать приоритет кнопке, когда вы нажимаете другие представления (текстовое представление), все вместе - это другая проблема.
Vinayak Bevinakatti,
0

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

интеист
источник
Разве это не создаст вокруг вида пустое пространство, которое нельзя использовать ни для чего другого? Что, если вы хотите отобразить информационный текст, закройте кнопку и используйте текстовую область для увеличения области нажатия кнопки?
Мартин
@Martin, это смотря как ты это делаешь. И должно быть легко делать все, что вы хотите - просто оберните все, что хотите (сколько угодно представлений), в прозрачное представление поверх всех этих представлений и назначьте щелчок даже для этой области.
inteist
0

Чтобы расширить сенсорную область в целом с небольшими ограничениями, используйте следующий код.

Это позволяет вам расширять сенсорную область данного viewв данном ancestorпредставлении expansionв пикселях. Вы можете выбрать любого предка, если данное представление находится в дереве макета предков.

    public static void expandTouchArea(final View view, final ViewGroup ancestor, final int expansion) {
    ancestor.post(new Runnable() {
        public void run() {
            Rect bounds = getRelativeBounds(view, ancestor);
            Rect expandedBounds = expand(bounds, expansion);
            // LOG.debug("Expanding touch area of {} within {} from {} by {}px to {}", view, ancestor, bounds, expansion, expandedBounds);
            ancestor.setTouchDelegate(new TouchDelegate(expandedBounds, view));
        }

        private Rect getRelativeBounds(View view, ViewGroup ancestor) {
            Point relativeLocation = getRelativeLocation(view, ancestor);
            return new Rect(relativeLocation.x, relativeLocation.y,
                            relativeLocation.x + view.getWidth(),
                            relativeLocation.y + view.getHeight());
        }

        private Point getRelativeLocation(View view, ViewGroup ancestor) {
            Point absoluteAncestorLocation = getAbsoluteLocation(ancestor);
            Point absoluteViewLocation = getAbsoluteLocation(view);
            return new Point(absoluteViewLocation.x - absoluteAncestorLocation.x,
                             absoluteViewLocation.y - absoluteAncestorLocation.y);
        }

        private Point getAbsoluteLocation(View view) {
            int[] absoluteLocation = new int[2];
            view.getLocationOnScreen(absoluteLocation);
            return new Point(absoluteLocation[0], absoluteLocation[1]);
        }

        private Rect expand(Rect rect, int by) {
            Rect expandedRect = new Rect(rect);
            expandedRect.left -= by;
            expandedRect.top -= by;
            expandedRect.right += by;
            expandedRect.bottom += by;
            return expandedRect;
        }
    });
}

Действующие ограничения:

  • Сенсорная область не может выходить за границы предка представления, поскольку предок должен уметь перехватить событие касания, чтобы переслать его представлению.
  • Только один TouchDelegateможет быть установлен на ViewGroup. Если вы хотите работать с несколькими сенсорными делегатами, выберите разных предков или используйте составляющий сенсорный делегат, как описано в разделе Как использовать множественный сенсорный делегат .
Бьорн Калерт
источник
0

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

public class TouchSizeIncreaser extends FrameLayout {
    public TouchSizeIncreaser(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        return true;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        final View child = getChildAt(0);
        if(child != null) {
            child.onTouchEvent(event);
        }
        return true;
    }
}

А затем в макете:

<ch.tutti.ui.util.TouchSizeIncreaser
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:padding="10dp">

    <Spinner
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"/>
</ch.tutti.ui.util.TouchSizeIncreaser>

Идея состоит в том, что TouchSizeIncreaser FrameLayout будет обертывать Spinner (может быть любым дочерним представлением) и пересылать все события касания, захваченные в его прямоугольнике, дочернему представлению. Он работает для щелчков, счетчик открывается, даже если щелкнуть за его пределами, не уверен, каковы последствия для других более сложных случаев.

Ади Б
источник