123
import uiautomator2 as u2
def save_xml_to_file(xml_content, filename="ui_dump.xml"):
"""保存XML内容到文件"""
try:
with open(filename, 'w', encoding='utf-8') as f:
f.write(xml_content)
print(f"XML内容已保存到文件: {filename}")
return True
except Exception as e:
print(f"保存XML文件时出错: {e}")
return False
def dump_ui_xml():
"""获取并保存当前界面的XML结构"""
try:
# 连接设备
d = u2.connect()
print("设备已连接")
# 检查设备连接状态
if not d.exists:
print("警告:设备可能未正确连接,请检查USB连接和调试模式")
return False
# 获取当前界面的UI层次结构
print("正在获取UI层次结构...")
xml_content = d.dump_hierarchy()
# 保存原始XML内容
if save_xml_to_file(xml_content):
print("UI层次结构已成功保存到 ui_dump.xml")
return True
return False
except Exception as e:
print(f"获取UI层次结构时出错: {e}")
return False
if __name__ == "__main__":
try:
print("正在获取当前界面的XML结构...")
dump_ui_xml()
except Exception as e:
print(f"发生错误: {e}")
print("\n请确保:")
print("1. 已安装所需库: pip install uiautomator2")
print("2. 设备已通过USB连接并已启用USB调试")
print("3. 已安装ATX代理 (python -m uiautomator2 init)")
print("4. 尝试重启ATX服务: python -m uiautomator2 restart")
输入id和class选择性的筛选。
import uiautomator2 as u2
import xml.etree.ElementTree as ET
import re
import os
from PIL import Image, ImageDraw, ImageFont
def save_xml_to_file(xml_content, filename="ui_dump.xml"):
"""保存XML内容到文件"""
try:
with open(filename, 'w', encoding='utf-8') as f:
f.write(xml_content)
print(f"XML内容已保存到文件: {filename}")
return True
except Exception as e:
print(f"保存XML文件时出错: {e}")
return False
def dump_ui_xml():
"""获取并保存当前界面的XML结构"""
try:
# 连接设备
d = u2.connect()
print("设备已连接")
# 检查设备连接状态
if not d.exists:
print("警告:设备可能未正确连接,请检查USB连接和调试模式")
return False, None
# 获取当前界面的UI层次结构
print("正在获取UI层次结构...")
xml_content = d.dump_hierarchy()
# 保存原始XML内容
if save_xml_to_file(xml_content):
print("UI层次结构已成功保存到 ui_dump.xml")
return xml_content, d
return False, None
except Exception as e:
print(f"获取UI层次结构时出错: {e}")
return False, None
def parse_bounds(bounds_str):
"""解析元素边界坐标"""
# 从字符串如 "[0,0][100,100]" 提取坐标
pattern = r'\[(\d+),(\d+)\]\[(\d+),(\d+)\]'
match = re.match(pattern, bounds_str)
if match:
x1, y1, x2, y2 = map(int, match.groups())
return (x1, y1, x2, y2)
return None
def list_all_elements():
"""列出所有可用的class和id"""
try:
# 获取XML内容和设备连接
result = dump_ui_xml()
if not result:
return False
xml_content, _ = result
# 解析XML
root = ET.fromstring(xml_content)
# 收集所有唯一的class和id
classes = set()
ids = set()
# 遍历所有节点
for elem in root.iter():
class_name = elem.attrib.get('class', '')
elem_id = elem.attrib.get('resource-id', '')
if class_name:
classes.add(class_name)
if elem_id:
ids.add(elem_id)
# 输出结果
print("\n可用的Class:")
for i, class_name in enumerate(sorted(classes)):
print(f"{i+1}. {class_name}")
print("\n可用的ID:")
for i, elem_id in enumerate(sorted(ids)):
print(f"{i+1}. {elem_id}")
return True
except Exception as e:
print(f"列出元素时出错: {e}")
return False
def highlight_elements_by_class_id(target_class=None, target_id=None, save_image=True):
"""根据指定的class和id标识UI元素并在截图上圈出来"""
try:
# 获取XML内容和设备连接
result = dump_ui_xml()
if not result:
return False
xml_content, d = result
# 截取当前屏幕
print("正在截取屏幕...")
screenshot_path = "screen.png"
d.screenshot(screenshot_path)
# 解析XML
root = ET.fromstring(xml_content)
# 查找所有元素
highlighted_elements = []
# 遍历所有节点
for elem in root.iter():
class_name = elem.attrib.get('class', '')
elem_id = elem.attrib.get('resource-id', '')
bounds = elem.attrib.get('bounds', '')
# 匹配条件:如果指定了class或id,则只匹配指定的元素
match_class = not target_class or (target_class in class_name)
match_id = not target_id or (target_id in elem_id)
# 如果同时指定了class和id,两者都要匹配
# 如果只指定了其中一个,则只需匹配指定的那个
is_match = False
if target_class and target_id:
is_match = match_class and match_id
else:
is_match = match_class or match_id
if is_match and bounds:
coords = parse_bounds(bounds)
if coords:
highlighted_elements.append({
'class': class_name,
'id': elem_id,
'bounds': bounds,
'coords': coords,
'text': elem.attrib.get('text', '')
})
# 在截图上标记元素
if save_image and highlighted_elements:
print("正在标记元素...")
img = Image.open(screenshot_path)
draw = ImageDraw.Draw(img)
# 尝试加载字体,如果失败则使用默认字体
try:
font = ImageFont.truetype("arial.ttf", 14)
except IOError:
font = ImageFont.load_default()
# 在图像上绘制矩形和标签
for i, elem in enumerate(highlighted_elements):
x1, y1, x2, y2 = elem['coords']
# 绘制矩形
draw.rectangle([x1, y1, x2, y2], outline="red", width=2)
# 准备标签文本
if elem['id']:
label = f"{i+1}. ID: {elem['id'].split('/')[-1]}"
else:
label = f"{i+1}. Class: {elem['class'].split('.')[-1]}"
# 绘制标签背景和文本
text_bbox = draw.textbbox((0, 0), label, font=font)
text_width = text_bbox[2] - text_bbox[0]
text_height = text_bbox[3] - text_bbox[1]
draw.rectangle([x1, y1-text_height-4, x1+text_width+4, y1], fill="red")
draw.text((x1+2, y1-text_height-2), label, fill="white", font=font)
# 保存标记后的图像
marked_image_path = "marked_elements.png"
img.save(marked_image_path)
print(f"已将标记元素的截图保存到: {os.path.abspath(marked_image_path)}")
# 输出结果
if highlighted_elements:
print(f"\n已找到 {len(highlighted_elements)} 个匹配的元素:")
for i, elem in enumerate(highlighted_elements):
print(f"{i+1}. Class: {elem['class']}")
if elem['id']:
print(f" ID: {elem['id']}")
print(f" 位置: {elem['bounds']}, 文本: {elem['text']}")
else:
print(f"\n未找到匹配的元素")
return highlighted_elements
except Exception as e:
print(f"处理元素时出错: {e}")
import traceback
traceback.print_exc()
return False
def main():
try:
print("欢迎使用UI元素标识工具")
print("------------------------")
# 先列出所有可用的class和id
print("正在获取当前界面的所有元素...")
list_all_elements()
# 询问用户要查找的元素
print("\n请输入要查找的元素属性(留空则匹配所有元素):")
target_class = input("Class (可选): ").strip()
target_id = input("ID (可选): ").strip()
if not target_class and not target_id:
print("未指定任何属性,将标记所有元素")
# 标记元素
print("\n正在查找并标记元素...")
highlight_elements_by_class_id(target_class, target_id)
except Exception as e:
print(f"发生错误: {e}")
print("\n请确保:")
print("1. 已安装所需库: pip install uiautomator2 pillow")
print("2. 设备已通过USB连接并已启用USB调试")
print("3. 已安装ATX代理 (python -m uiautomator2 init)")
print("4. 尝试重启ATX服务: python -m uiautomator2 restart")
if __name__ == "__main__":
main()
大小尺寸进行选择
import uiautomator2 as u2
import xml.etree.ElementTree as ET
import re
import os
from PIL import Image, ImageDraw, ImageFont
import math
from collections import defaultdict
def save_xml_to_file(xml_content, filename="ui_dump.xml"):
"""保存XML内容到文件"""
try:
with open(filename, 'w', encoding='utf-8') as f:
f.write(xml_content)
print(f"XML内容已保存到文件: {filename}")
return True
except Exception as e:
print(f"保存XML文件时出错: {e}")
return False
def dump_ui_xml():
"""获取并保存当前界面的XML结构"""
try:
# 连接设备
d = u2.connect()
print("设备已连接")
# 检查设备连接状态
if not d.exists:
print("警告:设备可能未正确连接,请检查USB连接和调试模式")
return False, None
# 获取当前界面的UI层次结构
print("正在获取UI层次结构...")
xml_content = d.dump_hierarchy()
# 保存原始XML内容
if save_xml_to_file(xml_content):
print("UI层次结构已成功保存到 ui_dump.xml")
return xml_content, d
return False, None
except Exception as e:
print(f"获取UI层次结构时出错: {e}")
return False, None
def parse_bounds(bounds_str):
"""解析元素边界坐标"""
# 从字符串如 "[0,0][100,100]" 提取坐标
pattern = r'\[(\d+),(\d+)\]\[(\d+),(\d+)\]'
match = re.match(pattern, bounds_str)
if match:
x1, y1, x2, y2 = map(int, match.groups())
return (x1, y1, x2, y2)
return None
def highlight_elements_by_class_id(target_class=None, target_id=None, save_image=True):
"""根据指定的class和id标识UI元素并在截图上圈出来,返回匹配的元素列表"""
try:
# 获取XML内容和设备连接
result = dump_ui_xml()
if not result:
return []
xml_content, d = result
# 截取当前屏幕
print("正在截取屏幕...")
screenshot_path = "screen.png"
d.screenshot(screenshot_path)
# 解析XML
root = ET.fromstring(xml_content)
# 查找所有元素
highlighted_elements = []
# 遍历所有节点
for elem in root.iter():
class_name = elem.attrib.get('class', '')
elem_id = elem.attrib.get('resource-id', '')
bounds = elem.attrib.get('bounds', '')
# 匹配条件:如果指定了class或id,则只匹配指定的元素
match_class = not target_class or (target_class in class_name)
match_id = not target_id or (target_id in elem_id)
# 如果同时指定了class和id,两者都要匹配
# 如果只指定了其中一个,则只需匹配指定的那个
is_match = False
if target_class and target_id:
is_match = match_class and match_id
else:
is_match = match_class or match_id
if is_match and bounds:
coords = parse_bounds(bounds)
if coords:
x1, y1, x2, y2 = coords
width = x2 - x1
height = y2 - y1
# 只添加有实际尺寸的元素
if width > 0 and height > 0:
highlighted_elements.append({
'class': class_name,
'id': elem_id,
'bounds': bounds,
'coords': coords,
'width': width,
'height': height,
'text': elem.attrib.get('text', '')
})
# 在截图上标记元素
if save_image and highlighted_elements:
print("正在标记元素...")
img = Image.open(screenshot_path)
draw = ImageDraw.Draw(img)
# 尝试加载字体,如果失败则使用默认字体
try:
font = ImageFont.truetype("arial.ttf", 14)
except IOError:
font = ImageFont.load_default()
# 在图像上绘制矩形和标签
for i, elem in enumerate(highlighted_elements):
x1, y1, x2, y2 = elem['coords']
# 绘制矩形
draw.rectangle([x1, y1, x2, y2], outline="red", width=2)
# 准备标签文本
if elem['id']:
label = f"{i+1}. ID: {elem['id'].split('/')[-1]}"
else:
label = f"{i+1}. Class: {elem['class'].split('.')[-1]}"
# 绘制标签背景和文本
text_bbox = draw.textbbox((0, 0), label, font=font)
text_width = text_bbox[2] - text_bbox[0]
text_height = text_bbox[3] - text_bbox[1]
draw.rectangle([x1, y1-text_height-4, x1+text_width+4, y1], fill="red")
draw.text((x1+2, y1-text_height-2), label, fill="white", font=font)
# 保存标记后的图像
marked_image_path = "marked_elements.png"
img.save(marked_image_path)
print(f"已将标记元素的截图保存到: {os.path.abspath(marked_image_path)}")
# 输出结果
if highlighted_elements:
print(f"\n已找到 {len(highlighted_elements)} 个匹配的元素:")
for i, elem in enumerate(highlighted_elements):
print(f"{i+1}. Class: {elem['class']}")
if elem['id']:
print(f" ID: {elem['id']}")
print(f" 位置: {elem['bounds']}, 尺寸: {elem['width']}x{elem['height']} 像素")
if elem['text']:
print(f" 文本: {elem['text']}")
else:
print(f"\n未找到匹配的元素")
return highlighted_elements
except Exception as e:
print(f"处理元素时出错: {e}")
import traceback
traceback.print_exc()
return []
def group_by_similar_size(elements, tolerance=0.1):
"""将元素按照相似尺寸分组
Args:
elements: 元素列表
tolerance: 尺寸相似度容差(0.1表示10%的误差范围)
Returns:
分组后的元素字典,键为(宽度, 高度)的元组,值为元素列表
"""
# 按尺寸分组的字典
size_groups = defaultdict(list)
# 已处理的尺寸集合,用于避免重复
processed_sizes = set()
# 先对元素按面积排序
elements.sort(key=lambda x: x['width'] * x['height'])
# 分组处理
for elem in elements:
width, height = elem['width'], elem['height']
# 检查是否已经有相似尺寸的组
found_group = False
for w, h in processed_sizes:
# 计算宽高的相对误差
width_diff = abs(width - w) / max(width, w)
height_diff = abs(height - h) / max(height, h)
# 如果宽高都在容差范围内,则认为是相似尺寸
if width_diff <= tolerance and height_diff <= tolerance:
size_groups[(w, h)].append(elem)
found_group = True
break
# 如果没找到相似尺寸的组,则创建新组
if not found_group:
size_groups[(width, height)].append(elem)
processed_sizes.add((width, height))
return size_groups
def show_size_groups(matched_elements):
"""显示按相似尺寸分组的元素集合,并允许用户选择集合进行标记"""
try:
if not matched_elements:
print("没有匹配的元素可供分组")
return False
# 按相似尺寸分组
size_groups = group_by_similar_size(matched_elements)
# 将分组转换为列表,便于索引
groups_list = []
for size, elements in size_groups.items():
width, height = size
groups_list.append({
'size': size,
'elements': elements,
'count': len(elements)
})
# 按元素数量排序,数量多的排在前面
groups_list.sort(key=lambda x: x['count'], reverse=True)
# 输出分组信息
print(f"\n已将 {len(matched_elements)} 个元素按相似尺寸分为 {len(groups_list)} 个集合:")
for i, group in enumerate(groups_list):
width, height = group['size']
print(f"{i+1}. 尺寸约 {width}x{height} 像素,包含 {group['count']} 个元素")
# 询问用户要查看的集合
while True:
try:
selection = input("\n请输入要查看的尺寸集合编号(输入0退出): ")
if selection == "0":
return True
# 解析用户输入的编号
group_idx = int(selection.strip())
if 1 <= group_idx <= len(groups_list):
selected_group = groups_list[group_idx-1]
show_group_elements(selected_group)
else:
print(f"编号 {group_idx} 无效,有效范围: 1-{len(groups_list)}")
except ValueError:
print("输入无效,请输入数字编号")
return True
except Exception as e:
print(f"处理尺寸分组时出错: {e}")
import traceback
traceback.print_exc()
return False
def show_group_elements(group):
"""显示指定尺寸集合中的元素,并允许用户选择特定元素标记"""
try:
width, height = group['size']
elements = group['elements']
print(f"\n尺寸约 {width}x{height} 像素的集合包含 {len(elements)} 个元素:")
for i, elem in enumerate(elements):
print(f"{i+1}. Class: {elem['class']}")
if elem['id']:
print(f" ID: {elem['id']}")
print(f" 位置: {elem['bounds']}, 精确尺寸: {elem['width']}x{elem['height']} 像素")
if elem['text']:
print(f" 文本: {elem['text']}")
print("---")
# 询问用户是否要标记所有元素或选择特定元素
print("\n请选择操作:")
print("1. 标记该集合中的所有元素")
print("2. 选择特定元素进行标记")
print("0. 返回上级菜单")
choice = input("\n请输入选项编号: ")
if choice == "1":
# 标记所有元素
mark_selected_elements(elements, list(range(1, len(elements)+1)), "screen.png")
elif choice == "2":
# 选择特定元素
while True:
try:
selection = input("\n请输入要单独标记的元素编号(多个编号用逗号分隔,输入0返回): ")
if selection == "0":
return
# 解析用户输入的编号
selected_indices = [int(idx.strip()) for idx in selection.split(",") if idx.strip()]
if not selected_indices:
print("未选择任何元素,请重新输入")
continue
# 验证编号是否有效
valid_indices = []
for idx in selected_indices:
if 1 <= idx <= len(elements):
valid_indices.append(idx)
else:
print(f"编号 {idx} 无效,有效范围: 1-{len(elements)}")
if valid_indices:
# 标记选定的元素
mark_selected_elements(elements, valid_indices, "screen.png")
break
except ValueError:
print("输入无效,请输入数字编号")
except Exception as e:
print(f"显示集合元素时出错: {e}")
import traceback
traceback.print_exc()
def show_rectangle_dimensions_by_filter(matched_elements):
"""显示匹配元素的矩形尺寸,并允许用户选择特定矩形进行标记"""
try:
if not matched_elements:
print("没有匹配的元素可供选择")
return False
# 输出所有匹配元素的尺寸信息
print(f"\n根据筛选条件找到 {len(matched_elements)} 个UI元素:")
for i, elem in enumerate(matched_elements):
print(f"{i+1}. 尺寸: {elem['width']}x{elem['height']} 像素")
print(f" 位置: {elem['bounds']}")
if elem['id']:
print(f" ID: {elem['id']}")
print(f" Class: {elem['class']}")
if elem['text']:
print(f" 文本: {elem['text']}")
print("---")
# 询问用户要标记的元素
while True:
try:
selection = input("\n请输入要单独标记的元素编号(多个编号用逗号分隔,输入0退出): ")
if selection == "0":
return True
# 解析用户输入的编号
selected_indices = [int(idx.strip()) for idx in selection.split(",") if idx.strip()]
if not selected_indices:
print("未选择任何元素,请重新输入")
continue
# 验证编号是否有效
valid_indices = []
for idx in selected_indices:
if 1 <= idx <= len(matched_elements):
valid_indices.append(idx)
else:
print(f"编号 {idx} 无效,有效范围: 1-{len(matched_elements)}")
if valid_indices:
# 标记选定的元素
mark_selected_elements(matched_elements, valid_indices, "screen.png")
break
except ValueError:
print("输入无效,请输入数字编号")
return True
except Exception as e:
print(f"处理元素尺寸时出错: {e}")
import traceback
traceback.print_exc()
return False
def mark_selected_elements(all_elements, selected_indices, screenshot_path):
"""标记用户选择的特定元素"""
try:
print("正在标记选定的元素...")
img = Image.open(screenshot_path)
draw = ImageDraw.Draw(img)
# 尝试加载字体,如果失败则使用默认字体
try:
font = ImageFont.truetype("arial.ttf", 14)
except IOError:
font = ImageFont.load_default()
# 在图像上只标记选定的元素
for idx in selected_indices:
elem = all_elements[idx-1] # 索引从0开始,但显示给用户是从1开始
x1, y1, x2, y2 = elem['coords']
# 绘制矩形,使用不同颜色
draw.rectangle([x1, y1, x2, y2], outline="blue", width=3)
# 准备标签文本
label = f"{idx}. {elem['width']}x{elem['height']}像素"
# 绘制标签背景和文本
text_bbox = draw.textbbox((0, 0), label, font=font)
text_width = text_bbox[2] - text_bbox[0]
text_height = text_bbox[3] - text_bbox[1]
draw.rectangle([x1, y1-text_height-4, x1+text_width+4, y1], fill="blue")
draw.text((x1+2, y1-text_height-2), label, fill="white", font=font)
# 保存标记后的图像
selected_image_path = "selected_elements.png"
img.save(selected_image_path)
print(f"已将选定元素的截图保存到: {os.path.abspath(selected_image_path)}")
# 输出选定元素的详细信息
print("\n选定元素的详细信息:")
for idx in selected_indices:
elem = all_elements[idx-1]
print(f"{idx}. 尺寸: {elem['width']}x{elem['height']} 像素")
print(f" 位置: {elem['bounds']}")
if elem['id']:
print(f" ID: {elem['id']}")
print(f" Class: {elem['class']}")
if elem['text']:
print(f" 文本: {elem['text']}")
print("---")
except Exception as e:
print(f"标记选定元素时出错: {e}")
import traceback
traceback.print_exc()
def main():
try:
print("欢迎使用UI元素标识工具")
print("------------------------")
while True:
print("\n请选择操作:")
print("1. 根据class或id标记元素并选择特定矩形")
print("2. 根据class或id标记元素并按相似尺寸分组")
print("0. 退出")
choice = input("\n请输入选项编号: ")
if choice == "1":
# 询问用户要查找的元素
print("\n请输入要查找的元素属性(留空则匹配所有元素):")
target_class = input("Class (可选): ").strip()
target_id = input("ID (可选): ").strip()
if not target_class and not target_id:
print("未指定任何属性,将标记所有元素")
# 标记元素并获取匹配的元素列表
print("\n正在查找并标记元素...")
matched_elements = highlight_elements_by_class_id(target_class, target_id)
# 显示匹配元素的尺寸并选择特定矩形标记
if matched_elements:
print("\n是否要根据尺寸选择特定矩形进行标记?(y/n)")
if input().lower() == 'y':
show_rectangle_dimensions_by_filter(matched_elements)
elif choice == "2":
# 询问用户要查找的元素
print("\n请输入要查找的元素属性(留空则匹配所有元素):")
target_class = input("Class (可选): ").strip()
target_id = input("ID (可选): ").strip()
if not target_class and not target_id:
print("未指定任何属性,将标记所有元素")
# 标记元素并获取匹配的元素列表
print("\n正在查找并标记元素...")
matched_elements = highlight_elements_by_class_id(target_class, target_id)
# 按相似尺寸分组并显示
if matched_elements:
show_size_groups(matched_elements)
elif choice == "0":
print("感谢使用,再见!")
break
else:
print("无效的选项,请重新输入")
except Exception as e:
print(f"发生错误: {e}")
print("\n请确保:")
print("1. 已安装所需库: pip install uiautomator2 pillow")
print("2. 设备已通过USB连接并已启用USB调试")
print("3. 已安装ATX代理 (python -m uiautomator2 init)")
print("4. 尝试重启ATX服务: python -m uiautomator2 restart")
if __name__ == "__main__":
main()