Есть ли пример того, как использовать TouchDelegate в Android для увеличения размера цели клика по представлению? - PullRequest
80 голосов
/ 27 августа 2009

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

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

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

Ответы [ 11 ]

97 голосов
/ 28 августа 2009

Я спросил друга в 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));
    }
});
20 голосов
/ 04 марта 2013

Мне удалось сделать это с помощью нескольких представлений (флажков) на одном экране в основном из этого сообщения в блоге . По сути, вы берете решение 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);

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

10 голосов
/ 07 ноября 2012

Это решение было опубликовано @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;
    }
}
6 голосов
/ 13 июня 2013

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

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

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

2 голосов
/ 05 апреля 2013

Согласно комментарию @Mason Lee, это решило мою проблему. Мой проект имел относительное расположение и одну кнопку. Таким образом, родитель -> макет, а ребенок -> кнопка.

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

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

Меня недавно спросили о том, как использовать TouchDelegate. Я был немного заржавел от этого и не смог найти хорошую документацию по этому вопросу. Вот код, который я написал после небольшой проб и ошибок. touch_delegate_view - это простая RelativeLayout с идентификатором touch_delegate_root. Я определил с единственным дочерним элементом макета, Кнопка делегированная_кнопка. В этом примере я расширяю кликабельную область кнопки на 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);
        }
      }
    });   
  } 
}

Приветствия, Джастин, Android Team @ Google

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

1 голос
/ 08 сентября 2014

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

/**
 * 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 для любого предка View, а не только для родительского View.

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

1 голос
/ 05 июля 2013

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

0 голосов
/ 15 июня 2017

Поскольку мне не нравилась идея ожидания прохода макета только для того, чтобы получить новый размер прямоугольника 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 (может быть любым дочерним представлением) и перенаправит все события касания, захваченные в его попадании, прямо в дочернее представление. Он работает для кликов, спиннер открывается, даже если щелкнуть за его пределами, не зная, каковы последствия для других более сложных случаев.

0 голосов
/ 18 мая 2017

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

Позволяет вам расширить область касания заданного 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. Если вы хотите работать с несколькими делегатами касания, выберите разных предков или используйте составного делегата касания, как описано в Как использовать несколько TouchDelegate .
0 голосов
/ 26 февраля 2013

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...