EditText与NestScrollView嵌套使用时,滑动冲突处理

期望

Android开发中经常会有在一个大页面中,包含一个EditText的情况,一般情况下,大页面会通过NestScrollView或者ScrollView当作根View

于是在布局文件中,我们常常这么写:

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>  
<androidx.core.widget.NestedScrollView  
	xmlns:android="http://schemas.android.com/apk/res/android"  
	xmlns:app="http://schemas.android.com/apk/res-auto"  
	xmlns:tools="http://schemas.android.com/tools"  
	android:layout_width="match_parent"  
	android:layout_height="match_parent"  
	tools:context=".EditTextScrollActivity">  
  
	<LinearLayout  
		android:layout_width="match_parent"  
		android:layout_height="wrap_content"  
		android:orientation="vertical" > 	 
	  
	  
		<View  
			android:layout_width="match_parent"  
			android:layout_height="400dp"  
			android:background="@android:color/holo_blue_bright"  />  
		  
		<EditText  
			android:id="@+id/et"  
			android:layout_width="match_parent"  
			android:layout_height="100dp"/>  
		  
		<View  
			android:layout_width="match_parent"  
			android:layout_height="600dp"  
			android:background="@android:color/holo_red_light"  />  
	  
	</LinearLayout>  
  
  
</androidx.core.widget.NestedScrollView>

EditText上下的两个View是为了模拟其他内容,以达到让整个页面显示超过屏幕高度的效果。

在这种情况下,我们根据UI稿设置了一个固定高度的EditText,这时直接运行时会发现一个问题,我们输入了过高的文字时,EditText内的文字无法正常上下划动了。

此时我们提出我们的期望:

  1. EditText内的内容,超出自身的高度时:
    1. 手指上划时,若EditText还没划到底,则划动EditText的内容部分;当Edit Text划到底时,整体页面上划。
    2. 手指下划时,若EditText还没划到顶,则划动EditText的内容部分;当Edit Text划到顶时,整体页面下划。
  2. 当EditText内的内容没有超出自身高度时:
    1. 划动整体页面

实现方案

这是典型的划动冲突 问题,解决方案就从事件分发入手了。

如果想要了解事件分发,请查阅我之前发表的博客。

事件分发汇总帖

总之,我们此刻一句话描述我们的处理逻辑:

  1. 若EditText内部内容能够划动,划内部,若EditText内部内容不能划动,划外部。
  2. 如何控制划内部外部?使用requestDisallowInterceptTouchEvent

接下来写一下代码吧!

kotlin 复制代码
public class EditTextTouchListener(view: EditText) : OnTouchListener {  
  
	private var viewConfig = ViewConfiguration.get(view.context)  
	  
	private var originalTouchY: Float = 0f  
	private var originalTouchX: Float = 0f  
	  
	@SuppressLint("ClickableViewAccessibility")  
	override fun onTouch(v: View?, event: MotionEvent?): Boolean {  
		event ?: return false  
		if (v is EditText) {  
			when (event.actionMasked) {  
				MotionEvent.ACTION_DOWN -> {  
					// 1 按下时,记录一下位置,并请求父布局不要拦截事件
					v.parent?.requestDisallowInterceptTouchEvent(true)  
					originalTouchY = event.y  
					originalTouchX = event.x  
				}  
			  
				MotionEvent.ACTION_MOVE -> {  
					val nowY = event.y  
					val nowX = event.x  
					// 计算X方向划动距离
					val distance = abs(nowX - originalTouchX)  
			  
					// 如果横向划动已经超过了最小值,交由EditText自己处理
					if (viewConfig.scaledTouchSlop < distance ) {  
						return false  
					}  
					// 计算竖直方向,如果划动距离小于最小值,交给EditText自己处理
					if (abs(nowY - originalTouchY) < viewConfig.scaledTouchSlop) {  
						return false  
					}  
					// 执行到此处,那么竖直方向一定划动超过了最小值,此时判断是,向上划动还是向下划动。
					// 如果当前位置Y大于  初始Y,即手指下划
					if (nowY > originalTouchY) {  
						return if (v.canScrollVertically(-1)) {  
								false  
							}else {  
								v.parent?.requestDisallowInterceptTouchEvent(false)  
								true  
							}  
					}else {  
						// 如果当前位置Y小于 初始Y,即手指上划
						if (nowY < originalTouchY) {  
							return if (v.canScrollVertically(1)) {  
								false  
								}else {  
									v.parent?.requestDisallowInterceptTouchEvent(false)  
								true  
							}  
						}  
					}  
				}  
			  
				else -> {  
					v.parent?.requestDisallowInterceptTouchEvent(false)  
				}  
			}  
		}  
		return false  
	}  
  
}

这种实现方案有一个问题:

当划动交给外部之后,便一直由外部处理了:

假设,EditText的内容可以往上划动,那我开始划动,划动到底之后,EditText不能继续往上划了之后,把滑动操作交给了外边的NestScrollView;此时,手不松开,开始反向滑动,EditText也不会滑动里边的内容了。

当然这种已经已经满足我的期望了。

延申

上面的实现方案满足了产品需求、UI需求,但是我在想,还可以实现另一种,就是当手指划出EditView的范围之后,就不再可划。

kotlin 复制代码
public class EditTextRangeTouchListener(view: EditText) : OnTouchListener {  
  
	private var viewConfig = ViewConfiguration.get(view.context)  
  
	private var originalTouchY: Float = 0f  
	private var originalTouchX: Float = 0f  
  
	@SuppressLint("ClickableViewAccessibility")  
	override fun onTouch(v: View?, event: MotionEvent?): Boolean {  
		event ?: return false  
		if (v is EditText) {  
		when (event.actionMasked) {  
			MotionEvent.ACTION_DOWN -> {  
				v.parent?.requestDisallowInterceptTouchEvent(true)  
				originalTouchY = event.y  
				originalTouchX = event.x  
			}  
	  
			MotionEvent.ACTION_MOVE -> {  
				val nowY = event.y  
				val nowX = event.x  
				val distance = abs(nowX - originalTouchX)  
	  
				if (nowY < 0 || nowY > v.height) {  
					v.parent?.requestDisallowInterceptTouchEvent(false)  
					return true  
				}  
	  
				if (viewConfig.scaledTouchSlop < distance ) {  
					return false  
				}  
	  
				if (abs(nowY - originalTouchY) < viewConfig.scaledTouchSlop) {  
					return false  
				}  
	  
				if (nowY > originalTouchY) {  
					return if (v.canScrollVertically(-1)) {  
						false  
					}else {  
						v.parent?.requestDisallowInterceptTouchEvent(false)  
						true  
					}  
				}else {  
					if (nowY < originalTouchY) {  
						return if (v.canScrollVertically(1)) {  
							false  
						}else {  
							v.parent?.requestDisallowInterceptTouchEvent(false)  
							true  
						}  
					}  
				}  
			}  
	  
			else -> {  
					v.parent?.requestDisallowInterceptTouchEvent(false)  
				}  
			}  
		}  
		return false  
	}  
	  
}

这种实现方式放到NestScrollView中,划起来还是挺怪的。这个方案不采用了。

相关推荐
拭心3 小时前
Google 提供的 Android 端上大模型组件:MediaPipe LLM 介绍
android
带电的小王6 小时前
WhisperKit: Android 端测试 Whisper -- Android手机(Qualcomm GPU)部署音频大模型
android·智能手机·whisper·qualcomm
梦想平凡6 小时前
PHP 微信棋牌开发全解析:高级教程
android·数据库·oracle
元争栈道6 小时前
webview和H5来实现的android短视频(短剧)音视频播放依赖控件
android·音视频
阿甘知识库7 小时前
宝塔面板跨服务器数据同步教程:双机备份零停机
android·运维·服务器·备份·同步·宝塔面板·建站
元争栈道8 小时前
webview+H5来实现的android短视频(短剧)音视频播放依赖控件资源
android·音视频
MuYe8 小时前
Android Hook - 动态加载so库
android
居居飒9 小时前
Android学习(四)-Kotlin编程语言-for循环
android·学习·kotlin
Henry_He11 小时前
桌面列表小部件不能点击的问题分析
android
工程师老罗12 小时前
Android笔试面试题AI答之Android基础(1)
android