【问题标题】:Is there an example of how to use a TouchDelegate in Android to increase the size of a view's click target?有没有一个例子说明如何在 Android 中使用 TouchDelegate 来增加视图点击目标的大小?
【发布时间】:2009-08-27 19:13:08
【问题描述】:

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

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

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

【问题讨论】:

标签: android


【解决方案1】:

我询问了 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));
    }
});

【讨论】:

  • 如果您希望在一个屏幕上有两个按钮怎么办?如果您再次 setTouchDelegate 则它会删除之前的触摸委托。
  • 这个对我不起作用...我认为委托是我试图将 TouchDelegate 添加到的视图。
  • @AmokraneChentir 我也遇到了同样的问题。我没有使用 TouchDelegates,而是有一个扩展按钮的类。在这个类中,我重写了 getHitRect。您可以将点击矩形扩展到父视图的大小。 public void getHitRect(Rect outRect) { outRect.set(getLeft() - mPadding, getTop() - mPadding, getRight() + mPadding, getTop() + mPadding); }
  • 这个解决方案安全吗?如果发布的 runnable 在布局发生之前执行怎么办?无论如何,这里过于依赖预期的行为——这不是未来的证据。
  • 根据@ZsoltSafrany,更安全的方法是通过 OnGlobalLayoutListener 并分配委托 onGlobalLayout()。
【解决方案2】:

我能够在一个主要来自this blog post 的屏幕上使用多个视图(复选框)来完成此操作。基本上,您采用 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);

对我来说工作得很好。

【讨论】:

    【解决方案3】:

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

    您可以覆盖ViewgetHitRect(Rect) 方法,而不是发送TouchDelegate(以防您要扩展一个)。

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

    【讨论】:

    • 有趣的想法。但不是意味着视图可以从布局创建吗?不,等等——如果我设计一个新的视图类并在布局中使用它。那可以工作。值得一试。
    • @Martin 这是正确的想法。不幸的是,从 ICS 开始,dispatchTouchEvent grepcode.com/file/repository.grepcode.com/java/ext/… 不再调用 getHitRect 覆盖 getHitRect 仅适用于姜饼及以下版本。
    【解决方案4】:

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

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

    【讨论】:

    • 这对我有用。它实际上使整个父母向孩子发送点击事件。它甚至不必是直接父母(可以是父母的父母)。
    • 如果您要委派一个视图,这可能会起作用。如果你有 50 个视图来增强触控区域怎么办? — 就像在这个屏幕截图中一样:plus.google.com/u/0/b/112127498414857833804/… — TouchDelegate 仍然是一个合适的解决方案吗?
    【解决方案5】:

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

    这是一个谷歌链接示例google code

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

    最近有人问我如何使用 TouchDelegate。我自己对此有点生疏,我找不到任何好的文档。 这是我经过反复试验后编写的代码。 touch_delegate_view 是一个带有 id 的简单 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 团队 @ Google

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

    【讨论】:

    • 这里的神奇提示似乎是 one button — 不知何故,我觉得 TouchDelegate 无法用于多个按钮。你能确认一下吗?
    【解决方案6】:

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

    【讨论】:

    • 这将使查看更大。如果您在按钮上方有一些不可点击的文本文本,并且您希望该文本的区域用于提高按钮的点击准确性,该怎么办。 — 就像在这个截图中一样:plus.google.com/u/0/b/112127498414857833804/…
    【解决方案7】:

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

    /**
     * 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 的布局发生变化时更新 TouchDelegate。

    【讨论】:

      【解决方案8】:

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

      灰色区域可以透明,增加触控面积。

      【讨论】:

      • 不会在视图周围创建不能用于其他任何用途的空白空间吗?如果您想在关闭按钮时显示信息性文本并使用文本区域来增强按钮单击区域怎么办?
      • 我并不是说它是最好的和最终的方法。但它适用于您想要显示一个小按钮图像但要扩展触摸区域的许多场景。在您的情况下,当您单击其他视图(文本视图)时,很难优先考虑按钮,这都是不同的挑战。
      【解决方案9】:

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

      【讨论】:

      • 不会在视图周围创建不能用于其他任何用途的空白空间吗?如果您想在关闭按钮时显示信息性文本并使用文本区域来增强按钮单击区域怎么办?
      • @Martin,这取决于你怎么做。并且应该很容易做任何你想做的事情 - 只需将你想要的任何东西(无论你想要多少视图)包装在所有这些视图之上的透明视图中,甚至为该区域分配一个点击。
      【解决方案10】:

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

      它可以让您在给定的ancestor 视图中以给定的expansion 为单位扩展给定view 的触摸区域(以像素为单位)。只要给定视图在祖先布局树中,您就可以选择任何祖先。

          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。如果您想使用多个触摸代理,请选择不同的祖先或使用组合触摸代理,如How To Use Multiple TouchDelegate 中所述。

      【讨论】:

        【解决方案11】:

        因为我不喜欢等待布局传递只是为了获得 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(可以是任何子视图)并将在它的命中矩形中捕获的所有触摸事件转发到子视图。它适用于点击,即使点击超出其范围,微调器也会打开,不确定对其他更复杂的情况有什么影响。

        【讨论】:

          猜你喜欢
          • 2022-10-15
          • 2010-12-21
          • 2013-12-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-07-09
          相关资源
          最近更新 更多