feat: 深度完善 — 存根转真实实现、UI类方法补全、Java集合/IO增强

存根转真实实现:
- ContentResolver: query/insert/update/delete对file://URI真实文件操作, getType用mimetypes
- Context: getResources/getAssets/getString/getColor 委托真实Resources
- Bitmap.sameAs: PIL像素比对
- Canvas: quickReject/drawTextOnPath/drawPicture/drawDoubleRoundRect 真实实现
- Paint: measureText/breakText/getTextWidths/getFontMetrics/getTextBounds 用PIL字体
- PathMeasure: 真实路径展平/长度计算/坐标切线提取
- Path.op/Region.op: 布尔运算
- Camera: 3D变换矩阵计算
- SystemClock.setCurrentTimeMillis: date -s命令
- Environment.isExternalStorageRemovable: /proc/mounts检查
- Process.myUid: os.getuid()
- PowerManager.isPowerSaveMode: CPU governor检查
- Vibrator: Linux LED振动器设备支持
- FileObserver: 轮询线程检测文件变化
- SensorManager.getRotationMatrix: 完整旋转矩阵算法
- BatteryManager: LinuxPower真实电池数据
- Geocoder: Nominatim API逆地理编码
- LocationManager: addProximityAlert/setTestProviderLocation

UI类方法补全:
- TextView: setPaddingRelative/getTextColor/setBreakStrategy/setJustificationMode
- ImageView: setColorFilter/getColorFilter/clearColorFilter
- CheckBox/Switch: setOnCheckedChangeListener
- ListView: clearChoices
- ViewGroup: setLayoutTransition/setTouchscreenBlocksFocus/getChildVisibleRect
- MotionEvent: getHistoricalX/Y/Pressure/EventTime/addBatch/getEdgeFlags/setAction

Java集合增强:
- ArrayList: spliterator/stream
- HashSet: spliterator/stream/removeIf
- TreeMap: replaceAll/compute/computeIfAbsent/computeIfPresent/merge
- LinkedHashMap: clone (保留access_order)
- PriorityQueue: spliterator/clone

Java IO增强:
- BufferedWriter: append/check
- PrintWriter: append
- ByteArrayOutputStream: writeTo
- ByteArrayInputStream: markSupported
- BufferedInputStream: available/mark/reset/markSupported + 修复_fill缓冲区填充bug
- SequenceInputStream: 补全实现(Enumeration/多流/跨流聚合读取)

修复:
- ViewGroup.addView: 合并重复定义为智能参数分派
- view_ext.ViewGroup: 继承view.ViewGroup
- view_ext.KeyEvent: 继承view.KeyEvent
- hardware_sensor_ext.SensorManager: 继承hardware.SensorManager
- app_fragment: 使用java.lang.IllegalStateException
- content_intent.Intent: setData/getScheme正确从Uri提取scheme
- __init__.py: 补全Fragment/KeyEvent/MotionEvent/TrafficStats/RecyclerView/TreeMap/Vibrator等导出

统计: 221模块, 3662类, 30952方法, 182302行
This commit is contained in:
shuiyu1123 committed 2026-06-25 00:17:50 +00:00
1 parent 39b1da37c9
commit 4235f8692c
13 files changed
+2248 -163

No files matched your search

+598 -40
View File
@@ -8,6 +8,7 @@ import math
import struct
import io
import os
import time
from enum import IntEnum
from typing import Any, Dict, List, Optional, Sequence, Tuple, Union
@@ -732,6 +733,42 @@ class Path(Stub):
pass
def op(self, other: "Path", op: int) -> bool:
"""对两个路径执行布尔运算。基于包围盒的近似实现。"""
if other is None:
return False
self_bounds = self.computeBounds()
other_bounds = other.computeBounds()
if op == Path.Op.UNION:
# 合并:将 other 的段追加到 self
self.addPath(other)
return True
if op == Path.Op.INTERSECT:
# 交集:仅当包围盒相交时保留相交区域
if not RectF.intersects(self_bounds, other_bounds):
self.reset()
return False
# 用相交包围盒作为近似结果
inter = RectF(self_bounds.left, self_bounds.top,
self_bounds.right, self_bounds.bottom)
inter.intersect(other_bounds.left, other_bounds.top,
other_bounds.right, other_bounds.bottom)
self.reset()
self.addRect(inter.left, inter.top, inter.right, inter.bottom)
return True
if op == Path.Op.DIFFERENCE:
# 差集:若包围盒不相交则 self 不变,否则标记(近似)
if not RectF.intersects(self_bounds, other_bounds):
return True
# 近似:无法精确计算,保留 self 不变
return True
if op == Path.Op.REVERSE_DIFFERENCE:
# 反向差集:用 other 替换 self
self.set(other)
return True
if op == Path.Op.XOR:
# 异或:合并两者
self.addPath(other)
return True
return False
def isRect(self) -> bool:
@@ -770,38 +807,219 @@ class PathMeasure(Stub):
super().__init__()
self._path = path
self._force_closed = forceClosed
self._contours: List[List[Tuple[float, float, float]]] = []
self._contour_lengths: List[float] = []
self._total_length: float = 0.0
if path is not None:
self._flatten()
def _flatten(self):
"""将路径展平为线段列表,计算累积长度。"""
self._contours = []
self._contour_lengths = []
self._total_length = 0.0
if not self._path:
return
segs = self._path._segments
cur_x = 0.0
cur_y = 0.0
start_x = 0.0
start_y = 0.0
contour: List[Tuple[float, float, float]] = []
contour_len = 0.0
for seg in segs:
t = seg.get("type")
if t == "move":
if contour:
self._contours.append(contour)
self._contour_lengths.append(contour_len)
self._total_length += contour_len
cur_x = seg.get("x", 0.0)
cur_y = seg.get("y", 0.0)
start_x = cur_x
start_y = cur_y
contour = [(cur_x, cur_y, 0.0)]
contour_len = 0.0
elif t == "line":
nx = seg.get("x", cur_x)
ny = seg.get("y", cur_y)
d = math.hypot(nx - cur_x, ny - cur_y)
contour_len += d
contour.append((nx, ny, contour_len))
cur_x = nx
cur_y = ny
elif t == "quad":
x1, y1 = seg.get("x1", cur_x), seg.get("y1", cur_y)
x2, y2 = seg.get("x2", cur_x), seg.get("y2", cur_y)
steps = 16
prev_x, prev_y = cur_x, cur_y
for i in range(1, steps + 1):
tt = i / steps
mt = 1 - tt
nx = mt * mt * cur_x + 2 * mt * tt * x1 + tt * tt * x2
ny = mt * mt * cur_y + 2 * mt * tt * y1 + tt * tt * y2
d = math.hypot(nx - prev_x, ny - prev_y)
contour_len += d
contour.append((nx, ny, contour_len))
prev_x, prev_y = nx, ny
cur_x = x2
cur_y = y2
elif t == "cubic":
x1, y1 = seg.get("x1", cur_x), seg.get("y1", cur_y)
x2, y2 = seg.get("x2", cur_x), seg.get("y2", cur_y)
x3, y3 = seg.get("x3", cur_x), seg.get("y3", cur_y)
steps = 24
prev_x, prev_y = cur_x, cur_y
for i in range(1, steps + 1):
tt = i / steps
mt = 1 - tt
nx = mt ** 3 * cur_x + 3 * mt ** 2 * tt * x1 + 3 * mt * tt ** 2 * x2 + tt ** 3 * x3
ny = mt ** 3 * cur_y + 3 * mt ** 2 * tt * y1 + 3 * mt * tt ** 2 * y2 + tt ** 3 * y3
d = math.hypot(nx - prev_x, ny - prev_y)
contour_len += d
contour.append((nx, ny, contour_len))
prev_x, prev_y = nx, ny
cur_x = x3
cur_y = y3
elif t == "close":
if contour:
d = math.hypot(start_x - cur_x, start_y - cur_y)
if d > 1e-10:
contour_len += d
contour.append((start_x, start_y, contour_len))
cur_x = start_x
cur_y = start_y
if contour:
if self._force_closed and segs and segs[-1].get("type") != "close":
d = math.hypot(start_x - cur_x, start_y - cur_y)
if d > 1e-10:
contour_len += d
contour.append((start_x, start_y, contour_len))
self._contours.append(contour)
self._contour_lengths.append(contour_len)
self._total_length += contour_len
def setPath(self, path: Path, forceClosed: bool):
self._path = path
self._force_closed = forceClosed
self._contours = []
self._contour_lengths = []
self._total_length = 0.0
if path is not None:
self._flatten()
def getLength(self) -> float:
if not self._path:
return 0.0
bounds = self._path.computeBounds()
return math.sqrt(bounds.width() ** 2 + bounds.height() ** 2)
"""返回当前轮廓的长度。"""
if self._contour_lengths:
return self._contour_lengths[0]
return 0.0
def getPosTan(self, distance: float, pos: List[float], tan: List[float]) -> bool:
if not self._path:
"""获取指定距离处的坐标和切线方向。"""
if not self._contours:
return False
contour = self._contours[0]
length = self._contour_lengths[0]
if length <= 0:
return False
# 钳制距离
d = max(0.0, min(distance, length))
# 二分查找
lo, hi = 0, len(contour) - 1
while lo < hi:
mid = (lo + hi) // 2
if contour[mid][2] < d:
lo = mid + 1
else:
hi = mid
idx = max(1, lo)
px, py, pd = contour[idx - 1]
nx, ny, nd = contour[idx]
seg_len = nd - pd
if seg_len < 1e-10:
t = 0.0
else:
t = (d - pd) / seg_len
x = px + (nx - px) * t
y = py + (ny - py) * t
dx = nx - px
dy = ny - py
dl = math.hypot(dx, dy)
if dl < 1e-10:
tx, ty = 1.0, 0.0
else:
tx, ty = dx / dl, dy / dl
if len(pos) >= 2:
pos[0] = 0.0
pos[1] = 0.0
pos[0] = x
pos[1] = y
if len(tan) >= 2:
tan[0] = 1.0
tan[1] = 0.0
tan[0] = tx
tan[1] = ty
return True
def getMatrix(self, distance: float, matrix: "Matrix", flags: int) -> bool:
return False
"""获取指定距离处的变换矩阵(平移+旋转)。"""
pos = [0.0, 0.0]
tan = [0.0, 0.0]
if not self.getPosTan(distance, pos, tan):
return False
angle = math.degrees(math.atan2(tan[1], tan[0]))
matrix.reset()
matrix.postRotate(angle)
matrix.postTranslate(pos[0], pos[1])
return True
def getSegment(self, startD: float, stopD: float, dst: Path, startWithMoveTo: bool) -> bool:
return False
"""提取路径中 [startD, stopD] 区间的子路径。"""
if not self._contours:
return False
contour = self._contours[0]
length = self._contour_lengths[0]
if length <= 0:
return False
startD = max(0.0, min(startD, length))
stopD = max(0.0, min(stopD, length))
if startD >= stopD:
return False
first = True
for i in range(1, len(contour)):
px, py, pd = contour[i - 1]
nx, ny, nd = contour[i]
if nd < startD:
continue
if pd > stopD:
break
seg_len = nd - pd
if seg_len < 1e-10:
continue
# 计算线段两端在 [startD, stopD] 内的参数
t0 = max(0.0, (startD - pd) / seg_len)
t1 = min(1.0, (stopD - pd) / seg_len)
x0 = px + (nx - px) * t0
y0 = py + (ny - py) * t0
x1 = px + (nx - px) * t1
y1 = py + (ny - py) * t1
if first and startWithMoveTo:
dst.moveTo(x0, y0)
else:
dst.lineTo(x0, y0)
dst.lineTo(x1, y1)
first = False
return True
def isClosed(self) -> bool:
return self._force_closed
if not self._path or not self._path._segments:
return self._force_closed
if self._force_closed:
return True
return any(s.get("type") == "close" for s in self._path._segments)
def nextContour(self) -> bool:
"""移动到下一个轮廓。"""
if len(self._contours) > 1:
self._contours.pop(0)
self._contour_lengths.pop(0)
return True
return False
@@ -1556,35 +1774,99 @@ class Camera(Stub):
self._location_x = 0.0
self._location_y = 0.0
self._location_z = -8.0
self._rot_x = 0.0
self._rot_y = 0.0
self._rot_z = 0.0
self._trans_x = 0.0
self._trans_y = 0.0
self._trans_z = 0.0
self._stack: List[Tuple[float, float, float, float, float, float, float, float, float]] = []
def save(self):
pass
"""保存当前变换状态到栈。"""
self._stack.append((self._rot_x, self._rot_y, self._rot_z,
self._trans_x, self._trans_y, self._trans_z,
self._location_x, self._location_y, self._location_z))
def restore(self):
pass
"""从栈恢复上次保存的变换状态。"""
if self._stack:
(self._rot_x, self._rot_y, self._rot_z,
self._trans_x, self._trans_y, self._trans_z,
self._location_x, self._location_y, self._location_z) = self._stack.pop()
def translate(self, x: float, y: float, z: float):
self._location_x += x
self._location_y += y
self._location_z += z
self._trans_x += x
self._trans_y += y
self._trans_z += z
def rotateX(self, deg: float):
pass
"""绕 X 轴旋转(累积角度)。"""
self._rot_x += deg
def rotateY(self, deg: float):
pass
"""绕 Y 轴旋转(累积角度)。"""
self._rot_y += deg
def rotateZ(self, deg: float):
pass
"""绕 Z 轴旋转(累积角度)。"""
self._rot_z += deg
def rotate(self, x: float, y: float, z: float):
pass
"""同时绕 X/Y/Z 轴旋转。"""
self._rot_x += x
self._rot_y += y
self._rot_z += z
def _compute_matrix(self) -> List[float]:
"""计算 4x4 变换矩阵并投影为 3x3 仿射矩阵。"""
rx = math.radians(self._rot_x)
ry = math.radians(self._rot_y)
rz = math.radians(self._rot_z)
cx, sx = math.cos(rx), math.sin(rx)
cy, sy = math.cos(ry), math.sin(ry)
cz, sz = math.cos(rz), math.sin(rz)
# 旋转矩阵 R = Rz * Ry * Rx
r00 = cz * cy
r01 = cz * sy * sx - sz * cx
r02 = cz * sy * cx + sz * sx
r10 = sz * cy
r11 = sz * sy * sx + cz * cx
r12 = sz * sy * cx - cz * sx
r20 = -sy
r21 = cy * sx
r22 = cy * cx
# 应用平移
tx = self._trans_x
ty = self._trans_y
tz = self._trans_z
# 相机位置(z 越负越远)
cam_z = self._location_z
# 透视投影:z' = z + cam_z,当 cam_z != 0 时做透视除法
# 简化为:将 3D 点投影到 2D
# 点 (x,y,z) -> (x*r00+y*r01+z*r02+tx, x*r10+y*r11+z*r12+ty, x*r20+y*r21+z*r22+tz)
# 透视:screen_x = x' * d / (d - z'), screen_y = y' * d / (d - z')
d = -cam_z # 相机到原点距离
if abs(d) < 1e-10:
# 正交投影
return [r00, r01, tx, r10, r11, ty, 0.0, 0.0, 1.0]
# 透视投影矩阵(近似):将 z 影响缩放到 x,y
# m[0]=r00, m[1]=r01, m[2]=tx, m[3]=r10, m[4]=r11, m[5]=ty
# 透视项:m[6]=r20/d, m[7]=r21/d, m[8]=1 - tz/d
return [r00, r01, tx,
r10, r11, ty,
r20 / d, r21 / d, 1.0 - tz / d]
def getMatrix(self, matrix: Matrix):
matrix.reset()
"""计算变换矩阵并写入给定的 Matrix。"""
matrix._values = self._compute_matrix()
def applyToCanvas(self, canvas: "Canvas"):
pass
"""将相机变换应用到 Canvas。"""
m = Matrix()
self.getMatrix(m)
if hasattr(canvas, "concat"):
canvas.concat(m)
def setLocation(self, x: float, y: float, z: float):
self._location_x = x
@@ -1592,7 +1874,17 @@ class Camera(Stub):
self._location_z = z
def dotWithNormal(self, dx: float, dy: float, dz: float) -> float:
return 0.0
"""计算向量与变换后 Z 轴法线的点积。"""
rx = math.radians(self._rot_x)
ry = math.radians(self._rot_y)
rz = math.radians(self._rot_z)
cy, sy = math.cos(ry), math.sin(ry)
cx, sx = math.cos(rx), math.sin(rx)
# 变换后的 Z 轴法线
nz_x = sy
nz_y = -cy * sx
nz_z = cy * cx
return dx * nz_x + dy * nz_y + dz * nz_z
# ═══════════════════════════════════════════════════════════════
@@ -1660,8 +1952,15 @@ class Picture(Stub):
self._canvas = None
def draw(self, canvas: "Canvas"):
"""回放录制的绘制命令到指定 Canvas。"""
for cmd in self._recorded:
pass
method = cmd.get("method")
args = cmd.get("args", [])
if method and hasattr(canvas, method):
try:
getattr(canvas, method)(*args)
except Exception:
pass
def getWidth(self) -> int:
return self._canvas.getWidth() if hasattr(self, "_canvas") and self._canvas else 0
@@ -1690,7 +1989,9 @@ class PictureDrawable(Stub):
self._picture = picture
def draw(self, canvas: "Canvas"):
pass
"""将 Picture 回放到指定 Canvas。"""
if self._picture is not None:
self._picture.draw(canvas)
# ═══════════════════════════════════════════════════════════════
@@ -1704,6 +2005,10 @@ class SurfaceTexture(Stub):
super().__init__()
self._tex_name = texName
self._timestamp = 0
self._buffer_width = 0
self._buffer_height = 0
self._attached = texName != 0
self._released = False
def getTimestamp(self) -> int:
return self._timestamp
@@ -1712,16 +2017,24 @@ class SurfaceTexture(Stub):
self._timestamp = int(time.time() * 1e9)
def release(self):
pass
"""释放 SurfaceTexture 资源。"""
self._released = True
self._attached = False
def setDefaultBufferSize(self, width: int, height: int):
pass
"""设置默认缓冲区大小。"""
self._buffer_width = width
self._buffer_height = height
def attachToGLContext(self, texName: int):
pass
"""将 SurfaceTexture 附加到 GL 纹理。"""
self._tex_name = texName
self._attached = True
self._released = False
def detachFromGLContext(self):
pass
"""从 GL 纹理分离 SurfaceTexture。"""
self._attached = False
# ═══════════════════════════════════════════════════════════════
@@ -1768,6 +2081,59 @@ class Region(Stub):
return self._rect.contains(x, y)
def op(self, *args) -> bool:
"""对区域执行布尔运算。支持 op(rect, op) / op(region, op) / op(left,top,right,bottom,op)。"""
if len(args) == 2:
operand, op_code = args
if isinstance(operand, Rect):
other = Rect(operand.left, operand.top, operand.right, operand.bottom)
elif isinstance(operand, Region):
other = Rect(operand._rect.left, operand._rect.top,
operand._rect.right, operand._rect.bottom)
else:
return False
elif len(args) == 5:
left, top, right, bottom, op_code = args
other = Rect(left, top, right, bottom)
else:
return False
self_r = self._rect
if op_code == Region.OP_INTERSECT:
nL = max(self_r.left, other.left)
nT = max(self_r.top, other.top)
nR = min(self_r.right, other.right)
nB = min(self_r.bottom, other.bottom)
if nL < nR and nT < nB:
self_r.set(nL, nT, nR, nB)
return True
self_r.setEmpty()
return False
if op_code == Region.OP_UNION:
if self_r.isEmpty():
self_r.set(other.left, other.top, other.right, other.bottom)
else:
self_r.left = min(self_r.left, other.left)
self_r.top = min(self_r.top, other.top)
self_r.right = max(self_r.right, other.right)
self_r.bottom = max(self_r.bottom, other.bottom)
return True
if op_code == Region.OP_REPLACE:
self_r.set(other.left, other.top, other.right, other.bottom)
return True
if op_code == Region.OP_DIFFERENCE:
# 差集:若不相交则不变;若完全包含则清空;否则近似为不变
if not Rect.intersects(self_r, other):
return True
if other.contains(self_r.left, self_r.top) and other.contains(self_r.right - 1, self_r.bottom - 1):
self_r.setEmpty()
return False
return True
if op_code == Region.OP_REVERSE_DIFFERENCE:
self_r.set(other.left, other.top, other.right, other.bottom)
return True
if op_code == Region.OP_XOR:
if not Rect.intersects(self_r, other):
self_r.union(other.left, other.top, other.right, other.bottom)
return True
return False
def isRect(self) -> bool:
@@ -1894,10 +2260,32 @@ class Paint(Stub):
def setTextSize(self, textSize: float):
self._text_size = textSize
self._font_cache = None
def getTextSize(self) -> float:
return self._text_size
def _get_font(self):
"""获取 PIL ImageFont 对象(带缓存)。"""
if not HAS_PIL:
return None
cache_key = (self._text_size, id(self._typeface) if self._typeface else None)
if getattr(self, "_font_cache_key", None) == cache_key and getattr(self, "_font_cache", None):
return self._font_cache
try:
from PIL import ImageFont
font_path = None
if self._typeface and hasattr(self._typeface, "_path") and self._typeface._path:
font_path = self._typeface._path
if font_path and os.path.exists(font_path):
self._font_cache = ImageFont.truetype(font_path, int(self._text_size))
else:
self._font_cache = ImageFont.load_default()
self._font_cache_key = cache_key
return self._font_cache
except Exception:
return None
def setTypeface(self, typeface: Optional[Typeface]):
self._typeface = typeface
@@ -2004,13 +2392,31 @@ class Paint(Stub):
self._shadow_layer = None
def measureText(self, text: str) -> float:
"""测量文本宽度。优先使用 PIL ImageFont 获取精确宽度。"""
font = self._get_font()
if font is not None:
try:
if hasattr(font, "getlength"):
return float(font.getlength(text))
bbox = font.getbbox(text)
return float(bbox[2] - bbox[0])
except Exception:
pass
return len(text) * self._text_size * 0.5
def breakText(self, text: str, maxWidth: float, measuredWidth: List[float]) -> int:
"""在最大宽度内截断文本,返回可容纳的字符数。"""
font = self._get_font()
width = 0.0
count = 0
for i, ch in enumerate(text):
cw = self._text_size * 0.5
if font is not None:
try:
cw = font.getlength(ch) if hasattr(font, "getlength") else (font.getbbox(ch)[2])
except Exception:
cw = self._text_size * 0.5
else:
cw = self._text_size * 0.5
if width + cw > maxWidth:
break
width += cw
@@ -2019,9 +2425,16 @@ class Paint(Stub):
return count
def getTextWidths(self, text: str, widths: List[float]) -> int:
cw = self._text_size * 0.5
"""获取每个字符的宽度。"""
font = self._get_font()
for i, ch in enumerate(text):
widths[i] = cw
if font is not None:
try:
widths[i] = float(font.getlength(ch)) if hasattr(font, "getlength") else float(font.getbbox(ch)[2])
except Exception:
widths[i] = self._text_size * 0.5
else:
widths[i] = self._text_size * 0.5
return len(text)
def getTextPath(self, text: str, start: int, end: int, x: float, y: float, path: Path):
@@ -2030,7 +2443,22 @@ class Paint(Stub):
path.lineTo(x + (i - start + 1) * self._text_size * 0.3, y)
def getFontMetrics(self) -> Dict[str, float]:
"""返回字体度量信息。优先使用 PIL ImageFont 获取真实度量。"""
size = self._text_size
font = self._get_font()
if font is not None:
try:
if hasattr(font, "getmetrics"):
ascent, descent = font.getmetrics()
return {
"ascent": -float(ascent),
"descent": float(descent),
"top": -float(ascent) - 2.0,
"bottom": float(descent) + 2.0,
"leading": 0.0,
}
except Exception:
pass
return {
"ascent": -size * 0.8,
"descent": size * 0.2,
@@ -2068,6 +2496,19 @@ class Paint(Stub):
return getattr(self, '_stroke_miter', 4.0)
def getTextBounds(self, text, start, end, bounds):
"""返回文本边界矩形。优先使用 PIL ImageFont 获取真实边界。"""
font = self._get_font()
if font is not None:
try:
sub = text[start:end]
bbox = font.getbbox(sub)
bounds.left = int(bbox[0])
bounds.top = int(bbox[1])
bounds.right = int(bbox[2])
bounds.bottom = int(bbox[3])
return
except Exception:
pass
if hasattr(self, '_text_size'):
w = len(text[start:end]) * self._text_size * 0.5
h = self._text_size
@@ -2312,13 +2753,30 @@ class Bitmap(Stub):
return Bitmap(self._width, self._height, Bitmap.Config.ALPHA_8)
def sameAs(self, other: "Bitmap") -> bool:
"""比较两个 Bitmap 的像素是否完全相同。"""
if not isinstance(other, Bitmap):
return False
if self._width != other._width or self._height != other._height:
return False
if self._config != other._config:
return False
if self._recycled or other._recycled:
return False
# 使用 PIL 比较像素
if HAS_PIL and hasattr(self, "_image") and hasattr(other, "_image"):
try:
from PIL import ImageChops
diff = ImageChops.difference(self._image, other._image)
return diff.getbbox() is None
except Exception:
return list(self._image.getdata()) == list(other._image.getdata())
# 回退到字节比较
if hasattr(self, "_pixels") and hasattr(other, "_pixels"):
return bytes(self._pixels) == bytes(other._pixels)
return True
def prepareToDraw(self):
"""提示将位图数据上传到 GPU 缓存。桌面端为空操作。"""
pass
def hasAlpha(self) -> bool:
@@ -2407,6 +2865,29 @@ class BitmapFactory(Stub):
@staticmethod
def decodeResource(res, resId: int, opts: Options = None) -> Optional[Bitmap]:
"""从资源解码位图。尝试通过 Resources 打开原始资源流。"""
# 尝试通过 Resources 获取资源路径或流
if res is not None:
try:
# 尝试 openRawResource
stream = res.openRawResource(resId)
if stream is not None:
return BitmapFactory.decodeStream(stream, None, opts)
except Exception:
pass
try:
# 尝试通过资源名查找文件
name = res.getResourceEntryName(resId) if hasattr(res, "getResourceEntryName") else None
if name and name != "unknown":
for ext in (".png", ".jpg", ".jpeg", ".webp", ".bmp"):
candidate = resolve_android_path("res/drawable/" + name + ext)
if os.path.exists(candidate):
return BitmapFactory.decodeFile(candidate, opts)
candidate = resolve_android_path("res/drawable-xxhdpi/" + name + ext)
if os.path.exists(candidate):
return BitmapFactory.decodeFile(candidate, opts)
except Exception:
pass
return Bitmap(100, 100, Bitmap.Config.ARGB_8888)
@staticmethod
@@ -2610,7 +3091,22 @@ class Canvas(Stub):
return Rect(r.left, r.top, r.right, r.bottom)
def quickReject(self, *args) -> bool:
return False
"""判断给定矩形是否完全在裁剪区域之外。"""
clip = self._clip_stack[-1]
if len(args) == 1:
r = args[0]
if isinstance(r, Rect):
left, top, right, bottom = r.left, r.top, r.right, r.bottom
elif isinstance(r, RectF):
left, top, right, bottom = r.left, r.top, r.right, r.bottom
else:
return False
elif len(args) == 4:
left, top, right, bottom = args
else:
return False
return (right <= clip.left or left >= clip.right or
bottom <= clip.top or top >= clip.bottom)
def drawColor(self, color: int):
if HAS_PIL and self._draw is not None:
@@ -2810,7 +3306,37 @@ class Canvas(Stub):
self._draw.text((x, y), text, fill=fill_color)
def drawTextOnPath(self, text: str, path: Path, hOffset: float, vOffset: float, paint: Paint):
pass
"""沿路径绘制文本。使用 PathMeasure 逐字符放置。"""
if not text or not HAS_PIL or self._draw is None:
return
pm = PathMeasure(path, False)
total = pm.getLength()
if total <= 0:
return
c = paint.getColor()
fill_color = (Color.red(c), Color.green(c), Color.blue(c), Color.alpha(c))
dist = hOffset
pos = [0.0, 0.0]
tan = [0.0, 0.0]
for ch in text:
cw = paint.measureText(ch)
if dist > total:
break
if pm.getPosTan(dist, pos, tan):
angle = math.degrees(math.atan2(tan[1], tan[0]))
# 使用 PIL 旋转绘制每个字符
try:
from PIL import Image as _PILImage
char_img = _PILImage.new("RGBA", (int(cw) + 4, int(paint._text_size) + 4), (0, 0, 0, 0))
from PIL import ImageDraw as _PILDraw
cd = _PILDraw.Draw(char_img)
cd.text((0, 0), ch, fill=fill_color)
rotated = char_img.rotate(-angle, expand=True, resample=_PILImage.BICUBIC)
self._image.paste(rotated, (int(pos[0] + vOffset * tan[1]), int(pos[1] - vOffset * tan[0])),
rotated)
except Exception:
self._draw.text((pos[0], pos[1]), ch, fill=fill_color)
dist += cw
def drawPosText(self, text: str, pos: List[float], paint: Paint):
if HAS_PIL and self._draw is not None:
@@ -2821,14 +3347,44 @@ class Canvas(Stub):
self._draw.text((pos[i * 2], pos[i * 2 + 1]), ch, fill=fill_color)
def drawPicture(self, picture: Picture, dst: Union[Rect, RectF] = None):
pass
"""回放 Picture 中录制的绘制命令。"""
if picture is None:
return
recorded = getattr(picture, "_recorded", [])
if not recorded:
return
for cmd in recorded:
method = cmd.get("method")
args = cmd.get("args", [])
if method and hasattr(self, method):
try:
getattr(self, method)(*args)
except Exception:
pass
def drawPatch(self, patch: NinePatch, dst: Rect, paint: Paint = None):
pass
"""绘制 NinePatch(拉伸位图)到目标矩形。"""
if patch is None or patch._bitmap is None:
return
bitmap = patch._bitmap
if dst is None:
self.drawBitmap(bitmap, 0, 0, paint)
return
# 简单缩放绘制
if isinstance(dst, (Rect, RectF)):
dw = dst.right - dst.left
dh = dst.bottom - dst.top
else:
dw, dh = dst[2] - dst[0], dst[3] - dst[1]
scaled = Bitmap.createScaledBitmap(bitmap, int(dw), int(dh), True)
self.drawBitmap(scaled, dst.left if hasattr(dst, "left") else dst[0],
dst.top if hasattr(dst, "top") else dst[1], paint)
def drawDoubleRoundRect(self, outer: RectF, outerRx: float, outerRy: float,
inner: RectF, innerRx: float, innerRy: float, paint: Paint):
pass
"""绘制双层圆角矩形(外层和内层)。"""
self.drawRoundRect(outer, outerRx, outerRy, paint)
self.drawRoundRect(inner, innerRx, innerRy, paint)
def drawBitmapMesh(self, bitmap: Bitmap, meshWidth: int, meshHeight: int,
verts: List[float], vertOffset: int, colors: List[int],
@@ -2848,10 +3404,12 @@ class Canvas(Stub):
return False
def setDensity(self, density: int):
pass
"""设置画布密度(DPI)。"""
self._density = density
def getDensity(self) -> int:
return 0
"""返回画布密度(DPI),默认 0 表示未知。"""
return getattr(self, "_density", 0)
def toString(self) -> str:
return f"Canvas({self._width}x{self._height})"