fix: 兼容纵向颜色规格列表 (#264)

This commit is contained in:
chengma
2026-08-18 11:36:04 +08:00
parent 6fa23dcb0b
commit d69c9cc31c
3 changed files with 332 additions and 2 deletions
+116 -2
View File
@@ -653,6 +653,51 @@ def _color_swipe_y(root: ET.Element, region: Bounds) -> int:
return region_center_y
def _color_scroll_direction(
root: ET.Element,
region: Bounds,
) -> Optional[str]:
"""判断颜色容器的主要滚动方向;证据不足时不猜测。"""
option_bounds: list[Bounds] = []
for node in root.iter("node"):
if node.get("clickable") != "true" or not _is_available(node):
continue
bounds = _parse_bounds(node.get("bounds", ""))
if bounds is None or not _center_in(bounds, region):
continue
if _intersection_area(bounds, region) <= 0:
continue
option_bounds.append(bounds)
unique_bounds = list(dict.fromkeys(option_bounds))
if len(unique_bounds) < 2:
return None
left_span = max(item[0] for item in unique_bounds) - min(
item[0] for item in unique_bounds
)
top_span = max(item[1] for item in unique_bounds) - min(
item[1] for item in unique_bounds
)
width = region[2] - region[0]
height = region[3] - region[1]
# 单列纵向列表的按钮宽度可能随文字变化,所以比较左上角分布,
# 不能用按钮中心点误把长短不一的同列按钮当成多列。
if top_span >= 80 and top_span > max(80, left_span * 2):
return "vertical"
if left_span >= 80 and left_span > max(80, top_span * 2):
return "horizontal"
# 多行网格同时具有 x/y 分布,只在容器长宽给出强证据时决定方向。
if height >= width * 1.15 and top_span >= 80:
return "vertical"
if width >= height * 1.4 and left_span >= 80:
return "horizontal"
return None
def _swipe_color_row(
device: Any,
root: ET.Element,
@@ -668,6 +713,21 @@ def _swipe_color_row(
device.swipe(start_x, y, end_x, y, duration=0.4)
def _swipe_panel_to_start(device: Any, region: Bounds) -> None:
"""手指向下滑,让纵向规格容器回到颜色列表起点。"""
left, top, right, bottom = region
height = bottom - top
x = (left + right) // 2
device.swipe(
x,
top + int(height * 0.25),
x,
top + int(height * 0.82),
duration=0.35,
)
def _swipe_panel_down(device: Any, region: Bounds) -> None:
left, top, right, bottom = region
height = bottom - top
@@ -689,7 +749,7 @@ def select_color(
max_swipes: int = 15,
action_delay: float = 0.5,
) -> bool:
"""边横向滑动边刷新 XML,目标颜色充分可见后点击并验证选中。"""
"""判断颜色列表方向,有界遍历后精确点击并验证目标颜色。"""
if not target_color.strip():
raise ValueError("target_color 不能为空")
@@ -713,11 +773,65 @@ def select_color(
if selected:
return True
if target_is_static:
# 静态面板中目标已经出现,点击失败后继续横向滑动只会增加等待,
# 静态面板中目标已经出现,点击失败后继续滑动只会增加等待,
# 还可能触碰其他控件;必须立即交给上层报告未确认。
return False
region = _horizontal_color_region(root, target_color) or region
direction = _color_scroll_direction(root, region)
if direction is None:
return False
if direction == "vertical":
# Adapter 正常会先把颜色标题恢复到顶部;独立调用若标题已经
# 滚出视口,则先有界回到起点,随后只向下扫描一次。
if _heading_bounds(root) is None:
previous_signature = _visible_signature(root, region)
unchanged_count = 0
for _ in range(max_swipes):
_swipe_panel_to_start(device, region)
time.sleep(action_delay)
root = _parse_xml(device.dump_hierarchy())
region = _target_scrollable_ancestor(root, target_color) or region
if _heading_bounds(root) is not None:
break
signature = _visible_signature(root, region)
unchanged_count = (
unchanged_count + 1
if signature == previous_signature
else 0
)
previous_signature = signature
if unchanged_count >= 2:
break
previous_signature = _visible_signature(root, region)
unchanged_count = 0
for _ in range(max_swipes):
_swipe_panel_down(device, region)
time.sleep(action_delay)
root = _parse_xml(device.dump_hierarchy())
region = _target_scrollable_ancestor(root, target_color) or region
selected, root = _click_target_and_verify(
device,
root,
target_color,
region,
action_delay,
require_horizontal_safe=True,
)
if selected:
return True
signature = _visible_signature(root, region)
unchanged_count = (
unchanged_count + 1 if signature == previous_signature else 0
)
previous_signature = signature
if unchanged_count >= 2:
break
return False
# 当前横向位置未知:先用手指向右滑到列表左端。每次刷新 XML 时,
# _click_target_and_verify 都会遍历整个 RecyclerView 内的所有行。
previous_signature = _visible_signature(root, region)