get view string

This commit is contained in:
best
2025-11-12 07:00:58 +08:00
parent 7506144410
commit 3587c1cad0
6 changed files with 261 additions and 12 deletions

View File

@@ -11,7 +11,10 @@ enum class ActionEvent {
QUERY_COMPANY_REGISTER("com.loveerror.bested.QUERY_COMPANY_REGISTER", "去全网查询",isDisplay = true),
QUERY_COMPANY_SEARCH_ACTION("com.loveerror.bested.QUERY_COMPANY_SEARCH_ACTION", "查询公司是否注册",isDisplay = true, menuWithText = listOf("companyNames")),
COORDINATE_CLICK("coordinate_click", "坐标点击", true),
COORDINATE_CLICK("coordinate_click", "获取坐标位置", true),
COORDINATE_CLICK_TEST("coordinate_click_test", "坐标点击测试", true, menuWithText = listOf("x", "y")),
UNKNOWN("com.loveerror.bested.UNKNOWN", "未知",isDisplay = false, enable = false);
@@ -40,5 +43,10 @@ enum class ActionEvent {
}
return UNKNOWN
}
// 新增辅助方法判断是否为打印当前页面事件
fun isPrintCurrentPage(event: ActionEvent): Boolean {
return event == PRINT_CURRENT_PAGE
}
}
}

View File

@@ -13,12 +13,21 @@ import android.widget.EditText
import android.widget.PopupMenu
import android.app.Activity
import android.content.ContextWrapper
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import android.widget.LinearLayout
import androidx.core.content.ContextCompat
import com.loveerror.bested.tool.CoordinateClickTool
import com.loveerror.bested.tool.CoordinateListener
import com.loveerror.bested.tool.CoordinateListenerImpl
import com.loveerror.bested.tool.SystemTool
class DragFloatingButton(context: Context) : View(context) {
// 添加坐标存储变量
private var lastClickX: Float = 0f
private var lastClickY: Float = 0f
private val paint = Paint().apply {
isAntiAlias = true
style = Paint.Style.FILL
@@ -32,6 +41,9 @@ class DragFloatingButton(context: Context) : View(context) {
private var originalY = 0
private var isDragging = false
// 在 DragFloatingButton.kt 中添加成员变量
private var dialogBroadcastReceiver: android.content.BroadcastReceiver? = null
init {
// 初始化时创建正确的 WindowManager.LayoutParams
this.layoutParams = WindowManager.LayoutParams(
@@ -40,8 +52,93 @@ class DragFloatingButton(context: Context) : View(context) {
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
PixelFormat.TRANSLUCENT
)
// 注册用于接收显示弹窗请求的广播
registerDialogReceiver()
}
// 添加注册广播接收器的方法
private fun registerDialogReceiver() {
dialogBroadcastReceiver = object : android.content.BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
"com.loveerror.bested.SHOW_VIEW_TREE_DIALOG" -> {
val viewTreeData = intent.getStringExtra("view_tree_data") ?: ""
showViewTreeDialog(viewTreeData)
}
}
}
}
val filter = IntentFilter("com.loveerror.bested.SHOW_VIEW_TREE_DIALOG")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.registerReceiver(dialogBroadcastReceiver, filter, Context.RECEIVER_NOT_EXPORTED)
} else {
ContextCompat.registerReceiver(
context,
dialogBroadcastReceiver,
filter,
ContextCompat.RECEIVER_NOT_EXPORTED
)
}
}
// 添加清理方法
fun cleanup() {
dialogBroadcastReceiver?.let {
try {
context.unregisterReceiver(it)
} catch (e: Exception) {
// 忽略异常
}
}
}
// 添加显示弹窗的方法
private fun showViewTreeDialog(data: String) {
// (context as? Activity)?.runOnUiThread {
//
// }
try {
val dialogBuilder = AlertDialog.Builder(context)
val editText = EditText(context).apply {
setText(data)
setTextIsSelectable(true)
movementMethod = ScrollingMovementMethod.getInstance()
setLines(15)
gravity = Gravity.TOP or Gravity.START
setBackgroundColor(Color.parseColor("#F0F0F0"))
}
val container = LinearLayout(context).apply {
orientation = LinearLayout.VERTICAL
setPadding(20, 20, 20, 20)
addView(editText)
}
val dialog = dialogBuilder
.setTitle("界面数据")
.setView(container)
.setPositiveButton("复制") { _, _ ->
SystemTool.copyToClipboard(context, data)
}
.setNegativeButton("关闭", null)
.create()
//
//.show()
// 设置对话框类型为系统级对话框
val window = dialog.window
window?.setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY)
dialog.show()
} catch (e: Exception) {
e.printStackTrace()
}
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
@@ -72,9 +169,16 @@ class DragFloatingButton(context: Context) : View(context) {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
startX = event.rawX
startY = event.rawY
// 记录点击坐标
lastClickX = startX
lastClickY = startY
// 确保 layoutParams 是 WindowManager.LayoutParams 类型
if (this.layoutParams is WindowManager.LayoutParams) {
layoutParams = this.layoutParams as WindowManager.LayoutParams
@@ -124,12 +228,17 @@ class DragFloatingButton(context: Context) : View(context) {
// 只在特定条件下移除覆盖层
if (!isDragging) {
val intent = android.content.Intent(ActionEvent.COORDINATE_CLICK.event).apply {
setPackage(context.packageName)
}
val coordinateListenerImpl =
CoordinateListenerImpl(event.x, event.y, intent, context = context)
CoordinateListenerImpl(event.x, event.y, intent, context = context, callback = fun(x: Float,y: Float){
// 记录点击坐标
lastClickX = x
lastClickY = y
})
coordinateClickTool.disableCoordinateClickMode(coordinateListener = coordinateListenerImpl)
@@ -146,16 +255,26 @@ class DragFloatingButton(context: Context) : View(context) {
private val coordinateClickTool = CoordinateClickTool(context, windowManager)
class FieldNameValue {
var name: String = ""
var value: String = ""
}
private fun showPopupMenu() {
val popup = PopupMenu(context, this)
// 添加菜单项 use ActionEvent
for (item in ActionEvent.entries){
if (item.isDisplay) {
val fieldNameValues = ArrayList<FieldNameValue>()
if(item == ActionEvent.COORDINATE_CLICK_TEST){
// default x,y value use lastClickX,lastClickY
fieldNameValues.add(FieldNameValue().apply {name="x";value=lastClickX.toString()})
fieldNameValues.add(FieldNameValue().apply {name="y";value=lastClickY.toString()})
}
// 检查是否需要文本输入
if (item.menuWithText.isNotEmpty()) {
popup.menu.add(0, item.ordinal, 0, item.menuName).setOnMenuItemClickListener {
showTextInputDialog(item)
showTextInputDialog(item, fieldNameValues)
true
}
} else {
@@ -177,7 +296,13 @@ class DragFloatingButton(context: Context) : View(context) {
}
private fun showTextInputDialog(actionEvent: ActionEvent) {
private fun showTextInputDialog(
actionEvent: ActionEvent,
fieldNameValues: ArrayList<FieldNameValue>
) {
// fieldNameValues 转成key value形式的map
val fieldNameValuesMap = fieldNameValues.associate { it.name to it.value }
println("Attempting to show text input dialog for: ${actionEvent.menuName}")
// 根据 actionEvent.menuWithText 创建对应数量的editText 放到一个 linearLayout 中
val editTexts = arrayListOf<EditText>()
@@ -200,6 +325,8 @@ class DragFloatingButton(context: Context) : View(context) {
hint = menuWithText
tag = menuWithText
val fieldNameValue = fieldNameValuesMap[menuWithText]
setText(fieldNameValue?:"")
// 添加双击全选支持
var lastClickTime = 0L
val doubleClickThreshold = 300L // 双击时间阈值(毫秒)
@@ -287,6 +414,29 @@ class DragFloatingButton(context: Context) : View(context) {
ActionEvent.COORDINATE_CLICK -> {
coordinateClickTool.enableCoordinateClickMode()
}
ActionEvent.COORDINATE_CLICK_TEST -> {
// 解析坐标参数
var xCoord = 0f
var yCoord = 0f
for (textWithTag in textInputs) {
when (textWithTag.tag) {
"x" -> xCoord = textWithTag.text.toFloatOrNull() ?: 0f
"y" -> yCoord = textWithTag.text.toFloatOrNull() ?: 0f
}
}
// 创建坐标监听器并执行点击
val intent = android.content.Intent(actionEvent.event).apply {
setPackage(context.packageName)
}
val coordinateListener = CoordinateListenerImpl(xCoord, yCoord, intent, context = context)
coordinateClickTool.disableCoordinateClickMode(coordinateListener = coordinateListener)
context.sendBroadcast(intent)
}
else -> {
val intent = android.content.Intent(actionEvent.event).apply {
setPackage(context.packageName)

View File

@@ -40,7 +40,16 @@ class ZjMccmCrm(private val rootNodeCallback: RootNodeCallback) {
}
ActionEvent.PRINT_CURRENT_PAGE.event -> {
AccessibilityTool.printViewTree(rootNodeCallback.getRootNodeReceived())
// AccessibilityTool.printViewTree(rootNodeCallback.getRootNodeReceived())
// 修改此处:获取界面数据并发送回 UI 界面显示
val viewTreeData = AccessibilityTool.getViewTreeAsString(rootNodeCallback.getRootNodeReceived())
// 发送广播给 DragFloatingButton 显示弹窗
val resultIntent = Intent("com.loveerror.bested.SHOW_VIEW_TREE_DIALOG").apply {
putExtra("view_tree_data", viewTreeData)
setPackage(context.packageName)
}
context.sendBroadcast(resultIntent)
}
ActionEvent.QUERY_COMPANY_REGISTER.event -> {

View File

@@ -434,6 +434,51 @@ class AccessibilityTool {
}
}
// 新增返回字符串版本的 printViewTree 方法
fun getViewTreeAsString(root: AccessibilityNodeInfo?): String {
if (root == null) return "Root node is null"
val sb = StringBuilder()
printNodeToString(root, 0, sb)
return sb.toString()
}
// 修改 printNodeToString 方法以包含更多布局信息
private fun printNodeToString(node: AccessibilityNodeInfo?, depth: Int, sb: StringBuilder) {
if (node == null) return
val indent = " ".repeat(depth)
sb.append("$indent${node.className}: \"${node.text}\"")
if (node.contentDescription != null) {
sb.append(" desc:\"${node.contentDescription}\"")
}
if (node.viewIdResourceName != null) {
sb.append(" id:${node.viewIdResourceName}")
}
// 添加节点的布局信息
val bounds = android.graphics.Rect()
node.getBoundsInScreen(bounds)
sb.append(" bounds:[${bounds.left},${bounds.top}][${bounds.right},${bounds.bottom}]")
// 添加节点的其他重要属性
sb.append(" clickable:${node.isClickable}")
sb.append(" visible:${node.isVisibleToUser}")
sb.append(" enabled:${node.isEnabled}")
sb.append(" focusable:${node.isFocusable}")
sb.append(" focused:${node.isFocused}")
sb.append(" selected:${node.isSelected}")
sb.append(" scrollable:${node.isScrollable}")
sb.append("\n")
for (i in 0 until node.childCount) {
printNodeToString(node.getChild(i), depth + 1, sb)
}
}
fun printViewTree(root: AccessibilityNodeInfo?, pre : String = "") {
if (root == null) {
println("=== ${pre} View Tree Start [root==null] ===")
@@ -471,6 +516,7 @@ class AccessibilityTool {
"[focusable=${node.isFocusable}] " +
"[accessible=${node.isAccessibilityFocused}] " +
"[visible=${node.isVisibleToUser}] " +
"[nodeToString=$node] " +
""
println("${pre} $indent$nodeInfo")

View File

@@ -7,13 +7,19 @@ interface CoordinateListener {
fun onDisable()
}
class CoordinateListenerImpl(private val x: Float,private val y: Float,private val intent: android.content.Intent,private val context: Context): CoordinateListener {
fun OtherAction() : Unit {
}
class CoordinateListenerImpl(private val x: Float,private val y: Float,private val intent: android.content.Intent,private val context: Context, private val callback: ((x: Float,y: Float) -> Unit)? = null): CoordinateListener {
override fun onDisable() {
intent.putExtra("x",x)
intent.putExtra("y",y)
context.sendBroadcast(intent)
// 执行回调函数
callback?.invoke(x, y)
return
}
}
@@ -34,15 +40,19 @@ class CoordinateClickTool(private val context: Context, private val windowManage
}
fun disableCoordinateClickMode(coordinateListener: CoordinateListener? = null) {
val canSend = isCoordinateClickMode
// val canSend = isCoordinateClickMode
isCoordinateClickMode = false
overlayTool.removeOverlayView()
if(canSend){
coordinateListener?.onDisable()
}
// if(canSend){
// coordinateListener?.onDisable()
// }
}
// fun sendCoordinate(x: Float, y: Float, intent: android.content.Intent, callback: ((x: Float,y: Float) -> Unit)? = null) {
// callback?.invoke(x, y)
// }
fun toggleCoordinateClickMode() {
if (isCoordinateClickMode) {

View File

@@ -0,0 +1,26 @@
package com.loveerror.bested.tool
import android.content.Context
class SystemTool {
companion object{
fun getCurrentTime(): String {
val currentTime = System.currentTimeMillis()
return currentTime.toString()
}
// 添加复制到剪贴板的方法
fun copyToClipboard(context: Context,text: String) {
try {
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as android.content.ClipboardManager
val clip = android.content.ClipData.newPlainText("界面数据", text)
clipboard.setPrimaryClip(clip)
// 显示复制成功提示
android.widget.Toast.makeText(context, "已复制到剪贴板", android.widget.Toast.LENGTH_SHORT).show()
} catch (e: Exception) {
e.printStackTrace()
android.widget.Toast.makeText(context, "复制失败", android.widget.Toast.LENGTH_SHORT).show()
}
}
}
}