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

相关推荐
DogDaoDao7 小时前
Android 硬件编码器参数完全指南:MediaCodec 深度解析
android·音视频·视频编解码·h264·硬编码·视频直播·mediacodec
JohnnyDeng948 小时前
Android 自定义 View:Canvas 绘图与事件分发深度解析
android
Android小码家11 小时前
Framework之Launcher小窗开发
android·framework·虚拟屏·小窗
赏金术士11 小时前
第七章:状态管理实战与架构总结
android·ui·kotlin·compose
颂love13 小时前
MySQL的执行流程
android·数据库·mysql
云起SAAS17 小时前
抖音小游戏源码 - 消消乐 | 含激励广告+成就系统 | 开箱即用商业级消除游戏模板
android·游戏·广告联盟·看激励广告联盟流量主·抖音小游戏源码 - 消消乐
大貔貅喝啤酒18 小时前
基于Windows下载安装Android Studio 3.3.2版本教程(2026详细图文版)
android·java·windows·android studio
程序员码歌18 小时前
OpenSpec 到 Superpowers:AI 编码从说清到做对
android·前端·人工智能
2501_9151063219 小时前
深入解析无源码iOS加固原理与方案,保护应用安全
android·安全·ios·小程序·uni-app·cocoa·iphone
黄林晴1 天前
重磅官宣:Android UI 开发正式进入 Compose-first 时代
android·google io