期望
在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
内的文字无法正常上下划动了。
此时我们提出我们的期望:
- 当
EditText
内的内容,超出自身的高度时:- 手指上划时,若
EditText
还没划到底,则划动EditText
的内容部分;当Edit Text
划到底时,整体页面上划。 - 手指下划时,若
EditText
还没划到顶,则划动EditText
的内容部分;当Edit Text
划到顶时,整体页面下划。
- 手指上划时,若
- 当EditText内的内容没有超出自身高度时:
- 划动整体页面
实现方案
这是典型的划动冲突 问题,解决方案就从事件分发入手了。
如果想要了解事件分发,请查阅我之前发表的博客。
总之,我们此刻一句话描述我们的处理逻辑:
- 若EditText内部内容能够划动,划内部,若EditText内部内容不能划动,划外部。
- 如何控制划内部外部?使用
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
中,划起来还是挺怪的。这个方案不采用了。