Android 拖拽布局DragLayout
class DraggableFrameLayout @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null,
) : FrameLayout(context, attrs) {
enum class DragState {
Draging, Idel
}
interface OnDragListener {
fun onDragStart(){}
fun onDragEnd(){}
fun onDragStageChanged(state: DragState){}
}
private val observable = XObservableImpl<OnDragListener>()
private var downX = 0f
private var downY = 0f
private var mLastX = 0f
private var mLastY = 0f
private val TAG = "DragableFrameLayout"
private val mSlop = ViewConfiguration.get(context).scaledTouchSlop
private var mDragging = false
set(value) {
field = value
LogUtil.d("drag:$value", TAG)
}
private var disableHorizontalDrag = false
private val edge = Rect()
init {
context.obtainStyledAttributes(attrs, R.styleable.DraggableFrameLayout).use {
disableHorizontalDrag = it.getBoolean(R.styleable.DraggableFrameLayout_disableHorizontalDrag, false)
it.getString(R.styleable.DraggableFrameLayout_edges)?.let { str ->
val edges = str.split(",").map { s -> s.toInt().dp }
edge.set(edges[0], edges[1], edges[2], edges[3])
}
}
}
fun addDragListener(listener: OnDragListener) {
observable.addListener(listener)
}
fun removeDragListener(listener: OnDragListener) {
observable.removeListener(listener)
}
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
handleEvent(ev)
return super.dispatchTouchEvent(ev)
}
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
return mDragging || super.onInterceptTouchEvent(ev)
}
private fun handleEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
LogUtil.d("ACTION_DOWN", TAG)
mLastX = event.rawX
mLastY = event.rawY
downX = mLastX
downY = mLastY
mDragging = false
}
MotionEvent.ACTION_MOVE -> {
val dx = event.rawX - mLastX
val dy = event.rawY - mLastY
mLastX = event.rawX
mLastY = event.rawY
if (!mDragging) {
val absX = abs(downX - event.rawX)
val absY = abs(downY - event.rawY)
LogUtil.d("ACTION_MOVE abx:$absX,aby:$absY,slop:$mSlop", TAG)
if (absY >= mSlop && absY > absX) {
mDragging = true
} else if (disableHorizontalDrag.not() && absX >= mSlop) {
mDragging = true
}
if (mDragging) {
dispatchDragStart()
}
}
if (mDragging) {
offsetLeftAndRight(resizeDx(dx))
offsetTopAndBottom(resizeDy(dy))
}
}
MotionEvent.ACTION_UP -> {
LogUtil.d("ACTION_UP", TAG)
if ((left + measuredWidth / 2f) > parentWidth / 2f) {
slideToRight()
} else {
slideToLeft()
}
dispatchDragEnd()
}
else -> {}
}
return mDragging
}
private fun dispatchDragEnd() {
observable.notify {
it.onDragStageChanged(DragState.Idel)
it.onDragEnd()
}
}
private fun dispatchDragStart() {
observable.notify {
it.onDragStageChanged(DragState.Draging)
it.onDragStart()
}
}
private fun slideToRight() {
LogUtil.d("slideToRight", TAG)
val layoutParams = layoutParams
var alignStart = true
if (layoutParams is LayoutParams) {
val flag = layoutParams.gravity and Gravity.HORIZONTAL_GRAVITY_MASK
alignStart = flag == Gravity.LEFT
} else if (layoutParams is ConstraintLayout.LayoutParams) {
alignStart = layoutParams.startToStart != ConstraintLayout.LayoutParams.UNSET
}
if (alignStart) {
animatedToLeft(left, parentWidth - measuredWidth - edge.left)
} else {
animateToRight(parentWidth - right, edge.right)
}
}
private fun slideToLeft() {
LogUtil.d("slide to Left", TAG)
val layoutParams = layoutParams
val start: Int = left
val end: Int = edge.left
var alignStart = true
if (layoutParams is LayoutParams) {
val flag = layoutParams.gravity and Gravity.HORIZONTAL_GRAVITY_MASK
alignStart = flag == Gravity.LEFT
} else if (layoutParams is ConstraintLayout.LayoutParams) {
alignStart = layoutParams.startToStart != ConstraintLayout.LayoutParams.UNSET
}
if (alignStart) {
animatedToLeft(start, end)
} else {
animateToRight(parentWidth - right, parentWidth - measuredWidth - edge.left)
}
}
private fun animateToRight(start: Int, end: Int) {
LogUtil.d("animateToRight:$start,$end", TAG)
val marginTop = top
val width = measuredWidth
val height = measuredHeight
val marginBottom = parentHeight - bottom
val lp = layoutParams
ValueAnimator.ofInt(start, end)
.apply {
duration = 350
addUpdateListener {
updateLayoutParams<MarginLayoutParams> {
this.rightMargin = it.animatedValue as Int
this.leftMargin = 0
this.bottomMargin = marginBottom
this.topMargin = marginTop
this.width = width
this.height = height
}
}
start()
}
}
private fun animatedToLeft(start: Int, end: Int) {
LogUtil.d("animatedToLeft:$start,$end", TAG)
val marginTop = top
val width = measuredWidth
val height = measuredHeight
val marginBottom = parentHeight - bottom
ValueAnimator.ofInt(start, end)
.apply {
duration = 350
addUpdateListener {
updateLayoutParams<MarginLayoutParams> {
this.leftMargin = it.animatedValue as Int
this.rightMargin = 0
this.bottomMargin = marginBottom
this.topMargin = marginTop
this.width = width
this.height = height
}
}
start()
}
}
private val parentWidth: Int
get() = (parent as View).measuredWidth
private val parentHeight: Int
get() = (parent as View).measuredHeight
private fun resizeDx(dx: Float): Int {
return MathUtils.clamp(dx, -(left - edge.left).toFloat(), (parentWidth - edge.right - right).toFloat()).toInt()
}
private fun resizeDy(dy: Float): Int {
return MathUtils.clamp(dy, -(top - edge.top).toFloat(), (parentHeight - bottom - edge.bottom).toFloat()).toInt()
}
}
作者:徐影魔
出处:https://www.cnblogs.com/xunevermore/p/17849138.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库