Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions app/src/main/java/com/petterp/floatingx/app/test/ScopeActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,24 @@ class ScopeActivity : AppCompatActivity() {
addItemView("允许触摸事件(允许拖动)-默认允许") {
scopeFx.configControl.setDisplayMode(FxDisplayMode.Normal)
}
addItemView("长按移动模式(解决内容滑动冲突)") {
scopeFx.configControl.setDisplayMode(FxDisplayMode.LongPressMove)
}
addItemView("测试长按移动模式 - 带可点击子视图") {
scopeFx.configControl.setDisplayMode(FxDisplayMode.LongPressMove)
scopeFx.updateView(R.layout.item_floating_clickable)
// Add click listeners to test immediate response
scopeFx.setViewLifecycle(object : com.petterp.floatingx.listener.IFxViewLifecycle {
override fun initView(holder: com.petterp.floatingx.view.FxViewHolder) {
holder.getView<android.widget.Button>(R.id.btnClickMe)?.setOnClickListener {
Toast.makeText(this@ScopeActivity, "Button clicked immediately!", Toast.LENGTH_SHORT).show()
}
holder.getView<android.widget.TextView>(R.id.tvClickable)?.setOnClickListener {
Toast.makeText(this@ScopeActivity, "Clickable text clicked!", Toast.LENGTH_SHORT).show()
}
}
})
}
addItemView("隐藏悬浮窗") {
scopeFx.hide()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@ class SimpleRvActivity : AppCompatActivity() {
if (customAdapter.sum < 0) customAdapter.sum = 1
customAdapter.notifyDataSetChanged()
}
addItemView("设置长按移动模式") {
FloatingX.controlOrNull(TAG)?.configControl?.setDisplayMode(FxDisplayMode.LongPressMove)
Toast.makeText(this@SimpleRvActivity, "长按移动模式:点击Header或RV即时响应,长按空白区域移动", Toast.LENGTH_LONG).show()
}
addItemView("设置普通移动模式") {
FloatingX.controlOrNull(TAG)?.configControl?.setDisplayMode(FxDisplayMode.Normal)
}
}
}
}
Expand Down Expand Up @@ -97,6 +104,11 @@ class SimpleRvActivity : AppCompatActivity() {
500,
200,
)
// Make header clickable to test immediate response in LongPressMove mode
isClickable = true
setOnClickListener {
Toast.makeText(context, "Header clicked immediately!", Toast.LENGTH_SHORT).show()
}
}
val rv = RecyclerView(context).apply {
layoutParams = ViewGroup.LayoutParams(
Expand Down
41 changes: 41 additions & 0 deletions app/src/main/res/layout/item_floating_clickable.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="200dp"
android:layout_height="150dp"
android:background="@android:color/holo_blue_light"
android:orientation="vertical"
android:padding="10dp">

<!-- Clickable button that should respond immediately -->
<Button
android:id="@+id/btnClickMe"
android:layout_width="match_parent"
android:layout_height="40dp"
android:text="Click Me!"
android:textSize="12sp" />

<!-- Non-clickable area that should require long press for movement -->
<TextView
android:id="@+id/tvNonClickable"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="@android:color/white"
android:gravity="center"
android:layout_marginTop="10dp"
android:text="Long press here to move"
android:textSize="12sp" />

<!-- Another clickable element -->
<TextView
android:id="@+id/tvClickable"
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="@android:color/holo_green_light"
android:clickable="true"
android:gravity="center"
android:layout_marginTop="5dp"
android:text="Clickable Text"
android:textSize="12sp" />

</LinearLayout>
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,14 @@ enum class FxDisplayMode {
ClickOnly,

// 展示模式:只用于展示,不响应任何事件不能移动
DisplayOnly;
DisplayOnly,

// 长按移动模式:长按后可移动,解决内容滑动冲突
LongPressMove;

val canMove: Boolean
get() = this == Normal

val canLongPressMove: Boolean
get() = this == LongPressMove
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,15 @@ class FxViewTouchHelper : FxViewBasicHelper() {
private var isEnableClick = true
private var mLastTouchDownTime = 0L
private var touchDownId = INVALID_TOUCH_ID
private var isLongPressActivated = false
private var hasChildWithClickEvents = false

@SuppressLint("ClickableViewAccessibility")
override fun initConfig(parentView: FxBasicContainerView) {
super.initConfig(parentView)
scaledTouchSlop = ViewConfiguration.get(parentView.context).scaledTouchSlop.toFloat()
// Check if any child view has click events for LongPressMove mode
hasChildWithClickEvents = checkIfChildHasClickEvents(parentView)
resetConfig()
}

Expand Down Expand Up @@ -57,7 +61,18 @@ class FxViewTouchHelper : FxViewBasicHelper() {

MotionEvent.ACTION_MOVE -> {
if (!isCurrentPointerId(event)) return false
return config.displayMode.canMove && canInterceptEvent(event)
// 长按移动模式:如果有子视图设置了点击事件,则浮窗优先拦截所有事件进行移动
return if (config.displayMode.canLongPressMove) {
if (hasChildWithClickEvents) {
// 有子视图点击事件时,浮窗拦截所有事件
canInterceptEvent(event)
} else {
// 没有子视图点击事件时,使用长按激活
isLongPressActivated && canInterceptEvent(event)
}
} else {
config.displayMode.canMove && canInterceptEvent(event)
}
}

MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
Expand Down Expand Up @@ -105,8 +120,28 @@ class FxViewTouchHelper : FxViewBasicHelper() {
private fun touchToMove(event: MotionEvent) {
if (!isCurrentPointerId(event)) return
checkClickState(event)
// Check if movement is allowed
val canMoveNow = if (config.displayMode.canLongPressMove) {
if (hasChildWithClickEvents) {
// 有子视图点击事件时,浮窗优先移动
true
} else {
// 没有子视图点击事件时,使用长按激活
val timeSinceDown = System.currentTimeMillis() - mLastTouchDownTime
if (timeSinceDown >= TOUCH_CLICK_LONG_TIME && !isLongPressActivated) {
isLongPressActivated = true
// Trigger haptic feedback for long press
basicView?.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
config.fxLog.d("fxView -> long press activated for movement")
}
isLongPressActivated
}
} else {
config.displayMode.canMove
}

// 不支持move时return掉
if (!config.displayMode.canMove) return
if (!canMoveNow) return
basicView?.onTouchMove(event)
val x = basicView?.x ?: 0f
val y = basicView?.y ?: 0f
Expand All @@ -115,7 +150,9 @@ class FxViewTouchHelper : FxViewBasicHelper() {
}

private fun touchCancel(event: MotionEvent) {
if (config.enableEdgeAdsorption && config.displayMode.canMove) basicView?.moveToEdge()
val canMoveForEdge = config.displayMode.canMove ||
(config.displayMode.canLongPressMove && (hasChildWithClickEvents || isLongPressActivated))
if (config.enableEdgeAdsorption && canMoveForEdge) basicView?.moveToEdge()
basicView?.onTouchCancel(event)
config.iFxTouchListener?.onUp()
performClickAction()
Expand All @@ -131,7 +168,8 @@ class FxViewTouchHelper : FxViewBasicHelper() {
basicView?.postDelayed({ isEnableClick = true }, config.clickTime)
}
config.iFxClickListener?.onClick(basicView)
} else if (diffTime >= TOUCH_CLICK_LONG_TIME) {
} else if (diffTime >= TOUCH_CLICK_LONG_TIME && !config.displayMode.canLongPressMove) {
// Only trigger long click listener if not in long press move mode (to avoid conflicts)
val isHandle = config.iFxLongClickListener?.onLongClick(basicView) ?: false
if (isHandle) basicView?.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
}
Expand All @@ -156,7 +194,39 @@ class FxViewTouchHelper : FxViewBasicHelper() {
isClickEvent = false
mLastTouchDownTime = 0L
touchDownId = INVALID_TOUCH_ID
isLongPressActivated = false
}

private fun hasMainPointerId() = touchDownId != INVALID_TOUCH_ID

/**
* Check if any child view in the floating window has click events set
* This determines whether the floating window should prioritize movement in LongPressMove mode
*/
private fun checkIfChildHasClickEvents(container: FxBasicContainerView): Boolean {
val childView = container.childView ?: return false
return hasClickEventsRecursive(childView)
}

/**
* Recursively check if any view or its descendants have click events
*/
private fun hasClickEventsRecursive(view: android.view.View): Boolean {
// Check if this view has click events
if (view.isClickable || view.hasOnClickListeners()) {
return true
}

// If this is a ViewGroup, check its children
if (view is android.view.ViewGroup) {
for (i in 0 until view.childCount) {
val child = view.getChildAt(i)
if (hasClickEventsRecursive(child)) {
return true
}
}
}

return false
}
}