[英]Android: How to make AlertDialog disappear on clicking ok button?
[英]Android make view disappear by clicking outside of it
我有一些视图可以在按下按钮时显示。 如果我在这些视图之外单击,我希望它们消失。
这将如何在 Android 上完成?
此外,我意识到“后退按钮”还可以帮助 Android 用户使用此功能 - 我可能会将其用作关闭视图的辅助方式 - 但有些平板电脑甚至不再使用“物理”后退按钮了,它已经非常不重视了。
一个简单/愚蠢的方法:
创建一个虚拟的空视图(假设 ImageView 没有源),使其填充父视图
如果它被点击,然后做你想做的事。
您需要将 XML 文件中的根标签作为相对布局。 它将包含两个元素:您的虚拟视图(设置其 position 以align the Parent Top
)。 另一个是包含视图和按钮的原始视图(此视图可能是 LinearLayout 或您制作的任何内容。不要忘记将其 position 设置为align the Parent Top
)
希望这对你有帮助,祝你好运!
找到视图矩形,然后检测点击事件是否在视图之外。
@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);
这是一个老问题,但我想我会给出一个不基于onTouch
事件的答案。 正如 RedLeader 所建议的,也可以使用焦点事件来实现这一点。 我有一个案例,我需要显示和隐藏排列在自定义弹出窗口中的一堆按钮,即所有按钮都放在同一个ViewGroup
中。 要完成这项工作,您需要做一些事情:
您希望隐藏的视图组需要设置View.setFocusableInTouchMode(true)
。 这也可以使用android:focusableintouchmode
在 XML 中设置。
您的视图根,即整个布局的根,可能是某种线性或相对布局,也需要能够按照上面的#1 聚焦
当显示视图组时,您调用View.requestFocus()
为其提供焦点。
您的视图组需要覆盖View.onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect)
或实现您自己的OnFocusChangeListener
并使用View.setOnFocusChangeListener()
当用户在您的视图之外点击时,焦点将转移到视图根(因为您在 #2 中将其设置为可聚焦)或另一个固有可聚焦的视图( EditText
或类似的)
当您使用 #4 中的一种方法检测到焦点丢失时,您知道焦点已转移到您的视图组之外的东西上,您可以将其隐藏。
我猜这个解决方案并不适用于所有场景,但它适用于我的具体情况,听起来好像它也适用于 OP。
我一直在寻找一种在触摸外部时关闭视野的方法,但这些方法都不能很好地满足我的需求。 我确实找到了解决方案,如果有人感兴趣,我会在这里发布。
我有一个基本活动,几乎我所有的活动都在扩展。 在里面我有:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (myViewIsVisible()){
closeMyView();
return true;
}
return super.dispatchTouchEvent(ev);
}
因此,如果我的视图可见,它就会关闭,如果不可见,它的行为就像正常的触摸事件一样。 不确定这是否是最好的方法,但它似乎对我有用。
基于王凯回答:我建议首先检查您的视图的可见性,根据我的场景,当用户点击 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);
}
我需要特定的能力,不仅可以在单击外部视图时删除视图,而且还需要允许单击正常传递到活动。 例如,我有一个单独的布局,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" ...
这将允许您与视图交互,并且当您在视图外部单击时,您将关闭视图并与活动正常交互。
第 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);
}
});
实现onTouchListener()
。 检查触摸的坐标是否在您的视图坐标之外。
可能有某种方法可以使用onFocus()
等 - 但我不知道。
我创建了自定义 ViewGroup 来显示锚定到另一个视图(弹出气球)的信息框。 Child view 是实际的信息框,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);
}
我想分享我的解决方案,我认为它在以下情况下会很有用:
首先,我们创建一个自定义的 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>
这是完成工作的简单方法:
第 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;
}
}
);
在视图外执行单击时隐藏视图:
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)
}
感谢@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 之外触摸时有效
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.