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_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")), 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); UNKNOWN("com.loveerror.bested.UNKNOWN", "未知",isDisplay = false, enable = false);
@@ -40,5 +43,10 @@ enum class ActionEvent {
} }
return UNKNOWN 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.widget.PopupMenu
import android.app.Activity import android.app.Activity
import android.content.ContextWrapper import android.content.ContextWrapper
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.core.content.ContextCompat
import com.loveerror.bested.tool.CoordinateClickTool import com.loveerror.bested.tool.CoordinateClickTool
import com.loveerror.bested.tool.CoordinateListener
import com.loveerror.bested.tool.CoordinateListenerImpl import com.loveerror.bested.tool.CoordinateListenerImpl
import com.loveerror.bested.tool.SystemTool
class DragFloatingButton(context: Context) : View(context) { class DragFloatingButton(context: Context) : View(context) {
// 添加坐标存储变量
private var lastClickX: Float = 0f
private var lastClickY: Float = 0f
private val paint = Paint().apply { private val paint = Paint().apply {
isAntiAlias = true isAntiAlias = true
style = Paint.Style.FILL style = Paint.Style.FILL
@@ -32,6 +41,9 @@ class DragFloatingButton(context: Context) : View(context) {
private var originalY = 0 private var originalY = 0
private var isDragging = false private var isDragging = false
// 在 DragFloatingButton.kt 中添加成员变量
private var dialogBroadcastReceiver: android.content.BroadcastReceiver? = null
init { init {
// 初始化时创建正确的 WindowManager.LayoutParams // 初始化时创建正确的 WindowManager.LayoutParams
this.layoutParams = 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, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
PixelFormat.TRANSLUCENT 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) { override fun onDraw(canvas: Canvas) {
super.onDraw(canvas) super.onDraw(canvas)
@@ -72,9 +169,16 @@ class DragFloatingButton(context: Context) : View(context) {
when (event.action) { when (event.action) {
MotionEvent.ACTION_DOWN -> { MotionEvent.ACTION_DOWN -> {
startX = event.rawX startX = event.rawX
startY = event.rawY startY = event.rawY
// 记录点击坐标
lastClickX = startX
lastClickY = startY
// 确保 layoutParams 是 WindowManager.LayoutParams 类型 // 确保 layoutParams 是 WindowManager.LayoutParams 类型
if (this.layoutParams is WindowManager.LayoutParams) { if (this.layoutParams is WindowManager.LayoutParams) {
layoutParams = this.layoutParams as WindowManager.LayoutParams layoutParams = this.layoutParams as WindowManager.LayoutParams
@@ -124,12 +228,17 @@ class DragFloatingButton(context: Context) : View(context) {
// 只在特定条件下移除覆盖层 // 只在特定条件下移除覆盖层
if (!isDragging) { if (!isDragging) {
val intent = android.content.Intent(ActionEvent.COORDINATE_CLICK.event).apply { val intent = android.content.Intent(ActionEvent.COORDINATE_CLICK.event).apply {
setPackage(context.packageName) setPackage(context.packageName)
} }
val coordinateListenerImpl = 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) coordinateClickTool.disableCoordinateClickMode(coordinateListener = coordinateListenerImpl)
@@ -146,16 +255,26 @@ class DragFloatingButton(context: Context) : View(context) {
private val coordinateClickTool = CoordinateClickTool(context, windowManager) private val coordinateClickTool = CoordinateClickTool(context, windowManager)
class FieldNameValue {
var name: String = ""
var value: String = ""
}
private fun showPopupMenu() { private fun showPopupMenu() {
val popup = PopupMenu(context, this) val popup = PopupMenu(context, this)
// 添加菜单项 use ActionEvent // 添加菜单项 use ActionEvent
for (item in ActionEvent.entries){ for (item in ActionEvent.entries){
if (item.isDisplay) { 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()) { if (item.menuWithText.isNotEmpty()) {
popup.menu.add(0, item.ordinal, 0, item.menuName).setOnMenuItemClickListener { popup.menu.add(0, item.ordinal, 0, item.menuName).setOnMenuItemClickListener {
showTextInputDialog(item) showTextInputDialog(item, fieldNameValues)
true true
} }
} else { } 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}") println("Attempting to show text input dialog for: ${actionEvent.menuName}")
// 根据 actionEvent.menuWithText 创建对应数量的editText 放到一个 linearLayout 中 // 根据 actionEvent.menuWithText 创建对应数量的editText 放到一个 linearLayout 中
val editTexts = arrayListOf<EditText>() val editTexts = arrayListOf<EditText>()
@@ -200,6 +325,8 @@ class DragFloatingButton(context: Context) : View(context) {
hint = menuWithText hint = menuWithText
tag = menuWithText tag = menuWithText
val fieldNameValue = fieldNameValuesMap[menuWithText]
setText(fieldNameValue?:"")
// 添加双击全选支持 // 添加双击全选支持
var lastClickTime = 0L var lastClickTime = 0L
val doubleClickThreshold = 300L // 双击时间阈值(毫秒) val doubleClickThreshold = 300L // 双击时间阈值(毫秒)
@@ -287,6 +414,29 @@ class DragFloatingButton(context: Context) : View(context) {
ActionEvent.COORDINATE_CLICK -> { ActionEvent.COORDINATE_CLICK -> {
coordinateClickTool.enableCoordinateClickMode() 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 -> { else -> {
val intent = android.content.Intent(actionEvent.event).apply { val intent = android.content.Intent(actionEvent.event).apply {
setPackage(context.packageName) setPackage(context.packageName)

View File

@@ -40,7 +40,16 @@ class ZjMccmCrm(private val rootNodeCallback: RootNodeCallback) {
} }
ActionEvent.PRINT_CURRENT_PAGE.event -> { 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 -> { 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 = "") { fun printViewTree(root: AccessibilityNodeInfo?, pre : String = "") {
if (root == null) { if (root == null) {
println("=== ${pre} View Tree Start [root==null] ===") println("=== ${pre} View Tree Start [root==null] ===")
@@ -471,6 +516,7 @@ class AccessibilityTool {
"[focusable=${node.isFocusable}] " + "[focusable=${node.isFocusable}] " +
"[accessible=${node.isAccessibilityFocused}] " + "[accessible=${node.isAccessibilityFocused}] " +
"[visible=${node.isVisibleToUser}] " + "[visible=${node.isVisibleToUser}] " +
"[nodeToString=$node] " +
"" ""
println("${pre} $indent$nodeInfo") println("${pre} $indent$nodeInfo")

View File

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