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中,划起来还是挺怪的。这个方案不采用了。

相关推荐
千里马学框架5 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台5 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone5 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc5 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo5 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077005 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼5 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone5 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen5 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone5 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui