跟随手指移动。通过 setx,y 以及 setTranslation 的区别
| 方法 | 作用 | 本质 | 特点 |
|---|---|---|---|
| setX(float x) | 设置 View 左上角的绝对 X 坐标(包含 translation) | 改的是 View 的layout 位置 + translation | 真正把 View 位置挪到 x。 |
| setTranslationX(float translationX) | 在现有 layout 位置基础上做偏移 | 改的是 View 的平移效果,不动 layout | 只是视觉移动,原本 layout 位置还在。 |
| 方法 | 代表的意义 | 坐标系 | 通常用在 |
|---|---|---|---|
| event.x | 手指相对于触发这个 View 左上角的位置(局部坐标) | 相对当前 View | 处理手指滑动、内部手势检测 |
| view.getX() | 这个 View 自己在父容器中的位置(layoutX + translationX) | 相对父布局 | 布局位置、移动 View |
| 方法 | 坐标基准 | 意义 |
|---|---|---|
| event.x | 当前触发 View 的左上角 (0,0) | 手指在 View 内部的位置 |
| event.rawX | 整个屏幕左上角 (0,0) | 手指在屏幕上的绝对位置 |
1.实现 view 的跟随手指移动,或者说跟随手指滑动的几种方式:
1)通过 setx,setY 或者通过setTranslationX,Y 的方式实现 view 的移动效果。
缺点不会改变 onlayout 的值。效率高。
2)通过触发 layout 方法进行重新的排版,来进行 view 的移动效果。会改变layout 的值,触发重新绘制。
3)通过设置 margin 值进行 view 的位置改变。 类似于layout 。
| 方式 | 原理 | 特点 | 是否真正改变 layout |
|---|---|---|---|
| 1. setTranslationX/Y() | 平移绘制位置 | 最简单、性能好 | ❌ 不改变 layout |
| 2. setX/Y() | 修改 x/y 属性(底层也是 translation) | 类似 translationX,但操作更直观 | ❌ 不改变 layout |
| 3. layout() | 直接设置新的 layout 坐标 | 真正移动 View 的位置 | ✅ 改变 layout |
| 4. 使用 Offset 系列方法(offsetLeftAndRight/offsetTopAndBottom ) | 直接移动 View 的物理坐标 | 相对移动,适合小幅调整 | ✅ 改变 layout |
| 内容 | 结论 |
|---|---|
| 调用 setX()/ setY() | 只是设置了 View 的位置属性 |
| left/ top/ right/ bottom | 不会变化,因为没有重新 layout |
| x/ y属性 | ✅ 变化了,新的值 = 原 layout 位置 + translation |
针对 setx或者 translationx 这种跟随移动的流程。
1.首先在 down 的时候获取到父容器 view 左上角的坐标点到原点的偏移量。
2.在移动时,通过获取手指相对原点的坐标点 减去偏移量。获取到 view 在父容器 view 的位置信息。其实就是
获取到子 view 在父 view 的相对位置信息的移动信息。
3.设置对应的位置坐标。即可实现移动效果。
总结:
大白话:其实 setX,Y 就是设置相对于父容器view 的左上角点坐标的偏移量。以父容器左上角坐标为原点。进行移动。
代码:
Plain Text复制代码
class MoveView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : AppCompatTextView(context, attrs, defStyleAttr) {
private var offsetX = 0f
private var offSetY = 0f
private var downX = 0f
private var downY = 0f
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent?): Boolean {
//获取到view 触摸点 到原点的位置信息
/* val rawX = event?.rawX ?: 0f
val rawY = event?.rawY ?: 0f*/
//获取到移动的时间
when (event?.action) {
MotionEvent.ACTION_DOWN -> {
//这个地方的意思 是获取到 view 在父容器内部的坐标。也可以说是位置信息。
// downX = x
// downY = y
// event.rawX 是获取到 手指触摸点 距离顶部(0.0)的位置,x 是 view在父容器的位置。
//两者相减 获取到手指触摸点,到 view 的差值。其实就是获取到定点(0,0)到父容器左上角坐标的距离。
offsetX = event.rawX - x
offSetY = event.rawY - y
Log.e(
"",
"------- > difx ${event.rawY} ... x $x "
)
}
MotionEvent.ACTION_MOVE -> {
//这个地方是获取 view 在父容器内部的位置信息差值。那么这个差值存在吗?存在
//这个地方用于记录 在父容器中的移动距离,前提 downX是在 down 的时候获取的,相当于初始化一次
// val difX = x - downX// getTranslation(x+left) - downX:相对于父容器左上角的位置信息,减去 点击起始点的位置坐标。
// val difY = y - downY
// 错误想法
// x = rawX + difX
// y = rawY + difY
//这种方式和上面的差值结合,获取到的 difX始终都是0.因为刚赋值就计算差值。
//如果想要每次移动的差值。那么需要加载触发移动前,记录当前的位置点,然后在进行移动,
//这样在move 方法开始调用时,获取的 x,y是移动后的位置,down x,y是上一次移动前的位置信息。
//上述想法错误; 因为就算在移动前进行位置信息的记录,但是 记录的位置信息始终还是最新的位置一样(前提通过偏移量移动)
//因此想要获取每次的偏移量。还是需要通过raw 的位置坐标进行计算。因为只有 raw 能获取到 view 针对原点的左边信息。
//而getX,Y 是获取 view 针对父容器的坐标;无法获取到手指触摸的移动信息(坐标点)
//同时通过 event.x 也可以计算触摸移动的距离,但是该信息值针对 手指在 view 中的移动信息。
// downX = x//这个地方需要记录手指触摸的位置信息,使用 x 是不正确的 getX = translation(left +x),是无法获取到手指真实的移动情况。
// downY = y
//获取到手指的坐标,减去两者的插值。那么这时候就获取到了 view 在父容器中的位置信息。
//思想就是获取到 子view 在父容器中的位置。但是:无法获取到子 view 在移动时到父容器的位置。
//因此。借助于获取触摸点到原点的位置信息,以及 子 view 初始化位置信息,获取到父容器左上角坐标到原点的距离。
//通过该固定的距离信息(就是偏移量)。在手指移动时获取到新的坐标信息,减去偏移量 。
// 就得到了实际在父容器内部的坐标点了。然后重置坐标信息,就能实现移动了。
// x = setTranslationX(x - mLeft):在移动时要将位置默认到父容器的左上角位置,这样移动才不会在例如最右侧的基础上进行偏移,
//导致直接偏移出屏幕。
x = event.rawX - offsetX //event.rawX - offsetX - left
y = event.rawY - offSetY // event.rawY - offSetY - top
//这个移动是相对于上次的位置进行偏移(如果处于父容器的原点,就是相对于原点进行移动)
//2. 如果设置的起始点是在最右侧那么将在最右侧的基础上再次往右侧偏移。
//移动后点击位置跟随 view,但是 layout 位置没有改变。如果想要实现 layout 的变化,可以通过 layout出发位置信息改变
// translationX = event.rawX - offsetX
// translationY = event.rawY - offSetY
// ----- > event .... 308.0 ..... 217.0 。。。 left 0 ... top 0
Log.e(
"",
"----- > event .... 。。。 left $left ... top $top ... right $right bottom $bottom"
)
return true
}
}
return super.onTouchEvent(event)
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
// Log.e("","----- > event .... left $left ... top $top")
}
}