0.3 view 滑动冲突

news/2026/1/20 23:07:33/文章来源:https://www.cnblogs.com/a867686201/p/19509027

Android 触摸事件分发与滑动冲突处理 — 完整文档

一、核心知识点总结

1. 事件分发流程(Event Flow)

  • 事件传递路径:Activity → Window (DecorView) → ViewGroup → View
  • 若所有子 View 均未消费事件,最终会回传到 Activity.onTouchEvent()
  • 一个完整事件序列 = 1 个 ACTION_DOWN + 多个 ACTION_MOVE + 1 个 ACTION_UP

2. 关键规则

  • 通常一个事件序列应由同一个 View 消费到底。
  • 若 View 在 ACTION_DOWN 返回 false,则后续事件不会交给它。
  • ViewGroup 默认不拦截事件(onInterceptTouchEvent 返回 false)。
  • 只有当 View 的 clickablelongClickabletrue 时,onTouchEvent 才会返回 true(即消费事件)。
  • OnTouchListener.onTouch() 优先于 onTouchEvent() 被调用;若其返回 true,则 onTouchEvent() 不再执行。
  • 子 View 可通过 parent.requestDisallowInterceptTouchEvent(true/false) 动态控制父容器是否拦截。

3. 滑动冲突类型与解决思路

  • 类型1:外层水平滑动(如 ViewPager),内层垂直滑动(如 ListView)
    → 根据 |Δx| > |Δy| 判断方向,决定由谁拦截。
  • 类型2:内外均为垂直滑动
    → 依赖业务逻辑(如是否滚动到顶部/底部)。
  • 类型3:嵌套上述两种情况。
  • 解决方法
    • 外部拦截法:在父容器的 onInterceptTouchEvent 中判断是否拦截。
    • 内部拦截法:子 View 主动调用 requestDisallowInterceptTouchEvent() 控制父容器行为。

4. 滑动实现方式

  • 三种滑动方式
    1. scrollTo / scrollBy:改变内容位置(非 View 本身)
    2. 属性动画(如 ObjectAnimator.ofFloat(view, "translationX", ...)
    3. 修改 LayoutParams 并调用 requestLayout()
  • 弹性滑动实现
    • 使用 Scroller + computeScroll()(推荐)
    • 使用 ValueAnimator 动态更新 scroll 值
    • 使用 Handler + postDelayed(较少用)

5. 辅助工具

  • VelocityTracker:计算滑动速度(用于 fling 判断)
  • ViewConfiguration.get(context).scaledTouchSlop:系统认定的最小滑动距离(防误触)

二、代码文件(带完整注释)

1. ViewActivity.kt

package com.shakespace.artofandroid.chapter03import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.shakespace.artofandroid.R
import com.shakespace.artofandroid.chapter03.basic.ViewCoordinateActivity
import com.shakespace.artofandroid.chapter03.conflict.ConflictActivity
import com.shakespace.firstlinecode.global.start
import kotlinx.android.synthetic.main.activity_view.*/*** 主入口 Activity,用于跳转到两个子功能模块:* - View 坐标系演示* - 滑动冲突演示*/
class ViewActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_view)// 点击跳转到坐标系演示页面tv_coordinate.setOnClickListener {start(ViewCoordinateActivity::class.java)}// 点击跳转到滑动冲突演示页面tv_conflict.setOnClickListener {start(ConflictActivity::class.java)}}
}

2. basic/DemoView.kt

package com.shakespace.artofandroid.chapter03.basicimport android.animation.ObjectAnimator
import android.animation.ValueAnimator
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.widget.Scroller/*** 自定义 View,演示三种滑动方式及弹性滑动实现*/
class DemoView @JvmOverloads constructor(context: Context?,attrs: AttributeSet? = null,defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {// Scroller 用于实现平滑滚动(弹性滑动)val scroller = Scroller(context)/*** 使用 Scroller 实现平滑滚动到目标位置* 注意:Scroller 本身不执行滚动,需配合 computeScroll()*/fun smoothScrollTo(destX: Int, destY: Int) {val deltaX = destX - scrollX// 保存滚动参数(起始值、偏移量、持续时间)scroller.startScroll(scrollX, scrollY, deltaX, destY - scrollY, 1000)// 触发重绘,从而调用 computeScroll()invalidate()}/*** 关键方法:在每次绘制前被调用,用于更新滚动位置* 若滚动未完成,则继续 scrollTo 并请求重绘*/override fun computeScroll() {if (scroller.computeScrollOffset()) {// 更新当前滚动位置scrollTo(scroller.currX, scroller.currY)// 继续触发下一次绘制postInvalidate()}}/*** 方式1:使用 scrollTo/scrollBy* - scrollX = View 左边缘 - 内容左边缘* - 向右滑动 → scrollX < 0*//*** 方式2:使用属性动画*/fun moveByAnimation() {// 改变 View 的实际位置(视觉位移,不影响布局)ObjectAnimator.ofFloat(this, "translationX", 0f, 100f).setDuration(100).start()// 使用 ValueAnimator + scrollTo 实现弹性滑动val startX = scrollXval deltaX = 100ValueAnimator.ofInt(0, 100).apply {duration = 1000addUpdateListener { animator ->val fraction = animator.animatedFraction // [0,1]scrollTo(startX + (deltaX * fraction).toInt(), 0)}start()}}/*** 方式3:修改 LayoutParams*/fun moveByChangeLayoutParam() {// 直接修改宽高(仅作演示,实际中需考虑父容器约束)layoutParams.width += 100layoutParams.height += 100this.layoutParams = layoutParams // 触发 requestLayout()}
}

3. basic/EventDemoView.kt

package com.shakespace.artofandroid.chapter03.basicimport android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup/*** 自定义 ViewGroup,用于演示事件分发机制* 注:此处重写了 dispatchTouchEvent 但最终仍调用 super,仅用于理解流程*/
class EventDemoView @JvmOverloads constructor(context: Context?,attrs: AttributeSet? = null,defStyleAttr: Int = 0
) : ViewGroup(context, attrs, defStyleAttr) {override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {// 空实现,实际使用需布局子 View}/*** 事件分发入口* 正常流程:*   - 先调用 onInterceptTouchEvent 判断是否拦截*   - 若拦截,则调用自己的 onTouchEvent*   - 否则,将事件分发给子 View*/override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {// 示例逻辑(注释掉,实际走默认流程)// var consume = false// if (onInterceptTouchEvent(ev)) {//     consume = onTouchEvent(ev)// } else {//     consume = child.dispatchTouchEvent(ev)// }// return consume// 实际使用默认分发机制return super.dispatchTouchEvent(ev)}/*** 是否拦截事件* 默认返回 false(不拦截)*/override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {return super.onInterceptTouchEvent(ev)}/*** 处理触摸事件* 若设置了 OnTouchListener,会先调用其 onTouch 方法*/override fun onTouchEvent(event: MotionEvent?): Boolean {return super.onTouchEvent(event)}private val touchListener = object : View.OnTouchListener {override fun onTouch(v: View?, event: MotionEvent?): Boolean {// TODO: 实现具体逻辑return false}}
}

4. basic/ViewCoordinateActivity.kt

package com.shakespace.artofandroid.chapter03.basicimport android.os.Bundle
import android.util.Log
import android.view.MotionEvent
import android.view.VelocityTracker
import android.view.ViewConfiguration
import androidx.appcompat.app.AppCompatActivity
import com.shakespace.artofandroid.R
import com.shakespace.firstlinecode.global.TAG
import kotlinx.android.synthetic.main.activity_view_coordinate.*/*** 演示 View 坐标系、touchSlop、VelocityTracker 等基础概念*/
class ViewCoordinateActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_view_coordinate)// 显示静态坐标说明val memo = """left = getLeft()right = getRight()top = getTop()bottom = getBottom()width = right - leftheight = bottom - top""".trimIndent()tv_coordinate.text = memo// 显示移动后的坐标关系val memo2 = """当发生位移时(left/top/right/bottom 不变):x = left + translationXy = top + translationY""".trimIndent()tv_coordinate_move.text = memo2// 获取系统最小滑动距离(防误触阈值)val scaledTouchSlop = ViewConfiguration.get(this).scaledTouchSlopval other = "scaledTouchSlop = $scaledTouchSlop"tv_memo.text = other// 测试 disabled View 的触摸行为tv_disable.setOnTouchListener { _, _ ->Log.e(TAG, "onCreate: tv_disable onTouch")false}tv_disable.setOnClickListener {Log.e(TAG, "onCreate: tv_disable onClick")}}/*** 演示 VelocityTracker 的基本用法* 注意:此处每次 MOVE 都新建 tracker,实际应复用*/override fun onTouchEvent(event: MotionEvent?): Boolean {val velocityTracker = VelocityTracker.obtain()when (event?.action) {MotionEvent.ACTION_MOVE -> {velocityTracker.addMovement(event)velocityTracker.computeCurrentVelocity(100) // 100ms 单位Log.e(TAG, "onTouchEvent: ${velocityTracker.xVelocity} -- ${velocityTracker.yVelocity}")}}velocityTracker.clear()velocityTracker.recycle()return super.onTouchEvent(event)}
}

5. conflict/ConflictActivity.kt

package com.shakespace.artofandroid.chapter03.conflictimport android.graphics.Color
import android.os.Bundle
import android.view.View
import android.widget.ArrayAdapter
import android.widget.ListView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.shakespace.artofandroid.R
import com.shakespace.firstlinecode.global.screenWidth
import kotlinx.android.synthetic.main.activity_confilct.*/*** 滑动冲突演示主界面* 创建 3 个页面,每个页面包含一个 ListView*/
class ConflictActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_confilct)// 添加 3 个页面到水平滑动容器for (i in 0..2) {val view = layoutInflater.inflate(R.layout.confilct_test_layout, horizontal_view, false)view.layoutParams.width = screenWidthval tvTitle: TextView = view.findViewById(R.id.title)tvTitle.text = "Page $i"// 设置背景色(避免全白)view.setBackgroundColor(Color.rgb(255 / (i + 1), 255 / (i * i + 1), 255 / (i * i * i * i + 1)))createList(view)horizontal_view.addView(view)}}/*** 为每个页面创建 ListView 数据*/private fun createList(view: View) {val listView = view.findViewById<ListView>(R.id.list_view)val dataList = ArrayList<String>().apply {for (i in 0..50) {add("item $i")}}val adapter = ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,android.R.id.text1,dataList)listView.adapter = adapter}
}

6. conflict/ConflictDemoView.kt

package com.shakespace.artofandroid.chapter03.conflictimport android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup/*** 演示两种滑动冲突处理方法:* - 方法1:外部拦截(重写 onInterceptTouchEvent)* - 方法2:内部拦截(子 View 调用 requestDisallowInterceptTouchEvent)*/
class ConflictDemoView @JvmOverloads constructor(context: Context?,attrs: AttributeSet? = null,defStyleAttr: Int = 0
) : ViewGroup(context, attrs, defStyleAttr) {override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {// 空实现}// ==================== 方法2:内部拦截法 ====================/*** 在子 View 的 dispatchTouchEvent 中控制父容器是否拦截* 注意:此方法需配合父容器的 onInterceptTouchEvent 使用*/override fun dispatchTouchEvent(ev: MotionEvent): Boolean {when (ev.action) {MotionEvent.ACTION_DOWN -> {// 允许自己先处理requestDisallowInterceptTouchEvent(true)}MotionEvent.ACTION_MOVE -> {// 根据业务逻辑决定是否允许父容器拦截// 此处简化为始终允许(实际应判断滑动方向)requestDisallowInterceptTouchEvent(false)}MotionEvent.ACTION_UP -> {// 无特殊处理}}return super.dispatchTouchEvent(ev)}// ==================== 方法1:外部拦截法 ====================var flag = true // 控制是否拦截(演示用)var lastX: Float = 0fvar lastY: Float = 0f/*** 父容器判断是否拦截事件*/override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {var intercepted = falsewhen (ev.action) {MotionEvent.ACTION_DOWN -> {intercepted = false // 必须放行 DOWN,否则子 View 收不到}MotionEvent.ACTION_MOVE -> {intercepted = flag // 根据业务逻辑决定}MotionEvent.ACTION_UP -> {intercepted = false // 通常不拦截 UP}}lastX = ev.xlastY = ev.yreturn intercepted}override fun onTouchEvent(event: MotionEvent?): Boolean {return super.onTouchEvent(event)}private val touchListener = object : View.OnTouchListener {override fun onTouch(v: View?, event: MotionEvent?): Boolean {return false}}
}

7. conflict/HorizontalScrollViewEx.kt

package com.shakespace.artofandroid.chapter03.conflictimport android.content.Context
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.VelocityTracker
import android.view.View
import android.view.ViewGroup
import android.widget.Scroller
import com.shakespace.firstlinecode.global.TAG/*** 自定义水平滑动容器,使用【外部拦截法】处理滑动冲突* 适用于 ViewPager + ListView 场景*/
class HorizontalScrollViewEx @JvmOverloads constructor(context: Context?,attrs: AttributeSet? = null,defStyleAttr: Int = 0
) : ViewGroup(context, attrs, defStyleAttr) {private var childrenSize = 0private var childWidth = 0private var childIndex = 0private var lastX = 0private var lastY = 0private var lastXInIntercept = 0private var lastYInIntercept = 0private val scroller = Scroller(context)private val velocityTracker = VelocityTracker.obtain()/*** 判断是否拦截事件(外部拦截法核心)*/override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {var intercepted = falseval eventX = ev.x.toInt()val eventY = ev.y.toInt()when (ev.action) {MotionEvent.ACTION_DOWN -> {// 如果正在滚动中,中断并接管事件if (!scroller.isFinished) {scroller.abortAnimation()intercepted = true} else {intercepted = false}lastXInIntercept = eventXlastYInIntercept = eventY}MotionEvent.ACTION_MOVE -> {val deltaX = eventX - lastXInInterceptval deltaY = eventY - lastYInIntercept// 水平滑动为主 → 父容器拦截intercepted = Math.abs(deltaX) > Math.abs(deltaY)}MotionEvent.ACTION_UP -> {intercepted = false}}Log.e(TAG, "onInterceptTouchEvent: intercepted=$intercepted")lastX = eventXlastY = eventYreturn intercepted}/*** 处理触摸事件,实现水平滑动和自动吸附*/override fun onTouchEvent(ev: MotionEvent): Boolean {velocityTracker.addMovement(ev)val eventX = ev.x.toInt()val eventY = ev.y.toInt()when (ev.action) {MotionEvent.ACTION_DOWN -> {if (!scroller.isFinished) {scroller.abortAnimation()}}MotionEvent.ACTION_MOVE -> {val deltaX = eventX - lastXscrollBy(-deltaX, 0) // 水平滚动}MotionEvent.ACTION_UP -> {velocityTracker.computeCurrentVelocity(1000)val xVelocity = velocityTracker.xVelocity// 根据速度或位置自动吸附到最近页面if (Math.abs(xVelocity) >= 50) {childIndex += if (xVelocity < 0) 1 else -1} else {childIndex = (scrollX + childWidth / 2) / childWidth}childIndex = Math.max(0, Math.min(childIndex, childrenSize - 1))val dx = childIndex * childWidth - scrollXsmoothScrollBy(dx, 0)velocityTracker.clear()}}lastX = eventXlastY = eventYreturn true}// --- 测量与布局 ---override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {super.onMeasure(widthMeasureSpec, heightMeasureSpec)measureChildren(widthMeasureSpec, heightMeasureSpec)val widthSize = MeasureSpec.getSize(widthMeasureSpec)val heightSize = MeasureSpec.getSize(heightMeasureSpec)val widthMode = MeasureSpec.getMode(widthMeasureSpec)val heightMode = MeasureSpec.getMode(heightMeasureSpec)if (childCount == 0) {setMeasuredDimension(0, 0)return}val firstChild = getChildAt(0)val childWidth = firstChild.measuredWidthval childHeight = firstChild.measuredHeightwhen {widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST ->setMeasuredDimension(childWidth * childCount, childHeight)heightMode == MeasureSpec.AT_MOST ->setMeasuredDimension(widthSize, childHeight)widthMode == MeasureSpec.AT_MOST ->setMeasuredDimension(childWidth * childCount, heightSize)}}override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {var childLeft = 0childrenSize = childCountfor (i in 0 until childCount) {val child = getChildAt(i)if (child.visibility != View.GONE) {childWidth = child.measuredWidthchild.layout(childLeft, 0, childLeft + childWidth, child.measuredHeight)childLeft += childWidth}}}private fun smoothScrollBy(dx: Int, dy: Int) {scroller.startScroll(scrollX, scrollY, dx, dy, 500)invalidate()}override fun computeScroll() {if (scroller.computeScrollOffset()) {scrollTo(scroller.currX, scroller.currY)postInvalidate()}}override fun onDetachedFromWindow() {velocityTracker.recycle()super.onDetachedFromWindow()}
}

8. conflict/ListViewEx.kt

package com.shakespace.artofandroid.chapter03.conflictimport android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.widget.ListView/*** 自定义 ListView,使用【内部拦截法】处理滑动冲突* 在 dispatchTouchEvent 中主动控制父容器是否拦截*/
class ListViewEx @JvmOverloads constructor(context: Context?,attrs: AttributeSet? = null,defStyleAttr: Int = 0
) : ListView(context, attrs, defStyleAttr) {private var lastX = 0private var lastY = 0override fun dispatchTouchEvent(ev: MotionEvent): Boolean {val eventX = ev.x.toInt()val eventY = ev.y.toInt()when (ev.action) {MotionEvent.ACTION_DOWN -> {// 先允许自己处理事件parent.requestDisallowInterceptTouchEvent(true)}MotionEvent.ACTION_MOVE -> {val deltaX = eventX - lastXval deltaY = eventY - lastY// 如果是垂直滑动,禁止父容器拦截;否则允许val disallowIntercept = Math.abs(deltaX) <= Math.abs(deltaY)parent.requestDisallowInterceptTouchEvent(disallowIntercept)}MotionEvent.ACTION_UP -> {// 无特殊处理}}lastX = eventXlastY = eventYreturn super.dispatchTouchEvent(ev)}
}

注:HorizontalScrollViewEx2.ktHorizontalScrollViewEx.kt 逻辑高度重复,且存在逻辑缺陷(onInterceptTouchEvent 在非 DOWN 事件直接返回 true),故未单独列出。建议以 HorizontalScrollViewEx.kt 为准。

三、文档笔记

z_memo01.txt

1. Event FlowActivity --> Window --> Viewif all views not handle the event, will back to Activity/*** Called when a touch screen event was not handled by any of the views* under it.  This is most useful to process touch events that happen* outside of your window bounds, where there is no view to receive it.** @param event The touch screen event being processed.** @return Return true if you have consumed the event, false if you haven't.* The default implementation always returns false.*/public boolean onTouchEvent(MotionEvent event) {if (mWindow.shouldCloseOnTouch(this, event)) {finish();return true;}return false;}@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)public boolean shouldCloseOnTouch(Context context, MotionEvent event) {final boolean isOutside =event.getAction() == MotionEvent.ACTION_UP && isOutOfBounds(context, event)|| event.getAction() == MotionEvent.ACTION_OUTSIDE;if (mCloseOnTouchOutside && peekDecorView() != null && isOutside) {return true;}return false;}2. An Event Sequence = a Down + many Move + a Up  (事件序列,由一个按下,多个移动,一个抬起组成)3. Normally, an event should be consumed only by one View  (通常一个序列应该由一个 view 处理)4. In an event sequence, if a view calls onInterceptTouchEvent, then the rest of the event sequence will all be handled by this view and onInterceptTouchEvent will not be called again.5. If a view returns false in Down Event, then the rest events will not be handled by this view  (如果一个 view 没有处理 DOWN,那么也不会处理后续)6. ViewGroup will not intercept any event by default → onInterceptTouchEvent returns false  (ViewGroup 默认不会拦截)7. If a view clickable = false and longClickable = false, then onTouchEvent will return false.8. Can call requestDisallowInterceptTouchEvent in child view.

z_memo02 source code.txt

1. Activitypublic boolean dispatchTouchEvent(MotionEvent ev) {if (ev.getAction() == MotionEvent.ACTION_DOWN) {onUserInteraction(); // empty method}if (getWindow().superDispatchTouchEvent(ev)) {return true;}return onTouchEvent(ev);}2. PhoneWindow@Overridepublic boolean superDispatchTouchEvent(MotionEvent event) {return mDecor.superDispatchTouchEvent(event);}// This is the top-level view of the window, containing the window decor.private DecorView mDecor;3. com.android.internal.policy.DecorView  // hide/** @hide */public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {private static final String TAG = "DecorView";....}4. ViewGroup: dispatchTouchEventfinal boolean intercepted;if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;if (!disallowIntercept) {intercepted = onInterceptTouchEvent(ev);ev.setAction(action); // restore action in case it was changed} else {intercepted = false;}}- mFirstTouchTarget: if there is a view that has handled the previous event, mFirstTouchTarget would not be null.- disallowIntercept: set by requestDisallowInterceptTouchEvent.(Except for DOWN event, ViewGroup will call onInterceptTouchEvent in each DOWN event because it resets FLAG_DISALLOW_INTERCEPT.)// 请求父类不要拦截:如果 disallowIntercept is true, parent does not intercept → intercepted = false// Note: onInterceptTouchEvent may not be called in an entire event sequence.5. If not intercepted:for (int i = childrenCount - 1; i >= 0; i--) {final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);if (childWithAccessibilityFocus != null) {if (childWithAccessibilityFocus != child) {continue;}childWithAccessibilityFocus = null;i = childrenCount - 1;}if (!child.canReceivePointerEvents()|| !isTransformedTouchPointInView(x, y, child, null)) {ev.setTargetAccessibilityFocus(false);continue;}newTouchTarget = getTouchTarget(child);if (newTouchTarget != null) {newTouchTarget.pointerIdBits |= idBitsToAssign;break;}resetCancelNextUpFlag(child);if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {mLastTouchDownTime = ev.getDownTime();if (preorderedList != null) {for (int j = 0; j < childrenCount; j++) {if (children[childIndex] == mChildren[j]) {mLastTouchDownIndex = j;break;}}} else {mLastTouchDownIndex = childIndex;}mLastTouchDownX = ev.getX();mLastTouchDownY = ev.getY();newTouchTarget = addTouchTarget(child, idBitsToAssign);  // set touchTargetalreadyDispatchedToNewTouchTarget = true;break;}ev.setTargetAccessibilityFocus(false);}// 可见性 和 动画,结合点击点是否在区域内// 当 view 可见时,返回 true;当 view 不可见,若设置了动画,也返回 true(因为动画可能使其隐藏??)protected boolean canReceivePointerEvents() {return (mViewFlags & VISIBILITY_MASK) == VISIBLE || getAnimation() != null;}private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,View child, int desiredPointerIdBits) {final boolean handled;final int oldAction = event.getAction();if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {event.setAction(MotionEvent.ACTION_CANCEL);if (child == null) {handled = super.dispatchTouchEvent(event); // → View.dispatchTouchEvent(ev)} else {handled = child.dispatchTouchEvent(event);}event.setAction(oldAction);return handled;}}6. In Viewif (onFilterTouchEventForSecurity(event)) {if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {result = true;}ListenerInfo li = mListenerInfo;if (li != null && li.mOnTouchListener != null&& (mViewFlags & ENABLED_MASK) == ENABLED&& li.mOnTouchListener.onTouch(this, event)) {result = true;}if (!result && onTouchEvent(event)) {result = true;}}- Will call mOnTouchListener.onTouch first.- onTouchEvent(ev)final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;if ((viewFlags & ENABLED_MASK) == DISABLED) {if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {setPressed(false);}mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;// A disabled view that is clickable still consumes the touch events, it just doesn't respond to them.return clickable;}if (mTouchDelegate != null) {if (mTouchDelegate.onTouchEvent(event)) {return true;}}// 可点击 / 长按 / 响应鼠标(或触控笔)就可以消费点击事件if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {....}// will return true.So: if view is disabled, onTouchListener won't work, but onTouchEvent still gets called.Returns true if clickable / longClickable / contextClickable / or (viewFlags & TOOLTIP) == TOOLTIP.7. In addition:public void setOnClickListener(@Nullable OnClickListener l) {if (!isClickable()) {setClickable(true);}getListenerInfo().mOnClickListener = l;}public void setOnLongClickListener(@Nullable OnLongClickListener l) {if (!isLongClickable()) {setLongClickable(true);}getListenerInfo().mOnLongClickListener = l;}

z_memo03 scroll conflict.txt

处理滑动冲突:Case 1. Outer horizontal, Inner vertical  (e.g., ViewPager + Fragment with ListView)- When MOVE is horizontal → OUTER should intercept the event.- When MOVE is vertical → INNER should intercept the event.- Check using |Δx| > |Δy|.Case 2. Outer vertical, Inner vertical  Depends on business logic (e.g., whether inner is at top/bottom).Case 3. Nested Case 1 and Case 2.

z_memo04滑动和弹性滑动.txt

// TODO 补充滑动和弹性滑动1. 滑动的三种方式:- scrollTo / scrollBy- 属性动画(ObjectAnimator / ValueAnimator)- 修改 LayoutParams 并调用 requestLayout()2. 弹性滑动的三种方式:- Scroller + computeScroll()- ValueAnimator + update listener- Handler + postDelayed(不推荐)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1191130.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OIFC 2026省选 0120

胜兵必骄 wars 称 \(a=1\) 为黑色,否则为白色。 注意到一次战斗本质是交换颜色,一条边被操作两次不会对颜色产生影响。最初的想法是找到一个黑点 \(u\),与白色儿子交换颜色,递归到子树处理;同色的儿子提前递归,回…

流量累计程序 博途v15编写的西门子流量累计程序,封装好的FB块直接可以拿来用,并且配有视频解说

流量累计程序 博途v15编写的西门子流量累计程序&#xff0c;封装好的FB块直接可以拿来用&#xff0c;并且配有视频解说&#xff0c;轻松学会最近在调试现场流量计项目&#xff0c;发现不少新手对累计流量功能实现总是卡壳。今天就给大家拆解一个我封装好的博途V15流量累计FB块&…

2026年广东比较好的刀塔机定制需要多少钱,Y轴/尾顶机/排刀机/数控4+4/正交Y/动力刀塔/直Y,刀塔机厂家推荐排行

近年来,随着中国制造业向高端化、智能化转型,刀塔机作为数控机床领域的核心设备,其技术迭代与定制化需求持续攀升。据行业统计,2025年国内刀塔机市场规模突破120亿元,广东作为制造业大省,占据全国35%以上的市场份…

qt之实现截图效果

以下是修改后的代码,实现鼠标滑动矩形区域截图功能: #include <QGuiApplication> #include <QScreen> #include <QPixmap> #include

【毕业设计】springboot基于大数据技术的诗词信息系统(源码+文档+远程调试,全bao定制等)

java毕业设计-基于springboot的(源码LW部署文档全bao远程调试代码讲解等) 博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、…

【Python】解决 Windows 下 pip 安装报错 OSError: [Errno 2] No such file or directory (路径过长问题)

目录 【Python】解决 Windows 下 pip 安装报错 OSError: [Errno 2] No such file or directory (路径过长问题) 1. 问题描述 2. 原因分析 3. 解决方案 方案一&#xff1a;修改临时目录路径&#xff08;最快&#xff0c;无需重启&#xff09; 方案二&#xff1a;解除 Wind…

深夜调模型的工程师都懂,燃油车和电动车之间总得有个“和事佬“——增程器。今天咱们聊的这个Cruise仿真模型,就是要把这个中间商做出价值

cruise模型&#xff0c;增程汽车仿真模型&#xff0c;串联混动&#xff0c;基于cruise/simulink联合仿真。 实现增程器多点控制策略&#xff0c;及电制动优先的能量回收策略。 提供cruise模型、控制策略文件以及策略说明文档&#xff0c;方便您在模型基础上进行扩展。先看底盘架…

《把脉行业与技术趋势》-72-伟大的组织,不只是会收割,更要会培育土壤。“春天开荒播种是为了秋天收获果实”。

导入期&#xff08;春&#xff09;&#xff1a;开荒播种&#xff08;技术商业可行&#xff09; 成长期&#xff08;夏&#xff09;&#xff1a;快速成长&#xff08;抢占市场份额&#xff09; 成熟期&#xff08;秋&#xff09;&#xff1a;收获果实&#xff08;获取利润收益&a…

【python实用小脚本-336】HR如何用Python改造敏感信息传递流程?信息安全×代码的化学反应,轻松实现音频隐写术

一、那个差点让我丢饭碗的"保密文件"事件 去年做HR总监助理时&#xff0c;我负责向15位高管同步一份未公布的组织架构调整方案。按照"传统流程"&#xff0c;我加密了PDF&#xff0c;单独发送了解压密码&#xff0c;还特意微信叮嘱"阅后即焚"。结…

【2026开年巨献】Gemini 3.0全面解析:从技术原理到商业落地,开发者不可错过的AI革命指南

&#x1f680; 一、开篇&#xff1a;当Gemini 3.0重塑AI格局&#xff0c;开发者如何把握机遇&#xff1f;&#x1f3af;&#x1f525; 2026年1月20日重磅&#xff1a;Google最新财报披露&#xff0c;Gemini已成公司增长新引擎&#xff01;全球10亿设备搭载Gemini&#xff0c;企…

【GoFrame (GF) 】高性能、模块化、企业级的 Go 语言开发框架

文章目录前言一、核心架构1. 基础层&#xff08;Core Foundation&#xff09;2. 核心层&#xff08;Core Modules&#xff09;3. 扩展层&#xff08;Extended Modules&#xff09;4. 基础设施层&#xff08;Infrastructure&#xff09;二、关键技术特性解析1. 高性能设计2. 易用…

【计算机毕业设计案例】基于springboot+大数据技术旅游商品管理系统大数据毕设选题推荐:基于大数据技术旅游商品管理系统基于springboot+大数据技术旅游商品管理系(程序+文档+讲解+定制)

java毕业设计-基于springboot的(源码LW部署文档全bao远程调试代码讲解等) 博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、…

【2026 深度观察】大模型战国时代:中美双极、四强争霸与生态分化

目录 【2026 深度观察】大模型战国时代&#xff1a;中美双极、四强争霸与生态分化 前言&#xff1a;告别“单体为王”&#xff0c;拥抱“各司其职” 一、 Anthropic&#xff1a;代码与 Agent 的“无冕之王” 二、 OpenAI&#xff1a;逻辑推理与数学的“象牙塔尖” 三、 Go…

同步FIFO的三种写法各有特点。计数器法直接用读写计数器差值判断空满,适合小深度场景。举个例子,当depth=1时可以直接用寄存器存储数据

FIFO verilogIP 包括深度为1的fifo 包括普通同步FIFO和异步FIFO&#xff0c;均为first word fall through模式&#xff0c;同步fifo三种写法&#xff0c;异步fifo三种写法&#xff0c;可参数化配置&#xff0c;接口为fifo的基础接口 提供基础的testbench&#xff0c;两种fif…

大数据领域 Elasticsearch 集群搭建全流程

大数据领域Elasticsearch集群搭建全流程&#xff1a;从环境准备到高可用优化 摘要/引言 在大数据场景下&#xff0c;单独的Elasticsearch节点无法应对海量数据存储、高并发查询和单点故障问题。集群化是解决这些问题的核心方案——通过将多个节点组成集群&#xff0c;可以实现…

自动聊天工具尝试一(寻找方向)

目录 一句话结论&#xff08;先给你定心&#xff09; 一、先整体看清这条完整路线&#xff08;你现在在第 0 步&#xff09; 二、真正的「第一步」&#xff1a;明确你要识别什么&#xff08;非常关键&#xff09; 场景 A&#xff08;最简单&#xff0c;强烈推荐新手&#xf…

一个python笔试题及扩展

笔试题来源 最近有位小伙伴面试失败,发来了面试复盘内容,其中有个笔试题还是挺有意思的,我特意摘出来分享给大家,同时做了扩展。 笔试题 建议大家先不运行,看能否给出答案并说明原因name = qzcsbjdef func1():glo…

支持付费内容与广告的社区论坛小程序商业化运营源码系统

温馨提示&#xff1a;文末有资源获取方式您是否在寻找一款能提升企业运营效率的社区论坛小程序系统&#xff1f;那么&#xff0c;您来对地方了&#xff01;我们推荐的这款源码系统&#xff0c;专为网络建站公司、IT工作室及创业者设计&#xff0c;提供多用户支持&#xff0c;让…

2025年最受物流企业青睐的自动化立体库解决方案TOP 5,贯通式货架/中型货架/平台货架/轻型货架/重型货架自动化立体库公司有哪些

随着智能制造与智慧物流的深度融合,自动化立体库作为现代仓储体系的核心,正成为企业降本增效、提升供应链韧性的关键基础设施。面对市场上琳琅满目的解决方案,如何选择一家技术可靠、服务专业的合作伙伴,成为众多物…

永久关闭windows系统的自动更新的6种方法 详细介绍

关闭Windows系统的自动更新可以通过多种方法实现&#xff0c;以下将详细介绍六种不同的方法。请注意&#xff0c;关闭自动更新可能会使您的系统面临安全风险&#xff0c;因为您将不会及时接收到最新的安全补丁和系统更新。在执行以下任何操作之前&#xff0c;请确保您了解潜在的…