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

相关推荐
西瓜本瓜@2 小时前
在Android中如何使用Protobuf上传协议
android·java·开发语言·git·学习·android-studio
似霰5 小时前
安卓adb shell串口基础指令
android·adb
fatiaozhang95278 小时前
中兴云电脑W102D_晶晨S905X2_2+16G_mt7661无线_安卓9.0_线刷固件包
android·adb·电视盒子·魔百盒刷机·魔百盒固件
CYRUS_STUDIO9 小时前
Android APP 热修复原理
android·app·hotfix
鸿蒙布道师9 小时前
鸿蒙NEXT开发通知工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
鸿蒙布道师9 小时前
鸿蒙NEXT开发网络相关工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
大耳猫9 小时前
【解决】Android Gradle Sync 报错 Could not read workspace metadata
android·gradle·android studio
ta叫我小白10 小时前
实现 Android 图片信息获取和 EXIF 坐标解析
android·exif·经纬度
dpxiaolong11 小时前
RK3588平台用v4l工具调试USB摄像头实践(亮度,饱和度,对比度,色相等)
android·windows
tangweiguo0305198712 小时前
Android 混合开发实战:统一 View 与 Compose 的浅色/深色主题方案
android