4777 lines
152 KiB
Python
4777 lines
152 KiB
Python
"""
|
|
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,
|
|
)
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# 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 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 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
|
|
|
|
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
|
|
|
|
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())
|
|
|
|
|
|
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):
|
|
pass
|
|
|
|
def notifyItemChanged(self, position: int):
|
|
pass
|
|
|
|
def notifyItemInserted(self, position: int):
|
|
pass
|
|
|
|
def notifyItemRemoved(self, position: int):
|
|
pass
|
|
|
|
def notifyItemMoved(self, from_position: int, to_position: int):
|
|
pass
|
|
|
|
def notifyItemRangeChanged(self, position_start: int, item_count: int):
|
|
pass
|
|
|
|
def notifyItemRangeInserted(self, position_start: int, item_count: int):
|
|
pass
|
|
|
|
def notifyItemRangeRemoved(self, position_start: int, item_count: int):
|
|
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: int, y: int):
|
|
self._scroll_position = y
|
|
|
|
def smoothScrollBy(self, dx: int, dy: int):
|
|
self._scroll_position += 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: int, y: int):
|
|
pass
|
|
|
|
def smoothScrollBy(self, dx: int, dy: int):
|
|
pass
|
|
|
|
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):
|
|
pass
|
|
|
|
def notifyDataSetInvalidated(self):
|
|
pass
|
|
|
|
def registerDataSetObserver(self, observer: Any):
|
|
pass
|
|
|
|
def unregisterDataSetObserver(self, observer: Any):
|
|
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):
|
|
pass
|
|
|
|
def cancel(self):
|
|
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",
|
|
] |