""" Android View Animation 组件 — Animation, AnimationSet, Interpolators, AnimationUtils, Transformation 等 方法签名完全对齐 Android SDK """ import math import sys import time from typing import Any, Callable, Dict, List, Optional, Tuple from .._core import Stub, AndroidOnlyError, android_context # ═══════════════════════════════════════════════════════════════ # Interpolator # ═══════════════════════════════════════════════════════════════ class Interpolator(Stub): """Interpolator — 插值器接口""" def getInterpolation(self, input: float) -> float: return input # ═══════════════════════════════════════════════════════════════ # AccelerateInterpolator # ═══════════════════════════════════════════════════════════════ class AccelerateInterpolator(Interpolator): """AccelerateInterpolator — 加速插值器(先慢后快)""" def __init__(self, factor: float = 1.0): super().__init__() self._factor = factor self._double_factor = 2.0 * factor def getInterpolation(self, input: float) -> float: if self._factor == 1.0: return input * input return input ** (self._double_factor) # ═══════════════════════════════════════════════════════════════ # DecelerateInterpolator # ═══════════════════════════════════════════════════════════════ class DecelerateInterpolator(Interpolator): """DecelerateInterpolator — 减速插值器(先快后慢)""" def __init__(self, factor: float = 1.0): super().__init__() self._factor = factor def getInterpolation(self, input: float) -> float: if self._factor == 1.0: return 1.0 - (1.0 - input) * (1.0 - input) return 1.0 - (1.0 - input) ** (2.0 * self._factor) # ═══════════════════════════════════════════════════════════════ # AccelerateDecelerateInterpolator # ═══════════════════════════════════════════════════════════════ class AccelerateDecelerateInterpolator(Interpolator): """AccelerateDecelerateInterpolator — 加速减速插值器""" def getInterpolation(self, input: float) -> float: return float(math.cos((input + 1) * math.pi) / 2.0 + 0.5) # ═══════════════════════════════════════════════════════════════ # LinearInterpolator # ═══════════════════════════════════════════════════════════════ class LinearInterpolator(Interpolator): """LinearInterpolator — 线性插值器""" def getInterpolation(self, input: float) -> float: return input # ═══════════════════════════════════════════════════════════════ # OvershootInterpolator # ═══════════════════════════════════════════════════════════════ class OvershootInterpolator(Interpolator): """OvershootInterpolator — 超出插值器(越过目标再回弹)""" def __init__(self, tension: float = 2.0): super().__init__() self._tension = tension def getInterpolation(self, input: float) -> float: input -= 1.0 return input * input * ((self._tension + 1) * input + self._tension) + 1.0 # ═══════════════════════════════════════════════════════════════ # AnticipateInterpolator # ═══════════════════════════════════════════════════════════════ class AnticipateInterpolator(Interpolator): """AnticipateInterpolator — 预判插值器(先反向后正向前进)""" def __init__(self, tension: float = 2.0): super().__init__() self._tension = tension def getInterpolation(self, input: float) -> float: return input * input * ((self._tension + 1) * input - self._tension) # ═══════════════════════════════════════════════════════════════ # AnticipateOvershootInterpolator # ═══════════════════════════════════════════════════════════════ class AnticipateOvershootInterpolator(Interpolator): """AnticipateOvershootInterpolator — 预判超出插值器""" def __init__(self, tension: float = 2.0, extra_tension: float = 1.5): super().__init__() self._tension = tension self._extra_tension = extra_tension def getInterpolation(self, input: float) -> float: if input < 0.5: t = input * 2.0 return 0.5 * t * t * ((self._tension + 1) * t - self._tension) else: t = input * 2.0 - 2.0 return 0.5 * (t * t * ((self._tension + 1) * t + self._tension) + 2.0) # ═══════════════════════════════════════════════════════════════ # BounceInterpolator # ═══════════════════════════════════════════════════════════════ class BounceInterpolator(Interpolator): """BounceInterpolator — 弹跳插值器""" @staticmethod def _bounce(t: float) -> float: t2 = t * t return t2 * 8.0 def getInterpolation(self, input: float) -> float: if input < 0.3535: return BounceInterpolator._bounce(input) elif input < 0.7408: return BounceInterpolator._bounce(input - 0.54719) + 0.7 elif input < 0.9644: return BounceInterpolator._bounce(input - 0.8526) + 0.9 else: return BounceInterpolator._bounce(input - 1.0435) + 0.95 # ═══════════════════════════════════════════════════════════════ # CycleInterpolator # ═══════════════════════════════════════════════════════════════ class CycleInterpolator(Interpolator): """CycleInterpolator — 周期插值器(正弦循环)""" def __init__(self, cycles: float = 1.0): super().__init__() self._cycles = cycles def getInterpolation(self, input: float) -> float: return float(math.sin(2.0 * math.pi * self._cycles * input)) # ═══════════════════════════════════════════════════════════════ # PathInterpolator # ═══════════════════════════════════════════════════════════════ class PathInterpolator(Interpolator): """PathInterpolator — 路径插值器""" def __init__(self, control_x1: float = 0.0, control_y1: float = 0.0, control_x2: float = 1.0, control_y2: float = 1.0): super().__init__() self._control_x1 = control_x1 self._control_y1 = control_y1 self._control_x2 = control_x2 self._control_y2 = control_y2 def getInterpolation(self, input: float) -> float: return self._cubic_bezier(input) def _cubic_bezier(self, t: float) -> float: u = 1.0 - t return (3.0 * u * u * t * self._control_y1 + 3.0 * u * t * t * self._control_y2 + t * t * t) # ═══════════════════════════════════════════════════════════════ # Transformation # ═══════════════════════════════════════════════════════════════ class Transformation(Stub): """Transformation — 动画变换矩阵""" TYPE_IDENTITY = 0 TYPE_ALPHA = 1 TYPE_BOTH = 2 def __init__(self): super().__init__() self._alpha: float = 1.0 self._matrix: Any = None # 占位,实际 Android 为 Matrix 对象 self._transformation_type: int = Transformation.TYPE_IDENTITY def getAlpha(self) -> float: return self._alpha def setAlpha(self, alpha: float): self._alpha = alpha def getMatrix(self) -> Any: return self._matrix def setMatrix(self, matrix: Any): self._matrix = matrix def setTransformationType(self, transformation_type: int): self._transformation_type = transformation_type def getTransformationType(self) -> int: return self._transformation_type def clear(self): self._alpha = 1.0 self._matrix = None self._transformation_type = Transformation.TYPE_IDENTITY def compose(self, other: "Transformation"): if other is None: return self._alpha *= other._alpha if self._matrix is None: self._matrix = other._matrix elif other._matrix is not None: pass # 占位:矩阵乘法 self._transformation_type = max(self._transformation_type, other._transformation_type) def toString(self) -> str: return (f"Transformation(alpha={self._alpha}, " f"type={self._transformation_type})") # ═══════════════════════════════════════════════════════════════ # Animation # ═══════════════════════════════════════════════════════════════ class Animation(Stub): """Animation — 动画基类""" INFINITE = -1 RESTART = 1 REVERSE = 2 ABSOLUTE = 0 RELATIVE_TO_SELF = 1 RELATIVE_TO_PARENT = 2 START_ON_FIRST_FRAME = -1 class AnimationListener(Stub): """AnimationListener — 动画监听器""" def onAnimationStart(self, animation: "Animation"): pass def onAnimationEnd(self, animation: "Animation"): pass def onAnimationRepeat(self, animation: "Animation"): pass def __init__(self): super().__init__() self._duration: int = 0 self._start_offset: int = 0 self._repeat_count: int = 0 self._repeat_mode: int = Animation.RESTART self._fill_before: bool = False self._fill_after: bool = False self._fill_enabled: bool = False self._z_adjustment: int = 0 self._interpolator: Optional[Interpolator] = None self._listener: Optional["Animation.AnimationListener"] = None self._start_time: Optional[int] = None self._started: bool = False self._ended: bool = False self._cycle: float = 0.0 self._current_repeat: int = 0 self._initialized: bool = False self._scale_factor: float = 1.0 self._paused: bool = False self._more: bool = True def start(self): self._start_time = self._current_time_millis() self._started = True self._ended = False self._current_repeat = 0 self._more = True if self._listener: self._listener.onAnimationStart(self) def startNow(self): self.start() def cancel(self): self._ended = True self._more = False def reset(self): self._start_time = None self._started = False self._ended = False self._current_repeat = 0 self._cycle = 0.0 self._more = True self._initialized = False def setDuration(self, duration_millis: int): self._duration = duration_millis def getDuration(self) -> int: return self._duration def setStartOffset(self, start_offset: int): self._start_offset = start_offset def getStartOffset(self) -> int: return self._start_offset def setRepeatCount(self, repeat_count: int): self._repeat_count = repeat_count def getRepeatCount(self) -> int: return self._repeat_count def setRepeatMode(self, repeat_mode: int): self._repeat_mode = repeat_mode def getRepeatMode(self) -> int: return self._repeat_mode def setInterpolator(self, interpolator: Optional[Interpolator]): self._interpolator = interpolator def getInterpolator(self) -> Optional[Interpolator]: return self._interpolator def setAnimationListener(self, listener: "Animation.AnimationListener"): self._listener = listener def getAnimationListener(self) -> Optional["Animation.AnimationListener"]: return self._listener def getTransformation(self, current_time: int, out_transformation: Transformation) -> bool: if self._start_time is None: self._start_time = current_time elapsed = current_time - self._start_time - self._start_offset if elapsed < 0: out_transformation.clear() return True duration = self._duration if duration == 0: duration = 1 normalized = elapsed / duration if normalized >= 1.0: if self._current_repeat < self._repeat_count or self._repeat_count == self.INFINITE: if self._repeat_mode == Animation.REVERSE: normalized = 0.0 else: normalized = 0.0 self._current_repeat += 1 if self._listener: self._listener.onAnimationRepeat(self) else: normalized = 1.0 self._ended = True self._more = False self._apply_transformation(1.0, out_transformation) if self._listener: self._listener.onAnimationEnd(self) return False if normalized > 1.0: normalized = 1.0 if self._interpolator: interpolated = self._interpolator.getInterpolation(normalized) else: interpolated = normalized self._cycle = normalized self._apply_transformation(interpolated, out_transformation) return self._more def _apply_transformation(self, interpolated_time: float, t: Transformation): pass def hasEnded(self) -> bool: return self._ended def hasStarted(self) -> bool: return self._started def setFillBefore(self, fill_before: bool): self._fill_before = fill_before def getFillBefore(self) -> bool: return self._fill_before def setFillAfter(self, fill_after: bool): self._fill_after = fill_after def getFillAfter(self) -> bool: return self._fill_after def setFillEnabled(self, fill_enabled: bool): self._fill_enabled = fill_enabled def getFillEnabled(self) -> bool: return self._fill_enabled def setZAdjustment(self, z_adjustment: int): self._z_adjustment = z_adjustment def getZAdjustment(self) -> int: return self._z_adjustment def willChangeBounds(self) -> bool: return True def willChangeTransformationMatrix(self) -> bool: return True def initialize(self, width: int, height: int, parent_width: int, parent_height: int): self._initialized = True def restrictDuration(self, duration_millis: int): if self._duration > duration_millis: self._duration = duration_millis def scaleCurrentDuration(self, scale: float): self._duration = int(self._duration * scale) @staticmethod def _current_time_millis() -> int: return int(time.time() * 1000) def toString(self) -> str: return (f"Animation(duration={self._duration}, " f"repeatCount={self._repeat_count}, " f"started={self._started})") # ═══════════════════════════════════════════════════════════════ # AlphaAnimation # ═══════════════════════════════════════════════════════════════ class AlphaAnimation(Animation): """AlphaAnimation — 透明度动画""" def __init__(self, from_alpha: float, to_alpha: float): super().__init__() self._from_alpha = from_alpha self._to_alpha = to_alpha def _apply_transformation(self, interpolated_time: float, t: Transformation): alpha = self._from_alpha + (self._to_alpha - self._from_alpha) * interpolated_time t.setAlpha(alpha) def getFromAlpha(self) -> float: return self._from_alpha def getToAlpha(self) -> float: return self._to_alpha # ═══════════════════════════════════════════════════════════════ # ScaleAnimation # ═══════════════════════════════════════════════════════════════ class ScaleAnimation(Animation): """ScaleAnimation — 缩放动画""" def __init__(self, from_x: float, to_x: float, from_y: float, to_y: float, pivot_x: float = 0.0, pivot_y: float = 0.0, pivot_x_type: int = Animation.ABSOLUTE, pivot_y_type: int = Animation.ABSOLUTE): super().__init__() self._from_x = from_x self._to_x = to_x self._from_y = from_y self._to_y = to_y self._pivot_x = pivot_x self._pivot_y = pivot_y self._pivot_x_type = pivot_x_type self._pivot_y_type = pivot_y_type def _apply_transformation(self, interpolated_time: float, t: Transformation): sx = self._from_x + (self._to_x - self._from_x) * interpolated_time sy = self._from_y + (self._to_y - self._from_y) * interpolated_time # 占位:Matrix 的缩放操作 if t.getMatrix() is not None: pass def getFromX(self) -> float: return self._from_x def getToX(self) -> float: return self._to_x def getFromY(self) -> float: return self._from_y def getToY(self) -> float: return self._to_y def getPivotX(self) -> float: return self._pivot_x def getPivotY(self) -> float: return self._pivot_y # ═══════════════════════════════════════════════════════════════ # TranslateAnimation # ═══════════════════════════════════════════════════════════════ class TranslateAnimation(Animation): """TranslateAnimation — 平移动画""" def __init__(self, from_x: float, to_x: float, from_y: float, to_y: float, from_x_type: int = Animation.ABSOLUTE, to_x_type: int = Animation.ABSOLUTE, from_y_type: int = Animation.ABSOLUTE, to_y_type: int = Animation.ABSOLUTE): super().__init__() self._from_x = from_x self._to_x = to_x self._from_y = from_y self._to_y = to_y self._from_x_type = from_x_type self._to_x_type = to_x_type self._from_y_type = from_y_type self._to_y_type = to_y_type def _apply_transformation(self, interpolated_time: float, t: Transformation): dx = self._from_x + (self._to_x - self._from_x) * interpolated_time dy = self._from_y + (self._to_y - self._from_y) * interpolated_time if t.getMatrix() is not None: pass def getFromX(self) -> float: return self._from_x def getToX(self) -> float: return self._to_x def getFromY(self) -> float: return self._from_y def getToY(self) -> float: return self._to_y # ═══════════════════════════════════════════════════════════════ # RotateAnimation # ═══════════════════════════════════════════════════════════════ class RotateAnimation(Animation): """RotateAnimation — 旋转动画""" def __init__(self, from_degrees: float, to_degrees: float, pivot_x: float = 0.0, pivot_y: float = 0.0, pivot_x_type: int = Animation.ABSOLUTE, pivot_y_type: int = Animation.ABSOLUTE): super().__init__() self._from_degrees = from_degrees self._to_degrees = to_degrees self._pivot_x = pivot_x self._pivot_y = pivot_y self._pivot_x_type = pivot_x_type self._pivot_y_type = pivot_y_type def _apply_transformation(self, interpolated_time: float, t: Transformation): degrees = self._from_degrees + (self._to_degrees - self._from_degrees) * interpolated_time if t.getMatrix() is not None: pass def getFromDegrees(self) -> float: return self._from_degrees def getToDegrees(self) -> float: return self._to_degrees def getPivotX(self) -> float: return self._pivot_x def getPivotY(self) -> float: return self._pivot_y # ═══════════════════════════════════════════════════════════════ # AnimationSet # ═══════════════════════════════════════════════════════════════ class AnimationSet(Animation): """AnimationSet — 动画集合""" def __init__(self, share_interpolator: bool = True): super().__init__() self._share_interpolator = share_interpolator self._animations: List[Animation] = [] def addAnimation(self, animation: Animation): self._animations.append(animation) if self._share_interpolator and self._interpolator: animation.setInterpolator(self._interpolator) def getAnimations(self) -> List[Animation]: return list(self._animations) def setDuration(self, duration_millis: int): super().setDuration(duration_millis) for anim in self._animations: anim.setDuration(duration_millis) def setFillAfter(self, fill_after: bool): super().setFillAfter(fill_after) for anim in self._animations: anim.setFillAfter(fill_after) def setFillBefore(self, fill_before: bool): super().setFillBefore(fill_before) for anim in self._animations: anim.setFillBefore(fill_before) def setRepeatMode(self, repeat_mode: int): super().setRepeatMode(repeat_mode) for anim in self._animations: anim.setRepeatMode(repeat_mode) def setRepeatCount(self, repeat_count: int): super().setRepeatCount(repeat_count) for anim in self._animations: anim.setRepeatCount(repeat_count) def setStartOffset(self, start_offset: int): super().setStartOffset(start_offset) for anim in self._animations: anim.setStartOffset(start_offset) def setInterpolator(self, interpolator: Optional[Interpolator]): super().setInterpolator(interpolator) if self._share_interpolator: for anim in self._animations: anim.setInterpolator(interpolator) def start(self): super().start() for anim in self._animations: anim.start() def reset(self): super().reset() for anim in self._animations: anim.reset() def cancel(self): super().cancel() for anim in self._animations: anim.cancel() def getTransformation(self, current_time: int, out_transformation: Transformation) -> bool: more = False for anim in self._animations: local_t = Transformation() if anim.getTransformation(current_time, local_t): more = True out_transformation.compose(local_t) return more def initialize(self, width: int, height: int, parent_width: int, parent_height: int): super().initialize(width, height, parent_width, parent_height) for anim in self._animations: anim.initialize(width, height, parent_width, parent_height) def willChangeBounds(self) -> bool: for anim in self._animations: if anim.willChangeBounds(): return True return False def willChangeTransformationMatrix(self) -> bool: for anim in self._animations: if anim.willChangeTransformationMatrix(): return True return False def getAnimations(self) -> List[Animation]: return list(self._animations) def restrictDuration(self, duration_millis: int): super().restrictDuration(duration_millis) for anim in self._animations: anim.restrictDuration(duration_millis) def scaleCurrentDuration(self, scale: float): super().scaleCurrentDuration(scale) for anim in self._animations: anim.scaleCurrentDuration(scale) # ═══════════════════════════════════════════════════════════════ # Animation.AnimationListener (已定义在 Animation 类中) # ═══════════════════════════════════════════════════════════════ # Animation.AnimationListener is defined inside the Animation class above # ═══════════════════════════════════════════════════════════════ # AnimationUtils # ═══════════════════════════════════════════════════════════════ class AnimationUtils(Stub): """AnimationUtils — 动画工具类""" @staticmethod def loadAnimation(context: Any, id: int) -> Animation: """加载动画资源(占位实现)""" sys.stderr.write(f"[AnimationUtils] loadAnimation: id={id}\n") return Animation() @staticmethod def makeInAnimation(context: Any, from_left: bool) -> Animation: """创建进入动画""" anim = AlphaAnimation(0.0, 1.0) anim.setDuration(300) return anim @staticmethod def makeOutAnimation(context: Any, to_left: bool) -> Animation: """创建退出动画""" anim = AlphaAnimation(1.0, 0.0) anim.setDuration(300) return anim @staticmethod def makeInChildBottomAnimation(context: Any) -> Animation: """创建子视图从底部进入动画""" anim = TranslateAnimation(0, 0, 1.0, 0.0, from_x_type=Animation.RELATIVE_TO_SELF, to_x_type=Animation.RELATIVE_TO_SELF, from_y_type=Animation.RELATIVE_TO_SELF, to_y_type=Animation.RELATIVE_TO_SELF) anim.setDuration(300) return anim @staticmethod def currentAnimationTimeMillis() -> int: return int(time.time() * 1000) # ═══════════════════════════════════════════════════════════════ # LayoutAnimationController # ═══════════════════════════════════════════════════════════════ class LayoutAnimationController(Stub): """LayoutAnimationController — 布局动画控制器""" ORDER_NORMAL = 0 ORDER_REVERSE = 1 ORDER_RANDOM = 2 def __init__(self, animation: Optional[Animation] = None): super().__init__() self._animation: Optional[Animation] = animation self._delay: float = 0.5 self._order: int = LayoutAnimationController.ORDER_NORMAL self._initialized: bool = False self._max_delay: int = 0 def setAnimation(self, animation: Animation): self._animation = animation def getAnimation(self) -> Optional[Animation]: return self._animation def setOrder(self, order: int): self._order = order def getOrder(self) -> int: return self._order def setDelay(self, delay: float): self._delay = delay def getDelay(self) -> float: return self._delay def getDelayForView(self, view: Any) -> int: if self._order == LayoutAnimationController.ORDER_NORMAL: return 0 elif self._order == LayoutAnimationController.ORDER_REVERSE: return 0 elif self._order == LayoutAnimationController.ORDER_RANDOM: import random return int(random.random() * self._max_delay) return 0 def willOverlap(self) -> bool: return self._delay < 1.0 def start(self): self._initialized = True def isDone(self) -> bool: return True def getAnimationForView(self, view: Any) -> Optional[Animation]: return self._animation def setInitialized(self, initialized: bool): self._initialized = initialized # ═══════════════════════════════════════════════════════════════ # GridLayoutAnimationController # ═══════════════════════════════════════════════════════════════ class GridLayoutAnimationController(LayoutAnimationController): """GridLayoutAnimationController — 网格布局动画控制器""" DIRECTION_LEFT_TO_RIGHT = 0x0 DIRECTION_RIGHT_TO_LEFT = 0x1 DIRECTION_TOP_TO_BOTTOM = 0x2 DIRECTION_BOTTOM_TO_TOP = 0x3 DIRECTION_HORIZONTAL_MASK = 0x1 DIRECTION_VERTICAL_MASK = 0x2 def __init__(self, animation: Optional[Animation] = None): super().__init__(animation) self._column_delay: float = 0.5 self._row_delay: float = 0.5 self._direction: int = GridLayoutAnimationController.DIRECTION_LEFT_TO_RIGHT self._columns: int = 1 self._rows: int = 1 def setColumnDelay(self, column_delay: float): self._column_delay = column_delay def getColumnDelay(self) -> float: return self._column_delay def setRowDelay(self, row_delay: float): self._row_delay = row_delay def getRowDelay(self) -> float: return self._row_delay def setDirection(self, direction: int): self._direction = direction def getDirection(self) -> int: return self._direction def setColumns(self, columns: int): self._columns = columns def getColumns(self) -> int: return self._columns def setRows(self, rows: int): self._rows = rows def getRows(self) -> int: return self._rows def getDelayForView(self, view: Any) -> int: duration = 0 if self._animation: duration = self._animation.getDuration() return int(duration * self._column_delay) def willOverlap(self) -> bool: return self._column_delay < 1.0 or self._row_delay < 1.0 def getAnimationForView(self, view: Any) -> Optional[Animation]: return self._animation # ═══════════════════════════════════════════════════════════════ # __all__ # ═══════════════════════════════════════════════════════════════ __all__ = [ "Animation", "AnimationSet", "AlphaAnimation", "ScaleAnimation", "TranslateAnimation", "RotateAnimation", "AnimationUtils", "Interpolator", "AccelerateInterpolator", "DecelerateInterpolator", "AccelerateDecelerateInterpolator", "LinearInterpolator", "OvershootInterpolator", "AnticipateInterpolator", "AnticipateOvershootInterpolator", "BounceInterpolator", "CycleInterpolator", "PathInterpolator", "Transformation", "LayoutAnimationController", "GridLayoutAnimationController", ]