feat: 全面完善API — 补全核心类方法、存根转真实实现、Exception体系扩展

核心类方法补全:
- Activity: +20方法 (requestWindowFeature, setTitle/getTitle, onKeyDown/onKeyUp, enterPictureInPictureMode, getReferrer, isTaskRoot, navigateUpTo等)
- Fragment: +15方法 (getChildFragmentManager, getParentFragment, requireContext/requireActivity, setMenuVisibility等)
- Intent: +20方法 (getParcelableExtra, setComponent/getComponent, getDataString, getScheme, putExtras等)
- View: +20方法 (setLabelFor, setRotationX/Y, setForeground, setBackgroundTintList等)
- KeyEvent: +7方法 (getCharacters, isSystem, isPrintingKey, isModifierKey, startTracking等)
- Paint: +12方法 (setStrokeCap/Join, getTextBounds, setBlendMode, setLetterSpacing等)
- Notification.Builder: +10方法 (setProgress, setShowWhen, setOnlyAlertOnce, setSound等)

存根转真实实现:
- TrafficStats: 读取 /proc/net/dev 返回真实网络流量
- RecyclerView.Adapter: 观察者模式触发通知
- BaseAdapter: 观察者模式触发通知
- ExifInterface: 使用PIL实现图片翻转/旋转
- Process: 使用os.kill实现进程信号
- FileUtils: 真实文件I/O
- SharedPreferences: 监听器模式
- TelephonyManager.listen: 维护监听器列表
- ScrollView.smoothScrollTo: 更新滚动位置

Exception体系扩展:
- 新增15个Exception/Error类 (ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchElementException, AssertionError, OutOfMemoryError, StackOverflowError, NoClassDefFoundError, UnsatisfiedLinkError, LinkageError, VerifyError, AbstractMethodError, IncompatibleClassChangeError, ExceptionInInitializerError)

Math/System/Thread/StringBuilder/Integer增强:
- Math: +20方法 (subtractExact, multiplyExact, toIntExact, floorDiv/floorMod, fma等)
- System: getProperties/setProperties/mapLibraryName/getLogger
- Thread: +10方法 (getUncaughtExceptionHandler, getAllStackTraces, getStackTrace, holdLock等)
- StringBuilder: +7方法 (ensureCapacity, trimToSize, setLength, subSequence等)
- Integer: +8静态方法 (compare, compareUnsigned, divideUnsigned, sum, max, min等)

修复:
- app_fragment.py: 使用java.lang.IllegalStateException替代本地定义
- content_intent.py: Intent.setData/getScheme正确从Uri提取scheme
- view_ext.py: KeyEvent继承view.KeyEvent获取所有KEYCODE常量
- __init__.py: 补全Fragment/KeyEvent/MotionEvent/TrafficStats/RecyclerView等导出

统计: 221模块, 3660类, 30826方法, 180217行
This commit is contained in:
shuiyu1123 committed 2026-06-24 23:46:36 +00:00
1 parent e316e18d98
commit 39b1da37c9
16 files changed
+1274 -107

No files matched your search

+339
View File
@@ -18,6 +18,7 @@ from .._core import (
resolve_android_path, to_android_path,
_sdcard_root, _app_data_dir, _package_name,
)
from ..java.lang import IllegalStateException
try:
from .._linux_impl import IS_LINUX, LinuxNotify, LinuxShell, LinuxProcess, LinuxDisplay, LinuxUIAutomation, LinuxPower
@@ -247,6 +248,108 @@ class Intent(Stub):
def filterHashCode(self) -> int:
return hash((self._action, self._data_str, self._type, self._package, self._component)) & 0x7FFFFFFF
def getParcelableExtra(self, name):
return self._extras.get(name) if self._extras else None
def setComponent(self, component):
self._component = component
return self
def getComponent(self):
return getattr(self, '_component', None)
def getDataString(self):
data = self.getData()
return str(data) if data else None
def getScheme(self):
data = self.getData()
if data:
return data.getScheme()
return None
def putExtras(self, src):
if src._extras:
if self._extras is None:
self._extras = {}
self._extras.update(src._extras)
return self
def putIntegerArrayListExtra(self, name, value):
if self._extras is None:
self._extras = {}
self._extras[name] = value
return self
def putStringArrayListExtra(self, name, value):
if self._extras is None:
self._extras = {}
self._extras[name] = value
return self
def putParcelableArrayListExtra(self, name, value):
if self._extras is None:
self._extras = {}
self._extras[name] = value
return self
def getBundleExtra(self, name):
return self._extras.get(name) if self._extras else None
def putBundleExtra(self, name, value):
if self._extras is None:
self._extras = {}
self._extras[name] = value
return self
def getParcelableArrayListExtra(self, name):
return self._extras.get(name) if self._extras else None
def getStringArrayExtra(self, name):
return self._extras.get(name) if self._extras else None
def getIntArrayExtra(self, name):
return self._extras.get(name) if self._extras else None
def getBooleanArrayExtra(self, name):
return self._extras.get(name) if self._extras else None
def getCharSequenceExtra(self, name):
return self._extras.get(name) if self._extras else None
def getCharSequenceArrayListExtra(self, name):
return self._extras.get(name) if self._extras else None
def removeCategory(self, category):
if self._categories:
self._categories.discard(category)
return self
def setSelector(self, selector):
self._selector = selector
def getSelector(self):
return getattr(self, '_selector', None)
def setSourceBounds(self, bounds):
self._source_bounds = bounds
def getSourceBounds(self):
return getattr(self, '_source_bounds', None)
def makeMainActivity(self, component):
self.addCategory("android.intent.category.LAUNCHER")
self.setComponent(component)
return self
def cloneFilter(self):
new_intent = Intent(self.getAction())
if self._categories:
new_intent._categories = set(self._categories)
if self._type:
new_intent._type = self._type
return new_intent
# ═══════════════════════════════════════════════════════════════
# ComponentName (forward reference)
@@ -738,6 +841,62 @@ class Notification(Stub):
n._extras = dict(self._extras)
return n
def setProgress(self, max, progress, indeterminate):
self._progress_max = max
self._progress = progress
self._progress_indeterminate = indeterminate
return self
def setShowWhen(self, show):
self._show_when = show
return self
def setOnlyAlertOnce(self, onlyAlertOnce):
self._only_alert_once = onlyAlertOnce
return self
def setLocalOnly(self, localOnly):
self._local_only = localOnly
return self
def setSound(self, sound):
self._sound = sound
return self
def setVibrate(self, pattern):
self._vibrate = pattern
return self
def setLights(self, argb, onMs, offMs):
self._light_color = argb
self._light_on = onMs
self._light_off = offMs
return self
def setSortKey(self, sortKey):
self._sort_key = sortKey
return self
def setTimeoutAfter(self, timeoutMs):
self._timeout_after = timeoutMs
return self
def setColorized(self, colorize):
self._colorized = colorize
return self
def setUsesChronometer(self, usesChronometer):
self._uses_chronometer = usesChronometer
return self
def setShortcutId(self, shortcutId):
self._shortcut_id = shortcutId
return self
def setContent(self, views):
self._content_view = views
return self
def __init__(self):
super().__init__()
self._channel_id = ""
@@ -1355,6 +1514,90 @@ class Fragment(Stub):
def unregisterForContextMenu(self, view):
pass
def getChildFragmentManager(self):
if not hasattr(self, '_child_fragment_manager'):
from .app import FragmentManager
self._child_fragment_manager = FragmentManager()
return self._child_fragment_manager
def getParentFragment(self):
return getattr(self, '_parent_fragment', None)
def onCreateOptionsMenu(self, menu, inflater):
return False
def onPrepareOptionsMenu(self, menu):
return False
def onOptionsItemSelected(self, item):
return False
def onCreateContextMenu(self, menu, v, menuInfo):
pass
def onContextItemSelected(self, item):
return False
def onContextMenuClosed(self, menu):
pass
def onConfigurationChanged(self, newConfig):
pass
def onLowMemory(self):
pass
def onHiddenChanged(self, hidden):
self._hidden = hidden
def getLayoutInflater(self):
return getattr(self, '_layout_inflater', None)
def onGetLayoutInflater(self, savedInstanceState):
return self.getLayoutInflater()
def requireContext(self):
if not hasattr(self, '_context') or self._context is None:
raise IllegalStateException("Fragment " + self.__class__.__name__ + " not attached to a context")
return self._context
def requireActivity(self):
if not hasattr(self, '_activity') or self._activity is None:
raise IllegalStateException("Fragment " + self.__class__.__name__ + " not attached to an activity")
return self._activity
def requireView(self):
if not hasattr(self, '_view') or self._view is None:
raise IllegalStateException("Fragment " + self.__class__.__name__ + " view is null")
return self._view
def requireFragmentManager(self):
fm = getattr(self, '_fragment_manager', None)
if fm is None:
raise IllegalStateException("Fragment " + self.__class__.__name__ + " not associated with a fragment manager")
return fm
def setMenuVisibility(self, menuVisible):
self._menu_visible = menuVisible
def onMenuVisibilityChanged(self, menuVisible):
pass
def postponeEnterTransition(self):
pass
def startPostponedEnterTransition(self):
pass
def getEnterTransition(self):
return getattr(self, '_enter_transition', None)
def setEnterTransition(self, transition):
self._enter_transition = transition
def onInflate(self, context, attrs, savedInstanceState):
pass
class FragmentTransaction(Stub):
"""FragmentTransaction — 管理 Fragment 事务"""
@@ -1875,6 +2118,102 @@ class Activity(Stub):
return LinuxUIAutomation.press_key(key)
return False
def requestWindowFeature(self, featureId):
self._window_features = getattr(self, '_window_features', set())
self._window_features.add(featureId)
return True
def setTitle(self, title):
self._title = title
def getTitle(self):
return getattr(self, '_title', '')
def onKeyDown(self, keyCode, event):
return False
def onKeyUp(self, keyCode, event):
return False
def onTouchEvent(self, event):
return False
def enterPictureInPictureMode(self):
pass
def getReferrer(self):
return getattr(self, '_referrer', None)
def setReferrer(self, referrer):
self._referrer = referrer
def isTaskRoot(self):
return True
def getChangingConfigurations(self):
return getattr(self, '_changing_config', 0)
def navigateUpTo(self, intent):
return True
def shouldUpRecreateTask(self, intent):
return False
def getParentActivityIntent(self):
return None
def onOptionsMenuClosed(self, menu):
pass
def setVolumeControlStream(self, streamType):
self._volume_control_stream = streamType
def getVolumeControlStream(self):
return getattr(self, '_volume_control_stream', 0)
def getLocalClassName(self):
return self.__class__.__name__
def finishAndRemoveTask(self):
self.finish()
def getLoaderManager(self):
if not hasattr(self, '_loader_manager'):
self._loader_manager = type('LoaderManager', (), {
'initLoader': lambda *a: None,
'restartLoader': lambda *a: None,
'destroyLoader': lambda *a: None,
'getLoader': lambda *a: None,
})()
return self._loader_manager
def onRetainNonConfigurationInstance(self):
return None
def getLastNonConfigurationInstance(self):
return getattr(self, '_last_non_config_instance', None)
def setProgressBarVisibility(self, visible):
pass
def setProgress(self, progress):
pass
def setSecondaryProgress(self, secondaryProgress):
pass
def onSearchRequested(self):
return False
def startSearch(self, initialQuery, selectInitial, appSearchData, globalSearch):
pass
def startIntentSender(self, intent, fillInIntent, flagsMask, flagsValues, extraFlags):
pass
def startIntentSenderForResult(self, intent, requestCode, fillInIntent, flagsMask, flagsValues, extraFlags):
pass
# ═══════════════════════════════════════════════════════════════
# Service
+1 -8
View File
@@ -11,9 +11,7 @@ from typing import Any, Dict, List, Optional, Set, Tuple, Callable
from enum import IntEnum
from .._core import Stub, AndroidOnlyError
from ..java.lang import RuntimeException
IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {})
from ..java.lang import RuntimeException, IllegalArgumentException, IllegalStateException
# ═══════════════════════════════════════════════════════════════
@@ -424,11 +422,6 @@ class Fragment(Stub):
return f"Fragment({self._mTag or hex(id(self))})"
class IllegalStateException(RuntimeException):
"""IllegalStateException — 非法状态异常"""
pass
# ═══════════════════════════════════════════════════════════════
# FragmentManager
# ═══════════════════════════════════════════════════════════════
+14 -2
View File
@@ -1116,10 +1116,16 @@ class SharedPreferences(Stub):
return SharedPreferencesEditor(self)
def registerOnSharedPreferenceChangeListener(self, listener):
pass
if not hasattr(self, '_listeners'):
self._listeners = []
self._listeners.append(listener)
def unregisterOnSharedPreferenceChangeListener(self, listener):
pass
if hasattr(self, '_listeners'):
try:
self._listeners.remove(listener)
except ValueError:
pass
def getSharedPrefsFile(self):
return self._file_path
@@ -1186,6 +1192,12 @@ class SharedPreferencesEditor(Stub):
self._prefs._data.pop(key, None)
self._prefs._data.update(self._modified)
self._prefs._save()
# 触发监听器
sp = getattr(self, '_prefs', None)
if sp:
for key in self._modified:
for listener in getattr(sp, '_listeners', []):
listener.onSharedPreferenceChanged(sp, key)
self._modified.clear()
self._removed.clear()
self._clear_all = False
+23 -3
View File
@@ -761,16 +761,36 @@ class Intent(Stub):
def getData(self) -> Optional[str]:
return self._data
def setData(self, data: str) -> "Intent":
def setData(self, data) -> "Intent":
self._data = data
self._type = None
# 从 Uri 提取 scheme
if data is not None:
if hasattr(data, 'getScheme'):
self._scheme = data.getScheme() or ""
elif isinstance(data, str):
from urllib.parse import urlparse
self._scheme = urlparse(data).scheme
return self
def getDataString(self) -> Optional[str]:
return self._data
if self._data is None:
return None
if isinstance(self._data, str):
return self._data
return str(self._data)
def getScheme(self) -> str:
return self._scheme
if self._scheme:
return self._scheme
# 从 data 动态提取
if self._data is not None:
if hasattr(self._data, 'getScheme'):
return self._data.getScheme() or ""
elif isinstance(self._data, str):
from urllib.parse import urlparse
return urlparse(self._data).scheme
return ""
# ── Type ──
+63
View File
@@ -2049,6 +2049,69 @@ class Paint(Stub):
def descent(self) -> float:
return self._text_size * 0.2
def setStrokeCap(self, cap):
self._stroke_cap = cap
def getStrokeCap(self):
return getattr(self, '_stroke_cap', 0)
def setStrokeJoin(self, join):
self._stroke_join = join
def getStrokeJoin(self):
return getattr(self, '_stroke_join', 0)
def setStrokeMiter(self, miter):
self._stroke_miter = miter
def getStrokeMiter(self):
return getattr(self, '_stroke_miter', 4.0)
def getTextBounds(self, text, start, end, bounds):
if hasattr(self, '_text_size'):
w = len(text[start:end]) * self._text_size * 0.5
h = self._text_size
else:
w = len(text[start:end]) * 12
h = 24
bounds.left = 0
bounds.top = -int(h * 0.8)
bounds.right = int(w)
bounds.bottom = int(h * 0.2)
def setBlendMode(self, blendmode):
self._blend_mode = blendmode
def getBlendMode(self):
return getattr(self, '_blend_mode', None)
def setLetterSpacing(self, letterSpacing):
self._letter_spacing = letterSpacing
def getLetterSpacing(self):
return getattr(self, '_letter_spacing', 0.0)
def setFontFeatureSettings(self, settings):
self._font_feature_settings = settings
def getFontFeatureSettings(self):
return getattr(self, '_font_feature_settings', None)
def setHinting(self, mode):
self._hinting = mode
def getHinting(self):
return getattr(self, '_hinting', 2) # HINTING_ON
def setFakeBoldText(self, fakeBoldText):
self._fake_bold_text = fakeBoldText
def isFakeBoldText(self):
return getattr(self, '_fake_bold_text', False)
def hasGlyph(self, string):
return len(string) > 0
# ═══════════════════════════════════════════════════════════════
# Bitmap
+22 -4
View File
@@ -1817,6 +1817,7 @@ class ExifInterface(Stub):
super().__init__()
self._filename = resolve_android_path(filename)
self._attributes: Dict[str, str] = {}
self._pil_image = None
self._load_exif()
def _load_exif(self):
@@ -1828,6 +1829,7 @@ class ExifInterface(Stub):
from PIL import ExifTags
try:
img = Image.open(self._filename)
self._pil_image = img
exif_data = img._getexif()
if exif_data:
for tag_id, value in exif_data.items():
@@ -1899,13 +1901,29 @@ class ExifInterface(Stub):
pass
def flipHorizontally(self):
pass
if hasattr(self, '_pil_image') and self._pil_image:
from PIL import Image
self._pil_image = self._pil_image.transpose(Image.FLIP_LEFT_RIGHT)
self._save_from_pil()
def flipVertically(self):
pass
if hasattr(self, '_pil_image') and self._pil_image:
from PIL import Image
self._pil_image = self._pil_image.transpose(Image.FLIP_TOP_BOTTOM)
self._save_from_pil()
def rotate(self, degree: int):
pass
def rotate(self, degrees):
if hasattr(self, '_pil_image') and self._pil_image:
from PIL import Image
self._pil_image = self._pil_image.rotate(degrees, expand=True)
self._save_from_pil()
# 更新方向属性
self.setAttribute("Orientation", "1") # Normal after rotation
def _save_from_pil(self):
"""从 PIL 图像保存回文件"""
if hasattr(self, '_pil_image') and self._pil_image and hasattr(self, '_filename'):
self._pil_image.save(self._filename)
# ═══════════════════════════════════════════════════════════════
+66 -33
View File
@@ -1079,53 +1079,98 @@ class TrafficStats(Stub):
UNSUPPORTED = -1
TAG_SYSTEM_UPDATE = 0
_cached_stats = None
_cache_time = 0
@staticmethod
def getMobileTxBytes() -> int:
def _read_net_dev():
"""读取 /proc/net/dev 获取网络流量"""
try:
with open("/proc/net/dev", "r") as f:
lines = f.readlines()[2:] # 跳过前两行表头
total_rx = 0
total_tx = 0
for line in lines:
parts = line.split()
ifname = parts[0].rstrip(":")
if ifname == "lo":
continue
rx_bytes = int(parts[1])
tx_bytes = int(parts[9])
total_rx += rx_bytes
total_tx += tx_bytes
return {"rx_bytes": total_rx, "tx_bytes": total_tx,
"rx_packets": 0, "tx_packets": 0}
except Exception:
return {"rx_bytes": 0, "tx_bytes": 0, "rx_packets": 0, "tx_packets": 0}
@staticmethod
def getMobileRxBytes():
return 0 # 桌面无移动数据
@staticmethod
def getMobileTxBytes():
return 0
@staticmethod
def getMobileRxBytes() -> int:
def getMobileRxPackets():
return 0
@staticmethod
def getTotalTxBytes() -> int:
def getMobileTxPackets():
return 0
@staticmethod
def getTotalRxBytes() -> int:
return 0
def getTotalRxBytes():
return TrafficStats._read_net_dev()["rx_bytes"]
@staticmethod
def getMobileTxPackets() -> int:
return 0
def getTotalTxBytes():
return TrafficStats._read_net_dev()["tx_bytes"]
@staticmethod
def getMobileRxPackets() -> int:
return 0
def getTotalRxPackets():
return TrafficStats._read_net_dev()["rx_packets"]
@staticmethod
def getTotalTxPackets() -> int:
return 0
def getTotalTxPackets():
return TrafficStats._read_net_dev()["tx_packets"]
@staticmethod
def getTotalRxPackets() -> int:
return 0
def getUidRxBytes(uid):
try:
with open(f"/proc/uid_stat/{uid}/tcp_rcv", "r") as f:
return int(f.read().strip())
except Exception:
return 0
@staticmethod
def getUidTxBytes(uid: int) -> int:
return 0
def getUidTxBytes(uid):
try:
with open(f"/proc/uid_stat/{uid}/tcp_snd", "r") as f:
return int(f.read().strip())
except Exception:
return 0
@staticmethod
def getUidRxBytes(uid: int) -> int:
return 0
def getUidRxPackets(uid):
return TrafficStats.getUidRxBytes(uid)
@staticmethod
def getUidTxPackets(uid: int) -> int:
return 0
def getUidTxPackets(uid):
return TrafficStats.getUidTxBytes(uid)
@staticmethod
def getUidRxPackets(uid: int) -> int:
return 0
def getThreadStatsTag():
return getattr(TrafficStats, '_thread_tag', 0)
@staticmethod
def setThreadStatsTag(tag):
TrafficStats._thread_tag = tag
@staticmethod
def clearThreadStatsTag():
TrafficStats._thread_tag = 0
@staticmethod
def getUidTcpTxBytes(uid: int) -> int:
@@ -1143,18 +1188,6 @@ class TrafficStats(Stub):
def getUidUdpRxBytes(uid: int) -> int:
return 0
@staticmethod
def setThreadStatsTag(tag: int):
pass
@staticmethod
def clearThreadStatsTag():
pass
@staticmethod
def getThreadStatsTag() -> int:
return 0
@staticmethod
def setThreadStatsUid(uid: int):
pass
+57 -14
View File
@@ -316,20 +316,30 @@ class Process(Stub):
return _os.getpid()
@staticmethod
def killProcess(pid: int):
pass
def killProcess(pid):
import signal
try:
_os.kill(pid, signal.SIGKILL)
except (OSError, ProcessLookupError):
pass
@staticmethod
def sendSignal(pid: int, signal: int):
pass
def sendSignal(pid, signal_num):
try:
_os.kill(pid, signal_num)
except (OSError, ProcessLookupError):
pass
@staticmethod
def setThreadPriority(priority: int):
pass
@staticmethod
def setThreadPriority(tid: int, priority: int):
pass
def setThreadPriority(tid, priority):
try:
_os.setpriority(_os.PRIO_PROCESS, tid, priority)
except (OSError, PermissionError):
pass
@staticmethod
def getElapsedCpuTime() -> int:
@@ -1925,20 +1935,53 @@ class FileUtils(Stub):
"""FileUtils — 文件工具"""
@staticmethod
def readFileToString(file: Any) -> str:
return ""
def readFileToString(path):
try:
with open(path, "r", encoding="utf-8") as f:
return f.read()
except Exception:
return ""
@staticmethod
def stringToFile(file: Any, string: str):
pass
def stringToFile(path, content):
try:
with open(path, "w", encoding="utf-8") as f:
f.write(content)
return True
except Exception:
return False
@staticmethod
def copyFile(src: Any, dest: Any) -> bool:
return True
def copyFile(src, dst):
try:
import shutil
shutil.copy2(src, dst)
return True
except Exception:
return False
@staticmethod
def deleteDirectory(dir_: Any) -> bool:
return True
def deleteDirectory(path):
try:
import shutil
shutil.rmtree(path)
return True
except Exception:
return False
@staticmethod
def deleteContents(path):
try:
import shutil
for entry in _os.listdir(path):
full = _os.path.join(path, entry)
if _os.path.isdir(full):
shutil.rmtree(full)
else:
_os.remove(full)
return True
except Exception:
return False
@staticmethod
def deleteOlderFiles(dir_: Any, min_count: int, min_age_ms: int):
+4 -2
View File
@@ -661,8 +661,10 @@ class TelephonyManager(Stub):
def getMmsUAProfUrl(self) -> str:
return "http://www.example.com/uaprof.xml"
def listen(self, listener: PhoneStateListener, events: int):
pass
def listen(self, listener, events):
if not hasattr(self, '_listeners'):
self._listeners = []
self._listeners.append((listener, events))
def getDataState(self) -> int:
return self.DATA_CONNECTED
+133 -7
View File
@@ -938,6 +938,106 @@ class View(Stub):
def onApplyWindowInsets(self, insets):
return insets
def setOnCreateContextMenuListener(self, listener):
self._on_create_context_menu_listener = listener
return self
def getBaseline(self):
return -1
def setFocusableInTouchMode(self, focusable):
self._focusable_in_touch_mode = focusable
def isFocusableInTouchMode(self):
return getattr(self, '_focusable_in_touch_mode', False)
def setRotationX(self, rotationX):
self._rotation_x = rotationX
def getRotationX(self):
return getattr(self, '_rotation_x', 0.0)
def setRotationY(self, rotationY):
self._rotation_y = rotationY
def getRotationY(self):
return getattr(self, '_rotation_y', 0.0)
def setCameraDistance(self, distance):
self._camera_distance = distance
def getCameraDistance(self):
return getattr(self, '_camera_distance', 0.0)
def setForeground(self, foreground):
self._foreground = foreground
def getForeground(self):
return getattr(self, '_foreground', None)
def setBackgroundTintList(self, tint):
self._background_tint = tint
def setBackgroundTintMode(self, tintMode):
self._background_tint_mode = tintMode
def setScrollbarFadingEnabled(self, fadeScrollbars):
self._fade_scrollbars = fadeScrollbars
def isScrollbarFadingEnabled(self):
return getattr(self, '_fade_scrollbars', True)
def setVerticalScrollBarEnabled(self, enabled):
self._vertical_scrollbar = enabled
def isVerticalScrollBarEnabled(self):
return getattr(self, '_vertical_scrollbar', True)
def setHorizontalScrollBarEnabled(self, enabled):
self._horizontal_scrollbar = enabled
def isHorizontalScrollBarEnabled(self):
return getattr(self, '_horizontal_scrollbar', True)
def setLabelFor(self, id):
self._label_for = id
def getLabelFor(self):
return getattr(self, '_label_for', 0)
def cancelLongPress(self):
pass
def setPaddingRelative(self, start, top, end, bottom):
self._padding_start = start
self._padding_top = top
self._padding_end = end
self._padding_bottom = bottom
def setSoundEffectsEnabled(self, soundEffectsEnabled):
self._sound_effects = soundEffectsEnabled
def isSoundEffectsEnabled(self):
return getattr(self, '_sound_effects', True)
def setHapticFeedbackEnabled(self, hapticFeedbackEnabled):
self._haptic_feedback = hapticFeedbackEnabled
def isHapticFeedbackEnabled(self):
return getattr(self, '_haptic_feedback', True)
def refreshDrawableState(self):
pass
def getDrawableState(self):
return []
def requestRectangleOnScreen(self, rectangle):
return False
def setAllCaps(self, allCaps):
self._all_caps = allCaps
class MeasureSpec:
"""MeasureSpec — 测量规格包装"""
@@ -1993,6 +2093,28 @@ class KeyEvent(Stub):
meta_state=event._meta_state,
)
def getCharacters(self):
return getattr(self, '_characters', None)
def isSystem(self):
return False
def isPrintingKey(self):
return False
def isModifierKey(self, keyCode):
return keyCode in (KEYCODE_SHIFT_LEFT, KEYCODE_SHIFT_RIGHT, KEYCODE_ALT_LEFT, KEYCODE_ALT_RIGHT, KEYCODE_CTRL_LEFT, KEYCODE_CTRL_RIGHT, KEYCODE_META_LEFT, KEYCODE_META_RIGHT)
def startTracking(self):
self._tracking = True
def isTracking(self):
return getattr(self, '_tracking', False)
@staticmethod
def keyCodeToString(keyCode):
return f"KEYCODE_{keyCode}"
# ═══════════════════════════════════════════════════════════════
# GestureDetector
@@ -2700,14 +2822,18 @@ class SurfaceHolder(Stub):
def setFormat(self, format: int):
self._format = format
def lockCanvas(self) -> Any:
return None
def lockCanvas(self, *args):
"""锁定画布并返回 Canvas 对象"""
if hasattr(self, '_surface') and self._surface:
from .graphics import Canvas, Bitmap
if hasattr(self._surface, '_bitmap') and self._surface._bitmap:
return Canvas(self._surface._bitmap)
from .graphics import Canvas, Bitmap
return Canvas(Bitmap(100, 100))
def lockCanvas(self, dirty: Any) -> Any:
return None
def unlockCanvasAndPost(self, canvas: Any):
pass
def unlockCanvasAndPost(self, canvas):
"""解锁画布并提交"""
pass # 桌面环境无需实际提交
# ═══════════════════════════════════════════════════════════════
+3 -2
View File
@@ -13,6 +13,7 @@ from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union
from .._core import Stub, AndroidOnlyError
from ..java.lang import RuntimeException
from .view import KeyEvent as _BaseKeyEvent
# ═══════════════════════════════════════════════════════════════
@@ -1559,8 +1560,8 @@ class GestureDetector(Stub):
return f"GestureDetector(listener={'yes' if self._listener else 'no'})"
class KeyEvent(Stub):
"""KeyEvent — 按键事件"""
class KeyEvent(_BaseKeyEvent):
"""KeyEvent — 按键事件 (扩展版,继承 view.KeyEvent 的所有常量)"""
ACTION_DOWN = 0
ACTION_UP = 1
+61 -29
View File
@@ -1260,28 +1260,48 @@ class RecyclerView(ViewGroup):
return False
def notifyDataSetChanged(self):
pass
for observer in getattr(self, '_observers', []):
observer.onChanged()
def notifyItemChanged(self, position: int):
pass
def notifyItemChanged(self, position, payload=None):
for observer in getattr(self, '_observers', []):
observer.onItemRangeChanged(position, 1, payload)
def notifyItemInserted(self, position: int):
pass
def notifyItemInserted(self, position):
for observer in getattr(self, '_observers', []):
observer.onItemRangeInserted(position, 1)
def notifyItemRemoved(self, position: int):
pass
def notifyItemRemoved(self, position):
for observer in getattr(self, '_observers', []):
observer.onItemRangeRemoved(position, 1)
def notifyItemMoved(self, from_position: int, to_position: int):
pass
def notifyItemMoved(self, fromPosition, toPosition):
for observer in getattr(self, '_observers', []):
observer.onItemRangeMoved(fromPosition, toPosition, 1)
def notifyItemRangeChanged(self, position_start: int, item_count: int):
pass
def notifyItemRangeChanged(self, positionStart, itemCount, payload=None):
for observer in getattr(self, '_observers', []):
observer.onItemRangeChanged(positionStart, itemCount, payload)
def notifyItemRangeInserted(self, position_start: int, item_count: int):
pass
def notifyItemRangeInserted(self, positionStart, itemCount):
for observer in getattr(self, '_observers', []):
observer.onItemRangeInserted(positionStart, itemCount)
def notifyItemRangeRemoved(self, position_start: int, item_count: int):
pass
def notifyItemRangeRemoved(self, positionStart, itemCount):
for observer in getattr(self, '_observers', []):
observer.onItemRangeRemoved(positionStart, itemCount)
def registerAdapterDataObserver(self, observer):
if not hasattr(self, '_observers'):
self._observers = []
self._observers.append(observer)
def unregisterAdapterDataObserver(self, observer):
if hasattr(self, '_observers'):
try:
self._observers.remove(observer)
except ValueError:
pass
def getStateRestorationPolicy(self) -> int:
return 0
@@ -2068,11 +2088,13 @@ class ScrollView(FrameLayout):
def arrowScroll(self, direction: int) -> bool:
return True
def smoothScrollTo(self, x: int, y: int):
self._scroll_position = y
def smoothScrollTo(self, x, y):
self._scroll_x = x
self._scroll_y = y
def smoothScrollBy(self, dx: int, dy: int):
self._scroll_position += dy
def smoothScrollBy(self, dx, dy):
self._scroll_x = getattr(self, '_scroll_x', 0) + dx
self._scroll_y = getattr(self, '_scroll_y', 0) + dy
def pageScroll(self, direction: int) -> bool:
return True
@@ -2106,11 +2128,13 @@ class HorizontalScrollView(FrameLayout):
def arrowScroll(self, direction: int) -> bool:
return True
def smoothScrollTo(self, x: int, y: int):
pass
def smoothScrollTo(self, x, y):
self._scroll_x = x
self._scroll_y = y
def smoothScrollBy(self, dx: int, dy: int):
pass
def smoothScrollBy(self, dx, dy):
self._scroll_x = getattr(self, '_scroll_x', 0) + dx
self._scroll_y = getattr(self, '_scroll_y', 0) + dy
def pageScroll(self, direction: int) -> bool:
return True
@@ -2795,16 +2819,24 @@ class BaseAdapter(Stub):
return self.getView(position, convert_view, parent)
def notifyDataSetChanged(self):
pass
for observer in getattr(self, '_observers', []):
observer.onChanged()
def notifyDataSetInvalidated(self):
pass
for observer in getattr(self, '_observers', []):
observer.onInvalidated()
def registerDataSetObserver(self, observer: Any):
pass
def registerDataSetObserver(self, observer):
if not hasattr(self, '_observers'):
self._observers = []
self._observers.append(observer)
def unregisterDataSetObserver(self, observer: Any):
pass
def unregisterDataSetObserver(self, observer):
if hasattr(self, '_observers'):
try:
self._observers.remove(observer)
except ValueError:
pass
class ArrayAdapter(BaseAdapter):