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
74 changes: 74 additions & 0 deletions BLOCK_OUTSIDE_CLICKS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Block Outside Clicks Feature

This document describes the new "Block Outside Clicks" feature that makes app-level floating windows behave like Dialog, preventing interactions with content behind the floating window.

## Usage

### Builder Pattern
```kotlin
// Create a floating window with block outside clicks enabled
private val modalFx by createFx {
setLayout(R.layout.item_floating)
setBlockOutsideClicks(true) // Enable blocking outside clicks
setGravity(FxGravity.CENTER)
build().toControl(this@YourActivity)
}

// Show the modal floating window
modalFx.show()
```

### Runtime Configuration
```kotlin
// Enable blocking outside clicks at runtime
floatingWindowControl.configControl.setBlockOutsideClicks(true)

// Disable blocking outside clicks at runtime
floatingWindowControl.configControl.setBlockOutsideClicks(false)
```

### Global FloatingX Installation
```kotlin
FloatingX.install {
setContext(context)
setLayout(R.layout.your_floating_layout)
setScopeType(FxScopeType.APP) // Only works with APP scope
setBlockOutsideClicks(true)
}.show()
```

## Features

- **Dialog-like behavior**: When enabled, the floating window prevents touches from reaching the content behind it
- **Touch area detection**: Only touches outside the floating window bounds are blocked
- **Runtime toggle**: Can be enabled/disabled at runtime using the config control
- **APP-level only**: This feature only works with app-level floating windows (`FxScopeType.APP`), not system-level windows
- **Efficient implementation**: Uses touch interception at the DecorView level for optimal performance

## Important Notes

1. **APP-level only**: This feature only works with app-level floating windows. System floating windows cannot block touches to other applications.

2. **Proper cleanup**: The touch interception is automatically cleaned up when the floating window is hidden or destroyed.

3. **Performance**: The implementation is lightweight and doesn't create additional overlay views.

## Demo

Check out the `BlockOutsideClicksTestActivity` in the demo app to see this feature in action. The demo shows:

- A background with clickable buttons
- A modal floating window that blocks clicks to the background
- A normal floating window for comparison
- Toggle functionality to enable/disable the blocking behavior

## Example Output

When the feature is enabled:
- Clicking on the floating window works normally
- Clicking anywhere else on the screen is blocked and doesn't reach the Activity's views
- The floating window behaves like a modal dialog

When the feature is disabled:
- Normal floating window behavior
- Background content remains interactive
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

- 支持 **JetPack Compose**
- 支持 **浮窗半隐藏模式**
- 支持 **阻挡外部点击**,类似Dialog的模态行为;
- 支持 **自定义隐藏显示动画**;
- 支持 **多指触摸**,精准决策触摸手势;
- 支持 自定义是否保存历史位置及还原;
Expand Down
1 change: 1 addition & 0 deletions README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

- Supports **JetPack Compose**
- Supports **semi-hidden floating window mode**
- Supports **blocking outside clicks**, similar to Dialog modal behavior;
- Supports **custom hide/show animations**;
- Supports **multi-touch**, precise touch gesture recognition;
- Supports custom history position saving and restoration;
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@
android:configChanges="keyboard|orientation|keyboardHidden|screenSize|smallestScreenSize|screenLayout|locale|navigation|fontScale|mcc|mnc|uiMode" />
<activity android:name=".test.BlackActivity" />
<activity android:name=".test.MultipleFxActivity" />
<activity
android:name=".test.BlockOutsideClicksTestActivity"
android:configChanges="keyboard|orientation|keyboardHidden|screenSize|smallestScreenSize|screenLayout|locale|navigation|fontScale|mcc|mnc|uiMode" />

<activity android:name=".TestActivity" />
<activity
Expand Down
14 changes: 14 additions & 0 deletions app/src/main/java/com/petterp/floatingx/app/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,23 @@ class MainActivity : AppCompatActivity() {
.setCardBackgroundColor(Color.GREEN)
}
}
addItemView("显示Activity悬浮窗-阻挡外部点击(类似Dialog)") {
activityFx.configControl.setBlockOutsideClicks(true)
activityFx.show()
activityFx.updateViewContent {
it.setText(R.id.tvItemFx, "阻挡外部")
it.getView<CardView>(R.id.cardItemFx).setCardBackgroundColor(Color.RED)
}
}
addItemView("关闭阻挡外部点击") {
activityFx.configControl.setBlockOutsideClicks(false)
}
addItemView("进入测试页面") {
TestActivity::class.java.start(this@MainActivity)
}
addItemView("进入阻挡外部点击测试页面") {
com.petterp.floatingx.app.test.BlockOutsideClicksTestActivity::class.java.start(this@MainActivity)
}
addItemView("进入system浮窗测试页面") {
SystemActivity::class.java.start(this@MainActivity)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package com.petterp.floatingx.app.test

import android.graphics.Color
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.petterp.floatingx.app.R
import com.petterp.floatingx.app.addItemView
import com.petterp.floatingx.app.addLinearLayout
import com.petterp.floatingx.app.addNestedScrollView
import com.petterp.floatingx.app.createLinearLayoutToParent
import com.petterp.floatingx.app.simple.FxAnimationImpl
import com.petterp.floatingx.assist.FxGravity
import com.petterp.floatingx.util.createFx

/**
* Test activity for block outside clicks functionality
* @author petterp
*/
class BlockOutsideClicksTestActivity : AppCompatActivity() {

private var isModalBlocking = true // Track the current state

// Create a floating window with block outside clicks enabled by default
private val modalFx by createFx {
setLayout(R.layout.item_floating)
setBlockOutsideClicks(true)
setGravity(FxGravity.CENTER)
setAnimationImpl(FxAnimationImpl())
setEnableAnimation(true)
setEnableLog(true, "modal_fx")
build().toControl(this@BlockOutsideClicksTestActivity)
}

// Create a normal floating window for comparison
private val normalFx by createFx {
setLayout(R.layout.item_floating)
setGravity(FxGravity.TOP_OR_LEFT)
setOffsetXY(50f, 100f)
setEnableLog(true, "normal_fx")
build().toControl(this@BlockOutsideClicksTestActivity)
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
createLinearLayoutToParent {
setBackgroundColor(Color.LTGRAY)
addNestedScrollView {
addLinearLayout {
addItemView("这是一个可点击的按钮") {
Toast.makeText(
this@BlockOutsideClicksTestActivity,
"背景按钮被点击了!",
Toast.LENGTH_SHORT
).show()
}
addItemView("显示模态浮窗(阻挡外部点击)") {
modalFx.show()
modalFx.updateViewContent { holder ->
holder.setText(R.id.tvItemFx, "模态")
}
}
addItemView("显示普通浮窗") {
normalFx.show()
normalFx.updateViewContent { holder ->
holder.setText(R.id.tvItemFx, "普通")
}
}
addItemView("隐藏模态浮窗") {
modalFx.hide()
}
addItemView("隐藏普通浮窗") {
normalFx.hide()
}
addItemView("切换模态浮窗的阻挡模式") {
// Toggle the block outside clicks setting
isModalBlocking = !isModalBlocking
modalFx.configControl.setBlockOutsideClicks(isModalBlocking)
val message = if (isModalBlocking) {
"模态浮窗现在阻挡外部点击"
} else {
"模态浮窗现在允许外部点击"
}
Toast.makeText(this@BlockOutsideClicksTestActivity, message, Toast.LENGTH_SHORT).show()
}
addItemView("另一个背景可点击按钮") {
Toast.makeText(
this@BlockOutsideClicksTestActivity,
"第二个背景按钮被点击了!",
Toast.LENGTH_SHORT
).show()
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,14 @@ class ScopeActivity : AppCompatActivity() {
}
}
}
addItemView("启用阻挡外部点击") {
scopeFx.configControl.setBlockOutsideClicks(true)
Toast.makeText(this@ScopeActivity, "已启用阻挡外部点击", Toast.LENGTH_SHORT).show()
}
addItemView("禁用阻挡外部点击") {
scopeFx.configControl.setBlockOutsideClicks(false)
Toast.makeText(this@ScopeActivity, "已禁用阻挡外部点击", Toast.LENGTH_SHORT).show()
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ buildscript {
plugins {
alias(libs.plugins.vanniketch.maven.publish) apply false
alias(libs.plugins.compose.compiler) apply false
alias(libs.plugins.android.lirary) apply false
alias(libs.plugins.android.library) apply false
alias(libs.plugins.android.application) apply false
alias(libs.plugins.jetbrains.kotlin.android) apply false
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ abstract class FxBasisHelper {
@JvmField
internal var enableAssistLocation: Boolean = false

@JvmField
internal var enableBlockOutsideClicks: Boolean = false

@JvmField
internal var iFxTouchListener: IFxTouchListener? = null

Expand Down Expand Up @@ -222,6 +225,7 @@ abstract class FxBasisHelper {
enableSaveDirection = this@Builder.enableSaveDirection
enableClickListener = this@Builder.enableClickListener
enableAssistLocation = assistLocation != null
enableBlockOutsideClicks = this@Builder.enableBlockOutsideClicks

enableDebugLog = this@Builder.enableDebugLog
fxLogTag = this@Builder.fxLogTag
Expand Down Expand Up @@ -292,6 +296,17 @@ abstract class FxBasisHelper {
return this as T
}

/**
* 设置是否阻止点击浮窗外部区域,类似Dialog的效果
* 当启用后,浮窗外部区域将无法响应点击事件,只有浮窗本身可以交互
* 注意:此功能仅对APP级别的浮窗有效,系统级浮窗不支持此功能
* @param isEnable 默认false
*/
fun setBlockOutsideClicks(isEnable: Boolean): T {
this.enableBlockOutsideClicks = isEnable
return this as T
}

/** 设置边缘吸附方向,默认 [FxAdsorbDirection.LEFT_OR_RIGHT] */
fun setEdgeAdsorbDirection(direction: FxAdsorbDirection): T {
this.edgeAdsorbDirection = direction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ open class FxBasicConfigProvider<F : FxBasisHelper, P : IFxPlatformProvider<F>>(
internalView?.moveToEdge()
}

override fun setBlockOutsideClicks(isEnable: Boolean) {
helper.enableBlockOutsideClicks = isEnable
// Notify the platform provider to update the outside click blocking
if (p is com.petterp.floatingx.imp.app.FxAppPlatformProvider) {
p.updateBlockOutsideClicks()
}
}

override fun setTouchListener(listener: IFxTouchListener) {
helper.iFxTouchListener = listener
}
Expand Down
Loading