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

问题描述 投票:80回答:11

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

但是,在Google上搜索使用示例会让很多人提出问题,但答案却很少。

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

android
11个回答
97
投票

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

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。如果您想使用多个触摸代理,请选择不同的祖先或使用组合触摸委托,如How To Use Multiple 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(可以是任何子视图),并将其点击rect中捕获的所有触摸事件转发给子视图。它适用于点击,即使在界限之外点击,微调器也会打开,不确定对其他更复杂的情况有什么影响。


20
投票

我能够通过一个主要来自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));
        }
   });
}

在我的情况下,我有一个图像视图的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);

为我工作很棒。


10
投票

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

您可以覆盖TouchDelegategetHitRect(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
投票

艾美奖的方法对我来说不起作用,但经过一些改动之后它做了:

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

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

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

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

我最近被问及如何使用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);
        }
      }
    });   
  } 
}

干杯,Justin Android Team @ Google

我的几句话:如果你想扩展左侧,你用减号给出值,如果你想扩展对象的右侧,你给出加值的值。这与顶部和底部的工作方式相同。


1
投票

给这个特定组件(Button)提供填充不是更好的想法。


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,而不仅仅是父视图。

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


0
投票

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

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


0
投票

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

© www.soinside.com 2019 - 2024. All rights reserved.