Android 通过点击视图外部使视图消失

发布于 2024-11-23 16:38:51 字数 168 浏览 6 评论 0原文

我有一些在按下按钮时可见的视图。如果我在这些视图之外单击,我希望它们消失。

在 Android 上这将如何完成?

另外,我意识到“后退按钮”也可以为 Android 用户提供帮助 - 我可能会使用它作为关闭视图的辅助方式 - 但有些平板电脑甚至不再使用“物理”后退按钮,它已经被非常弱化了。

I have some views that I make visible upon a button press. I want them to disappear if I click outside of those views.

How would this be done on Android?

Also, I realize that the "back button" can also assist Android users with this - I might use that as a secondary way to close the views - but some of the tablets aren't even using a 'physical' back button anymore, it has been very de-emphasized.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(14

故事灯 2024-11-30 16:38:51

一种简单/愚蠢的方法:

  • 创建一个虚拟的空视图(假设没有源的 ImageView),使其填充父级

  • < p>如果它被点击,那么就做你想做的事。

您需要将 XML 文件中的根标记设为relativelayout。它将包含两个元素:您的虚拟视图(将其位置设置为与父顶部对齐)。另一个是包含视图和按钮的原始视图(该视图可能是 LinearLayout 或您制作的任何视图。不要忘记将其位置设置为对齐父顶部

希望这会帮助你,祝你好运!

An easy/stupid way:

  • Create a dummy empty view (let's say ImageView with no source), make it fill parent

  • If it is clicked, then do what you want to do.

You need to have the root tag in your XML file to be a RelativeLayout. It will contain two element: your dummy view (set its position to align the Parent Top). The other one is your original view containing the views and the button (this view might be a LinearLayout or whatever you make it. don't forget to set its position to align the Parent Top)

Hope this will help you, Good Luck !

梦里梦着梦中梦 2024-11-30 16:38:51

找到视图矩形,然后检测点击事件是否在视图之外。

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    Rect viewRect = new Rect();
    mTooltip.getGlobalVisibleRect(viewRect);
    if (!viewRect.contains((int) ev.getRawX(), (int) ev.getRawY())) {
        setVisibility(View.GONE);
    }
    return true;
}

如果你想在其他地方使用触摸事件,请尝试

return super.dispatchTouchEvent(ev);

Find the view rectangle, and then detect whether the click event is outside the view.

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    Rect viewRect = new Rect();
    mTooltip.getGlobalVisibleRect(viewRect);
    if (!viewRect.contains((int) ev.getRawX(), (int) ev.getRawY())) {
        setVisibility(View.GONE);
    }
    return true;
}

If you want to use the touch event other place, try

return super.dispatchTouchEvent(ev);
记忆里有你的影子 2024-11-30 16:38:51

这是一个老问题,但我想我会给出一个不基于 onTouch 事件的答案。正如 RedLeader 所建议的,也可以使用焦点事件来实现这一点。我遇到过这样的情况,我需要显示和隐藏一堆排列在自定义弹出窗口中的按钮,即这些按钮都放置在同一个 ViewGroup 中。要实现此功能,您需要执行一些操作:

  1. 您希望隐藏的视图组需要设置View.setFocusableInTouchMode(true)。也可以使用 android:focusableintouchmode 在 XML 中进行设置。

  2. 你的视图根,即整个布局的根,可能是某种线性或相对布局,也需要能够按照上面的#1进行聚焦

  3. 当显示视图组时,您调用 View.requestFocus() 来给出它聚焦。

  4. 您的视图组需要重写View.onFocusChanged(boolean GainFocus, int Direction, Rect previousFocusedRect)或实现您自己的OnFocusChangeListener并使用View.setOnFocusChangeListener ()

  5. 当用户点击视图外部时,焦点将转移到任一视图root(因为您在 #2 中将其设置为可聚焦)或本质上可聚焦的另一个视图(EditText 或类似)

  6. 当您使用#4中的方法之一检测到焦点丢失时您知道焦点已转移到视图组之外的某些内容,您可以隐藏它。

我想这个解决方案并不适用于所有场景,但它适用于我的特定情况,而且听起来好像它也适用于 OP。

This is an old question but I thought I'd give an answer that isn't based on onTouch events. As was suggested by RedLeader it's also possible to achieve this using focus events. I had a case where I needed to show and hide a bunch of buttons arranged in a custom popup, ie the buttons were all placed in the same ViewGroup. Some things you need to do to make this work:

  1. The view group that you wish to hide needs to have View.setFocusableInTouchMode(true) set. This can also be set in XML using android:focusableintouchmode.

  2. Your view root, i.e. the root of your entire layout, probably some kind of Linear or Relative Layout, also needs to be able to be focusable as per #1 above

  3. When the view group is shown you call View.requestFocus() to give it focus.

  4. Your view group need to either override View.onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) or implement your own OnFocusChangeListener and use View.setOnFocusChangeListener()

  5. When the user taps outside your view focus is transferred to either the view root (since you set it as focusable in #2) or to another view that inherently is focusable (EditText or similar)

  6. When you detect focus loss using one of the methods in #4 you know that focus has be transferred to something outside your view group and you can hide it.

I guess this solution doesn't work in all scenarios, but it worked in my specific case and it sounds as if it could work for the OP as well.

初与友歌 2024-11-30 16:38:51

我一直在寻找一种在接触外界时关闭视野的方法,但这些方法都不能很好地满足我的需求。我确实找到了解决方案,并将其发布在这里,以防有人感兴趣。

我有一个基本活动,几乎我所有的活动都扩展了它。在其中我有:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (myViewIsVisible()){
            closeMyView();
        return true;
    }
    return super.dispatchTouchEvent(ev);
}

因此,如果我的视图可见,它将关闭,如果不是,它将表现得像正常的触摸事件。不确定这是否是最好的方法,但它似乎对我有用。

I've been looking for a way to close my view when touching outside and none of these methods fit my needs really well. I did find a solution and will just post it here in case anyone is interested.

I have a base activity which pretty much all my activities extend. In it I have:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (myViewIsVisible()){
            closeMyView();
        return true;
    }
    return super.dispatchTouchEvent(ev);
}

So if my view is visible it will just close, and if not it will behave like a normal touch event. Not sure if it's the best way to do it, but it seems to work for me.

熟人话多 2024-11-30 16:38:51

基于王凯的回答:我建议首先检查您的视图的可见性,根据我的场景,当用户单击 fab myView 时变得可见,然后当用户单击外部时 myView 消失

  @Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    Rect viewRect = new Rect();
    myView.getGlobalVisibleRect(viewRect);
    if (myView.getVisibility() == View.VISIBLE && !viewRect.contains((int) ev.getRawX(), (int) ev.getRawY())) {
        goneAnim(myView);
        return true;
    }
    return super.dispatchTouchEvent(ev);
}

base on Kai Wang answer : i suggest first check visibility of Your view , base on my scenario when user clicked on fab myView become visible and then when user click outside myView disappears

  @Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    Rect viewRect = new Rect();
    myView.getGlobalVisibleRect(viewRect);
    if (myView.getVisibility() == View.VISIBLE && !viewRect.contains((int) ev.getRawX(), (int) ev.getRawY())) {
        goneAnim(myView);
        return true;
    }
    return super.dispatchTouchEvent(ev);
}
烟花易冷人易散 2024-11-30 16:38:51

我需要特定的能力,不仅可以在单击视图外部时删除视图,还可以允许单击正常传递到活动。例如,我有一个单独的布局 notification_bar.xml,我需要在需要时动态扩展并添加到当前活动的任何内容。

如果我创建一个屏幕大小的覆盖视图来接收 notification_bar 视图之外的任何点击,并在点击时删除这两个视图,则父视图(活动的主视图)仍然没有收到任何点击,这意味着,当 notification_bar 可见时,需要单击两次才能单击按钮(一次单击关闭 notification_bar 视图,另一次单击按钮)。

要解决这个问题,您可以创建自己的 DismissViewGroup 来扩展 ViewGroup 并重写以下方法:

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    ViewParent parent = getParent();
    if(parent != null && parent instanceof ViewGroup) {
        ((ViewGroup) parent).removeView(this);
    }
    return super.onInterceptTouchEvent(ev);
}

然后您的动态添加的视图将看起来有点像:

<com.example.DismissViewGroup android:id="@+id/touch_interceptor_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/transparent" ...
    <LinearLayout android:id="@+id/notification_bar_view" ...

这将允许您与视图交互,并且当您在视图外部单击时,你们都关闭视图并与活动正常交互。

I needed the specific ability to not only remove a view when clicking outside it, but also allow the click to pass through to the activity normally. For example, I have a separate layout, notification_bar.xml, that I need to dynamically inflate and add to whatever the current activity is when needed.

If I create an overlay view the size of the screen to receive any clicks outside of the notification_bar view and remove both these views on a click, the parent view (the main view of the activity) has still not received any clicks, which means, when the notification_bar is visible, it takes two clicks to click a button (one to dismiss the notification_bar view, and one to click the button).

To solve this, you can just create your own DismissViewGroup that extends ViewGroup and overrides the following method:

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    ViewParent parent = getParent();
    if(parent != null && parent instanceof ViewGroup) {
        ((ViewGroup) parent).removeView(this);
    }
    return super.onInterceptTouchEvent(ev);
}

And then your dynamically added view will look a little like:

<com.example.DismissViewGroup android:id="@+id/touch_interceptor_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/transparent" ...
    <LinearLayout android:id="@+id/notification_bar_view" ...

This will allow you to interact with the view, and the moment you click outside the view, you both dismiss the view and interact normally with the activity.

霓裳挽歌倾城醉 2024-11-30 16:38:51

实现onTouchListener()。检查触摸的坐标是否在您的视图坐标之外。

可能有某种方法可以用 onFocus() 等来做到这一点 - 但我不知道。

Implement onTouchListener(). Check that the coordinates of the touch are outside of the coordinates of your view.

There is probably some kind of way to do it with onFocus(), etc. - But I don't know it.

过潦 2024-11-30 16:38:51

第 1 步:通过 Fragmelayout 创建一个包装视图,它将覆盖您的主布局。

 <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
     <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    <!-- This is your main layout-->
     </RelativeLayout>
    
            <View
                android:id="@+id/v_overlay"
                android:layout_width="match_parent"
                android:layout_height="match_parent">
    <!-- This is the wrapper layout-->
            </View>
        </FrameLayout>

第 2 步: 现在在 Java 代码中添加逻辑,如下所示 -

         View viewOverlay = findViewById(R.id.v_overlay);
         View childView = findViewByID(R.id.childView);
         Button button = findViewByID(R.id.button);
    
         viewOverlay.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        childView.setVisibility(View.GONE);
                        view.setVisibility(View.GONE);
                    }
                });
    
          
         button.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                       childView.setVisibility(View.VISIBLE);
   // Make the wrapper view visible now after making the child view visible for handling the 
  // main visibility task. 
                       viewOverlay.setVisibility(View.VISIBLE);
                        
                    }
                });

Step 1: Make a wrapper view by Fragmelayout which will cover your main layout.

 <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
     <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    <!-- This is your main layout-->
     </RelativeLayout>
    
            <View
                android:id="@+id/v_overlay"
                android:layout_width="match_parent"
                android:layout_height="match_parent">
    <!-- This is the wrapper layout-->
            </View>
        </FrameLayout>

Step 2: Now add logic in your java code like that -

         View viewOverlay = findViewById(R.id.v_overlay);
         View childView = findViewByID(R.id.childView);
         Button button = findViewByID(R.id.button);
    
         viewOverlay.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        childView.setVisibility(View.GONE);
                        view.setVisibility(View.GONE);
                    }
                });
    
          
         button.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                       childView.setVisibility(View.VISIBLE);
   // Make the wrapper view visible now after making the child view visible for handling the 
  // main visibility task. 
                       viewOverlay.setVisibility(View.VISIBLE);
                        
                    }
                });
冰葑 2024-11-30 16:38:51

要在视图外部执行单击时隐藏视图:

override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
      if (isMenuVisible) {
          if (!isWithinViewBounds(ev.rawX.toInt(), ev.rawY.toInt())) {
               hideYourView()
               return true
          }
      }
   return super.dispatchTouchEvent(ev)
}

创建一个方法来获取视图的边界(高度和宽度),因此当您单击视图外部时,它将隐藏视图,而单击视图时不会隐藏视图:

private fun isWithinViewBounds(xPoint: Int, yPoint: Int): Boolean {
        val l = IntArray(2)
        llYourView.getLocationOnScreen(l)
        val x = l[0]
        val y = l[1]
        val w: Int = llYourView.width
        val h: Int = llYourView.height
        return !(xPoint < x || xPoint > x + w || yPoint < y || yPoint > y + h)
}

To hide the view when click performs outside the view:

override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
      if (isMenuVisible) {
          if (!isWithinViewBounds(ev.rawX.toInt(), ev.rawY.toInt())) {
               hideYourView()
               return true
          }
      }
   return super.dispatchTouchEvent(ev)
}

create a method to get the bounds(height & width) of your view, so when you click outside of your view it will hide the view and when click on the view will not hide:

private fun isWithinViewBounds(xPoint: Int, yPoint: Int): Boolean {
        val l = IntArray(2)
        llYourView.getLocationOnScreen(l)
        val x = l[0]
        val y = l[1]
        val w: Int = llYourView.width
        val h: Int = llYourView.height
        return !(xPoint < x || xPoint > x + w || yPoint < y || yPoint > y + h)
}
翻身的咸鱼 2024-11-30 16:38:51

当在给定视图之外发生单击时通知我们的包装布局:

class OutsideClickConstraintLayout(context: Context, attrs: AttributeSet?) :
    ConstraintLayout(context, attrs) {

    private var viewOutsideClickListenerMap = mutableMapOf<View, () -> Unit>()

    fun setOnOutsideClickListenerForView(view: View, listener: () -> Unit) {
        viewOutsideClickListenerMap[view] = listener
    }

    override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
        viewOutsideClickListenerMap.forEach { (view, function) ->
            if (isMotionEventOutsideView(view, ev)) function.invoke()
        }
        return super.onInterceptTouchEvent(ev)
    }

    private fun isMotionEventOutsideView(view: View, motionEvent: MotionEvent): Boolean {
        val viewRectangle = Rect()
        view.getGlobalVisibleRect(viewRectangle)
        return !viewRectangle.contains(motionEvent.rawX.toInt(), motionEvent.rawY.toInt())
    }
}

用法:

....
        outsideClickContainerView.setOnOutsideClickListenerForView(someView) {
            // handle click outside someView
        }
....

Wrapper layout that notifies us when a click occurred outside a given view:

class OutsideClickConstraintLayout(context: Context, attrs: AttributeSet?) :
    ConstraintLayout(context, attrs) {

    private var viewOutsideClickListenerMap = mutableMapOf<View, () -> Unit>()

    fun setOnOutsideClickListenerForView(view: View, listener: () -> Unit) {
        viewOutsideClickListenerMap[view] = listener
    }

    override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
        viewOutsideClickListenerMap.forEach { (view, function) ->
            if (isMotionEventOutsideView(view, ev)) function.invoke()
        }
        return super.onInterceptTouchEvent(ev)
    }

    private fun isMotionEventOutsideView(view: View, motionEvent: MotionEvent): Boolean {
        val viewRectangle = Rect()
        view.getGlobalVisibleRect(viewRectangle)
        return !viewRectangle.contains(motionEvent.rawX.toInt(), motionEvent.rawY.toInt())
    }
}

Usage:

....
        outsideClickContainerView.setOnOutsideClickListenerForView(someView) {
            // handle click outside someView
        }
....
山人契 2024-11-30 16:38:51

我创建了自定义 ViewGroup 来显示锚定到另一个视图(弹出气球)的信息框。
子视图是实际的信息框,BalloonView是全屏的,用于子视图的绝对定位,并拦截触摸。

public BalloonView(View anchor, View child) {
    super(anchor.getContext());
    //calculate popup position relative to anchor and do stuff
    init(...);
    //receive child via constructor, or inflate/create default one
    this.child = child;
    //this.child = inflate(...);
    //this.child = new SomeView(anchor.getContext());
    addView(child);
    //this way I don't need to create intermediate ViewGroup to hold my View
    //but it is fullscreen (good for dialogs and absolute positioning)
    //if you need relative positioning, see @iturki answer above 
    ((ViewGroup) anchor.getRootView()).addView(this);
}

private void dismiss() {
    ((ViewGroup) getParent()).removeView(this);
}

处理子项内部的点击:

child.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        //write your code here to handle clicks inside
    }
});

通过单击外部而不将触摸委托给底层视图来关闭我的视图:

BalloonView.this.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        dismiss();
    }
});

通过单击外部将触摸委托给底层视图来关闭我的视图:

@Override
public boolean onTouchEvent(MotionEvent event) {
    dismiss();
    return false; //allows underlying View to handle touch
}

按下“后退”按钮时关闭:

//do this in constructor to be able to intercept key
setFocusableInTouchMode(true);
requestFocus();

@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK) {
        dismiss();
        return true;
    }
    return super.onKeyPreIme(keyCode, event);
}

I've created custom ViewGroup to display info box anchored to another view (popup balloon).
Child view is actual info box, BalloonView is fullscreen for absolute positioning of child, and intercepting touch.

public BalloonView(View anchor, View child) {
    super(anchor.getContext());
    //calculate popup position relative to anchor and do stuff
    init(...);
    //receive child via constructor, or inflate/create default one
    this.child = child;
    //this.child = inflate(...);
    //this.child = new SomeView(anchor.getContext());
    addView(child);
    //this way I don't need to create intermediate ViewGroup to hold my View
    //but it is fullscreen (good for dialogs and absolute positioning)
    //if you need relative positioning, see @iturki answer above 
    ((ViewGroup) anchor.getRootView()).addView(this);
}

private void dismiss() {
    ((ViewGroup) getParent()).removeView(this);
}

Handle clicks inside child:

child.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        //write your code here to handle clicks inside
    }
});

To dismiss my View by click outside WITHOUT delegating touch to underlying View:

BalloonView.this.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        dismiss();
    }
});

To dismiss my View by click outside WITH delegating touch to underlying View:

@Override
public boolean onTouchEvent(MotionEvent event) {
    dismiss();
    return false; //allows underlying View to handle touch
}

To dismiss on Back button pressed:

//do this in constructor to be able to intercept key
setFocusableInTouchMode(true);
requestFocus();

@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK) {
        dismiss();
        return true;
    }
    return super.onKeyPreIme(keyCode, event);
}
遗心遗梦遗幸福 2024-11-30 16:38:51

那么它可能会很有用

  • 我想分享我的解决方案,我认为如果您能够添加自定义 ViewGroup 作为根布局,
  • 并且您想要消失的视图可以是自定义视图,

。首先,我们创建一个自定义 ViewGroup 来拦截触摸事件:

class OutsideTouchDispatcherLayout @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {

    private val rect = Rect()

    override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
        if (ev.action == MotionEvent.ACTION_DOWN) {
            val x = ev.x.roundToInt()
            val y = ev.y.roundToInt()
            traverse { view ->
                if (view is OutsideTouchInterceptor) {
                    view.getGlobalVisibleRect(rect)
                    val isOutside = rect.contains(x, y).not()
                    if (isOutside) {
                        view.interceptOutsideTouch(ev)
                    }
                }
            }
        }
        return false
    }

    interface OutsideTouchInterceptor {
        fun interceptOutsideTouch(ev: MotionEvent)
    }
}

fun ViewGroup.traverse(process: (View) -> Unit) {
    for (i in 0 until childCount) {
        val child = getChildAt(i)
        process(child)
        if (child is ViewGroup) {
            child.traverse(process)
        }
    }
}

如您所见,OutsideTouchDispatcherLayout 拦截触摸事件并通知每个实现 OutsideTouchInterceptor 的子视图,某些触摸事件发生在该视图之外看法。

以下是后代视图如何处理此事件。请注意,它必须实现 OutsideTouchInterceptor 接口:

class OutsideTouchInterceptorView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr),
    OutsideTouchDispatcherLayout.OutsideTouchInterceptor {

    override fun interceptOutsideTouch(ev: MotionEvent) {
        visibility = GONE
    }

}

然后您只需通过子父关系即可轻松进行外部触摸检测:

<?xml version="1.0" encoding="utf-8"?>
<com.example.touchinterceptor.OutsideTouchDispatcherLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.example.touchinterceptor.OutsideTouchInterceptorView
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:background="#eee"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</com.example.touchinterceptor.OutsideTouchDispatcherLayout>

I want to share my solution which I think it could be useful if :

  • you are able to add a custom ViewGroup as root layout
  • also the view which you want to disappear can be a custom one.

First, we create a custom ViewGroup to intercept touch events:

class OutsideTouchDispatcherLayout @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {

    private val rect = Rect()

    override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
        if (ev.action == MotionEvent.ACTION_DOWN) {
            val x = ev.x.roundToInt()
            val y = ev.y.roundToInt()
            traverse { view ->
                if (view is OutsideTouchInterceptor) {
                    view.getGlobalVisibleRect(rect)
                    val isOutside = rect.contains(x, y).not()
                    if (isOutside) {
                        view.interceptOutsideTouch(ev)
                    }
                }
            }
        }
        return false
    }

    interface OutsideTouchInterceptor {
        fun interceptOutsideTouch(ev: MotionEvent)
    }
}

fun ViewGroup.traverse(process: (View) -> Unit) {
    for (i in 0 until childCount) {
        val child = getChildAt(i)
        process(child)
        if (child is ViewGroup) {
            child.traverse(process)
        }
    }
}

As you see, OutsideTouchDispatcherLayout intercepts touch events and informs each descendent view which implenets OutsideTouchInterceptor that some touch event occured outside of that view.

Here is how the descendent view could handle this event. Notice that it must implement OutsideTouchInterceptor interface:

class OutsideTouchInterceptorView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr),
    OutsideTouchDispatcherLayout.OutsideTouchInterceptor {

    override fun interceptOutsideTouch(ev: MotionEvent) {
        visibility = GONE
    }

}

Then you have outside touch detection easily just by a child-parent relation:

<?xml version="1.0" encoding="utf-8"?>
<com.example.touchinterceptor.OutsideTouchDispatcherLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.example.touchinterceptor.OutsideTouchInterceptorView
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:background="#eee"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</com.example.touchinterceptor.OutsideTouchDispatcherLayout>
笑着哭最痛 2024-11-30 16:38:51

以下是完成工作的简单方法:

第 1 步:为要为其生成点击外部事件的元素的外部容器创建一个 ID。

就我而言,它是一个线性布局,我将 id 指定为“outsideContainer”

第 2 步: 为该外部容器设置一个 onTouchListener,它将简单地充当内部元素的点击外部事件!

outsideContainer.setOnTouchListener(new View.OnTouchListener() {
                                        @Override
                                        public boolean onTouch(View v, MotionEvent event) {
                                            // perform your intended action for click outside here
                                            Toast.makeText(YourActivity.this, "Clicked outside!", Toast.LENGTH_SHORT).show();
                                            return false;
                                        }
                                    }
);

Here's a simple approach to get your work done:

Step 1: Create an ID for the outside container of your element for which you want to generate a click outside event.

In my case, it is a Linear Layout for which I've given id as 'outsideContainer'

Step 2: Set an onTouchListener for that outside container which will simply act as a click outside event for your inner elements!

outsideContainer.setOnTouchListener(new View.OnTouchListener() {
                                        @Override
                                        public boolean onTouch(View v, MotionEvent event) {
                                            // perform your intended action for click outside here
                                            Toast.makeText(YourActivity.this, "Clicked outside!", Toast.LENGTH_SHORT).show();
                                            return false;
                                        }
                                    }
);
蓝天 2024-11-30 16:38:51

感谢@ituki的想法

FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/search_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#80000000"
android:clickable="true">

<LinearLayout
    android:clickable="true" // not trigger
    android:layout_width="match_parent"
    android:layout_height="300dp" 
    android:background="#FFF"
    android:orientation="vertical"
    android:padding="20dp">

    ...............

</LinearLayout>
</FrameLayout>

和java代码,

mContainer = (View) view.findViewById(R.id.search_container);
    mContainer.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            if(event.getAction() == MotionEvent.ACTION_DOWN){
                Log.d("aaaaa", "outsite");
                return true;
            }
            return false;
        }
    });

当触摸LinearLayout之外时它可以工作

thank @ituki for idea

FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/search_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#80000000"
android:clickable="true">

<LinearLayout
    android:clickable="true" // not trigger
    android:layout_width="match_parent"
    android:layout_height="300dp" 
    android:background="#FFF"
    android:orientation="vertical"
    android:padding="20dp">

    ...............

</LinearLayout>
</FrameLayout>

and java code

mContainer = (View) view.findViewById(R.id.search_container);
    mContainer.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            if(event.getAction() == MotionEvent.ACTION_DOWN){
                Log.d("aaaaa", "outsite");
                return true;
            }
            return false;
        }
    });

it's work when touch outside LinearLayout

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文