我希望有一个ViewGroup
可以拦截用户的手势(因为MotionEvent
或onInterceptTouchEvents()
中的onTouchEvent()
序列都没有关系) ,对其进行分析,然后决定-是自行处理还是将其返回传递给此ViewGroup
的父级。
更具体地说,让我们假设我在TextView
中有一个LinearLayout
。而此LinearLayout
又位于某个滚动容器内-通常是{strong>水平滚动的RecyclerView
(或ViewPager
或ScrollView
)内>。
我想在该TextView
上实现滑动(左,右),将其传递到封闭的容器(RecyclerView
)并在那里进行处理,滚动。另一方面,我还希望允许用户直接单击TextView
并获得回调。因此,这听起来像是一个已知的问题,需要同时单击和滑动某些View
。
但是我的问题是我不知道如何将整个手势放在LinearLayout
中,然后将其返回传递给父母 (RecyclerView
),前提是该手势不是点击,而是像滑动一样。
例如,我以以下方式扩展了LinearLayout
(请参见下文)。然后,我想拦截手势,检查它是否是直接点击(由触摸倾斜给出的小半径范围内的点击),如果是,则触发回调,否则我要整个手势将被传递回父母以在那里处理,例如-在LinearLayout
上滑动:
class SwipeLinearLayout : LinearLayout {
private var listener: (() -> Unit)? = null
constructor(context: Context): this(context, null)
constructor(context: Context, attributes: AttributeSet?): this(context, attributes, 0)
constructor(context: Context, attributes: AttributeSet?, defStyleAttr: Int): super(context, attributes, defStyleAttr)
fun setOnDirectClickListener(l: (() -> Unit)?) {
listener = l
}
private var dispatching = false
private var prevX: Float = 0.0f
private var prevY: Float = 0.0f
private var startX: Float = 0.0f
private var startY: Float = 0.0f
private var directClick: Boolean = false
private var startClick: Boolean = false
private val touchSlop = ViewConfiguration.get(context).scaledTouchSlop
private val track = mutableListOf<MotionEvent>()
private var trackOpen = true
fun dispatchAll() {
dispatching = true
var y = 0.0f
loop@ for (i in 0 until track.size) {
val it = track[i]
when (it.actionMasked) {
MotionEvent.ACTION_MOVE -> {
if (y == 0.0f) y = it.y
if (abs(y - it.y) > touchSlop) {
it.recycle()
continue@loop // skip non-swipe motion events
}
}
}
(parent as View).dispatchTouchEvent(it)
it.recycle()
}
track.clear()
dispatching = false
}
fun distance(event: MotionEvent): Float {
val x = abs(event.x - prevX)
val y = abs(event.y - prevY)
return x*x + y*y
}
fun initCoordinates(event: MotionEvent) {
prevX = event.x
prevY = event.y
}
fun _invalidate() {
directClick = false
startClick = false
track.clear()
}
// ------------------
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
var result = super.onInterceptTouchEvent(event)
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> result = false
}
return result
}
override fun onTouchEvent(event: MotionEvent): Boolean {
if (dispatching) {
return false
}
var result = super.onTouchEvent(event)
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
startX = event.x
startY = event.y
trackOpen = true
}
}
if (trackOpen) {
val e = when (event.actionMasked) {
MotionEvent.ACTION_UP -> MotionEvent.obtain(event).apply { action = MotionEvent.ACTION_CANCEL }
else -> MotionEvent.obtain(event)
}
track.add(e)
}
result = _onTouchEvent(event)
return result
}
fun _onTouchEvent(event: MotionEvent): Boolean {
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
initCoordinates(event)
startClick = true
return true
}
MotionEvent.ACTION_MOVE -> {
directClick = distance(event) < touchSlop
if (!directClick) {
// track.add(MotionEvent.obtain(event).apply { action = MotionEvent.ACTION_CANCEL })
trackOpen = false
dispatchAll()
return true
}
return directClick
}
MotionEvent.ACTION_UP -> {
initCoordinates(event)
if (startClick && directClick) {
_invalidate()
listener?.invoke()
return true
} else {
dispatchAll()
}
}
MotionEvent.ACTION_CANCEL -> {
initCoordinates(event)
dispatchAll()
}
}
return false
}
}
我所做的是我记录了整个手势,并将其存储在track
数组中,当我识别出该手势不是直接单击时(即,其运动偏差超出了触摸倾斜的范围) ),我将整个手势通过dispatchAll()
方法传递给 Parent 。
但是,这无法正常工作-有时可以工作,而滑动则可以直接单击(单击)分开,有时不行,而且我在父级上看到一些错误的动作或根本没有动作
我还认为我的技术是不可接受的:我从未在Android文档和教程中看到有人记录该手势并将其传递回 Parent 。但是我不知道该怎么做,因为我需要分析手势,如果它不是我期望的手势,我完全不会使用它,而是让它由 Parent处理。我该如何实现?
P.S .:我已经尝试过GestureDetector
,但并没有帮助我。另外,GestureDetector
不会阻止父级处理直接点击,但我需要仅在该父级 内的LinearLayout
中对其进行处理>,然后让 Parent 处理其他手势。
PSS:我也不想在某些根视图或Activity
中截取手势,因为它破坏了封装:我想拥有一个自定义LinearLayout
,我可以将其包括在内任何布局,而自定义LinearLayout
可以发挥作用-在单击和在其上滑动之间进行清晰的区分。因此,如果实际上是滑动手势,则将其放到更多的根容器中;否则,如果单击,则将其处理并向监听器触发回调。