是否有示例说明如何在Android中使用TouchDelegate来增加视图的点击目标的大小?


82

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

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

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


3
下面是关于如何使用TouchDelegate额外的好帖子:groups.google.com/group/android-developers/browse_thread/thread/...
李淳

Answers:


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

16
如果您要在一个屏幕上有两个按钮,该怎么办?如果您再次设置TouchDelegate,则它将擦除先前的触摸委托。
cottonBallPaws

2
这个对我不起作用...我假设委托是我要向其中添加TouchDelegate的视图。
凯文·帕克

20
@AmokraneChentir我有同样的问题。我没有使用TouchDelegates,而是具有扩展按钮的类。在此类中,我重写了getHitRect。您可以将命中矩形扩展到父视图的大小。公共无效getHitRect(Rect outRect){outRect.set(getLeft()-mPadding,getTop()-mPadding,getRight()+ mPadding,getTop()+ mPadding); }
布伦丹·温斯坦

5
@Amokrane:事实证明我提供的解决方案在ICS中不起作用。我写了一篇博客文章,介绍了两种不同的触摸代表策略brendanweinstein.me/2012/06/26/…,您可以在github.com/brendanw/Touch-Delegates/blob/master/src/com/上
布伦丹·温斯坦

8
对于@ZsoltSafrany,更安全的方法是通过OnGlobalLayoutListener并分配委托onGlobalLayout()。
greg7gkb

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

在我的情况下,我有一个imageviews的gridview,上面有复选框,并按如下方式调用该方法:

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
@Martin是正确的主意。不幸的是,dispatchTouchEvent grepcode.com/file/repository.grepcode.com/java/ext/不再从ICS开始调用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的评论,这解决了我的问题。我的项目有相对布局和一个按钮。因此父级是->布局,子级是->一个按钮。

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

如果删除他非常有价值的答案,我将他的答案放在这里。

最近有人问我如何使用TouchDelegate。我对此有些生疏,找不到任何好的文档。这是我经过反复尝试后编写的代码。touch_delegate_view是一个简单的RelativeLayout,其ID为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);
        }
      }
    });   
  } 
}

Google贾斯汀Android团队欢呼声@ Google

我的话很少:如果要扩展左侧,则用减号给出值,如果要扩展对象右侧,则用加号给出值。顶部和底部的工作原理相同。


这里的魔术提示似乎只是一个按钮-某种程度上,我感觉TouchDelegate无法用于多个按钮。你能确认吗?
马丁


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。


0

如果不想以编程方式进行操作,则只需在图像周围创建透明区域即可。如果您将图像用作按钮(视图)的背景。

灰色区域可以是透明的以增加触摸区域。

在此处输入图片说明


1
这样会不会在视图周围创建空白空间,而该空白空间无法用于其他任何用途?如果要显示参考性文本,请关闭按钮并使用文本区域来增强按钮单击区域,该怎么办?
马丁

我没有说这是最好的也是最终的方法。但是它适用于许多情况,在这些情况下您想显示一个很小的按钮图像,但要扩展触摸区域。在您的情况下,当您单击其他视图(文本视图)时很难赋予按钮优先权​​,这是不同的挑战。
Vinayak Bevinakatti 2015年

0

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


这样会不会在视图周围创建空白空间,而该空白空间无法用于其他任何用途?如果要显示参考性文本,请关闭按钮并使用文本区域来增强按钮单击区域,该怎么办?
马丁

@Martin,这取决于您的操作方式。而且应该容易做任何您想做的事情–只需将您想要的任何内容(无论您想要多少个视图)包装在所有这些视图之上的透明视图中,并为该区域分配一个点击即可。
inteist 2013年

0

一般而言,要以很少的限制扩展触摸区域,请使用以下代码。

它使您可以将给viewancestor视图内给定的触摸区域扩展给定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

因为我不喜欢仅仅为了获得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(可以是任何子视图),并将在命中时捕获的所有触摸事件转发到子视图。它适用于单击,即使在其边界之外单击,微调器也会打开,不确定其他更复杂情况的含义是什么。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.