Browse Source

feat(悬浮窗): 添加窗口大小调整功能并改进资源清理

- 新增右下角拖拽调整窗口大小的功能
- 改进onDestroy方法中的资源清理逻辑
- 添加窗口权限检查和安全异常处理
- 优化UI缩放和布局参数管理
newdev_shunjiawei
lxm 9 months ago
parent
commit
fab60b68ba
  1. 3
      android/app/src/main/kotlin/com/saitong/voitrans/FloatingWindowService.kt
  2. 252
      local_plugins/native_plugin/android/src/main/kotlin/com/example/native_plugin/FloatingWindowService.kt

3
android/app/src/main/kotlin/com/saitong/voitrans/FloatingWindowService.kt

@ -370,8 +370,9 @@ class FloatingWindowService : Service() {
}
override fun onDestroy() {
super.onDestroy()
floatingView?.let { windowManager?.removeView(it) }
serviceInstance = null
methodChannel = null
super.onDestroy()
}
}

252
local_plugins/native_plugin/android/src/main/kotlin/com/example/native_plugin/FloatingWindowService.kt

@ -11,6 +11,7 @@ import android.os.Build
import android.os.Handler
import android.os.IBinder
import android.os.Looper
import android.provider.Settings
import android.text.TextUtils
import android.util.TypedValue
import android.view.Gravity
@ -59,9 +60,13 @@ class FloatingWindowService : Service() {
private var expandedView: View? = null
private var minimizedTextView: TextView? = null
private var closeButton: TextView? = null
private var expandedContainer: LinearLayout? = null
private var expandedContainer: ViewGroup? = null
private var expandedContentLayout: LinearLayout? = null
private var resizeHandleView: View? = null
private var baseExpandedWidth: Int = 0
private var lastEffectiveWidth: Int = 0
private var lastEffectiveHeight: Int = 0
private var uiScale: Float = 1.0f
private val backgroundBaseColor: Int = Color.argb(150, 40, 40, 40)
private val uiHandler: Handler = Handler(Looper.getMainLooper())
@ -90,14 +95,36 @@ class FloatingWindowService : Service() {
override fun onDestroy() {
stopAllLoading()
uiHandler.removeCallbacksAndMessages(null)
removeFloatingWindow()
sourceTextView = null
translatedTextView = null
sourceLoadingView = null
translatedLoadingView = null
languageInfoView = null
minimizedView = null
expandedView = null
minimizedTextView = null
closeButton = null
resizeHandleView = null
expandedContentLayout = null
expandedContainer = null
layoutParams = null
windowManager = null
serviceInstance = null
methodChannel = null
super.onDestroy()
}
private fun createFloatingWindow() {
windowManager = getSystemService(WINDOW_SERVICE) as? WindowManager ?: return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {
notifyStateChanged("hidden")
stopSelf()
return
}
floatingView = createFloatingLayout()
initViews()
setupClickListeners()
@ -122,7 +149,16 @@ class FloatingWindowService : Service() {
y = dp(100)
}
windowManager?.addView(floatingView, layoutParams)
try {
windowManager?.addView(floatingView, layoutParams)
} catch (_: SecurityException) {
notifyStateChanged("hidden")
stopSelf()
return
} catch (_: Exception) {
stopSelf()
return
}
updateViewState(true)
}
@ -202,7 +238,7 @@ class FloatingWindowService : Service() {
}
private fun createExpandedView(): View {
val container = LinearLayout(this)
val root = FrameLayout(this)
val maxWidth = dp(360)
val minWidth = dp(280)
val horizontalMargins = dp(8) * 2
@ -212,19 +248,28 @@ class FloatingWindowService : Service() {
val lp = FrameLayout.LayoutParams(targetWidth, ViewGroup.LayoutParams.WRAP_CONTENT)
val margin = dp(8)
lp.setMargins(margin, margin, margin, margin)
container.layoutParams = lp
container.orientation = LinearLayout.VERTICAL
container.setPadding(dp(16), dp(16), dp(16), dp(16))
container.visibility = View.GONE
container.elevation = dp(12).toFloat()
expandedContainer = container
root.layoutParams = lp
root.setPadding(0, 0, 0, 0)
root.visibility = View.GONE
root.elevation = dp(12).toFloat()
expandedContainer = root
val bg = GradientDrawable().apply {
setColor(backgroundBaseColor)
cornerRadius = dp(16).toFloat()
setStroke(dp(1), Color.argb(46, 255, 255, 255))
}
container.background = bg
root.background = bg
val contentLayout = LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
setPadding(dp(16), dp(16), dp(16), dp(16))
layoutParams = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
}
expandedContentLayout = contentLayout
val titleRow = LinearLayout(this).apply {
orientation = LinearLayout.HORIZONTAL
@ -303,13 +348,101 @@ class FloatingWindowService : Service() {
titleRow.addView(spacer, spacerLp)
titleRow.addView(closeButton)
container.addView(titleRow)
container.addView(sourceTextView)
container.addView(sourceLoadingView)
container.addView(translatedTextView)
container.addView(translatedLoadingView)
contentLayout.addView(titleRow)
contentLayout.addView(sourceTextView)
contentLayout.addView(sourceLoadingView)
contentLayout.addView(translatedTextView)
contentLayout.addView(translatedLoadingView)
root.addView(contentLayout)
val handleSize = dp(30)
val resizeHandle = TextView(this).apply {
text = "⤢"
setTextColor(Color.argb(190, 255, 255, 255))
setTextSize(TypedValue.COMPLEX_UNIT_SP, 16f)
gravity = Gravity.CENTER
background = null
setPadding(0, 0, 0, 0)
scaleX = -1f
}
resizeHandleView = resizeHandle
val handleLp = FrameLayout.LayoutParams(handleSize, handleSize, Gravity.BOTTOM or Gravity.END).apply {
setMargins(0, 0, 0, 0)
}
root.addView(resizeHandle, handleLp)
resizeHandle.setOnTouchListener(object : View.OnTouchListener {
private var downRawX: Float = 0f
private var downRawY: Float = 0f
private var startWidth: Int = 0
private var startHeight: Int = 0
override fun onTouch(v: View, event: MotionEvent): Boolean {
val expanded = expandedContainer ?: return false
val rootLp = (expanded.layoutParams as? FrameLayout.LayoutParams) ?: return false
val size = v.width.takeIf { it > 0 } ?: return false
val localX = event.x
val localY = event.y
if (!isInBottomRightTriangle(localX, localY, size.toFloat())) {
return false
}
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
downRawX = event.rawX
downRawY = event.rawY
startWidth = if (rootLp.width > 0) rootLp.width else expanded.width.takeIf { it > 0 } ?: baseExpandedWidth
val resolvedHeight = if (rootLp.height > 0 && rootLp.height != ViewGroup.LayoutParams.WRAP_CONTENT) {
rootLp.height
} else {
expanded.height.takeIf { it > 0 } ?: dp(220)
}
rootLp.height = resolvedHeight
expanded.layoutParams = rootLp
startHeight = resolvedHeight
baseExpandedWidth = startWidth
return true
}
MotionEvent.ACTION_MOVE -> {
val dx = (event.rawX - downRawX).toInt()
val dy = (event.rawY - downRawY).toInt()
val minW = dp(240)
val maxW = (resources.displayMetrics.widthPixels - dp(8) * 2).coerceAtLeast(minW)
val minH = dp(140)
val maxH = (resources.displayMetrics.heightPixels - dp(12) * 2).coerceAtLeast(minH)
val nextW = (startWidth + dx).coerceIn(minW, maxW)
val nextH = (startHeight + dy).coerceIn(minH, maxH)
if (rootLp.width != nextW || rootLp.height != nextH) {
rootLp.width = nextW
rootLp.height = nextH
expanded.layoutParams = rootLp
baseExpandedWidth = nextW
floatingView?.post {
val wm = windowManager ?: return@post
val floatingLp = layoutParams ?: return@post
val (effectiveWidth, effectiveHeight) = getEffectiveFloatingSize()
val maxX = resources.displayMetrics.widthPixels - effectiveWidth
val maxY = resources.displayMetrics.heightPixels - effectiveHeight
if (maxX > 0) floatingLp.x = floatingLp.x.coerceIn(0, maxX)
if (maxY > 0) floatingLp.y = floatingLp.y.coerceIn(0, maxY)
wm.updateViewLayout(floatingView, floatingLp)
}
}
return true
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_CANCEL -> return true
else -> return false
}
}
})
applyUiScale(uiScale)
return container
return root
}
private fun initViews() {
@ -356,6 +489,9 @@ class FloatingWindowService : Service() {
) {
return false
}
if (!isMinimized && isTouchingView(resizeHandleView, event.rawX, event.rawY)) {
return false
}
dragging = false
scaling = false
@ -363,7 +499,17 @@ class FloatingWindowService : Service() {
}
MotionEvent.ACTION_POINTER_DOWN -> {
if (event.pointerCount >= 2) {
if (!isMinimized && event.pointerCount >= 2) {
expandedContainer?.let { expanded ->
val expandedLp = expanded.layoutParams as? FrameLayout.LayoutParams
if (expandedLp != null && expandedLp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
val lockedHeight = expanded.height.takeIf { it > 0 } ?: dp(220)
expandedLp.height = lockedHeight
expanded.layoutParams = expandedLp
}
val width = (expanded.layoutParams as? FrameLayout.LayoutParams)?.width ?: 0
if (width > 0) baseExpandedWidth = width
}
scaling = true
dragging = false
initialSpan = pointerSpan(event)
@ -379,18 +525,25 @@ class FloatingWindowService : Service() {
if (initialSpan > 0f && span > 0f) {
val next = (initialScale * (span / initialSpan)).coerceIn(0.85f, 1.4f)
setUiScale(next)
val (effectiveWidth, effectiveHeight) = getEffectiveFloatingSize()
val maxX = resources.displayMetrics.widthPixels - effectiveWidth
val maxY = resources.displayMetrics.heightPixels - effectiveHeight
if (maxX > 0) lp.x = lp.x.coerceIn(0, maxX)
if (maxY > 0) lp.y = lp.y.coerceIn(0, maxY)
wm.updateViewLayout(floatingView, lp)
}
return true
}
val dx = (event.rawX - initialTouchX).toInt()
val dy = (event.rawY - initialTouchY).toInt()
if (!dragging && (kotlin.math.abs(dx) > threshold || kotlin.math.abs(dy) > threshold)) {
dragging = true
}
val maxX =
resources.displayMetrics.widthPixels - (floatingView?.width ?: 0)
val maxY =
resources.displayMetrics.heightPixels - (floatingView?.height ?: 0)
val (effectiveWidth, effectiveHeight) = getEffectiveFloatingSize()
val maxX = resources.displayMetrics.widthPixels - effectiveWidth
val maxY = resources.displayMetrics.heightPixels - effectiveHeight
var nextX = initialX + dx
var nextY = initialY + dy
if (maxX > 0) nextX = nextX.coerceIn(0, maxX)
@ -549,26 +702,70 @@ class FloatingWindowService : Service() {
if (baseExpandedWidth <= 0) return
languageInfoView?.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f * scale)
languageInfoView?.setPadding(
(dp(10) * scale).toInt().coerceIn(dp(6), dp(16)),
(dp(6) * scale).toInt().coerceIn(dp(4), dp(12)),
(dp(10) * scale).toInt().coerceIn(dp(6), dp(16)),
(dp(6) * scale).toInt().coerceIn(dp(4), dp(12))
)
sourceTextView?.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f * scale)
translatedTextView?.setTextSize(TypedValue.COMPLEX_UNIT_SP, 15f * scale)
sourceLoadingView?.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f * scale)
translatedLoadingView?.setTextSize(TypedValue.COMPLEX_UNIT_SP, 15f * scale)
val buttonTextSizeSp = (18f * scale).coerceIn(14f, 22f)
closeButton?.setTextSize(TypedValue.COMPLEX_UNIT_SP, buttonTextSizeSp)
val closeTextSizeSp = (18f * scale).coerceIn(14f, 22f)
closeButton?.setTextSize(TypedValue.COMPLEX_UNIT_SP, closeTextSizeSp)
val closeHPad = (dp(8) * scale).toInt().coerceIn(dp(6), dp(16))
val closeVPad = (dp(8) * scale).toInt().coerceIn(dp(4), dp(12))
closeButton?.setPadding(closeHPad, closeVPad, closeHPad, closeVPad)
val padding = (dp(16) * scale).toInt().coerceIn(dp(12), dp(24))
container.setPadding(padding, padding, padding, padding)
container.setPadding(0, 0, 0, 0)
val contentPadding = (dp(16) * scale).toInt().coerceIn(dp(12), dp(24))
expandedContentLayout?.setPadding(contentPadding, contentPadding, contentPadding, contentPadding)
val maxLines = if (scale >= 1.15f) 4 else 3
sourceTextView?.maxLines = maxLines
translatedTextView?.maxLines = maxLines
}
private fun getEffectiveFloatingSize(): Pair<Int, Int> {
val view = floatingView
if (view != null) {
val w = view.width
val h = view.height
if (w > 0 && h > 0) {
lastEffectiveWidth = w
lastEffectiveHeight = h
return w to h
}
}
layoutParams?.let { lp ->
val w = lp.width
val h = lp.height
if (w > 0 && h > 0) {
lastEffectiveWidth = w
lastEffectiveHeight = h
return w to h
}
}
(expandedContainer?.layoutParams as? FrameLayout.LayoutParams)?.let { lp ->
val w = lp.width
val h = lp.height
if (w > 0 && h > 0) {
lastEffectiveWidth = w
lastEffectiveHeight = h
return w to h
}
}
if (lastEffectiveWidth <= 0) lastEffectiveWidth = dp(280)
if (lastEffectiveHeight <= 0) lastEffectiveHeight = dp(220)
return lastEffectiveWidth to lastEffectiveHeight
}
private fun startSourceLoading() {
val view = sourceLoadingView ?: return
if (view.visibility == View.VISIBLE && sourceDotsRunnable != null) return
@ -635,4 +832,9 @@ class FloatingWindowService : Service() {
val dy = event.getY(0) - event.getY(1)
return kotlin.math.sqrt(dx * dx + dy * dy)
}
private fun isInBottomRightTriangle(x: Float, y: Float, size: Float): Boolean {
if (x < 0f || y < 0f || x > size || y > size) return false
return x >= size - y
}
}

Loading…
Cancel
Save