当我按下按钮时,我会显示一些视图。如果我在这些视图之外点击,我希望它们消失。
在Android上,应该如何实现这个功能呢?
此外,我意识到“返回按钮”也可以帮助Android用户完成这个操作-我可能会将其用作关闭视图的辅助方法-但是一些平板电脑甚至不再使用“物理”返回按钮,因为它已经几乎不被强调了。
我创建了自定义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);
}
这里有一个简单的方法来完成你的工作:
步骤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;
}
}
);
我想分享我的解决方案,如果:
首先,我们创建一个自定义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>
感谢 @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 之外时,它会起作用。