""" Android Widget 组件 — TextView, Button, RecyclerView, ListView, WebView 等 方法签名完全对齐 Android SDK """ import math import time from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union from .._core import Stub, AndroidOnlyError, android_context from .view import ( View, ViewGroup, ViewTreeObserver, MotionEvent, KeyEvent, Gravity, SurfaceView, ViewGroup as VG, ) try: from .._linux_impl import IS_LINUX, LinuxNotify, LinuxClipboard _USE_LINUX = IS_LINUX except ImportError: _USE_LINUX = False # ═══════════════════════════════════════════════════════════════ # TextView # ═══════════════════════════════════════════════════════════════ class TextView(View): """TextView — 文本显示视图""" # 自动链接 AUTO_LINK_NONE = 0x00 AUTO_LINK_WEB_URLS = 0x01 AUTO_LINK_EMAIL_ADDRESSES = 0x02 AUTO_LINK_PHONE_NUMBERS = 0x04 AUTO_LINK_MAP_ADDRESSES = 0x08 AUTO_LINK_ALL = 0x0F # Buffer类型 BUFFER_TYPE_NORMAL = 0 BUFFER_TYPE_SPANNABLE = 1 BUFFER_TYPE_EDITABLE = 2 # 截断位置 TRUNCATE_AT_END = 0 TRUNCATE_AT_MIDDLE = 1 TRUNCATE_AT_START = 2 # 文本方向 TEXT_DIRECTION_INHERIT = 0 TEXT_DIRECTION_FIRST_STRONG = 1 TEXT_DIRECTION_ANY_RTL = 2 TEXT_DIRECTION_LTR = 3 TEXT_DIRECTION_RTL = 4 TEXT_DIRECTION_LOCALE = 5 TEXT_DIRECTION_FIRST_STRONG_LTR = 6 TEXT_DIRECTION_FIRST_STRONG_RTL = 7 # 文本对齐 TEXT_ALIGNMENT_INHERIT = 0 TEXT_ALIGNMENT_GRAVITY = 1 TEXT_ALIGNMENT_TEXT_START = 2 TEXT_ALIGNMENT_TEXT_END = 3 TEXT_ALIGNMENT_CENTER = 4 TEXT_ALIGNMENT_VIEW_START = 5 TEXT_ALIGNMENT_VIEW_END = 6 def __init__(self, context=None): super().__init__(context) self._text: str = "" self._hint: str = "" self._text_size: float = 14.0 self._text_color: int = 0xFF000000 self._hint_text_color: int = 0xFF808080 self._link_text_color: int = 0xFFFF0000 self._highlight_color: int = 0x556633CC self._gravity = Gravity.NO_GRAVITY self._typeface: Any = None self._input_type: int = 0 self._lines: int = 0 self._max_lines: int = 0 self._min_lines: int = 0 self._single_line: bool = False self._ellipsize: Optional[str] = None # "end", "start", "middle", "marquee" self._movement_method: Any = None self._auto_link_mask: int = TextView.AUTO_LINK_NONE self._transformation_method: Optional[Callable] = None self._shadow_radius: float = 0.0 self._shadow_dx: float = 0.0 self._shadow_dy: float = 0.0 self._shadow_color: int = 0 self._line_spacing_extra: float = 0.0 self._line_spacing_mult: float = 1.0 self._compound_drawables: List[Any] = [None, None, None, None] self._compound_drawable_padding: int = 0 self._include_font_padding: bool = True self._elegant_text_height: bool = False self._letter_spacing: float = 0.0 self._font_feature_settings: str = "" self._key_listener: Any = None self._filters: List[Any] = [] self._select_all_on_focus: bool = False self._horizontally_scrolling: bool = False self._on_editor_action_listener: Optional[Callable] = None self._paint: Any = None self._line_count: int = 1 self._layout: Any = None self._links_clickable: bool = False self._cursor_visible: bool = True self._editable: bool = False self._selection_start: int = 0 self._selection_end: int = 0 self._scroll_horizontally: bool = False # ─── text ────────────────────────────────────────────────── def setText(self, text: str, buffer_type: int = BUFFER_TYPE_NORMAL): self._text = str(text) if text is not None else "" self._line_count = self._text.count("\n") + 1 if self._text else 0 def getText(self) -> str: return self._text def append(self, text: str): self._text += str(text) if text is not None else "" self._line_count = self._text.count("\n") + 1 if self._text else 0 # ─── text size / color ───────────────────────────────────── def setTextSize(self, size: float, unit: int = 2): # unit=2 means SP self._text_size = size def getTextSize(self) -> float: return self._text_size def setTextColor(self, color: int): self._text_color = color def getCurrentTextColor(self) -> int: return self._text_color def setHintTextColor(self, color: int): self._hint_text_color = color def getHintTextColor(self) -> int: return self._hint_text_color def setLinkTextColor(self, color: int): self._link_text_color = color def getLinkTextColor(self) -> int: return self._link_text_color def setHighlightColor(self, color: int): self._highlight_color = color def getHighlightColor(self) -> int: return self._highlight_color # ─── gravity ────────────────────────────────────────────── def setGravity(self, gravity: int): self._gravity = gravity def getGravity(self) -> int: return self._gravity # ─── typeface ────────────────────────────────────────────── def setTypeface(self, typeface: Any, style: int = 0): self._typeface = typeface def getTypeface(self) -> Any: return self._typeface # ─── hint ────────────────────────────────────────────────── def setHint(self, hint: str): self._hint = str(hint) if hint is not None else "" def getHint(self) -> str: return self._hint # ─── input type ──────────────────────────────────────────── def setInputType(self, input_type: int): self._input_type = input_type def getInputType(self) -> int: return self._input_type # ─── lines ───────────────────────────────────────────────── def setLines(self, lines: int): self._lines = lines def setMaxLines(self, max_lines: int): self._max_lines = max_lines def getMaxLines(self) -> int: return self._max_lines def setMinLines(self, min_lines: int): self._min_lines = min_lines def getMinLines(self) -> int: return self._min_lines def getLineCount(self) -> int: return self._line_count def setSingleLine(self, single_line: bool = True): self._single_line = single_line # ─── ellipsize ───────────────────────────────────────────── def setEllipsize(self, where: Optional[str]): self._ellipsize = where def getEllipsize(self) -> Optional[str]: return self._ellipsize # ─── movement method ────────────────────────────────────── def setMovementMethod(self, movement: Any): self._movement_method = movement def getMovementMethod(self) -> Any: return self._movement_method # ─── auto link ───────────────────────────────────────────── def setAutoLinkMask(self, mask: int): self._auto_link_mask = mask def getAutoLinkMask(self) -> int: return self._auto_link_mask # ─── transformation ─────────────────────────────────────── def setTransformationMethod(self, method: Optional[Callable]): self._transformation_method = method def getTransformationMethod(self) -> Optional[Callable]: return self._transformation_method # ─── shadow ──────────────────────────────────────────────── def setShadowLayer(self, radius: float, dx: float, dy: float, color: int): self._shadow_radius = radius self._shadow_dx = dx self._shadow_dy = dy self._shadow_color = color # ─── line spacing ───────────────────────────────────────── def setLineSpacing(self, extra: float, multiplier: float): self._line_spacing_extra = extra self._line_spacing_mult = multiplier # ─── compound drawables ─────────────────────────────────── def setCompoundDrawables(self, left: Any, top: Any, right: Any, bottom: Any): self._compound_drawables = [left, top, right, bottom] def setCompoundDrawablesWithIntrinsicBounds(self, left: Any, top: Any, right: Any, bottom: Any): self._compound_drawables = [left, top, right, bottom] def setCompoundDrawablePadding(self, pad: int): self._compound_drawable_padding = pad def getCompoundDrawablePadding(self) -> int: return self._compound_drawable_padding def getCompoundDrawables(self) -> List[Any]: return list(self._compound_drawables) # ─── editor action ──────────────────────────────────────── def setOnEditorActionListener(self, listener: Optional[Callable]): self._on_editor_action_listener = listener # ─── key listener ───────────────────────────────────────── def setKeyListener(self, key_listener: Any): self._key_listener = key_listener def getKeyListener(self) -> Any: return self._key_listener # ─── filters ─────────────────────────────────────────────── def setFilters(self, filters: List[Any]): self._filters = list(filters) def getFilters(self) -> List[Any]: return list(self._filters) # ─── misc ────────────────────────────────────────────────── def setSelectAllOnFocus(self, select_all_on_focus: bool): self._select_all_on_focus = select_all_on_focus def setHorizontallyScrolling(self, horizontally_scrolling: bool): self._horizontally_scrolling = horizontally_scrolling def setIncludeFontPadding(self, include_font_padding: bool): self._include_font_padding = include_font_padding def setElegantTextHeight(self, elegant: bool): self._elegant_text_height = elegant def setLetterSpacing(self, letter_spacing: float): self._letter_spacing = letter_spacing def getLetterSpacing(self) -> float: return self._letter_spacing def setFontFeatureSettings(self, settings: str): self._font_feature_settings = settings def getFontFeatureSettings(self) -> str: return self._font_feature_settings def getPaint(self) -> Any: return self._paint def getLayout(self) -> Any: return self._layout def setLinksClickable(self, links_clickable: bool): self._links_clickable = links_clickable def getLinksClickable(self) -> bool: return self._links_clickable def setCursorVisible(self, visible: bool): self._cursor_visible = visible def isCursorVisible(self) -> bool: return self._cursor_visible def setTextCursorDrawable(self, drawable: Any): pass def setFreezesText(self, freezes_text: bool): pass def getFreezesText(self) -> bool: return False def setEditableFactory(self, factory: Any): pass def setSpannableFactory(self, factory: Any): pass def getTextSize(self) -> float: return self._text_size def setBackgroundColor(self, color: int): super().setBackgroundColor(color) def setPadding(self, left: int, top: int, right: int, bottom: int): super().setPadding(left, top, right, bottom) def setPaddingRelative(self, start: int, top: int, end: int, bottom: int): self._padding_left = start self._padding_top = top self._padding_right = end self._padding_bottom = bottom # ─── text color getter ─────────────────────────────────── def getTextColor(self) -> int: return self._text_color # ─── selection action mode ─────────────────────────────── def setCustomSelectionActionModeCallback(self, callback: Any): self._custom_selection_action_mode_callback = callback def getCustomSelectionActionModeCallback(self) -> Any: return getattr(self, '_custom_selection_action_mode_callback', None) # ─── break / justification strategy ────────────────────── def setBreakStrategy(self, break_strategy: int): self._break_strategy = break_strategy def getBreakStrategy(self) -> int: return getattr(self, '_break_strategy', 0) def setJustificationMode(self, justification_mode: int): self._justification_mode = justification_mode def getJustificationMode(self) -> int: return getattr(self, '_justification_mode', 0) def onMeasure(self, width_measure_spec: int, height_measure_spec: int): from .view import MeasureSpec w = MeasureSpec.getSize(width_measure_spec) h = MeasureSpec.getSize(height_measure_spec) if MeasureSpec.getMode(width_measure_spec) == MeasureSpec.AT_MOST: text_len = len(self._text) * 4 w = min(w, max(100, text_len)) if MeasureSpec.getMode(height_measure_spec) == MeasureSpec.AT_MOST: text_lines = self._line_count h = min(h, max(20, text_lines * int(self._text_size) + 10)) self.setMeasuredDimension(w, h) def onDraw(self, canvas: Any): pass # ═══════════════════════════════════════════════════════════════ # EditText # ═══════════════════════════════════════════════════════════════ class EditText(TextView): """EditText — 可编辑文本""" def __init__(self, context=None): super().__init__(context) self._editable = True self._selection_start = 0 self._selection_end = 0 def setSelection(self, start: int, stop: Optional[int] = None): if stop is None: self._selection_start = start self._selection_end = start else: self._selection_start = start self._selection_end = stop def getSelectionStart(self) -> int: return self._selection_start def getSelectionEnd(self) -> int: return self._selection_end def selectAll(self): self._selection_start = 0 self._selection_end = len(self._text) def extendSelection(self, index: int): self._selection_end = index def setText(self, text: str, buffer_type: int = TextView.BUFFER_TYPE_EDITABLE): super().setText(text, buffer_type) def getEditableText(self): return self._text # ═══════════════════════════════════════════════════════════════ # Button # ═══════════════════════════════════════════════════════════════ class Button(TextView): """Button — 按钮""" def __init__(self, context=None): super().__init__(context) self._clickable = True self._focusable = True def onTouchEvent(self, event: MotionEvent) -> bool: if event.getAction() == MotionEvent.ACTION_UP: self.performClick() return super().onTouchEvent(event) class CheckBox(Button): """CheckBox — 复选框""" def __init__(self, context=None): super().__init__(context) self._checked = False def setChecked(self, checked: bool): self._checked = checked def isChecked(self) -> bool: return self._checked def toggle(self): self._checked = not self._checked def setOnCheckedChangeListener(self, listener: Optional[Callable]): self._on_checked_change_listener = listener def getOnCheckedChangeListener(self) -> Optional[Callable]: return getattr(self, '_on_checked_change_listener', None) def performClick(self) -> bool: self.toggle() return super().performClick() class RadioButton(Button): """RadioButton — 单选按钮""" def __init__(self, context=None): super().__init__(context) self._checked = False def setChecked(self, checked: bool): self._checked = checked def isChecked(self) -> bool: return self._checked def toggle(self): self._checked = not self._checked def performClick(self) -> bool: self.toggle() return super().performClick() class ToggleButton(Button): """ToggleButton — 开关按钮""" def __init__(self, context=None): super().__init__(context) self._checked = False self._text_on = "ON" self._text_off = "OFF" self._disabled_alpha = 0.5 def setChecked(self, checked: bool): self._checked = checked if self._checked: self.setText(self._text_on) else: self.setText(self._text_off) def isChecked(self) -> bool: return self._checked def toggle(self): self.setChecked(not self._checked) def setTextOn(self, text_on: str): self._text_on = text_on if self._checked: self.setText(text_on) def getTextOn(self) -> str: return self._text_on def setTextOff(self, text_off: str): self._text_off = text_off if not self._checked: self.setText(text_off) def getTextOff(self) -> str: return self._text_off def performClick(self) -> bool: self.toggle() return super().performClick() class Switch(Button): """Switch — 开关组件""" def __init__(self, context=None): super().__init__(context) self._checked = False self._show_text = True self._split_track = False self._thumb_text_padding: int = 0 self._switch_min_width: int = 0 self._switch_padding: int = 0 self._thumb_drawable: Any = None self._track_drawable: Any = None self._text_on = "ON" self._text_off = "OFF" self._thumb_width = 40 self._switch_width = 100 def isChecked(self) -> bool: return self._checked def setChecked(self, checked: bool): self._checked = checked def toggle(self): self._checked = not self._checked def setShowText(self, show_text: bool): self._show_text = show_text def getShowText(self) -> bool: return self._show_text def setSplitTrack(self, split_track: bool): self._split_track = split_track def getSplitTrack(self) -> bool: return self._split_track def setThumbTextPadding(self, padding: int): self._thumb_text_padding = padding def getThumbTextPadding(self) -> int: return self._thumb_text_padding def setSwitchMinWidth(self, min_width: int): self._switch_min_width = min_width def getSwitchMinWidth(self) -> int: return self._switch_min_width def setSwitchPadding(self, padding: int): self._switch_padding = padding def getSwitchPadding(self) -> int: return self._switch_padding def getThumbDrawable(self) -> Any: return self._thumb_drawable def setThumbDrawable(self, drawable: Any): self._thumb_drawable = drawable def getTrackDrawable(self) -> Any: return self._track_drawable def setTrackDrawable(self, drawable: Any): self._track_drawable = drawable def setTextOn(self, text_on: str): self._text_on = text_on def getTextOn(self) -> str: return self._text_on def setTextOff(self, text_off: str): self._text_off = text_off def getTextOff(self) -> str: return self._text_off # ─── checked change listener ───────────────────────────── def setOnCheckedChangeListener(self, listener: Optional[Callable]): self._on_checked_change_listener = listener def getOnCheckedChangeListener(self) -> Optional[Callable]: return getattr(self, '_on_checked_change_listener', None) # ─── switch text appearance ────────────────────────────── def setSwitchTextAppearance(self, context: Any, resid: int): self._switch_text_appearance = resid def getSwitchTextAppearance(self) -> int: return getattr(self, '_switch_text_appearance', 0) def performClick(self) -> bool: self.toggle() return super().performClick() def onMeasure(self, width_measure_spec: int, height_measure_spec: int): from .view import MeasureSpec w = MeasureSpec.getSize(width_measure_spec) h = MeasureSpec.getSize(height_measure_spec) self.setMeasuredDimension(w, max(30, h)) # ═══════════════════════════════════════════════════════════════ # ImageView # ═══════════════════════════════════════════════════════════════ class ImageView(View): """ImageView — 图片视图""" # ScaleType常量 ScaleType = type("ScaleType", (), { "MATRIX": 0, "FIT_XY": 1, "FIT_START": 2, "FIT_CENTER": 3, "FIT_END": 4, "CENTER": 5, "CENTER_CROP": 6, "CENTER_INSIDE": 7, }) def __init__(self, context=None): super().__init__(context) self._drawable: Any = None self._image_resource: int = 0 self._uri: Any = None self._scale_type: int = ImageView.ScaleType.FIT_CENTER self._max_width: int = 0 self._max_height: int = 0 self._adjust_view_bounds: bool = False self._crop_to_padding: bool = False self._baseline: int = 0 self._baseline_align_bottom: bool = False self._image_matrix: Any = None self._image_bitmap: Any = None def setImageDrawable(self, drawable: Any): self._drawable = drawable def getDrawable(self) -> Any: return self._drawable def setImageResource(self, res_id: int): self._image_resource = res_id def setImageURI(self, uri: Any): self._uri = uri def setImageBitmap(self, bitmap: Any): self._image_bitmap = bitmap def getImageBitmap(self) -> Any: return self._image_bitmap def setScaleType(self, scale_type: int): self._scale_type = scale_type def getScaleType(self) -> int: return self._scale_type def setMaxWidth(self, max_width: int): self._max_width = max_width def setMaxHeight(self, max_height: int): self._max_height = max_height def setAdjustViewBounds(self, adjust: bool): self._adjust_view_bounds = adjust def getAdjustViewBounds(self) -> bool: return self._adjust_view_bounds def setCropToPadding(self, crop: bool): self._crop_to_padding = crop def getCropToPadding(self) -> bool: return self._crop_to_padding # ─── color filter ───────────────────────────────────────── def setColorFilter(self, color_filter: Any): self._color_filter = color_filter def getColorFilter(self) -> Any: return getattr(self, '_color_filter', None) def clearColorFilter(self): self._color_filter = None def setBaseline(self, baseline: int): self._baseline = baseline def getBaseline(self) -> int: return self._baseline def setBaselineAlignBottom(self, aligned: bool): self._baseline_align_bottom = aligned def getBaselineAlignBottom(self) -> bool: return self._baseline_align_bottom def setImageMatrix(self, matrix: Any): self._image_matrix = matrix def getImageMatrix(self) -> Any: return self._image_matrix def invalidateDrawable(self, dr: Any): pass def onMeasure(self, width_measure_spec: int, height_measure_spec: int): from .view import MeasureSpec w = MeasureSpec.getSize(width_measure_spec) h = MeasureSpec.getSize(height_measure_spec) if w == 0: w = 100 if h == 0: h = 100 self.setMeasuredDimension(w, h) class ImageButton(ImageView): """ImageButton — 图片按钮""" def __init__(self, context=None): super().__init__(context) self._clickable = True self._focusable = True def onTouchEvent(self, event: MotionEvent) -> bool: if event.getAction() == MotionEvent.ACTION_UP: self.performClick() return super().onTouchEvent(event) # ═══════════════════════════════════════════════════════════════ # ProgressBar # ═══════════════════════════════════════════════════════════════ class ProgressBar(View): """ProgressBar — 进度条""" def __init__(self, context=None, attrs: Any = None, def_style_attr: int = 0, def_style_res: int = 0): super().__init__(context) self._progress: int = 0 self._max: int = 100 self._indeterminate: bool = False self._indeterminate_drawable: Any = None self._progress_drawable: Any = None self._secondary_progress: int = 0 self._min: int = 0 self._interpolator: Any = None self._animation: Any = None def setProgress(self, progress: int): self._progress = max(self._min, min(self._max, progress)) def getProgress(self) -> int: return self._progress def setMax(self, max_val: int): self._max = max_val if self._progress > self._max: self._progress = self._max def getMax(self) -> int: return self._max def setMin(self, min_val: int): self._min = min_val def getMin(self) -> int: return self._min def incrementProgressBy(self, diff: int): self.setProgress(self._progress + diff) def setIndeterminate(self, indeterminate: bool): self._indeterminate = indeterminate def isIndeterminate(self) -> bool: return self._indeterminate def getIndeterminateDrawable(self) -> Any: return self._indeterminate_drawable def setIndeterminateDrawable(self, drawable: Any): self._indeterminate_drawable = drawable def setProgressDrawable(self, drawable: Any): self._progress_drawable = drawable def getProgressDrawable(self) -> Any: return self._progress_drawable def setSecondaryProgress(self, secondary_progress: int): self._secondary_progress = max(self._min, min(self._max, secondary_progress)) def getSecondaryProgress(self) -> int: return self._secondary_progress def setInterpolator(self, interpolator: Any): self._interpolator = interpolator def getInterpolator(self) -> Any: return self._interpolator def setAnimation(self, animation: Any): self._animation = animation def onMeasure(self, width_measure_spec: int, height_measure_spec: int): from .view import MeasureSpec w = MeasureSpec.getSize(width_measure_spec) h = MeasureSpec.getSize(height_measure_spec) if h == 0: h = 20 self.setMeasuredDimension(w, h) class SeekBar(ProgressBar): """SeekBar — 可拖动进度条""" def __init__(self, context=None, attrs: Any = None, def_style_attr: int = 0, def_style_res: int = 0): super().__init__(context, attrs, def_style_attr, def_style_res) self._on_seek_bar_change_listener: Optional[Callable] = None class OnSeekBarChangeListener: def onProgressChanged(self, seek_bar: "SeekBar", progress: int, from_user: bool): pass def onStartTrackingTouch(self, seek_bar: "SeekBar"): pass def onStopTrackingTouch(self, seek_bar: "SeekBar"): pass def setOnSeekBarChangeListener(self, listener: Optional[OnSeekBarChangeListener]): self._on_seek_bar_change_listener = listener def getOnSeekBarChangeListener(self) -> Optional[OnSeekBarChangeListener]: return self._on_seek_bar_change_listener def setThumb(self, thumb: Any): pass def getThumb(self) -> Any: return None def setThumbOffset(self, thumb_offset: int): pass def getThumbOffset(self) -> int: return 0 def setKeyProgressIncrement(self, increment: int): pass def getKeyProgressIncrement(self) -> int: return 1 class RatingBar(ProgressBar): """RatingBar — 评分条""" def __init__(self, context=None, attrs: Any = None, def_style_attr: int = 0, def_style_res: int = 0): super().__init__(context, attrs, def_style_attr, def_style_res) self._num_stars: int = 5 self._rating: float = 0.0 self._step_size: float = 0.5 self._is_indicator: bool = False self._on_rating_bar_change_listener: Optional[Callable] = None class OnRatingBarChangeListener: def onRatingChanged(self, rating_bar: "RatingBar", rating: float, from_user: bool): pass def setOnRatingBarChangeListener(self, listener: Optional[OnRatingBarChangeListener]): self._on_rating_bar_change_listener = listener def getOnRatingBarChangeListener(self) -> Optional[OnRatingBarChangeListener]: return self._on_rating_bar_change_listener def setNumStars(self, num_stars: int): self._num_stars = max(1, num_stars) def getNumStars(self) -> int: return self._num_stars def setRating(self, rating: float): self._rating = max(0.0, min(float(self._num_stars), rating)) def getRating(self) -> float: return self._rating def setStepSize(self, step_size: float): self._step_size = max(0.1, step_size) def getStepSize(self) -> float: return self._step_size def setIsIndicator(self, is_indicator: bool): self._is_indicator = is_indicator def isIndicator(self) -> bool: return self._is_indicator # ═══════════════════════════════════════════════════════════════ # AdapterView # ═══════════════════════════════════════════════════════════════ class AdapterView(ViewGroup): """AdapterView — 适配器视图基类""" CHOICE_MODE_NONE = 0 CHOICE_MODE_SINGLE = 1 CHOICE_MODE_MULTIPLE = 2 CHOICE_MODE_MULTIPLE_MODAL = 3 INVALID_POSITION = -1 INVALID_ROW_ID = -9223372036854775808 # Long.MIN_VALUE def __init__(self, context=None): super().__init__(context) self._adapter: Any = None self._on_item_click_listener: Optional[Callable] = None self._on_item_long_click_listener: Optional[Callable] = None self._on_item_selected_listener: Optional[Callable] = None self._choice_mode = AdapterView.CHOICE_MODE_NONE self._checked_item_positions: Dict[int, bool] = {} self._checked_id_positions: Dict[int, bool] = {} self._selected_position = AdapterView.INVALID_POSITION self._empty_view: Optional[View] = None self._selection_notifier: Any = None def setAdapter(self, adapter: Any): self._adapter = adapter def getAdapter(self) -> Any: return self._adapter def setOnItemClickListener(self, listener: Optional[Callable]): self._on_item_click_listener = listener def getOnItemClickListener(self) -> Optional[Callable]: return self._on_item_click_listener def setOnItemLongClickListener(self, listener: Optional[Callable]): self._on_item_long_click_listener = listener def getOnItemLongClickListener(self) -> Optional[Callable]: return self._on_item_long_click_listener def setOnItemSelectedListener(self, listener: Optional[Callable]): self._on_item_selected_listener = listener def getOnItemSelectedListener(self) -> Optional[Callable]: return self._on_item_selected_listener def getSelectedItemPosition(self) -> int: return self._selected_position def getSelectedItemId(self) -> int: return 0 def getSelectedItem(self) -> Any: if self._adapter is not None and 0 <= self._selected_position < self._adapter.getCount(): return self._adapter.getItem(self._selected_position) return None def setSelection(self, position: int): self._selected_position = position def setEmptyView(self, empty_view: View): self._empty_view = empty_view def getEmptyView(self) -> Optional[View]: return self._empty_view def setChoiceMode(self, choice_mode: int): self._choice_mode = choice_mode def getChoiceMode(self) -> int: return self._choice_mode def setItemChecked(self, position: int, checked: bool): if self._choice_mode == AdapterView.CHOICE_MODE_SINGLE: self._checked_item_positions.clear() self._checked_item_positions[position] = checked elif self._choice_mode == AdapterView.CHOICE_MODE_MULTIPLE: self._checked_item_positions[position] = checked def isItemChecked(self, position: int) -> bool: return self._checked_item_positions.get(position, False) def getCheckedItemPositions(self) -> Dict[int, bool]: return dict(self._checked_item_positions) def getCheckedItemCount(self) -> int: return len(self._checked_item_positions) def getCheckedItemIds(self) -> List[int]: return list(self._checked_id_positions.keys()) def performItemClick(self, view: View, position: int, id_: int) -> bool: if self._on_item_click_listener: self._on_item_click_listener(self, view, position, id_) return True return False # ═══════════════════════════════════════════════════════════════ # ListView # ═══════════════════════════════════════════════════════════════ class ListView(AdapterView): """ListView — 列表视图""" CHOICE_MODE_NONE = 0 CHOICE_MODE_SINGLE = 1 CHOICE_MODE_MULTIPLE = 2 CHOICE_MODE_MULTIPLE_MODAL = 3 TRANSCRIPT_MODE_DISABLED = 0 TRANSCRIPT_MODE_NORMAL = 1 TRANSCRIPT_MODE_ALWAYS_SCROLL = 2 def __init__(self, context=None): super().__init__(context) self._divider: Any = None self._divider_height: int = 0 self._footer_divider: Any = None self._header_divider: Any = None self._footer_views: List[View] = [] self._header_views: List[View] = [] self._transcript_mode = ListView.TRANSCRIPT_MODE_DISABLED self._items_can_focus = True self._smooth_scroll_position = 0 self._stack_from_bottom = False self._selection_division = 0 def setAdapter(self, adapter: Any): super().setAdapter(adapter) def getAdapter(self) -> Any: return super().getAdapter() def setSelection(self, position: int): super().setSelection(position) def smoothScrollToPosition(self, position: int): self._smooth_scroll_position = position def setDivider(self, divider: Any): self._divider = divider def setDividerHeight(self, height: int): self._divider_height = height def getDivider(self) -> Any: return self._divider def getDividerHeight(self) -> int: return self._divider_height def addFooterView(self, v: View, data: Any = None, is_selectable: bool = True): self._footer_views.append(v) def removeFooterView(self, v: View) -> bool: if v in self._footer_views: self._footer_views.remove(v) return True return False def addHeaderView(self, v: View, data: Any = None, is_selectable: bool = True): self._header_views.append(v) def removeHeaderView(self, v: View) -> bool: if v in self._header_views: self._header_views.remove(v) return True return False def getHeaderViewsCount(self) -> int: return len(self._header_views) def getFooterViewsCount(self) -> int: return len(self._footer_views) def setTranscriptMode(self, mode: int): self._transcript_mode = mode def getTranscriptMode(self) -> int: return self._transcript_mode def setItemsCanFocus(self, items_can_focus: bool): self._items_can_focus = items_can_focus def getItemsCanFocus(self) -> bool: return self._items_can_focus def setStackFromBottom(self, stack_from_bottom: bool): self._stack_from_bottom = stack_from_bottom def isStackFromBottom(self) -> bool: return self._stack_from_bottom def getCheckItemIds(self) -> List[int]: return list(self._checked_id_positions.keys()) def clearChoices(self): self._checked_item_positions.clear() self._checked_id_positions.clear() class GridView(AdapterView): """GridView — 网格视图""" def __init__(self, context=None): super().__init__(context) self._num_columns: int = 1 self._column_width: int = 0 self._horizontal_spacing: int = 0 self._vertical_spacing: int = 0 self._stretch_mode: int = 0 # NO_STRETCH self._gravity = Gravity.LEFT STRETCH_NO_STRETCH = 0 STRETCH_STRETCH_COLUMN_WIDTH = 1 STRETCH_STRETCH_SPACING = 2 STRETCH_STRETCH_SPACING_UNIFORM = 3 STRETCH_STRETCH_COLUMN_WIDTH_UNIFORM = 4 def setNumColumns(self, num_columns: int): self._num_columns = max(1, num_columns) def getNumColumns(self) -> int: return self._num_columns def setColumnWidth(self, column_width: int): self._column_width = column_width def getColumnWidth(self) -> int: return self._column_width def setHorizontalSpacing(self, horizontal_spacing: int): self._horizontal_spacing = horizontal_spacing def getHorizontalSpacing(self) -> int: return self._horizontal_spacing def setVerticalSpacing(self, vertical_spacing: int): self._vertical_spacing = vertical_spacing def getVerticalSpacing(self) -> int: return self._vertical_spacing def setStretchMode(self, stretch_mode: int): self._stretch_mode = stretch_mode def getStretchMode(self) -> int: return self._stretch_mode def setGravity(self, gravity: int): self._gravity = gravity def getGravity(self) -> int: return self._gravity def setAdapter(self, adapter: Any): super().setAdapter(AdapterView) # ═══════════════════════════════════════════════════════════════ # RecyclerView # ═══════════════════════════════════════════════════════════════ class RecyclerView(ViewGroup): """RecyclerView — 可回收视图列表""" HORIZONTAL = 0 VERTICAL = 1 NO_POSITION = -1 INVALID_TYPE = -1 TOUCH_SLOP_DEFAULT = 0 TOUCH_SLOP_PAGING = 1 def __init__(self, context=None): super().__init__(context) self._adapter: Optional["RecyclerView.Adapter"] = None self._layout_manager: Optional["RecyclerView.LayoutManager"] = None self._item_decorations: List[Any] = [] self._recycler: Any = None self._child_attached_listener: Optional[Callable] = None self._child_detached_listener: Optional[Callable] = None self._item_animator: Any = None self._on_scroll_listener: Optional[Callable] = None self._nested_scrolling_enabled = True self._scroll_state = RecyclerView.SCROLL_STATE_IDLE self._layout_frozen = False self._suppress_layout = False self._clip_to_padding = True SCROLL_STATE_IDLE = 0 SCROLL_STATE_DRAGGING = 1 SCROLL_STATE_SETTLING = 2 # ─── Adapter ───────────────────────────────────────────── class Adapter(Stub): """RecyclerView.Adapter — 适配器""" def __init__(self): super().__init__() self._observable: Any = None self._has_stable_ids = False self._state_restoration_policy = 0 def onCreateViewHolder(self, parent: "RecyclerView", view_type: int) -> "RecyclerView.ViewHolder": raise NotImplementedError def onBindViewHolder(self, holder: "RecyclerView.ViewHolder", position: int): raise NotImplementedError def getItemCount(self) -> int: return 0 def getItemViewType(self, position: int) -> int: return 0 def getItemId(self, position: int) -> int: return position def hasStableIds(self) -> bool: return self._has_stable_ids def setHasStableIds(self, has_stable_ids: bool): self._has_stable_ids = has_stable_ids def onAttachedToRecyclerView(self, recycler_view: "RecyclerView"): pass def onDetachedFromRecyclerView(self, recycler_view: "RecyclerView"): pass def onViewAttachedToWindow(self, holder: "RecyclerView.ViewHolder"): pass def onViewDetachedFromWindow(self, holder: "RecyclerView.ViewHolder"): pass def onViewRecycled(self, holder: "RecyclerView.ViewHolder"): pass def onFailedToRecycleView(self, holder: "RecyclerView.ViewHolder") -> bool: return False def notifyDataSetChanged(self): for observer in getattr(self, '_observers', []): observer.onChanged() def notifyItemChanged(self, position, payload=None): for observer in getattr(self, '_observers', []): observer.onItemRangeChanged(position, 1, payload) def notifyItemInserted(self, position): for observer in getattr(self, '_observers', []): observer.onItemRangeInserted(position, 1) def notifyItemRemoved(self, position): for observer in getattr(self, '_observers', []): observer.onItemRangeRemoved(position, 1) def notifyItemMoved(self, fromPosition, toPosition): for observer in getattr(self, '_observers', []): observer.onItemRangeMoved(fromPosition, toPosition, 1) def notifyItemRangeChanged(self, positionStart, itemCount, payload=None): for observer in getattr(self, '_observers', []): observer.onItemRangeChanged(positionStart, itemCount, payload) def notifyItemRangeInserted(self, positionStart, itemCount): for observer in getattr(self, '_observers', []): observer.onItemRangeInserted(positionStart, itemCount) 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 # ─── ViewHolder ─────────────────────────────────────────── class ViewHolder(Stub): """RecyclerView.ViewHolder — 视图持有者""" FLAG_BOUND = 1 FLAG_NOT_RECYCLABLE = 2 FLAG_RETURNED_FROM_SCRAP = 4 FLAG_CHANGED = 8 FLAG_IGNORE = 16 FLAG_TMP_DETACHED = 32 FLAG_UPDATE = 64 FLAG_ADAPTER_POSITION_UNKNOWN = 128 FLAG_ADAPTER_FULLUPDATE = 256 FLAG_MOVED = 512 FLAG_APPEARED_IN_PRE_LAYOUT = 1024 def __init__(self, item_view: View): super().__init__() self.itemView = item_view self._position = RecyclerView.NO_POSITION self._old_position = RecyclerView.NO_POSITION self._item_id = RecyclerView.NO_ID self._item_view_type = RecyclerView.INVALID_TYPE self._flags = 0 self._pre_layout_position = RecyclerView.NO_POSITION self._is_recyclable = True self._scrap_container: Optional[RecyclerView] = None self._shadowed_holder: Optional["RecyclerView.ViewHolder"] = None self._shadowing_holder: Optional["RecyclerView.ViewHolder"] = None NO_ID = -1 def getAdapterPosition(self) -> int: return self._position def getLayoutPosition(self) -> int: return self._position def getOldPosition(self) -> int: return self._old_position def getItemId(self) -> int: return self._item_id def getItemViewType(self) -> int: return self._item_view_type def setIsRecyclable(self, recyclable: bool): self._is_recyclable = recyclable def isRecyclable(self) -> bool: return self._is_recyclable def getBindingAdapterPosition(self) -> int: return self._position # ─── LayoutManager ──────────────────────────────────────── class LayoutManager(Stub): """RecyclerView.LayoutManager — 布局管理器""" def __init__(self): super().__init__() self._recycler_view: Optional["RecyclerView"] = None self._child_count = 0 self._width = 0 self._height = 0 self._auto_measure = True self._item_prefetch_enabled = True def onLayoutChildren(self, recycler: Any, state: Any): pass def getChildCount(self) -> int: return self._child_count def getPosition(self, view: View) -> int: if isinstance(view, View): return 0 return RecyclerView.NO_POSITION def scrollToPosition(self, position: int): pass def smoothScrollToPosition(self, recycler_view: "RecyclerView", state: Any, position: int): pass def onAdapterChanged(self, old_adapter: Any, new_adapter: Any): pass def generateDefaultLayoutParams(self) -> ViewGroup.LayoutParams: return ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) def getWidth(self) -> int: return self._width def getHeight(self) -> int: return self._height def setAutoMeasureEnabled(self, enabled: bool): self._auto_measure = enabled def isAutoMeasureEnabled(self) -> bool: return self._auto_measure def requestLayout(self): pass def requestSimpleAnimationsInNextLayout(self): pass def assertNotInLayoutOrScroll(self, tag: str = ""): pass def getPaddingLeft(self) -> int: return 0 def getPaddingTop(self) -> int: return 0 def getPaddingRight(self) -> int: return 0 def getPaddingBottom(self) -> int: return 0 # ─── ItemDecoration ────────────────────────────────────── class ItemDecoration(Stub): """RecyclerView.ItemDecoration — 条目装饰""" def onDraw(self, c: Any, parent: "RecyclerView", state: Any): pass def onDrawOver(self, c: Any, parent: "RecyclerView", state: Any): pass def getItemOffsets(self, out_rect: Any, view: View, parent: "RecyclerView", state: Any): if hasattr(out_rect, "set"): out_rect.set(0, 0, 0, 0) # ─── RecycledViewPool ───────────────────────────────────── class RecycledViewPool(Stub): """RecyclerView.RecycledViewPool — 回收视图池""" def __init__(self): super().__init__() self._max_recycled_views = 5 def getRecycledView(self, view_type: int) -> Optional["RecyclerView.ViewHolder"]: return None def putRecycledView(self, scrap: "RecyclerView.ViewHolder"): pass def setMaxRecycledViews(self, view_type: int, max_count: int): self._max_recycled_views = max_count def clear(self): pass # ─── Recycler ───────────────────────────────────────────── class Recycler(Stub): """RecyclerView.Recycler — 回收器""" def __init__(self): super().__init__() self._view_cache: Dict[int, List["RecyclerView.ViewHolder"]] = {} def getViewForPosition(self, position: int) -> View: raise AndroidOnlyError("RecyclerView.Recycler.getViewForPosition") def recycleView(self, view: View): pass def getRecycledViewPool(self) -> "RecyclerView.RecycledViewPool": return RecyclerView.RecycledViewPool() def setViewCacheSize(self, view_count: int): pass def clear(self): self._view_cache.clear() # ─── RecyclerView Instance Methods ─────────────────────── def setAdapter(self, adapter: Optional["RecyclerView.Adapter"]): self._adapter = adapter def getAdapter(self) -> Optional["RecyclerView.Adapter"]: return self._adapter def setLayoutManager(self, layout_manager: Optional["RecyclerView.LayoutManager"]): self._layout_manager = layout_manager if layout_manager: layout_manager._recycler_view = self def getLayoutManager(self) -> Optional["RecyclerView.LayoutManager"]: return self._layout_manager def addItemDecoration(self, decoration: "RecyclerView.ItemDecoration", index: int = -1): if index < 0 or index >= len(self._item_decorations): self._item_decorations.append(decoration) else: self._item_decorations.insert(index, decoration) def removeItemDecoration(self, decoration: "RecyclerView.ItemDecoration"): if decoration in self._item_decorations: self._item_decorations.remove(decoration) def getItemDecorationAt(self, index: int) -> "RecyclerView.ItemDecoration": return self._item_decorations[index] def getItemDecorationCount(self) -> int: return len(self._item_decorations) def swapAdapter(self, adapter: Optional["RecyclerView.Adapter"], remove_and_recycle: bool): self._adapter = adapter def getRecycledViewPool(self) -> "RecyclerView.RecycledViewPool": return RecyclerView.RecycledViewPool() def setRecycledViewPool(self, pool: "RecyclerView.RecycledViewPool"): pass def getRecycler(self) -> "RecyclerView.Recycler": return RecyclerView.Recycler() def setItemAnimator(self, animator: Any): self._item_animator = animator def getItemAnimator(self) -> Any: return self._item_animator def setOnScrollListener(self, listener: Optional[Callable]): self._on_scroll_listener = listener def addOnScrollListener(self, listener: Callable): pass def removeOnScrollListener(self, listener: Callable): pass def clearOnScrollListeners(self): pass def addOnChildAttachStateChangeListener(self, listener: Callable): self._child_attached_listener = listener def removeOnChildAttachStateChangeListener(self, listener: Callable): pass def setHasFixedSize(self, has_fixed_size: bool): pass def hasFixedSize(self) -> bool: return False def setNestedScrollingEnabled(self, enabled: bool): self._nested_scrolling_enabled = enabled def isNestedScrollingEnabled(self) -> bool: return self._nested_scrolling_enabled def startNestedScroll(self, axes: int) -> bool: return False def stopNestedScroll(self): pass def hasNestedScrollingParent(self) -> bool: return False def dispatchNestedPreScroll(self, dx: int, dy: int, consumed: List[int], offset: List[int]) -> bool: return False def dispatchNestedScroll(self, dx_consumed: int, dy_consumed: int, dx_unconsumed: int, dy_unconsumed: int, offset: List[int]) -> bool: return False def dispatchNestedPreFling(self, velocity_x: float, velocity_y: float) -> bool: return False def dispatchNestedFling(self, velocity_x: float, velocity_y: float, consumed: bool) -> bool: return False def stopScroll(self): pass def getScrollState(self) -> int: return self._scroll_state def setLayoutFrozen(self, frozen: bool): self._layout_frozen = frozen def isLayoutFrozen(self) -> bool: return self._layout_frozen def smoothScrollBy(self, dx: int, dy: int): pass def scrollBy(self, x: int, y: int): pass def computeHorizontalScrollOffset(self) -> int: return 0 def computeHorizontalScrollRange(self) -> int: return 0 def computeVerticalScrollOffset(self) -> int: return 0 def computeVerticalScrollRange(self) -> int: return 0 def findViewHolderForAdapterPosition(self, position: int) -> Optional["RecyclerView.ViewHolder"]: return None def findViewHolderForLayoutPosition(self, position: int) -> Optional["RecyclerView.ViewHolder"]: return None def findViewHolderForItemId(self, id_: int) -> Optional["RecyclerView.ViewHolder"]: return None NO_ID = -1 # 为 __all__ 导出创建别名 RecyclerViewAdapter = RecyclerView.Adapter RecyclerViewViewHolder = RecyclerView.ViewHolder RecyclerViewLayoutManager = RecyclerView.LayoutManager class LinearLayoutManager(RecyclerView.LayoutManager): """LinearLayoutManager — 线性布局管理器""" HORIZONTAL = 0 VERTICAL = 1 def __init__(self, context: Any = None, orientation: int = VERTICAL, reverse_layout: bool = False): super().__init__() self._orientation = orientation self._reverse_layout = reverse_layout self._stack_from_end = False self._item_count = 0 def setOrientation(self, orientation: int): self._orientation = orientation def getOrientation(self) -> int: return self._orientation def setReverseLayout(self, reverse_layout: bool): self._reverse_layout = reverse_layout def getReverseLayout(self) -> bool: return self._reverse_layout def setStackFromEnd(self, stack_from_end: bool): self._stack_from_end = stack_from_end def getStackFromEnd(self) -> bool: return self._stack_from_end def generateDefaultLayoutParams(self) -> ViewGroup.LayoutParams: return ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) def onLayoutChildren(self, recycler: Any, state: Any): pass class GridLayoutManager(LinearLayoutManager): """GridLayoutManager — 网格布局管理器""" def __init__(self, context: Any = None, span_count: int = 1, orientation: int = LinearLayoutManager.VERTICAL, reverse_layout: bool = False): super().__init__(context, orientation, reverse_layout) self._span_count = max(1, span_count) class SpanSizeLookup(Stub): """GridLayoutManager.SpanSizeLookup — 跨度大小查找""" def __init__(self): super().__init__() self._span_index_cache = {} def getSpanSize(self, position: int) -> int: return 1 def getSpanIndex(self, position: int, span_count: int) -> int: return position % span_count def getSpanGroupIndex(self, position: int, span_count: int) -> int: return position // span_count def invalidateSpanIndexCache(self): self._span_index_cache.clear() def setSpanCount(self, span_count: int): self._span_count = max(1, span_count) def getSpanCount(self) -> int: return self._span_count def setSpanSizeLookup(self, span_size_lookup: SpanSizeLookup): pass def getSpanSizeLookup(self) -> SpanSizeLookup: return GridLayoutManager.SpanSizeLookup() class StaggeredGridLayoutManager(RecyclerView.LayoutManager): """StaggeredGridLayoutManager — 瀑布流布局管理器""" HORIZONTAL = 0 VERTICAL = 1 GAP_HANDLING_NONE = 0 GAP_HANDLING_LAZY = 1 GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS = 2 def __init__(self, span_count: int = 1, orientation: int = VERTICAL): super().__init__() self._span_count = max(1, span_count) self._orientation = orientation self._gap_strategy = StaggeredGridLayoutManager.GAP_HANDLING_LAZY def setSpanCount(self, span_count: int): self._span_count = max(1, span_count) def getSpanCount(self) -> int: return self._span_count def setOrientation(self, orientation: int): self._orientation = orientation def getOrientation(self) -> int: return self._orientation def setGapStrategy(self, gap_strategy: int): self._gap_strategy = gap_strategy def getGapStrategy(self) -> int: return self._gap_strategy # ═══════════════════════════════════════════════════════════════ # LinearLayout # ═══════════════════════════════════════════════════════════════ class LinearLayout(ViewGroup): """LinearLayout — 线性布局""" HORIZONTAL = 0 VERTICAL = 1 SHOW_DIVIDER_NONE = 0 SHOW_DIVIDER_BEGINNING = 1 SHOW_DIVIDER_MIDDLE = 2 SHOW_DIVIDER_END = 4 def __init__(self, context=None): super().__init__(context) self._orientation = LinearLayout.VERTICAL self._gravity = Gravity.NO_GRAVITY self._weight_sum: float = 0.0 self._baseline_aligned = True self._baseline_aligned_child_index = -1 self._divider: Any = None self._divider_padding: int = 0 self._divider_show: int = LinearLayout.SHOW_DIVIDER_NONE self._total_length: int = 0 def setOrientation(self, orientation: int): self._orientation = orientation def getOrientation(self) -> int: return self._orientation def setGravity(self, gravity: int): self._gravity = gravity def getGravity(self) -> int: return self._gravity def setWeightSum(self, weight_sum: float): self._weight_sum = weight_sum def getWeightSum(self) -> float: return self._weight_sum def setBaselineAligned(self, baseline_aligned: bool): self._baseline_aligned = baseline_aligned def isBaselineAligned(self) -> bool: return self._baseline_aligned def setDividerDrawable(self, divider: Any): self._divider = divider def setShowDividers(self, show_dividers: int): self._divider_show = show_dividers def getShowDividers(self) -> int: return self._divider_show def getDividerDrawable(self) -> Any: return self._divider def setDividerPadding(self, padding: int): self._divider_padding = padding def getDividerPadding(self) -> int: return self._divider_padding def getBaseline(self) -> int: return -1 def onMeasure(self, width_measure_spec: int, height_measure_spec: int): from .view import MeasureSpec w = MeasureSpec.getSize(width_measure_spec) h = MeasureSpec.getSize(height_measure_spec) self.setMeasuredDimension(w, h) def onLayout(self, changed: bool, l: int, t: int, r: int, b: int): pass class LayoutParams(ViewGroup.MarginLayoutParams): """LinearLayout.LayoutParams — 线性布局参数""" def __init__(self, width: int = ViewGroup.LayoutParams.WRAP_CONTENT, height: int = ViewGroup.LayoutParams.WRAP_CONTENT, weight: float = 0.0): super().__init__(width, height) self.weight = weight self.gravity = -1 def setLayoutWeight(self, weight: float): self.weight = weight # ═══════════════════════════════════════════════════════════════ # RelativeLayout # ═══════════════════════════════════════════════════════════════ class RelativeLayout(ViewGroup): """RelativeLayout — 相对布局""" TRUE = -1 FALSE = 0 # 规则常量 ALIGN_PARENT_LEFT = 0 ALIGN_PARENT_TOP = 1 ALIGN_PARENT_RIGHT = 2 ALIGN_PARENT_BOTTOM = 3 ALIGN_PARENT_START = 4 ALIGN_PARENT_END = 5 CENTER_IN_PARENT = 6 CENTER_HORIZONTAL = 7 CENTER_VERTICAL = 8 ABOVE = 9 BELOW = 10 LEFT_OF = 11 RIGHT_OF = 12 ALIGN_LEFT = 13 ALIGN_TOP = 14 ALIGN_RIGHT = 15 ALIGN_BOTTOM = 16 ALIGN_BASELINE = 17 ALIGN_WITH_PARENT_IF_MISSING = 18 START_OF = 19 END_OF = 20 ALIGN_START = 21 ALIGN_END = 22 def __init__(self, context=None): super().__init__(context) self._gravity = Gravity.NO_GRAVITY self._ignore_gravity_of_view_ids: List[int] = [] self._has_left_weight = False self._has_right_weight = False self._has_top_weight = False self._has_bottom_weight = False def setGravity(self, gravity: int): self._gravity = gravity def getGravity(self) -> int: return self._gravity def setHorizontalGravity(self, horizontal_gravity: int): pass def setVerticalGravity(self, vertical_gravity: int): pass def onMeasure(self, width_measure_spec: int, height_measure_spec: int): from .view import MeasureSpec w = MeasureSpec.getSize(width_measure_spec) h = MeasureSpec.getSize(height_measure_spec) self.setMeasuredDimension(w, h) def onLayout(self, changed: bool, l: int, t: int, r: int, b: int): pass class LayoutParams(ViewGroup.MarginLayoutParams): """RelativeLayout.LayoutParams — 相对布局参数""" def __init__(self, width: int = ViewGroup.LayoutParams.WRAP_CONTENT, height: int = ViewGroup.LayoutParams.WRAP_CONTENT): super().__init__(width, height) self._rules: Dict[int, int] = {} self.align_with_parent = False def addRule(self, verb: int, subject: int = -1): self._rules[verb] = subject def removeRule(self, verb: int): self._rules.pop(verb, None) def getRule(self, verb: int) -> int: return self._rules.get(verb, 0) def getRules(self) -> Dict[int, int]: return dict(self._rules) def addLayoutRules(self, layout_rules: int): pass def removeLayoutRule(self, layout_rule: int): pass # ═══════════════════════════════════════════════════════════════ # FrameLayout # ═══════════════════════════════════════════════════════════════ class FrameLayout(ViewGroup): """FrameLayout — 帧布局""" def __init__(self, context=None): super().__init__(context) self._foreground: Any = None self._foreground_gravity = Gravity.FILL self._measure_all_children = False self._foreground_bounds_padding: int = 0 self._foreground_in_padding = False def setForeground(self, drawable: Any): self._foreground = drawable def getForeground(self) -> Any: return self._foreground def setForegroundGravity(self, gravity: int): self._foreground_gravity = gravity def getForegroundGravity(self) -> int: return self._foreground_gravity def setMeasureAllChildren(self, measure_all: bool): self._measure_all_children = measure_all def getMeasureAllChildren(self) -> bool: return self._measure_all_children def setForegroundBoundsPadding(self, padding: int): self._foreground_bounds_padding = padding def onMeasure(self, width_measure_spec: int, height_measure_spec: int): from .view import MeasureSpec w = MeasureSpec.getSize(width_measure_spec) h = MeasureSpec.getSize(height_measure_spec) if self._measure_all_children: for child in self._children: child.measure(width_measure_spec, height_measure_spec) self.setMeasuredDimension(w, h) def onLayout(self, changed: bool, l: int, t: int, r: int, b: int): pass def gatherTransparentRegion(self, region: Any) -> bool: return False # ═══════════════════════════════════════════════════════════════ # TableLayout / TableRow # ═══════════════════════════════════════════════════════════════ class TableLayout(LinearLayout): """TableLayout — 表格布局""" def __init__(self, context=None): super().__init__(context) self._stretchable_columns: List[bool] = [] self._shrinkable_columns: List[bool] = [] self._collapsed_columns: List[bool] = [] self._max_columns: int = 0 def setStretchAllColumns(self, stretch_all: bool): pass def setShrinkAllColumns(self, shrink_all: bool): pass def setColumnStretchable(self, column_index: int, stretchable: bool): pass def setColumnShrinkable(self, column_index: int, shrinkable: bool): pass def setColumnCollapsed(self, column_index: int, collapsed: bool): pass def isColumnStretchable(self, column_index: int) -> bool: return False def isColumnShrinkable(self, column_index: int) -> bool: return False def isColumnCollapsed(self, column_index: int) -> bool: return False class TableRow(LinearLayout): """TableRow — 表格行""" def __init__(self, context=None): super().__init__(context) self._layout_params: Dict[int, Any] = {} class LayoutParams(LinearLayout.LayoutParams): """TableRow.LayoutParams — 表格行布局参数""" def __init__(self, width: int = ViewGroup.LayoutParams.WRAP_CONTENT, height: int = ViewGroup.LayoutParams.WRAP_CONTENT): super().__init__(width, height) self.column = -1 self.span = 1 # ═══════════════════════════════════════════════════════════════ # ScrollView / HorizontalScrollView # ═══════════════════════════════════════════════════════════════ class ScrollView(FrameLayout): """ScrollView — 滚动视图""" def __init__(self, context=None): super().__init__(context) self._fill_viewport = False self._smooth_scrolling_enabled = True self._max_scroll_amount = 0 self._scroll_position = 0 self._is_layout_after_scroll = False def setFillViewport(self, fill_viewport: bool): self._fill_viewport = fill_viewport def isFillViewport(self) -> bool: return self._fill_viewport def setSmoothScrollingEnabled(self, enabled: bool): self._smooth_scrolling_enabled = enabled def isSmoothScrollingEnabled(self) -> bool: return self._smooth_scrolling_enabled def fullScroll(self, direction: int) -> bool: return True def arrowScroll(self, direction: int) -> bool: return True def smoothScrollTo(self, x, y): self._scroll_x = x self._scroll_y = y 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 def getMaxScrollAmount(self) -> int: return self._max_scroll_amount def computeScrollDeltaToGetChildRectOnScreen(self, rect: Any) -> int: return 0 def isLayoutAfterScroll(self) -> bool: return self._is_layout_after_scroll class HorizontalScrollView(FrameLayout): """HorizontalScrollView — 水平滚动视图""" def __init__(self, context=None): super().__init__(context) self._fill_viewport = False def setFillViewport(self, fill_viewport: bool): self._fill_viewport = fill_viewport def isFillViewport(self) -> bool: return self._fill_viewport def fullScroll(self, direction: int) -> bool: return True def arrowScroll(self, direction: int) -> bool: return True def smoothScrollTo(self, x, y): self._scroll_x = x self._scroll_y = y 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 # ═══════════════════════════════════════════════════════════════ # WebView # ═══════════════════════════════════════════════════════════════ class WebView(ViewGroup): """WebView — 网页视图""" # 布局模式 NORMAL = 0 SMALL_SCREEN = 1 LARGE_SCREEN = 2 def __init__(self, context=None): super().__init__(context) self._settings: "WebSettings" = WebSettings() self._web_view_client: Optional[Callable] = None self._web_chrome_client: Optional[Callable] = None self._can_go_back: bool = False self._can_go_forward: bool = False self._current_url: str = "" self._history: List[str] = [] self._current_index: int = -1 self._download_listener: Optional[Callable] = None self._find_listener: Optional[Callable] = None self._picture_listener: Optional[Callable] = None def loadUrl(self, url: str, additional_headers: Optional[Dict[str, str]] = None): self._current_url = url self._history = self._history[:self._current_index + 1] self._history.append(url) self._current_index = len(self._history) - 1 self._can_go_back = self._current_index > 0 self._can_go_forward = False def loadData(self, data: str, mime_type: str = "text/html", encoding: str = "utf-8"): self._current_url = "about:blank" def loadDataWithBaseURL(self, base_url: str, data: str, mime_type: str, encoding: str, history_url: str): self._current_url = base_url or "about:blank" def getSettings(self) -> "WebSettings": return self._settings def setWebViewClient(self, client: Any): self._web_view_client = client def getWebViewClient(self) -> Any: return self._web_view_client def setWebChromeClient(self, client: Any): self._web_chrome_client = client def getWebChromeClient(self) -> Any: return self._web_chrome_client def goBack(self): if self._can_go_back: self._current_index -= 1 self._current_url = self._history[self._current_index] self._can_go_back = self._current_index > 0 self._can_go_forward = True def goForward(self): if self._can_go_forward: self._current_index += 1 self._current_url = self._history[self._current_index] self._can_go_forward = self._current_index < len(self._history) - 1 self._can_go_back = True def reload(self): pass def canGoBack(self) -> bool: return self._can_go_back def canGoForward(self) -> bool: return self._can_go_forward def clearHistory(self): self._history.clear() self._current_index = -1 self._can_go_back = False self._can_go_forward = False def destroy(self): pass def onPause(self): pass def onResume(self): pass def pauseTimers(self): pass def resumeTimers(self): pass def getUrl(self) -> Optional[str]: return self._current_url if self._current_url else None def getTitle(self) -> Optional[str]: return None def evaluateJavascript(self, script: str, callback: Optional[Callable] = None): if callback: callback("") def addJavascriptInterface(self, obj: Any, name: str): pass def removeJavascriptInterface(self, name: str): pass def canGoBackOrForward(self, steps: int) -> bool: target = self._current_index + steps return 0 <= target < len(self._history) def goBackOrForward(self, steps: int): target = self._current_index + steps if 0 <= target < len(self._history): self._current_index = target self._current_url = self._history[target] self._can_go_back = target > 0 self._can_go_forward = target < len(self._history) - 1 def setDownloadListener(self, listener: Optional[Callable]): self._download_listener = listener def setFindListener(self, listener: Optional[Callable]): self._find_listener = listener class WebViewTransport: def setWebView(self, web_view: "WebView"): pass def getWebView(self) -> Optional["WebView"]: return None class HitTestResult: UNKNOWN_TYPE = 0 ANCHOR_TYPE = 1 EDIT_TEXT_TYPE = 2 IMAGE_TYPE = 3 SRC_ANCHOR_TYPE = 4 SRC_IMAGE_ANCHOR_TYPE = 5 PHONE_TYPE = 6 GEO_TYPE = 7 EMAIL_TYPE = 8 def __init__(self): self._type = WebView.HitTestResult.UNKNOWN_TYPE self._extra = "" def getType(self) -> int: return self._type def getExtra(self) -> str: return self._extra def getHitTestResult(self) -> HitTestResult: return WebView.HitTestResult() def findAllAsync(self, find_str: str): pass def findNext(self, forward: bool): pass def clearMatches(self): pass def zoomIn(self) -> bool: return False def zoomOut(self) -> bool: return False def setInitialScale(self, scale_in_percent: int): pass def getScale(self) -> float: return 1.0 def setPictureListener(self, listener: Optional[Callable]): self._picture_listener = listener class WebSettings(Stub): """WebSettings — WebView 设置""" # 渲染模式 RENDER_GOES_BROWSER = 0 RENDER_GOES_SURFACE_VIEW = 1 RENDER_GOES_DISPLAY_LIST = 2 RENDER_GOES_GPU = 3 RENDER_GOES_SURFACE = 4 # 布局算法 LAYOUT_ALGORITHM_NORMAL = 0 LAYOUT_ALGORITHM_SINGLE_COLUMN = 1 LAYOUT_ALGORITHM_NARROW_COLUMNS = 2 LAYOUT_ALGORITHM_TEXT_AUTOSIZING = 3 def __init__(self): super().__init__() self._javascript_enabled = False self._dom_storage_enabled = False self._allow_file_access = True self._built_in_zoom_controls = True self._display_zoom_controls = True self._load_with_overview_mode = False self._support_zoom = True self._use_wide_view_port = False self._database_enabled = False self._allow_content_access = True self._cache_mode = -1 # LOAD_DEFAULT self._user_agent_string: str = "Mozilla/5.0 (Linux; Android 13)" self._default_font_size = 16 self._minimum_font_size = 8 self._text_zoom = 100 self._standard_font_family = "sans-serif" self._serif_font_family = "serif" self._sans_serif_font_family = "sans-serif" self._fixed_font_family = "monospace" self._cursive_font_family = "cursive" self._fantasy_font_family = "fantasy" self._default_encoding = "utf-8" self._layout_algorithm = WebSettings.LAYOUT_ALGORITHM_NORMAL self._block_network_image = False self._block_network_loads = False self._mixed_content_mode = 0 # MIXED_CONTENT_ALWAYS_ALLOW self._offscreen_pre_raster = False self._safe_browsing_enabled = True def setJavaScriptEnabled(self, enabled: bool): self._javascript_enabled = enabled def getJavaScriptEnabled(self) -> bool: return self._javascript_enabled def setDomStorageEnabled(self, enabled: bool): self._dom_storage_enabled = enabled def getDomStorageEnabled(self) -> bool: return self._dom_storage_enabled def setAllowFileAccess(self, allow: bool): self._allow_file_access = allow def getAllowFileAccess(self) -> bool: return self._allow_file_access def setBuiltInZoomControls(self, enabled: bool): self._built_in_zoom_controls = enabled def getBuiltInZoomControls(self) -> bool: return self._built_in_zoom_controls def setDisplayZoomControls(self, enabled: bool): self._display_zoom_controls = enabled def getDisplayZoomControls(self) -> bool: return self._display_zoom_controls def setSupportZoom(self, support: bool): self._support_zoom = support def supportZoom(self) -> bool: return self._support_zoom def setLoadWithOverviewMode(self, overview: bool): self._load_with_overview_mode = overview def getLoadWithOverviewMode(self) -> bool: return self._load_with_overview_mode def setUseWideViewPort(self, use: bool): self._use_wide_view_port = use def getUseWideViewPort(self) -> bool: return self._use_wide_view_port def setDatabaseEnabled(self, enabled: bool): self._database_enabled = enabled def getDatabaseEnabled(self) -> bool: return self._database_enabled def setAllowContentAccess(self, allow: bool): self._allow_content_access = allow def getAllowContentAccess(self) -> bool: return self._allow_content_access def setCacheMode(self, mode: int): self._cache_mode = mode def getCacheMode(self) -> int: return self._cache_mode LOAD_DEFAULT = -1 LOAD_CACHE_ELSE_NETWORK = 1 LOAD_NO_CACHE = 2 LOAD_CACHE_ONLY = 3 def setUserAgentString(self, ua: str): self._user_agent_string = ua def getUserAgentString(self) -> str: return self._user_agent_string def setDefaultFontSize(self, size: int): self._default_font_size = size def getDefaultFontSize(self) -> int: return self._default_font_size def setMinimumFontSize(self, size: int): self._minimum_font_size = size def getMinimumFontSize(self) -> int: return self._minimum_font_size def setTextZoom(self, text_zoom: int): self._text_zoom = text_zoom def getTextZoom(self) -> int: return self._text_zoom def setStandardFontFamily(self, font: str): self._standard_font_family = font def setSerifFontFamily(self, font: str): self._serif_font_family = font def setSansSerifFontFamily(self, font: str): self._sans_serif_font_family = font def setFixedFontFamily(self, font: str): self._fixed_font_family = font def setCursiveFontFamily(self, font: str): self._cursive_font_family = font def setFantasyFontFamily(self, font: str): self._fantasy_font_family = font def setDefaultTextEncodingName(self, encoding: str): self._default_encoding = encoding def getDefaultTextEncodingName(self) -> str: return self._default_encoding def setLayoutAlgorithm(self, algorithm: int): self._layout_algorithm = algorithm def getLayoutAlgorithm(self) -> int: return self._layout_algorithm def setBlockNetworkImage(self, block: bool): self._block_network_image = block def getBlockNetworkImage(self) -> bool: return self._block_network_image def setBlockNetworkLoads(self, block: bool): self._block_network_loads = block def getBlockNetworkLoads(self) -> bool: return self._block_network_loads def setMixedContentMode(self, mode: int): self._mixed_content_mode = mode def getMixedContentMode(self) -> int: return self._mixed_content_mode MIXED_CONTENT_ALWAYS_ALLOW = 0 MIXED_CONTENT_NEVER_ALLOW = 1 MIXED_CONTENT_COMPATIBILITY_MODE = 2 def setOffscreenPreRaster(self, enabled: bool): self._offscreen_pre_raster = enabled def getOffscreenPreRaster(self) -> bool: return self._offscreen_pre_raster def setSafeBrowsingEnabled(self, enabled: bool): self._safe_browsing_enabled = enabled def getSafeBrowsingEnabled(self) -> bool: return self._safe_browsing_enabled def setNeedInitialFocus(self, flag: bool): pass # ═══════════════════════════════════════════════════════════════ # ViewAnimator / ViewFlipper / ViewSwitcher etc. # ═══════════════════════════════════════════════════════════════ class ViewAnimator(FrameLayout): """ViewAnimator — 视图动画容器""" def __init__(self, context=None): super().__init__(context) self._which_child: int = 0 self._animate_first_view: bool = False self._in_animation: Any = None self._out_animation: Any = None def setDisplayedChild(self, which_child: int): if 0 <= which_child < self.getChildCount(): self._which_child = which_child def getDisplayedChild(self) -> int: return self._which_child def showNext(self): if self._which_child < self.getChildCount() - 1: self._which_child += 1 def showPrevious(self): if self._which_child > 0: self._which_child -= 1 def setInAnimation(self, in_animation: Any): self._in_animation = in_animation def getInAnimation(self) -> Any: return self._in_animation def setOutAnimation(self, out_animation: Any): self._out_animation = out_animation def getOutAnimation(self) -> Any: return self._out_animation def setAnimateFirstView(self, animate: bool): self._animate_first_view = animate def getAnimateFirstView(self) -> bool: return self._animate_first_view def getBaseline(self) -> int: return -1 class ViewFlipper(ViewAnimator): """ViewFlipper — 视图翻转器""" def __init__(self, context=None): super().__init__(context) self._flip_interval: int = 3000 self._auto_start: bool = False self._running: bool = False self._flip_timer: Any = None def setFlipInterval(self, milliseconds: int): self._flip_interval = milliseconds def getFlipInterval(self) -> int: return self._flip_interval def setAutoStart(self, auto_start: bool): self._auto_start = auto_start def isAutoStart(self) -> bool: return self._auto_start def startFlipping(self): self._running = True def stopFlipping(self): self._running = False def isFlipping(self) -> bool: return self._running def onDetachedFromWindow(self): super().onDetachedFromWindow() self._running = False def onAttachedToWindow(self): super().onAttachedToWindow() if self._auto_start: self._running = True class ViewSwitcher(ViewAnimator): """ViewSwitcher — 视图切换器""" def __init__(self, context=None): super().__init__(context) self._factory: Optional[Callable] = None class ViewFactory: def makeView(self) -> View: return View() def setFactory(self, factory: ViewFactory): self._factory = factory def getCurrentView(self) -> Optional[View]: return self.getChildAt(self._which_child) def getNextView(self) -> Optional[View]: next_index = self._which_child + 1 if next_index < self.getChildCount(): return self.getChildAt(next_index) return None def setInAnimation(self, in_animation: Any): super().setInAnimation(in_animation) def setOutAnimation(self, out_animation: Any): super().setOutAnimation(out_animation) class ImageSwitcher(ViewSwitcher): """ImageSwitcher — 图片切换器""" def setImageDrawable(self, drawable: Any): pass def setImageResource(self, resid: int): pass def setImageURI(self, uri: Any): pass def setImageBitmap(self, bitmap: Any): pass class TextSwitcher(ViewSwitcher): """TextSwitcher — 文本切换器""" def setCurrentText(self, text: str): if self.getChildCount() > 0: child = self.getChildAt(0) if hasattr(child, "setText"): child.setText(text) def setText(self, text: str): self.setCurrentText(text) class AdapterViewAnimator(AdapterView): """AdapterViewAnimator — 适配器视图动画器""" def __init__(self, context=None): super().__init__(context) self._in_animation: Any = None self._out_animation: Any = None self._displayed_child: int = 0 self._loop_views: bool = False def setDisplayedChild(self, which_child: int): self._displayed_child = which_child def getDisplayedChild(self) -> int: return self._displayed_child def showNext(self): self._displayed_child = (self._displayed_child + 1) % max(1, self.getCount()) def showPrevious(self): last = max(1, self.getCount()) self._displayed_child = (self._displayed_child - 1) % last def setInAnimation(self, in_animation: Any): self._in_animation = in_animation def getInAnimation(self) -> Any: return self._in_animation def setOutAnimation(self, out_animation: Any): self._out_animation = out_animation def getOutAnimation(self) -> Any: return self._out_animation def getCurrentView(self) -> Optional[View]: return self.getChildAt(self._displayed_child) def getBaseline(self) -> int: return -1 def getCount(self) -> int: if self._adapter: return self._adapter.getCount() return 0 class AdapterViewFlipper(AdapterViewAnimator): """AdapterViewFlipper — 适配器视图翻转器""" def __init__(self, context=None): super().__init__(context) self._flip_interval: int = 10000 self._auto_start: bool = False self._running: bool = False def setFlipInterval(self, milliseconds: int): self._flip_interval = milliseconds def getFlipInterval(self) -> int: return self._flip_interval def startFlipping(self): self._running = True def stopFlipping(self): self._running = False def isFlipping(self) -> bool: return self._running def setAutoStart(self, auto_start: bool): self._auto_start = auto_start def isAutoStart(self) -> bool: return self._auto_start class StackView(AdapterViewAnimator): """StackView — 栈视图""" pass # ═══════════════════════════════════════════════════════════════ # Adapters # ═══════════════════════════════════════════════════════════════ class BaseAdapter(Stub): """BaseAdapter — 基础适配器""" def __init__(self): super().__init__() self._data_set_observable: Any = None def getCount(self) -> int: return 0 def getItem(self, position: int) -> Any: return None def getItemId(self, position: int) -> int: return position def getView(self, position: int, convert_view: Optional[View], parent: Optional[ViewGroup]) -> View: return View() def getItemViewType(self, position: int) -> int: return 0 def getViewTypeCount(self) -> int: return 1 def hasStableIds(self) -> bool: return False def isEmpty(self) -> bool: return self.getCount() == 0 def areAllItemsEnabled(self) -> bool: return True def isEnabled(self, position: int) -> bool: return True def getDropDownView(self, position: int, convert_view: Optional[View], parent: Optional[ViewGroup]) -> View: return self.getView(position, convert_view, parent) def notifyDataSetChanged(self): for observer in getattr(self, '_observers', []): observer.onChanged() def notifyDataSetInvalidated(self): for observer in getattr(self, '_observers', []): observer.onInvalidated() def registerDataSetObserver(self, observer): if not hasattr(self, '_observers'): self._observers = [] self._observers.append(observer) def unregisterDataSetObserver(self, observer): if hasattr(self, '_observers'): try: self._observers.remove(observer) except ValueError: pass class ArrayAdapter(BaseAdapter): """ArrayAdapter — 数组适配器""" def __init__(self, context: Any, resource: int, objects: Optional[List[Any]] = None): super().__init__() self._context = context self._resource = resource self._drop_down_resource = resource self._objects: List[Any] = list(objects) if objects is not None else [] self._field_id = 0 self._notify_on_change = True def getCount(self) -> int: return len(self._objects) def getItem(self, position: int) -> Any: if 0 <= position < len(self._objects): return self._objects[position] return None def getPosition(self, item: Any) -> int: try: return self._objects.index(item) except ValueError: return -1 def getView(self, position: int, convert_view: Optional[View], parent: Optional[ViewGroup]) -> View: if convert_view is not None: tv = convert_view else: tv = TextView(self._context) if hasattr(tv, "setText"): tv.setText(str(self.getItem(position) if self.getItem(position) is not None else "")) return tv def add(self, obj: Any): self._objects.append(obj) if self._notify_on_change: self.notifyDataSetChanged() def addAll(self, *items: Any): for item in items: self._objects.append(item) if self._notify_on_change: self.notifyDataSetChanged() def insert(self, obj: Any, index: int): self._objects.insert(index, obj) if self._notify_on_change: self.notifyDataSetChanged() def remove(self, obj: Any): if obj in self._objects: self._objects.remove(obj) if self._notify_on_change: self.notifyDataSetChanged() def clear(self): self._objects.clear() if self._notify_on_change: self.notifyDataSetChanged() def sort(self, comparator: Callable): self._objects.sort(key=comparator) if self._notify_on_change: self.notifyDataSetChanged() def setNotifyOnChange(self, notify_on_change: bool): self._notify_on_change = notify_on_change def getContext(self): return self._context def getCount(self) -> int: return len(self._objects) def setDropDownViewResource(self, resource: int): self._drop_down_resource = resource def getDropDownViewResource(self) -> int: return self._drop_down_resource class SimpleAdapter(BaseAdapter): """SimpleAdapter — 简单适配器""" def __init__(self, context: Any, data: List[Dict[str, Any]], resource: int, from_cols: List[str], to_ids: List[int]): super().__init__() self._context = context self._data = data self._resource = resource self._from = from_cols self._to = to_ids self._drop_down_resource = resource def getCount(self) -> int: return len(self._data) def getItem(self, position: int) -> Any: if 0 <= position < len(self._data): return self._data[position] return None def getView(self, position: int, convert_view: Optional[View], parent: Optional[ViewGroup]) -> View: if convert_view is not None: v = convert_view else: v = View(self._context) return v def setDropDownViewResource(self, resource: int): self._drop_down_resource = resource class CursorAdapter(BaseAdapter): """CursorAdapter — 游标适配器""" FLAG_AUTO_REQUERY = 0x01 FLAG_REGISTER_CONTENT_OBSERVER = 0x02 def __init__(self, context: Any, c: Any, flags: int = FLAG_AUTO_REQUERY): super().__init__() self._context = context self._cursor = c self._flags = flags def getCount(self) -> int: return 0 def getItem(self, position: int) -> Any: return None def getView(self, position: int, convert_view: Optional[View], parent: Optional[ViewGroup]) -> View: return View(self._context) def bindView(self, view: View, context: Any, cursor: Any): pass def newView(self, context: Any, cursor: Any, parent: ViewGroup) -> View: return View(context) def getCursor(self) -> Any: return self._cursor def changeCursor(self, cursor: Any): self._cursor = cursor self.notifyDataSetChanged() def swapCursor(self, new_cursor: Any) -> Any: old = self._cursor self._cursor = new_cursor self.notifyDataSetChanged() return old # ═══════════════════════════════════════════════════════════════ # Spinner # ═══════════════════════════════════════════════════════════════ class Spinner(AdapterView): """Spinner — 下拉选择器""" MODE_DIALOG = 0 MODE_DROPDOWN = 1 def __init__(self, context=None, mode: int = MODE_DROPDOWN): super().__init__(context) self._mode = mode self._prompt: str = "" self._popup: Any = None self._on_item_selected_listener: Optional[Callable] = None class OnItemSelectedListener: def onItemSelected(self, parent: AdapterView, view: View, position: int, id_: int): pass def onNothingSelected(self, parent: AdapterView): pass def setOnItemSelectedListener(self, listener: Optional[OnItemSelectedListener]): self._on_item_selected_listener = listener def getOnItemSelectedListener(self) -> Optional[OnItemSelectedListener]: return self._on_item_selected_listener def setSelection(self, position: int, animate: bool = True): super().setSelection(position) def getSelectedItem(self) -> Any: return super().getSelectedItem() def getSelectedItemPosition(self) -> int: return self._selected_position def getSelectedView(self) -> Optional[View]: return None def setPrompt(self, prompt: str): self._prompt = prompt def getPrompt(self) -> str: return self._prompt def setPopupBackgroundDrawable(self, drawable: Any): pass def getPopupBackgroundDrawable(self) -> Any: return None def performClick(self) -> bool: return True # ═══════════════════════════════════════════════════════════════ # AutoCompleteTextView # ═══════════════════════════════════════════════════════════════ class AutoCompleteTextView(EditText): """AutoCompleteTextView — 自动完成文本""" def __init__(self, context=None): super().__init__(context) self._threshold: int = 2 self._completion_hint: str = "" self._drop_down_anchor: Optional[View] = None self._drop_down_horizontal_offset: int = 0 self._drop_down_vertical_offset: int = 0 self._drop_down_width: int = 0 self._drop_down_height: int = 0 self._on_item_click_listener: Optional[Callable] = None self._on_dismiss_listener: Optional[Callable] = None self._validator: Optional[Callable] = None def setThreshold(self, threshold: int): self._threshold = max(1, threshold) def getThreshold(self) -> int: return self._threshold def setAdapter(self, adapter: Any): self._adapter = adapter def getAdapter(self) -> Any: return self._adapter def setOnItemClickListener(self, listener: Optional[Callable]): self._on_item_click_listener = listener def getOnItemClickListener(self) -> Optional[Callable]: return self._on_item_click_listener def setCompletionHint(self, hint: str): self._completion_hint = hint def getCompletionHint(self) -> str: return self._completion_hint def setDropDownAnchor(self, anchor_id: int): pass def setDropDownHorizontalOffset(self, offset: int): self._drop_down_horizontal_offset = offset def getDropDownHorizontalOffset(self) -> int: return self._drop_down_horizontal_offset def setDropDownVerticalOffset(self, offset: int): self._drop_down_vertical_offset = offset def getDropDownVerticalOffset(self) -> int: return self._drop_down_vertical_offset def setDropDownWidth(self, width: int): self._drop_down_width = width def getDropDownWidth(self) -> int: return self._drop_down_width def showDropDown(self): pass def dismissDropDown(self): pass def isDropDownShowing(self) -> bool: return False def enoughToFilter(self) -> bool: return len(self._text) >= self._threshold def setValidator(self, validator: Optional[Callable]): self._validator = validator def getValidator(self) -> Optional[Callable]: return self._validator def performValidation(self): pass def setOnDismissListener(self, listener: Optional[Callable]): self._on_dismiss_listener = listener def performFiltering(self, text: str, key_code: int): pass def onFilterComplete(self, count: int): pass class MultiAutoCompleteTextView(AutoCompleteTextView): """MultiAutoCompleteTextView — 多值自动完成文本""" def __init__(self, context=None): super().__init__(context) self._tokenizer: Optional[Callable] = None class CommaTokenizer: def tokenize(self, text: str) -> List[str]: return text.split(",") def findTokenStart(self, text: str, cursor: int) -> int: return text.rfind(",", 0, cursor) + 1 def findTokenEnd(self, text: str, cursor: int) -> int: next_comma = text.find(",", cursor) return next_comma if next_comma >= 0 else len(text) def terminateToken(self, text: str) -> str: return text + "," def setTokenizer(self, tokenizer: Any): self._tokenizer = tokenizer def getTokenizer(self) -> Any: return self._tokenizer # ═══════════════════════════════════════════════════════════════ # DatePicker / TimePicker / CalendarView / NumberPicker # ═══════════════════════════════════════════════════════════════ class DatePicker(FrameLayout): """DatePicker — 日期选择器""" def __init__(self, context=None): super().__init__(context) self._year: int = 2024 self._month: int = 0 # 0-based self._day_of_month: int = 1 self._min_date: int = 0 self._max_date: int = 0 self._spinners_shown: bool = True self._calendar_view_shown: bool = False self._on_date_changed_listener: Optional[Callable] = None self._calendar_view: Optional[CalendarView] = None class OnDateChangedListener: def onDateChanged(self, view: "DatePicker", year: int, month_of_year: int, day_of_month: int): pass def init(self, year: int, month: int, day_of_month: int, listener: Optional[OnDateChangedListener] = None): self._year = year self._month = month self._day_of_month = day_of_month self._on_date_changed_listener = listener if listener: listener.onDateChanged(self, year, month, day_of_month) def getYear(self) -> int: return self._year def getMonth(self) -> int: return self._month def getDayOfMonth(self) -> int: return self._day_of_month def setMinDate(self, min_date: int): self._min_date = min_date def getMinDate(self) -> int: return self._min_date def setMaxDate(self, max_date: int): self._max_date = max_date def getMaxDate(self) -> int: return self._max_date def setSpinnersShown(self, shown: bool): self._spinners_shown = shown def setCalendarViewShown(self, shown: bool): self._calendar_view_shown = shown def getCalendarView(self) -> Optional["CalendarView"]: return self._calendar_view def setOnDateChangedListener(self, listener: Optional[OnDateChangedListener]): self._on_date_changed_listener = listener def updateDate(self, year: int, month: int, day_of_month: int): self._year = year self._month = month self._day_of_month = day_of_month if self._on_date_changed_listener: self._on_date_changed_listener.onDateChanged(self, year, month, day_of_month) class TimePicker(FrameLayout): """TimePicker — 时间选择器""" MODE_CLOCK = 0 MODE_SPINNER = 1 def __init__(self, context=None, mode: int = MODE_CLOCK): super().__init__(context) self._current_hour: int = 12 self._current_minute: int = 0 self._is24_hour_view: Optional[bool] = None # None = system default self._mode = mode self._on_time_changed_listener: Optional[Callable] = None class OnTimeChangedListener: def onTimeChanged(self, view: "TimePicker", hour_of_day: int, minute: int): pass def setOnTimeChangedListener(self, listener: Optional[OnTimeChangedListener]): self._on_time_changed_listener = listener def getOnTimeChangedListener(self) -> Optional[OnTimeChangedListener]: return self._on_time_changed_listener def getCurrentHour(self) -> int: return self._current_hour def setCurrentHour(self, current_hour: int): self._current_hour = current_hour % 24 if self._on_time_changed_listener: self._on_time_changed_listener.onTimeChanged(self, self._current_hour, self._current_minute) def getCurrentMinute(self) -> int: return self._current_minute def setCurrentMinute(self, current_minute: int): self._current_minute = current_minute % 60 if self._on_time_changed_listener: self._on_time_changed_listener.onTimeChanged(self, self._current_hour, self._current_minute) def setIs24HourView(self, is24_hour_view: Optional[bool]): self._is24_hour_view = is24_hour_view def is24HourView(self) -> bool: if self._is24_hour_view is not None: return self._is24_hour_view return False def getMode(self) -> int: return self._mode def setHour(self, hour: int): self.setCurrentHour(hour) def getHour(self) -> int: return self.getCurrentHour() def setMinute(self, minute: int): self.setCurrentMinute(minute) def getMinute(self) -> int: return self.getCurrentMinute() class CalendarView(FrameLayout): """CalendarView — 日历视图""" def __init__(self, context=None): super().__init__(context) self._date: int = 0 self._min_date: int = 0 self._max_date: int = 0 self._first_day_of_week: int = 1 # Sunday=1 self._week_separator: bool = False self._on_date_change_listener: Optional[Callable] = None self._shown_week_count: int = 6 class OnDateChangeListener: def onSelectedDayChange(self, view: "CalendarView", year: int, month: int, day_of_month: int): pass def setOnDateChangeListener(self, listener: Optional[OnDateChangeListener]): self._on_date_change_listener = listener def getDate(self) -> int: return self._date def setDate(self, date: int, animate: bool = True, center: bool = True): self._date = date def getMinDate(self) -> int: return self._min_date def setMinDate(self, min_date: int): self._min_date = min_date def getMaxDate(self) -> int: return self._max_date def setMaxDate(self, max_date: int): self._max_date = max_date def getFirstDayOfWeek(self) -> int: return self._first_day_of_week def setFirstDayOfWeek(self, first_day_of_week: int): self._first_day_of_week = first_day_of_week def setWeekSeparatorDrawable(self, drawable: Any): self._week_separator = True def getShownWeekCount(self) -> int: return self._shown_week_count def setShownWeekCount(self, count: int): self._shown_week_count = count class NumberPicker(ViewGroup): """NumberPicker — 数字选择器""" def __init__(self, context=None): super().__init__(context) self._min_value: int = 0 self._max_value: int = 100 self._value: int = 0 self._displayed_values: Optional[List[str]] = None self._on_value_change_listener: Optional[Callable] = None self._on_scroll_listener: Optional[Callable] = None self._wrap_selector_wheel: bool = True self._selection_divider: bool = True class OnValueChangeListener: def onValueChange(self, picker: "NumberPicker", old_val: int, new_val: int): pass class OnScrollListener: def onScrollStateChange(self, picker: "NumberPicker", state: int): pass SCROLL_STATE_IDLE = 0 SCROLL_STATE_TOUCH_SCROLL = 1 SCROLL_STATE_FLING = 2 def setMinValue(self, min_value: int): self._min_value = min_value if self._value < self._min_value: self._value = self._min_value def getMinValue(self) -> int: return self._min_value def setMaxValue(self, max_value: int): self._max_value = max_value if self._value > self._max_value: self._value = self._max_value def getMaxValue(self) -> int: return self._max_value def setValue(self, value: int): old_value = self._value self._value = max(self._min_value, min(self._max_value, value)) if self._on_value_change_listener and old_value != self._value: self._on_value_change_listener.onValueChange(self, old_value, self._value) def getValue(self) -> int: return self._value def setDisplayedValues(self, displayed_values: Optional[List[str]]): self._displayed_values = displayed_values def getDisplayedValues(self) -> Optional[List[str]]: return self._displayed_values def setOnValueChangedListener(self, listener: Optional[OnValueChangeListener]): self._on_value_change_listener = listener def setOnScrollListener(self, listener: Optional[OnScrollListener]): self._on_scroll_listener = listener def setWrapSelectorWheel(self, wrap: bool): self._wrap_selector_wheel = wrap def getWrapSelectorWheel(self) -> bool: return self._wrap_selector_wheel def setSelectionDivider(self, divider: Any): self._selection_divider = True # ═══════════════════════════════════════════════════════════════ # VideoView / MediaController # ═══════════════════════════════════════════════════════════════ class VideoView(SurfaceView): """VideoView — 视频播放视图""" def __init__(self, context=None): super().__init__(context) self._video_path: Optional[str] = None self._uri: Any = None self._media_controller: Optional["MediaController"] = None self._on_prepared_listener: Optional[Callable] = None self._on_completion_listener: Optional[Callable] = None self._on_error_listener: Optional[Callable] = None self._is_playing: bool = False self._current_position: int = 0 self._duration: int = 0 self._can_seek_back: bool = True self._can_seek_forward: bool = True self._can_pause: bool = True self._video_width: int = 0 self._video_height: int = 0 def setVideoPath(self, path: str): self._video_path = path def setVideoURI(self, uri: Any): self._uri = uri def setMediaController(self, controller: "MediaController"): self._media_controller = controller def setOnPreparedListener(self, listener: Optional[Callable]): self._on_prepared_listener = listener def setOnCompletionListener(self, listener: Optional[Callable]): self._on_completion_listener = listener def setOnErrorListener(self, listener: Optional[Callable]): self._on_error_listener = listener def start(self): self._is_playing = True def stopPlayback(self): self._is_playing = False self._current_position = 0 def pause(self): self._is_playing = False def resume(self): self._is_playing = True def seekTo(self, msec: int): self._current_position = max(0, min(msec, self._duration)) def getCurrentPosition(self) -> int: return self._current_position def getDuration(self) -> int: return self._duration def isPlaying(self) -> bool: return self._is_playing def canSeekForward(self) -> bool: return self._can_seek_forward def canSeekBackward(self) -> bool: return self._can_seek_back def canPause(self) -> bool: return self._can_pause def getBufferPercentage(self) -> int: return 100 def resolveAdjustedSize(self, desired_size: int, measure_spec: int) -> int: from .view import MeasureSpec return MeasureSpec.getSize(measure_spec) def setVideoWidth(self, video_width: int): self._video_width = video_width def setVideoHeight(self, video_height: int): self._video_height = video_height @property def videoWidth(self) -> int: return self._video_width @property def videoHeight(self) -> int: return self._video_height class MediaController(FrameLayout): """MediaController — 媒体控制器""" def __init__(self, context=None, target: Optional[VideoView] = None): super().__init__(context) self._target = target self._anchor_view: Optional[View] = None self._is_showing: bool = False self._set_up_listener: Optional[Callable] = None def setMediaPlayer(self, player: "MediaController.MediaPlayerControl"): pass def setAnchorView(self, view: View): self._anchor_view = view def show(self, timeout: int = 3000): self._is_showing = True def hide(self): self._is_showing = False def isShowing(self) -> bool: return self._is_showing def setEnabled(self, enabled: bool): super().setEnabled(enabled) def setOnPrepareListener(self, listener: Optional[Callable]): self._set_up_listener = listener class MediaPlayerControl: def start(self): pass def pause(self): pass def getDuration(self) -> int: return 0 def getCurrentPosition(self) -> int: return 0 def seekTo(self, pos: int): pass def isPlaying(self) -> bool: return False def getBufferPercentage(self) -> int: return 0 def canPause(self) -> bool: return False def canSeekBackward(self) -> bool: return False def canSeekForward(self) -> bool: return False def getAudioSessionId(self) -> int: return 0 # ═══════════════════════════════════════════════════════════════ # Toast # ═══════════════════════════════════════════════════════════════ class Toast(Stub): """Toast — 提示消息""" LENGTH_SHORT = 0 LENGTH_LONG = 1 def __init__(self, context=None): super().__init__() if context is None: context = android_context self._context = context self._duration = Toast.LENGTH_SHORT self._gravity = Gravity.NO_GRAVITY self._x_offset: int = 0 self._y_offset: int = 0 self._view: Optional[View] = None self._text: str = "" self._horizontal_margin: float = 0.0 self._vertical_margin: float = 0.0 self._next_view: Optional[View] = None @staticmethod def makeText(context: Any, text: str, duration: int) -> "Toast": toast = Toast(context) toast._text = text toast._duration = duration return toast def show(self): if _USE_LINUX: LinuxNotify.send("Toast", self._text) else: print(f"[Toast] {self._text}") def cancel(self): if _USE_LINUX: # 桌面通知自动消失,无需操作 return pass def setGravity(self, gravity: int, x_offset: int, y_offset: int): self._gravity = gravity self._x_offset = x_offset self._y_offset = y_offset def setDuration(self, duration: int): self._duration = duration def getDuration(self) -> int: return self._duration def setView(self, view: View): self._view = view def getView(self) -> Optional[View]: return self._view def setMargin(self, horizontal_margin: float, vertical_margin: float): self._horizontal_margin = horizontal_margin self._vertical_margin = vertical_margin def getHorizontalMargin(self) -> float: return self._horizontal_margin def getVerticalMargin(self) -> float: return self._vertical_margin def setText(self, text: str): self._text = text def setNextAnnouncementView(self, view: View): self._next_view = view def getNextAnnouncementView(self) -> Optional[View]: return self._next_view def isTouchable(self) -> bool: return False # ═══════════════════════════════════════════════════════════════ # PopupMenu / PopupWindow # ═══════════════════════════════════════════════════════════════ class PopupMenu(Stub): """PopupMenu — 弹出菜单""" def __init__(self, context: Any, anchor: View, gravity: int = Gravity.NO_GRAVITY): super().__init__() self._context = context self._anchor = anchor self._gravity = gravity self._menu: "PopupMenu.Menu" = PopupMenu.Menu() self._on_menu_item_click_listener: Optional[Callable] = None self._on_dismiss_listener: Optional[Callable] = None class Menu(Stub): """PopupMenu.Menu — 弹出菜单项列表""" def __init__(self): super().__init__() self._items: List[Dict] = [] def add(self, title: str) -> Any: item_id = len(self._items) self._items.append({"id": item_id, "title": title, "enabled": True}) return item_id def findItem(self, id_: int) -> Optional[Dict]: for item in self._items: if item["id"] == id_: return item return None def removeItem(self, id_: int): for i, item in enumerate(self._items): if item["id"] == id_: self._items.pop(i) break def size(self) -> int: return len(self._items) def getItem(self, index: int) -> Optional[Dict]: if 0 <= index < len(self._items): return self._items[index] return None def clear(self): self._items.clear() def setOnMenuItemClickListener(self, listener: Optional[Callable]): pass def getMenu(self) -> "PopupMenu.Menu": return self._menu def inflate(self, menu_res_id: int): pass def show(self): pass def dismiss(self): pass def setOnMenuItemClickListener(self, listener: Optional[Callable]): self._on_menu_item_click_listener = listener def setOnDismissListener(self, listener: Optional[Callable]): self._on_dismiss_listener = listener def getDragToOpenListener(self) -> Optional[Callable]: return None def setForceShowIcon(self, force_show: bool): pass class PopupWindow(Stub): """PopupWindow — 弹出窗口""" def __init__(self, content_view: Optional[View] = None, width: int = ViewGroup.LayoutParams.WRAP_CONTENT, height: int = ViewGroup.LayoutParams.WRAP_CONTENT, focusable: bool = True): super().__init__() self._content_view = content_view self._width = width self._height = height self._focusable = focusable self._is_showing: bool = False self._outside_touchable: bool = False self._touchable: bool = True self._background_drawable: Any = None self._animation_style: int = -1 self._elevation: float = 0.0 self._input_method_mode: int = -1 self._soft_input_mode: int = -1 self._on_dismiss_listener: Optional[Callable] = None self._window_layout_type: int = 1000 # TYPE_APPLICATION_PANEL self._overlap_anchor: bool = True self._clip_to_screen: bool = True self._split_enabled: bool = False self._width_mode: int = 0 self._height_mode: int = 0 def showAsDropDown(self, anchor: View, x_off: int = 0, y_off: int = 0, gravity: int = Gravity.NO_GRAVITY): self._is_showing = True def showAtLocation(self, parent: View, gravity: int, x: int, y: int): self._is_showing = True def dismiss(self): self._is_showing = False if self._on_dismiss_listener: self._on_dismiss_listener() def isShowing(self) -> bool: return self._is_showing def setContentView(self, content_view: View): self._content_view = content_view def getContentView(self) -> Optional[View]: return self._content_view def setWidth(self, width: int): self._width = width def getWidth(self) -> int: return self._width def setHeight(self, height: int): self._height = height def getHeight(self) -> int: return self._height def setFocusable(self, focusable: bool): self._focusable = focusable def isFocusable(self) -> bool: return self._focusable def setOutsideTouchable(self, touchable: bool): self._outside_touchable = touchable def isOutsideTouchable(self) -> bool: return self._outside_touchable def setTouchable(self, touchable: bool): self._touchable = touchable def isTouchable(self) -> bool: return self._touchable def setBackgroundDrawable(self, background: Any): self._background_drawable = background def getBackground(self) -> Any: return self._background_drawable def setAnimationStyle(self, anim_style: int): self._animation_style = anim_style def getAnimationStyle(self) -> int: return self._animation_style def setElevation(self, elevation: float): self._elevation = elevation def getElevation(self) -> float: return self._elevation def setOnDismissListener(self, listener: Optional[Callable]): self._on_dismiss_listener = listener def setTouchInterceptor(self, interceptor: Optional[Callable]): pass def setClippingEnabled(self, enabled: bool): self._clip_to_screen = enabled def isClippingEnabled(self) -> bool: return self._clip_to_screen def setSplitTouchEnabled(self, enabled: bool): self._split_enabled = enabled def isSplitTouchEnabled(self) -> bool: return self._split_enabled def setWindowLayoutType(self, layout_type: int): self._window_layout_type = layout_type def getWindowLayoutType(self) -> int: return self._window_layout_type def setSoftInputMode(self, mode: int): self._soft_input_mode = mode def getSoftInputMode(self) -> int: return self._soft_input_mode def setInputMethodMode(self, mode: int): self._input_method_mode = mode def getInputMethodMode(self) -> int: return self._input_method_mode INPUT_METHOD_FROM_FOCUSABLE = 0 INPUT_METHOD_NEEDED = 1 INPUT_METHOD_NOT_NEEDED = 2 # ═══════════════════════════════════════════════════════════════ # Toolbar # ═══════════════════════════════════════════════════════════════ class Toolbar(ViewGroup): """Toolbar — 工具栏""" def __init__(self, context=None): super().__init__(context) self._title: str = "" self._subtitle: str = "" self._navigation_icon: Any = None self._navigation_content_description: str = "" self._logo: Any = None self._logo_description: str = "" self._title_text_color: int = 0 self._subtitle_text_color: int = 0 self._collapse_icon: Any = None self._menu: Any = None self._on_menu_item_click_listener: Optional[Callable] = None self._navigation_on_click_listener: Optional[Callable] = None self._popup_theme: int = 0 self._content_inset_start: int = 16 self._content_inset_end: int = 16 self._title_margin_start: int = 0 def setTitle(self, title: str): self._title = title def getTitle(self) -> str: return self._title def setSubtitle(self, subtitle: str): self._subtitle = subtitle def getSubtitle(self) -> str: return self._subtitle def setNavigationIcon(self, icon: Any): self._navigation_icon = icon def getNavigationIcon(self) -> Any: return self._navigation_icon def setNavigationContentDescription(self, description: str): self._navigation_content_description = description def getNavigationContentDescription(self) -> str: return self._navigation_content_description def setLogo(self, logo: Any): self._logo = logo def getLogo(self) -> Any: return self._logo def setLogoDescription(self, description: str): self._logo_description = description def getLogoDescription(self) -> str: return self._logo_description def setTitleTextColor(self, color: int): self._title_text_color = color def setSubtitleTextColor(self, color: int): self._subtitle_text_color = color def inflateMenu(self, menu_res_id: int): pass def getMenu(self) -> Any: return self._menu def setOnMenuItemClickListener(self, listener: Optional[Callable]): self._on_menu_item_click_listener = listener def setNavigationOnClickListener(self, listener: Optional[Callable]): self._navigation_on_click_listener = listener def setCollapseIcon(self, icon: Any): self._collapse_icon = icon def getCollapseIcon(self) -> Any: return self._collapse_icon def setPopupTheme(self, res_id: int): self._popup_theme = res_id def getPopupTheme(self) -> int: return self._popup_theme def setContentInsetsRelative(self, content_inset_start: int, content_inset_end: int): self._content_inset_start = content_inset_start self._content_inset_end = content_inset_end def getContentInsetStart(self) -> int: return self._content_inset_start def getContentInsetEnd(self) -> int: return self._content_inset_end def collapseActionView(self): pass def dismissPopupMenus(self): pass def isOverflowMenuShowing(self) -> bool: return False def showOverflowMenu(self) -> bool: return False def hideOverflowMenu(self) -> bool: return False def getOverflowIcon(self) -> Any: return None def setOverflowIcon(self, icon: Any): pass class ActionMenuView(LinearLayout): """ActionMenuView — 操作菜单视图""" pass # ═══════════════════════════════════════════════════════════════ # TabHost / TabWidget # ═══════════════════════════════════════════════════════════════ class TabHost(FrameLayout): """TabHost — 标签主机""" def __init__(self, context=None): super().__init__(context) self._tab_widget: Optional["TabWidget"] = None self._tab_content: Optional[FrameLayout] = None self._tabs: List["TabHost.TabSpec"] = [] self._current_tab: int = 0 self._on_tab_changed_listener: Optional[Callable] = None self._local_activity_manager: Any = None class TabSpec(Stub): """TabHost.TabSpec — 标签规格""" def __init__(self, tag: str): super().__init__() self._tag = tag self._indicator: Any = None self._content: Any = None self._content_view: Optional[View] = None def setIndicator(self, indicator: Any) -> "TabHost.TabSpec": self._indicator = indicator return self def setContent(self, content: Any) -> "TabHost.TabSpec": self._content = content return self def setContent(self, view_id: int) -> "TabHost.TabSpec": return self def setContent(self, view: View) -> "TabHost.TabSpec": self._content_view = view return self def getTag(self) -> str: return self._tag def getIndicator(self) -> Any: return self._indicator class OnTabChangeListener: def onTabChanged(self, tab_id: str): pass def setup(self): pass def newTabSpec(self, tag: str) -> "TabHost.TabSpec": spec = TabHost.TabSpec(tag) self._tabs.append(spec) return spec def addTab(self, tab_spec: "TabHost.TabSpec"): if tab_spec not in self._tabs: self._tabs.append(tab_spec) def setCurrentTab(self, index: int): if 0 <= index < len(self._tabs): self._current_tab = index def setCurrentTabByTag(self, tag: str): for i, tab in enumerate(self._tabs): if tab.getTag() == tag: self._current_tab = i break def getCurrentTab(self) -> int: return self._current_tab def getCurrentTabTag(self) -> str: if 0 <= self._current_tab < len(self._tabs): return self._tabs[self._current_tab].getTag() return "" def getTabWidget(self) -> "TabWidget": return self._tab_widget def getTabContentView(self) -> FrameLayout: return self._tab_content def setOnTabChangedListener(self, listener: Optional[OnTabChangeListener]): self._on_tab_changed_listener = listener def clearAllTabs(self): self._tabs.clear() self._current_tab = 0 class TabWidget(LinearLayout): """TabWidget — 标签部件""" def __init__(self, context=None): super().__init__(context) self._strip: Any = None self._left_strip: Any = None self._right_strip: Any = None self._selected_tab: int = -1 self._draw_full_strip: bool = False def setStripEnabled(self, strip_enabled: bool): self._draw_full_strip = strip_enabled def isStripEnabled(self) -> bool: return self._draw_full_strip def setLeftStripDrawable(self, drawable: Any): self._left_strip = drawable def setRightStripDrawable(self, drawable: Any): self._right_strip = drawable def setDividerDrawable(self, drawable: Any): pass def focusCurrentTab(self, index: int): self._selected_tab = index def isEnabled(self) -> bool: return True # ═══════════════════════════════════════════════════════════════ # ExpandableListView # ═══════════════════════════════════════════════════════════════ class ExpandableListView(ListView): """ExpandableListView — 可展开列表视图""" PACKED_POSITION_TYPE_GROUP = 0 PACKED_POSITION_TYPE_CHILD = 1 PACKED_POSITION_TYPE_NULL = 2 PACKED_POSITION_VALUE_NULL = 0xFFFFFFFF def __init__(self, context=None): super().__init__(context) self._expandable_adapter: Optional["ExpandableListAdapter"] = None self._on_group_collapse_listener: Optional[Callable] = None self._on_group_expand_listener: Optional[Callable] = None self._on_child_click_listener: Optional[Callable] = None self._on_group_click_listener: Optional[Callable] = None def setAdapter(self, adapter: "ExpandableListAdapter"): self._expandable_adapter = adapter self._adapter = adapter def getExpandableListAdapter(self) -> Optional["ExpandableListAdapter"]: return self._expandable_adapter def setOnGroupCollapseListener(self, listener: Optional[Callable]): self._on_group_collapse_listener = listener def setOnGroupExpandListener(self, listener: Optional[Callable]): self._on_group_expand_listener = listener def setOnChildClickListener(self, listener: Optional[Callable]): self._on_child_click_listener = listener def setOnGroupClickListener(self, listener: Optional[Callable]): self._on_group_click_listener = listener def expandGroup(self, group_pos: int, animate: bool = True) -> bool: return True def collapseGroup(self, group_pos: int) -> bool: return True def setOnItemClickListener(self, listener: Optional[Callable]): self._on_item_click_listener = listener def getOnItemClickListener(self) -> Optional[Callable]: return self._on_item_click_listener def setOnItemLongClickListener(self, listener: Optional[Callable]): self._on_item_long_click_listener = listener def getOnItemLongClickListener(self) -> Optional[Callable]: return self._on_item_long_click_listener def getPackedPositionType(self, packed_position: int) -> int: if packed_position == ExpandableListView.PACKED_POSITION_VALUE_NULL: return ExpandableListView.PACKED_POSITION_TYPE_NULL return ExpandableListView.PACKED_POSITION_TYPE_GROUP def getPackedPositionGroup(self, packed_position: int) -> int: return packed_position & 0xFFFF def getPackedPositionChild(self, packed_position: int) -> int: return (packed_position >> 16) & 0xFFFF @staticmethod def getPackedPositionForChild(group_position: int, child_position: int) -> int: return (child_position << 16) | group_position @staticmethod def getPackedPositionForGroup(group_position: int) -> int: return group_position def isGroupExpanded(self, group_position: int) -> bool: return False def setSelectedGroup(self, group_position: int): pass def setSelectedChild(self, group_position: int, child_position: int, should_expand_group: bool): pass def smoothScrollToPosition(self, position: int): pass # ═══════════════════════════════════════════════════════════════ # ExpandableListAdapter # ═══════════════════════════════════════════════════════════════ class ExpandableListAdapter(Stub): """ExpandableListAdapter — 可扩展列表适配器接口""" def __init__(self): super().__init__() def getGroupCount(self) -> int: raise AndroidOnlyError("ExpandableListAdapter.getGroupCount: not implemented") def getChildrenCount(self, group_position: int) -> int: raise AndroidOnlyError("ExpandableListAdapter.getChildrenCount: not implemented") def getGroup(self, group_position: int) -> Any: raise AndroidOnlyError("ExpandableListAdapter.getGroup: not implemented") def getChild(self, group_position: int, child_position: int) -> Any: raise AndroidOnlyError("ExpandableListAdapter.getChild: not implemented") def getGroupId(self, group_position: int) -> int: raise AndroidOnlyError("ExpandableListAdapter.getGroupId: not implemented") def getChildId(self, group_position: int, child_position: int) -> int: raise AndroidOnlyError("ExpandableListAdapter.getChildId: not implemented") def hasStableIds(self) -> bool: return False def getGroupView(self, group_position: int, is_expanded: bool, convert_view: Optional[Any], parent: Optional[Any]) -> Any: raise AndroidOnlyError("ExpandableListAdapter.getGroupView: not implemented") def getChildView(self, group_position: int, child_position: int, is_last_child: bool, convert_view: Optional[Any], parent: Optional[Any]) -> Any: raise AndroidOnlyError("ExpandableListAdapter.getChildView: not implemented") def isChildSelectable(self, group_position: int, child_position: int) -> bool: return True def areAllItemsEnabled(self) -> bool: return True def isEmpty(self) -> bool: return self.getGroupCount() == 0 def onGroupCollapsed(self, group_position: int): pass def onGroupExpanded(self, group_position: int): pass def getCombinedGroupId(self, group_id: int) -> int: return group_id def getCombinedChildId(self, group_id: int, child_id: int) -> int: return (group_id << 32) | child_id # ═══════════════════════════════════════════════════════════════ # SimpleExpandableListAdapter # ═══════════════════════════════════════════════════════════════ class SimpleExpandableListAdapter(ExpandableListAdapter, BaseAdapter): """SimpleExpandableListAdapter — 简单的可展开列表适配器""" def __init__(self, context: Any, group_data: List[Dict[str, Any]], group_layout: int, group_from: List[str], group_to: List[int], child_data: List[List[Dict[str, Any]]], child_layout: int, child_from: List[str], child_to: List[int]): super().__init__() self._context = context self._group_data = group_data self._group_layout = group_layout self._group_from = group_from self._group_to = group_to self._child_data = child_data self._child_layout = child_layout self._child_from = child_from self._child_to = child_to def getGroupCount(self) -> int: return len(self._group_data) def getChildrenCount(self, group_position: int) -> int: if 0 <= group_position < len(self._child_data): return len(self._child_data[group_position]) return 0 def getGroup(self, group_position: int) -> Any: if 0 <= group_position < len(self._group_data): return self._group_data[group_position] return None def getChild(self, group_position: int, child_position: int) -> Any: if 0 <= group_position < len(self._child_data): children = self._child_data[group_position] if 0 <= child_position < len(children): return children[child_position] return None def getGroupId(self, group_position: int) -> int: return group_position def getChildId(self, group_position: int, child_position: int) -> int: return child_position def getGroupView(self, group_position: int, is_expanded: bool, convert_view: Optional[Any], parent: Optional[Any]) -> Any: if convert_view is not None: view = convert_view else: view = TextView(self._context) view.setText(str(self.getGroup(group_position))) return view def getChildView(self, group_position: int, child_position: int, is_last_child: bool, convert_view: Optional[Any], parent: Optional[Any]) -> Any: if convert_view is not None: view = convert_view else: view = TextView(self._context) child = self.getChild(group_position, child_position) view.setText(str(child) if child else "") return view def isChildSelectable(self, group_position: int, child_position: int) -> bool: return True def getCount(self) -> int: return self.getGroupCount() def getItem(self, position: int) -> Any: return self.getGroup(position) def getItemId(self, position: int) -> int: return position def getView(self, position: int, convert_view: Optional[Any], parent: Optional[Any]) -> Any: return self.getGroupView(position, False, convert_view, parent) # ═══════════════════════════════════════════════════════════════ # BaseExpandableListAdapter # ═══════════════════════════════════════════════════════════════ class BaseExpandableListAdapter(ExpandableListAdapter, BaseAdapter): """BaseExpandableListAdapter — 可展开列表适配器基类""" def __init__(self): super().__init__() def getGroupCount(self) -> int: return 0 def getChildrenCount(self, group_position: int) -> int: return 0 def getGroup(self, group_position: int) -> Any: return None def getChild(self, group_position: int, child_position: int) -> Any: return None def getGroupId(self, group_position: int) -> int: return group_position def getChildId(self, group_position: int, child_position: int) -> int: return child_position def getGroupView(self, group_position: int, is_expanded: bool, convert_view: Optional[Any], parent: Optional[Any]) -> Any: if convert_view is not None: view = convert_view else: view = TextView(android_context) view.setText(f"Group {group_position}") return view def getChildView(self, group_position: int, child_position: int, is_last_child: bool, convert_view: Optional[Any], parent: Optional[Any]) -> Any: if convert_view is not None: view = convert_view else: view = TextView(android_context) view.setText(f"Child {group_position}:{child_position}") return view def isChildSelectable(self, group_position: int, child_position: int) -> bool: return True def areAllItemsEnabled(self) -> bool: return True def onGroupCollapsed(self, group_position: int): pass def onGroupExpanded(self, group_position: int): pass def notifyDataSetChanged(self): pass def notifyDataSetInvalidated(self): pass def getCount(self) -> int: return self.getGroupCount() def getItem(self, position: int) -> Any: return self.getGroup(position) def getItemId(self, position: int) -> int: return position def getView(self, position: int, convert_view: Optional[Any], parent: Optional[Any]) -> Any: return self.getGroupView(position, False, convert_view, parent) # ═══════════════════════════════════════════════════════════════ # SearchView # ═══════════════════════════════════════════════════════════════ class SearchView(LinearLayout): """SearchView — 搜索视图""" def __init__(self, context=None): super().__init__(context) self._query: str = "" self._query_hint: Optional[str] = None self._iconified: bool = True self._iconified_by_default: bool = True self._submit_button_enabled: bool = False self._query_refinement_enabled: bool = False self._searchable_info: Any = None self._on_query_text_listener: Optional[Callable] = None self._on_close_listener: Optional[Callable] = None self._on_suggestion_listener: Optional[Callable] = None self._on_query_text_focus_change_listener: Optional[Callable] = None self._suggestion_adapter: Optional["CursorAdapter"] = None self._max_width: int = sys.maxsize self._on_search_click_listener: Optional[Callable] = None class OnQueryTextListener: def onQueryTextSubmit(self, query: str) -> bool: return False def onQueryTextChange(self, new_text: str) -> bool: return False class OnCloseListener: def onClose(self) -> bool: return False class OnSuggestionListener: def onSuggestionSelect(self, position: int) -> bool: return False def onSuggestionClick(self, position: int) -> bool: return False def setQuery(self, query: str, submit: bool = False): self._query = query if self._on_query_text_listener: self._on_query_text_listener.onQueryTextChange(query) if submit and self._on_query_text_listener: self._on_query_text_listener.onQueryTextSubmit(query) def getQuery(self) -> str: return self._query def setQueryHint(self, hint: Optional[str]): self._query_hint = hint def getQueryHint(self) -> Optional[str]: return self._query_hint def setIconified(self, iconified: bool): self._iconified = iconified def isIconified(self) -> bool: return self._iconified def setIconifiedByDefault(self, iconified: bool): self._iconified_by_default = iconified def isIconifiedByDefault(self) -> bool: return self._iconified_by_default def setSubmitButtonEnabled(self, enabled: bool): self._submit_button_enabled = enabled def isSubmitButtonEnabled(self) -> bool: return self._submit_button_enabled def setQueryRefinementEnabled(self, enable: bool): self._query_refinement_enabled = enable def isQueryRefinementEnabled(self) -> bool: return self._query_refinement_enabled def setOnQueryTextListener(self, listener: Optional["SearchView.OnQueryTextListener"]): self._on_query_text_listener = listener def getOnQueryTextListener(self) -> Optional["SearchView.OnQueryTextListener"]: return self._on_query_text_listener def setOnCloseListener(self, listener: Optional["SearchView.OnCloseListener"]): self._on_close_listener = listener def setOnSuggestionListener(self, listener: Optional["SearchView.OnSuggestionListener"]): self._on_suggestion_listener = listener def setOnSearchClickListener(self, listener: Optional[Callable]): self._on_search_click_listener = listener def setSuggestionsAdapter(self, adapter: Optional["CursorAdapter"]): self._suggestion_adapter = adapter def getSuggestionsAdapter(self) -> Optional["CursorAdapter"]: return self._suggestion_adapter def setMaxWidth(self, max_width: int): self._max_width = max_width def getMaxWidth(self) -> int: return self._max_width def onActionViewExpanded(self): self._iconified = False def onActionViewCollapsed(self): self._iconified = True def onClose(self) -> bool: if self._on_close_listener: return self._on_close_listener.onClose() self._iconified = self._iconified_by_default return True def clearFocus(self): pass # ═══════════════════════════════════════════════════════════════ # ZoomControls # ═══════════════════════════════════════════════════════════════ class ZoomControls(LinearLayout): """ZoomControls — 缩放控件""" def __init__(self, context=None): super().__init__(context) self._zoom_in_enabled: bool = True self._zoom_out_enabled: bool = True self._zoom_speed: int = 300 self._on_zoom_in_listener: Optional[Callable] = None self._on_zoom_out_listener: Optional[Callable] = None def show(self): self.setVisibility(View.VISIBLE) def hide(self): self.setVisibility(View.GONE) def setIsZoomInEnabled(self, is_enabled: bool): self._zoom_in_enabled = is_enabled def setIsZoomOutEnabled(self, is_enabled: bool): self._zoom_out_enabled = is_enabled def setOnZoomInClickListener(self, listener: Optional[Callable]): self._on_zoom_in_listener = listener def setOnZoomOutClickListener(self, listener: Optional[Callable]): self._on_zoom_out_listener = listener def setZoomSpeed(self, speed: int): self._zoom_speed = speed def getZoomSpeed(self) -> int: return self._zoom_speed def hasFocus(self) -> bool: return False # ═══════════════════════════════════════════════════════════════ # DigitalClock # ═══════════════════════════════════════════════════════════════ class DigitalClock(TextView): """DigitalClock — 数字时钟(API 14+ 已弃用)""" def __init__(self, context=None): super().__init__(context) self._format: str = "%H:%M:%S" self._update_interval: int = 1000 self._running: bool = False self._timer: Optional[Any] = None def start(self): self._running = True def stop(self): self._running = False def setFormat(self, fmt: str): self._format = fmt def getFormat(self) -> str: return self._format def onTimeChanged(self): now = datetime.now().strftime(self._format) if hasattr(datetime, 'now') else "00:00:00" self.setText(now) # ═══════════════════════════════════════════════════════════════ # AnalogClock # ═══════════════════════════════════════════════════════════════ class AnalogClock(View): """AnalogClock — 模拟时钟(API 14+ 已弃用)""" def __init__(self, context=None): super().__init__(context) self._dial: Optional[Any] = None self._hand_hour: Optional[Any] = None self._hand_minute: Optional[Any] = None self._running: bool = False self._update_interval: int = 1000 def start(self): self._running = True self.invalidate() def stop(self): self._running = False def onDraw(self, canvas: Any): super().onDraw(canvas) def setDialDrawable(self, drawable: Any): self._dial = drawable def setHourDrawable(self, drawable: Any): self._hand_hour = drawable def setMinuteDrawable(self, drawable: Any): self._hand_minute = drawable # ═══════════════════════════════════════════════════════════════ # Chronometer # ═══════════════════════════════════════════════════════════════ class Chronometer(TextView): """Chronometer — 计时器""" def __init__(self, context=None): super().__init__(context) self._base: int = 0 self._format: Optional[str] = None self._running: bool = False self._visible: bool = True self._on_chronometer_tick_listener: Optional[Callable] = None self._count_down: bool = False self._elapsed: float = 0.0 def start(self): self._running = True def stop(self): self._running = False def setBase(self, base: int): self._base = base def getBase(self) -> int: return self._base def setFormat(self, fmt: str): self._format = fmt def getFormat(self) -> Optional[str]: return self._format def setOnChronometerTickListener(self, listener: Optional[Callable]): self._on_chronometer_tick_listener = listener def getOnChronometerTickListener(self) -> Optional[Callable]: return self._on_chronometer_tick_listener def isCountDown(self) -> bool: return self._count_down def setCountDown(self, count_down: bool): self._count_down = count_down def getElapsedRealtime(self) -> float: return self._elapsed def setVisibility(self, visibility: int): self._visible = (visibility == View.VISIBLE) super().setVisibility(visibility) def onDraw(self, canvas: Any): super().onDraw(canvas) if self._running and self._on_chronometer_tick_listener: try: self._on_chronometer_tick_listener(self) except Exception: pass # ═══════════════════════════════════════════════════════════════ # __all__ # ═══════════════════════════════════════════════════════════════ __all__ = [ # TextView hierarchy "TextView", "EditText", "Button", "ImageButton", "CheckBox", "RadioButton", "ToggleButton", "Switch", # ImageView "ImageView", # Progress "ProgressBar", "SeekBar", "RatingBar", # AdapterView hierarchy "AdapterView", "ListView", "GridView", "ExpandableListView", "Spinner", "AutoCompleteTextView", "MultiAutoCompleteTextView", # RecyclerView "RecyclerView", "RecyclerViewAdapter", "RecyclerViewViewHolder", "RecyclerViewLayoutManager", "LinearLayoutManager", "GridLayoutManager", "StaggeredGridLayoutManager", # Layouts "LinearLayout", "RelativeLayout", "FrameLayout", "TableLayout", "TableRow", # Scrolling "ScrollView", "HorizontalScrollView", # WebView "WebView", "WebSettings", # Adapters "BaseAdapter", "ArrayAdapter", "SimpleAdapter", "CursorAdapter", "ExpandableListAdapter", "SimpleExpandableListAdapter", "BaseExpandableListAdapter", # ViewAnimator "ViewAnimator", "ViewFlipper", "ViewSwitcher", "ImageSwitcher", "TextSwitcher", "AdapterViewAnimator", "StackView", "AdapterViewFlipper", # Date/Time "DatePicker", "TimePicker", "CalendarView", "NumberPicker", "DigitalClock", "AnalogClock", "Chronometer", # Media "VideoView", "MediaController", # Toast/Popup "Toast", "PopupMenu", "PopupWindow", # Toolbar "Toolbar", "ActionMenuView", # Tabs "TabHost", "TabWidget", # Search "SearchView", # Zoom "ZoomControls", ]