有没有一个例子说明如何在 Android 中使用 TouchDelegate 来增加视图点击目标的大小?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1343222/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 02:58:00  来源:igfitidea点击:

Is there an example of how to use a TouchDelegate in Android to increase the size of a view's click target?

android

提问by emmby

My understanding is that when you have a view that's too small to easily touch, you're supposed to use a TouchDelegate to increase the clickable region for that view.

我的理解是,当您的视图太小而无法轻松触摸时,您应该使用 TouchDelegate 来增加该视图的可点击区域。

However, searching for usage examples on Google turn up multiple people asking the question, but few answers.

但是,在 Google 上搜索用法示例会发现很多人问这个问题,但答案很少。

Does anyone know the proper way to set up a touch delegate for a view to, say, increase its clickable region by 4 pixels in every direction?

有谁知道为视图设置触摸委托的正确方法,例如,将其可点击区域在每个方向上增加 4 个像素?

回答by emmby

I asked a friend at Google and they were able to help me figure out how to use TouchDelegate. Here's what we came up with:

我问了谷歌的一个朋友,他们能够帮助我弄清楚如何使用 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));
    }
});

回答by Kyle Clegg

I was able to accomplish this with multiple views (checkboxes) on one screen drawing largely from this blog post. Basically you take emmby's solution and apply it to each button and its parent individually.

我能够在一个屏幕上通过多个视图(复选框)完成此操作,主要来自这篇博文。基本上,您采用 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));
        }
   });
}

In my case I had a gridview of imageviews with checkboxes overlaid on top, and called the method as follows:

在我的情况下,我有一个图像视图的网格视图,上面覆盖了复选框,并按如下方式调用了该方法:

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

Working great for me.

对我来说很棒。

回答by Dmitry Zaytsev

This solution was posted by @BrendanWeinstein in comments.

此解决方案由@BrendanWeinstein 在评论中发布。

Instead of sending a TouchDelegateyou can override getHitRect(Rect)method of your View(in case that you are extending one).

而不是发送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;
    }
}

回答by vir us

emmby's approch didn't work for me but after a little changes it did:

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

Maybe it can save someone's time

也许它可以节省某人的时间

回答by deadfish

According to @Mason Lee comment, this solved my problem. My project had relative layout and one button. So parent is -> layout and child is -> a button.

根据@Mason Lee 的评论,这解决了我的问题。我的项目有相对布局和一个按钮。所以父级是 -> 布局,子级是 -> 一个按钮。

Here is a google link example google code

这是一个谷歌链接示例谷歌代码

In case of deleting his very valuable answer I put here his answer.

在删除他非常有价值的答案的情况下,我把他的答案放在这里。

I was recently asked about how to use a TouchDelegate. I was a bit rusty myself on this and I couldn't find any good documentation on it. Here's the code I wrote after a little trial and error. touch_delegate_view is a simple RelativeLayout with the id touch_delegate_root. I defined with a single, child of the layout, the button delegated_button. In this example I expand the clickable area of the button to 200 pixels above the top of my button.

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

Cheers, Justin Android Team @ Google

最近有人问我如何使用 TouchDelegate。我自己对此有点生疏,我找不到任何关于它的好的文档。这是我经过一些试验和错误后编写的代码。touch_delegate_view 是一个简单的RelativeLayout,id 为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);
        }
      }
    });   
  } 
}

干杯,贾斯汀安卓团队@谷歌

Few words from me: if you want expand left side you give value with minus, and if you want expand right side of object, you give value with plus. This works the same with top and bottom.

我的几句话:如果你想扩大左边,你用减号赋值,如果你想扩大对象的右侧,你用加号赋值。这与顶部和底部相同。

回答by user2454519

Isn't it the better Idea of giving Padding to that particular component(Button).

为该特定组件(按钮)提供填充不是更好的想法吗?

回答by Stephen Talley

A bit late to the party, but after much research, I'm now using:

聚会有点晚,但经过大量研究,我现在正在使用:

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

This is better than the accepted solution because it also allows a TouchDelegate to be set on any ancestor View, not just the parent View.

这比公认的解决方案更好,因为它还允许在任何祖先视图上设置 TouchDelegate,而不仅仅是父视图。

Unlike the accepted solution, it also updates the TouchDelegate whenever there is a change in the ancestor View's layout.

与公认的解决方案不同的是,只要祖先视图的布局发生变化,它也会更新 TouchDelegate。

回答by Bj?rn Kahlert

To expand the touch area generically with pretty few restrictions use the following code.

要在几乎没有限制的情况下扩展触摸区域,请使用以下代码。

It lets you expand the touch area of the given viewwithin the given ancestorview by the given expansionin pixels. You can choose any ancestor as long as the given view is in the ancestors layout tree.

它可以让你在给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;
        }
    });
}

Restrictions that apply:

适用的限制:

  • The touch area can not exceed the bounds of the view's ancestor since the ancestor must be able to catch the touch event in order to forward it to the view.
  • Only one TouchDelegatecan be set to a ViewGroup. If you want to work with multiple touch delegates, choose different ancestors or use a composing touch delegate like explained in How To Use Multiple TouchDelegate.
  • 触摸区域不能超出视图祖先的边界,因为祖先必须能够捕获触摸事件才能将其转发到视图。
  • 只能TouchDelegate设置一个ViewGroup。如果您想使用多个触摸委托,请选择不同的祖先或使用组合触摸委托,如如何使用多个 TouchDelegate 中所述

回答by Adi B

Because I didn't like the idea of waiting for the layout pass just to get the new size of the TouchDelegate's rectangle, I went for a different solution:

因为我不喜欢等待布局传递只是为了获得 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;
    }
}

And then, in a layout:

然后,在布局中:

<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>

The idea is that TouchSizeIncreaser FrameLayout will wrap the Spinner (could be any child View) and forward all the touch events captured in it's hit rect to the child View. It works for clicks, the spinner opens even if clicked outside its bounds, not sure what are the implications for other more complex cases.

这个想法是 TouchSizeIncreaser FrameLayout 将包装 Spinner(可以是任何子视图)并将在它的命中矩形中捕获的所有触摸事件转发到子视图。它适用于点击,即使点击超出其边界,微调器也会打开,不确定对其他更复杂的情况有什么影响。

回答by inteist

In most cases, you can wrap the view that requires a larger touch area in another headless view (artificial transparent view) and add padding/margin to the wrapper view and attach the click/touch even to the wrapper view instead of the original view that has to have a larger touch area.

在大多数情况下,您可以将需要更大触摸区域的视图包装在另一个无头视图(人工透明视图)中,并向包装视图添加填充/边距,并将点击/触摸甚至附加到包装视图而不是原始视图必须有更大的触摸区域。