解决Fragment内点击穿透问题

本文探讨了在Android应用中从AFragment跳转到BFragment时遇到的两个问题:点击穿透到上一个fragment和两个fragment重叠显示。通过在Fragment布局根节点设置背景色、重写onTouch方法和在onCreateView方法中添加onTouchListener,有效解决了这两个问题。

问题描述:

从A fragment跳转到B fragment ,为了返回时不从新加载A fragment内容,通常使用add方法来将a添加到后退栈.

但是这样会出现一个小问题:

在B Fragment 中点击一个空白区域,如果A Fragment的该位置正好有一个可以点击的控件,则A Fragment会响应到点击事件,更有甚者,如果B Fragment有时候还会和A Fragment重叠显示到一起(B Fragment 透明).

其实两个问题都好解决:

1.重叠显示问题:只要在Fragment的布局文件根节点设置一下backgroud就可以可,除了trasparent外都可以使两个fragment不在重叠显示在一起。

2.点击穿透到上一个fragment的问题:只要在Fragment中重写onTouch方法(Fragment要实现OnTouchListener).返回true.

  1. @Override  

  2.     public boolean onTouch(View v, MotionEvent event) {  

  3.         return true;  

  4.     }  

Fragment是存在于Activity中的碎片,所以Fragment的onTouch有时候响应不到(没验证是否是有时候响应不到,也可能是永远响应不到,全部被Activity给拦截掉了,如果是这样,那么上面的点击穿透解决方案就是完全无效的)。比较保守的解决方法就是再添加一步操作,在onCreateView方法的view 里面,给view添加上onTouchListener,这样就能让fragment点击的时候不再穿透到上一层的Fragment中去了


  1. @Override  

  2.     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {  

  3.         View view = inflater.inflate(R.layout.activity_bs_bookstore, null);  

  4.         view.setOnTouchListener(this);  

  5. }  


转载于:https://my.oschina.net/ljhUncle/blog/471021

<think>我们将在Fragment中实现点击空白区域(即非特定视图区域)的监听。通常,我们可以通过以下步骤实现: 1. 在Fragment的根布局上设置点击监听器。 2. 当点击事件发生时,判断点击的位置是否在需要排除的子视图区域之外。 3. 如果在空白区域,则执行隐藏键盘、清除焦点等操作。 但是,注意:Fragment的根布局可能包含多个子视图,我们只希望在点击非输入区域(比如EditText)时隐藏键盘。 以下是一种常见的实现方式: 步骤: 1. 在Fragment的onViewCreated方法中获取根视图。 2. 为根视图设置OnTouchListener,在触摸事件中判断当前触摸点是否在需要排除的视图(如EditText)内。 3. 如果不在,则隐藏键盘并清除焦点。 但是,更简单的方法是:在根布局上设置点击事件,然后检查当前焦点视图,如果焦点视图是EditText且点击事件发生在EditText之外,则隐藏键盘。 以下代码示例展示了如何实现: ```kotlin class MyFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 设置根视图的点击监听 view.setOnClickListener { // 当点击根视图时,判断当前是否有EditText获得焦点 val currentFocus = activity?.currentFocus if (currentFocus is EditText) { // 隐藏键盘 hideKeyboard(currentFocus) // 清除焦点 currentFocus.clearFocus() } } } private fun hideKeyboard(view: View) { val imm = requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager imm.hideSoftInputFromWindow(view.windowToken, 0) } } ``` 但是,上述方法有一个问题:当点击非EditText区域时,会隐藏键盘,但如果点击的是其他可点击的控件(比如按钮),则不会触发空白点击。因此,我们需要确保只有点击真正的空白区域(即没有任何子视图消费点击事件的区域)才触发。 另一种方法:在根布局上设置一个TouchListener,检测触摸事件,如果事件没有被消费(即点击在空白处),则执行隐藏键盘的操作。 我们可以这样实现: ```kotlin view.setOnTouchListener { _, event -> if (event.action == MotionEvent.ACTION_DOWN) { val currentFocus = activity?.currentFocus if (currentFocus is EditText) { val outRect = Rect() currentFocus.getGlobalVisibleRect(outRect) // 如果触摸点不在EditText的区域内 if (!outRect.contains(event.rawX.toInt(), event.rawY.toInt())) { hideKeyboard(currentFocus) currentFocus.clearFocus() } } } false // 返回false,表示不消费事件,让事件继续传递 } ``` 但是,这种方法可能会干扰其他触摸事件的处理。 因此,更推荐的做法是:在Fragment的根布局上设置一个点击监听,但要求根布局是一个可点击的布局(如FrameLayout, LinearLayout等),并且设置其clickable="true",这样它就会消费点击事件。然后,我们只需要在根布局的点击事件中处理即可。 注意:如果根布局已经有子控件设置了点击事件,那么点击子控件时,根布局的点击事件不会被触发(因为子控件消费了事件)。所以,我们可以在根布局的点击事件中直接隐藏键盘。 具体步骤: 1. 在Fragment的布局文件中,将根布局设置为可点击(android:clickable="true"),并且设置android:focusableInTouchMode="true"(可选,用于获取焦点)。 2. 在onViewCreated中,为根布局设置点击事件。 示例布局: ```xml <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/rootView" android:layout_width="match_parent" android:layout_height="match_parent" android:clickable="true" android:focusableInTouchMode="true"> <!-- 其他子视图 --> </FrameLayout> ``` 在Fragment中: ```kotlin override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val rootView = view as ViewGroup rootView.setOnClickListener { // 隐藏键盘 hideKeyboard() } } private fun hideKeyboard() { val view = activity?.currentFocus view?.let { val imm = requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager imm.hideSoftInputFromWindow(it.windowToken, 0) it.clearFocus() } } ``` 注意:这种方法会使得整个布局的空白区域(没有被子视图消费的区域)点击都会触发隐藏键盘。如果布局中有其他需要点击的控件,那么点击那些控件时不会触发根布局的点击事件(因为子控件消费了事件),所以不会影响其他控件的点击。 但是,如果有一个EditText,当它获得焦点时,我们点击它,键盘会显示,然后我们点击空白区域(即根布局),键盘会隐藏。同时,EditText会失去焦点。 另外,如果根布局中有多个EditText,我们也可以这样处理,因为activity?.currentFocus会返回当前获得焦点的视图。 总结:在Fragment中实现空白区域点击监听,主要步骤是: 1. 设置根布局为可点击(clickable=true)。 2. 在根布局的点击事件中隐藏键盘并清除当前焦点视图的焦点。 这种方法简单且有效。 引用说明:在Fragment中设置监听的方式与Activity类似,但需要注意Fragment视图的生命周期[^1][^2][^3][^4]。</think>### 在 Kotlin 中实现 Fragment 空白区域点击监听 在 Android Fragment 中实现空白区域点击监听,可以通过以下步骤实现: #### 核心实现方案 ```kotlin class MyFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 1. 获取根布局 val rootView = view.findViewById<ViewGroup>(R.id.root_container) // 2. 设置空白区域点击监听 rootView.setOnTouchListener { _, event -> if (event.action == MotionEvent.ACTION_DOWN) { // 3. 隐藏键盘 hideKeyboard() // 4. 清除焦点 clearFocusOnEditTexts(rootView) } false // 返回false让事件继续传递 } } // 隐藏软键盘 private fun hideKeyboard() { val imm = requireContext().getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager imm.hideSoftInputFromWindow(view?.windowToken, 0) } // 递归清除所有EditText的焦点 private fun clearFocusOnEditTexts(viewGroup: ViewGroup) { for (i in 0 until viewGroup.childCount) { val child = viewGroup.getChildAt(i) if (child is EditText) { child.clearFocus() } else if (child is ViewGroup) { clearFocusOnEditTexts(child) } } } } ``` #### XML 布局配置 ```xml <!-- fragment_layout.xml --> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/root_container" android:layout_width="match_parent" android:layout_height="match_parent" android:clickable="true" <!-- 关键属性 --> android:focusableInTouchMode="true"> <!-- 关键属性 --> <!-- 其他子视图 --> <EditText android:id="@+id/et_username" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="用户名"/> <EditText android:id="@+id/et_password" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="密码" android:inputType="textPassword"/> <Button android:id="@+id/btn_submit" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="提交"/> </FrameLayout> ``` #### 实现原理说明 1. **根布局捕获事件** 通过设置根布局的 `android:clickable="true"` 属性,使其能够接收点击事件[^1] 2. **触摸事件处理** 使用 `setOnTouchListener` 监听 ACTION_DOWN 事件,确保在用户触摸屏幕时立即响应 3. **键盘隐藏** 使用 `InputMethodManager` 隐藏当前显示的软键盘 4. **焦点管理** 递归遍历所有子视图,清除 EditText 的焦点状态 #### 高级优化方案 ```kotlin // 在 Fragment 基类中实现通用功能 abstract class BaseFragment : Fragment() { // 注册空白区域点击监听 fun registerBlankAreaClick(root: ViewGroup) { root.setOnTouchListener { _, event -> if (event.action == MotionEvent.ACTION_DOWN) { handleBlankAreaClick() } false } } private fun handleBlankAreaClick() { hideKeyboard() view?.let { clearFocusRecursive(it) } } private fun clearFocusRecursive(view: View) { if (view is ViewGroup) { for (i in 0 until view.childCount) { clearFocusRecursive(view.getChildAt(i)) } } else { view.clearFocus() } } } // 具体 Fragment 实现 class LoginFragment : BaseFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val root = view.findViewById<ViewGroup>(R.id.root_container) registerBlankAreaClick(root) } } ``` #### 不同场景下的处理方案 | 场景 | 解决方案 | 代码示例 | |------|----------|----------| | 简单布局 | 直接设置根布局点击监听 | `rootView.setOnClickListener { hideKeyboard() }` | | 复杂布局 | 递归清除焦点 | `clearFocusRecursive(rootView)` | | 需要排除特定区域 | 添加触摸点检测 | `if (!isPointInsideView(event, excludedView)) { ... }` | | 对话框Fragment | 自定义对话框背景点击 | `dialog.window?.decorView?.setOnClickListener { dismiss() }` | #### 排除特定区域的实现 ```kotlin // 检查触摸点是否在特定视图内 private fun isPointInsideView(event: MotionEvent, view: View): Boolean { val location = IntArray(2) view.getLocationOnScreen(location) val x = event.rawX val y = event.rawY return x > location[0] && x < location[0] + view.width && y > location[1] && y < location[1] + view.height } // 在监听器中使用 rootView.setOnTouchListener { _, event -> if (event.action == MotionEvent.ACTION_DOWN) { val excludeView = view.findViewById<View>(R.id.excluded_area) if (!isPointInsideView(event, excludeView)) { handleBlankAreaClick() } } false } ``` #### 常见问题解决 1. **事件冲突** 设置 `android:clickable="true"` 防止事件穿透[^2] 2. **键盘不隐藏** 确保使用正确的窗口令牌:`requireActivity().window.decorView.windowToken` 3. **焦点不清除** 使用递归方法遍历所有子视图,确保清除所有输入框焦点 4. **对话框Fragment特殊处理** ```kotlin override fun onStart() { super.onStart() dialog?.window?.decorView?.setOnClickListener { dismiss() } } ``` 通过以上实现,可以在 Fragment 中高效处理空白区域点击事件,提升用户体验[^3][^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值