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

时间:2022-08-26 20:06:36

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?

如何在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.

此外,我意识到“后退按钮”也可以帮助Android用户 - 我可能会将其作为关闭视图的第二种方式 - 但有些平板电脑甚至不再使用“物理”后退按钮了,它一直非常不再强调。

9 个解决方案

#1


30  

An easy/stupid way:

一个简单/愚蠢的方式:

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

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

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

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

Hope this will help you, Good Luck !

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

#2


20  

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:

这是一个老问题,但我想我会给出一个不基于onTouch事件的答案。正如RedLeader所建议的那样,使用焦点事件也可以实现这一点。我有一个案例,我需要显示和隐藏自定义弹出窗口中排列的一堆按钮,即按钮都放在同一个ViewGroup中。要使其工作,您需要做的一些事情:

  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.

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

  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

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

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

    显示视图组时,调用View.requestFocus()为其提供焦点。

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

    您的视图组需要覆盖View.onFocusChanged(boolean gainFocus,int direction,Rect previousFocusedRect)或实现您自己的OnFocusChangeListener并使用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)

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

  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.

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

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.

我想这个解决方案在所有情况下都不起作用,但它在我的特定情况下起作用,听起来好像它也适用于OP。

#3


19  

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

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

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

#4


10  

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.

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

#5


2  

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.

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

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

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

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

要解决此问题,您可以创建自己的扩展ViewGroup的DismissViewGroup并覆盖以下方法:

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

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

#6


0  

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

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

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

使用onFocus()等可能有某种方法 - 但我不知道。

#7


0  

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.

我已经创建了自定义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);
}

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

#8


0  

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

基于Kai Wang回答:我建议首先检查你的视图的可见性,根据用户点击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);
}

#9


-1  

thank @ituki for idea

感谢@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>

and java code

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

it's work when touch outside LinearLayout

在LinearLayout外面触摸它是有效的

#1


30  

An easy/stupid way:

一个简单/愚蠢的方式:

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

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

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

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

Hope this will help you, Good Luck !

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

#2


20  

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:

这是一个老问题,但我想我会给出一个不基于onTouch事件的答案。正如RedLeader所建议的那样,使用焦点事件也可以实现这一点。我有一个案例,我需要显示和隐藏自定义弹出窗口中排列的一堆按钮,即按钮都放在同一个ViewGroup中。要使其工作,您需要做的一些事情:

  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.

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

  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

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

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

    显示视图组时,调用View.requestFocus()为其提供焦点。

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

    您的视图组需要覆盖View.onFocusChanged(boolean gainFocus,int direction,Rect previousFocusedRect)或实现您自己的OnFocusChangeListener并使用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)

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

  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.

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

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.

我想这个解决方案在所有情况下都不起作用,但它在我的特定情况下起作用,听起来好像它也适用于OP。

#3


19  

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

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

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

#4


10  

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.

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

#5


2  

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.

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

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

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

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

要解决此问题,您可以创建自己的扩展ViewGroup的DismissViewGroup并覆盖以下方法:

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

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

#6


0  

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

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

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

使用onFocus()等可能有某种方法 - 但我不知道。

#7


0  

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.

我已经创建了自定义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);
}

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

#8


0  

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

基于Kai Wang回答:我建议首先检查你的视图的可见性,根据用户点击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);
}

#9


-1  

thank @ituki for idea

感谢@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>

and java code

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

it's work when touch outside LinearLayout

在LinearLayout外面触摸它是有效的