1128 lines
38 KiB
Python
1128 lines
38 KiB
Python
"""
|
|
Android Graphics Drawable 组件 — Drawable, BitmapDrawable, ColorDrawable 等
|
|
方法签名完全对齐 Android SDK
|
|
"""
|
|
|
|
import math
|
|
from typing import Any, Dict, List, Optional, Sequence, Tuple, Union
|
|
|
|
from .._core import Stub, AndroidOnlyError, android_context, HAS_PIL, resolve_android_path
|
|
from .graphics import (
|
|
Bitmap, Canvas, Color, ColorFilter, Paint, PorterDuff, PorterDuffColorFilter,
|
|
Rect, RectF, Matrix, Path, Shader, Rect,
|
|
BlendMode, BlendModeColorFilter,
|
|
)
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# ColorStateList
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class ColorStateList(Stub):
|
|
"""ColorStateList — 颜色状态列表"""
|
|
|
|
def __init__(self, states: List[List[int]], colors: List[int]):
|
|
super().__init__()
|
|
self._states = list(states)
|
|
self._colors = list(colors)
|
|
self._default_color = colors[-1] if colors else 0
|
|
|
|
def getColorForState(self, stateSet: List[int], defaultColor: int = 0) -> int:
|
|
return self._default_color
|
|
|
|
def getDefaultColor(self) -> int:
|
|
return self._default_color
|
|
|
|
def isStateful(self) -> bool:
|
|
return len(self._states) > 0
|
|
|
|
def withAlpha(self, alpha: int) -> "ColorStateList":
|
|
new_colors = []
|
|
for c in self._colors:
|
|
a = Color.alpha(c)
|
|
new_a = int(a * alpha / 255)
|
|
new_colors.append(Color.argb(new_a, Color.red(c), Color.green(c), Color.blue(c)))
|
|
return ColorStateList(self._states, new_colors)
|
|
|
|
@staticmethod
|
|
def valueOf(color: int) -> "ColorStateList":
|
|
return ColorStateList([[]], [color])
|
|
|
|
def toString(self) -> str:
|
|
return f"ColorStateList({len(self._states)} states)"
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# Drawable
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class Drawable(Stub):
|
|
"""Drawable — 可绘制对象的抽象基类"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._bounds = Rect(0, 0, 0, 0)
|
|
self._alpha = 255
|
|
self._color_filter = None
|
|
self._callback = None
|
|
self._visible = True
|
|
self._level = 0
|
|
self._intrinsic_width = -1
|
|
self._intrinsic_height = -1
|
|
|
|
def setBounds(self, left: int, top: int, right: int, bottom: int):
|
|
self._bounds.set(left, top, right, bottom)
|
|
|
|
def setBounds(self, bounds: Rect):
|
|
self._bounds.set(bounds.left, bounds.top, bounds.right, bounds.bottom)
|
|
|
|
def getBounds(self) -> Rect:
|
|
return self._bounds
|
|
|
|
def copyBounds(self) -> Rect:
|
|
return Rect(self._bounds.left, self._bounds.top,
|
|
self._bounds.right, self._bounds.bottom)
|
|
|
|
def copyBounds(self, out: Rect) -> Rect:
|
|
out.set(self._bounds.left, self._bounds.top,
|
|
self._bounds.right, self._bounds.bottom)
|
|
return out
|
|
|
|
def getIntrinsicWidth(self) -> int:
|
|
return self._intrinsic_width
|
|
|
|
def getIntrinsicHeight(self) -> int:
|
|
return self._intrinsic_height
|
|
|
|
def setAlpha(self, alpha: int):
|
|
self._alpha = alpha
|
|
|
|
def getAlpha(self) -> int:
|
|
return self._alpha
|
|
|
|
def setColorFilter(self, colorFilter: Optional[ColorFilter]):
|
|
self._color_filter = colorFilter
|
|
|
|
def getColorFilter(self) -> Optional[ColorFilter]:
|
|
return self._color_filter
|
|
|
|
def getOpacity(self) -> int:
|
|
return 0 # PixelFormat.UNKNOWN
|
|
|
|
def invalidateSelf(self):
|
|
if self._callback:
|
|
self._callback.invalidateDrawable(self)
|
|
|
|
def setCallback(self, cb):
|
|
self._callback = cb
|
|
|
|
def getCallback(self):
|
|
return self._callback
|
|
|
|
def scheduleDrawable(self, what, who, when: int):
|
|
if self._callback:
|
|
self._callback.scheduleDrawable(what, who, when)
|
|
|
|
def unscheduleDrawable(self, what, who):
|
|
if self._callback:
|
|
self._callback.unscheduleDrawable(what, who)
|
|
|
|
def draw(self, canvas: Canvas):
|
|
pass
|
|
|
|
def setVisible(self, visible: bool, restart: bool = False) -> bool:
|
|
old = self._visible
|
|
self._visible = visible
|
|
return old
|
|
|
|
def isVisible(self) -> bool:
|
|
return self._visible
|
|
|
|
def setLevel(self, level: int) -> bool:
|
|
self._level = level
|
|
return True
|
|
|
|
def getLevel(self) -> int:
|
|
return self._level
|
|
|
|
def getConstantState(self) -> Optional["DrawableConstantState"]:
|
|
return None
|
|
|
|
def getCurrent(self) -> "Drawable":
|
|
return self
|
|
|
|
def mutate(self) -> "Drawable":
|
|
return self
|
|
|
|
def getMinimumWidth(self) -> int:
|
|
return 0
|
|
|
|
def getMinimumHeight(self) -> int:
|
|
return 0
|
|
|
|
def setDither(self, dither: bool):
|
|
pass
|
|
|
|
def setFilterBitmap(self, filter: bool):
|
|
pass
|
|
|
|
def setHotspot(self, x: float, y: float):
|
|
pass
|
|
|
|
def setHotspotBounds(self, left: int, top: int, right: int, bottom: int):
|
|
pass
|
|
|
|
def getHotspotBounds(self, outRect: Rect):
|
|
outRect.set(0, 0, 0, 0)
|
|
|
|
def isStateful(self) -> bool:
|
|
return False
|
|
|
|
def setState(self, stateSet: List[int]) -> bool:
|
|
return False
|
|
|
|
def getState(self) -> List[int]:
|
|
return []
|
|
|
|
def jumpToCurrentState(self):
|
|
pass
|
|
|
|
def clearColorFilter(self):
|
|
self._color_filter = None
|
|
|
|
def getTransparentRegion(self):
|
|
from .graphics import Region
|
|
return Region(self._bounds)
|
|
|
|
def onBoundsChange(self, bounds: Rect):
|
|
pass
|
|
|
|
def onLevelChange(self, level: int) -> bool:
|
|
return False
|
|
|
|
def onStateChange(self, stateSet: List[int]) -> bool:
|
|
return False
|
|
|
|
def toString(self) -> str:
|
|
return f"{self.__class__.__name__}({self._bounds})"
|
|
|
|
|
|
class DrawableConstantState(Stub):
|
|
"""DrawableConstantState — Drawable 常量状态"""
|
|
pass
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# BitmapDrawable
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class BitmapDrawable(Drawable):
|
|
"""BitmapDrawable — 位图 Drawable"""
|
|
|
|
def __init__(self, res=None, bitmap: Bitmap = None):
|
|
super().__init__()
|
|
self._bitmap = bitmap if bitmap else Bitmap(0, 0, Bitmap.Config.ARGB_8888)
|
|
self._paint = Paint()
|
|
self._target_density = 160
|
|
self._gravity = 0x33 # Gravity.FILL
|
|
self._tile_mode_x = None
|
|
self._tile_mode_y = None
|
|
self._mutated = False
|
|
if self._bitmap:
|
|
self._intrinsic_width = self._bitmap.getWidth()
|
|
self._intrinsic_height = self._bitmap.getHeight()
|
|
|
|
def getBitmap(self) -> Bitmap:
|
|
return self._bitmap
|
|
|
|
def getPaint(self) -> Paint:
|
|
return self._paint
|
|
|
|
def setAntiAlias(self, aa: bool):
|
|
self._paint.setAntiAlias(aa)
|
|
|
|
def setFilterBitmap(self, filter: bool):
|
|
self._paint.setFilterBitmap(filter)
|
|
|
|
def setDither(self, dither: bool):
|
|
self._paint.setDither(dither)
|
|
|
|
def setTileModeX(self, mode):
|
|
self._tile_mode_x = mode
|
|
|
|
def setTileModeY(self, mode):
|
|
self._tile_mode_y = mode
|
|
|
|
def getTileModeX(self):
|
|
return self._tile_mode_x
|
|
|
|
def getTileModeY(self):
|
|
return self._tile_mode_y
|
|
|
|
def setGravity(self, gravity: int):
|
|
self._gravity = gravity
|
|
|
|
def getGravity(self) -> int:
|
|
return self._gravity
|
|
|
|
def setTargetDensity(self, density: int):
|
|
self._target_density = density
|
|
|
|
def getTargetDensity(self) -> int:
|
|
return self._target_density
|
|
|
|
def draw(self, canvas: Canvas):
|
|
if self._bitmap:
|
|
bounds = self._bounds
|
|
if HAS_PIL and hasattr(canvas, "_image") and hasattr(self._bitmap, "_image"):
|
|
bm_img = self._bitmap._image
|
|
if bm_img:
|
|
resized = bm_img.resize((bounds.width(), bounds.height()))
|
|
canvas._image.paste(resized, (bounds.left, bounds.top))
|
|
|
|
def getIntrinsicWidth(self) -> int:
|
|
return self._bitmap.getWidth() if self._bitmap else -1
|
|
|
|
def getIntrinsicHeight(self) -> int:
|
|
return self._bitmap.getHeight() if self._bitmap else -1
|
|
|
|
def getOpacity(self) -> int:
|
|
if self._bitmap:
|
|
return 1 if self._bitmap.hasAlpha() else -1
|
|
return 0
|
|
|
|
def mutate(self) -> "BitmapDrawable":
|
|
if not self._mutated:
|
|
self._bitmap = Bitmap.createBitmap(self._bitmap)
|
|
self._paint = Paint()
|
|
self._mutated = True
|
|
return self
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# ColorDrawable
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class ColorDrawable(Drawable):
|
|
"""ColorDrawable — 纯色 Drawable"""
|
|
|
|
def __init__(self, color: int = 0):
|
|
super().__init__()
|
|
self._color = color
|
|
self._paint = Paint()
|
|
self._paint.setColor(color)
|
|
|
|
def getColor(self) -> int:
|
|
return self._color
|
|
|
|
def setColor(self, color: int):
|
|
self._color = color
|
|
self._paint.setColor(color)
|
|
|
|
def setAlpha(self, alpha: int):
|
|
self._paint.setAlpha(alpha)
|
|
|
|
def getAlpha(self) -> int:
|
|
return self._paint.getAlpha()
|
|
|
|
def draw(self, canvas: Canvas):
|
|
canvas.drawRect(self._bounds, self._paint)
|
|
|
|
def getOpacity(self) -> int:
|
|
if Color.alpha(self._color) == 255:
|
|
return -1 # OPAQUE
|
|
return -3 # TRANSLUCENT
|
|
|
|
def mutate(self) -> "ColorDrawable":
|
|
self._paint = Paint()
|
|
self._paint.setColor(self._color)
|
|
return self
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# GradientDrawable
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class GradientDrawable(Drawable):
|
|
"""GradientDrawable — 渐变 Drawable"""
|
|
|
|
RECTANGLE = 0
|
|
OVAL = 1
|
|
LINE = 2
|
|
RING = 3
|
|
|
|
class Orientation:
|
|
TOP_BOTTOM = 0
|
|
TR_BL = 1
|
|
RIGHT_LEFT = 2
|
|
BR_TL = 3
|
|
BOTTOM_TOP = 4
|
|
BL_TR = 5
|
|
LEFT_RIGHT = 6
|
|
TL_BR = 7
|
|
|
|
def __init__(self, orientation: int = Orientation.TOP_BOTTOM, colors: List[int] = None):
|
|
super().__init__()
|
|
self._orientation = orientation
|
|
self._colors = colors if colors else [Color.BLACK, Color.WHITE]
|
|
self._shape = GradientDrawable.RECTANGLE
|
|
self._corner_radius = 0.0
|
|
self._corner_radii = None
|
|
self._stroke_width = 0
|
|
self._stroke_color = 0
|
|
self._gradient_type = 0 # LINEAR_GRADIENT
|
|
self._gradient_center_x = 0.5
|
|
self._gradient_center_y = 0.5
|
|
self._use_level = False
|
|
self._width = -1
|
|
self._height = -1
|
|
self._color = None
|
|
|
|
def setShape(self, shape: int):
|
|
self._shape = shape
|
|
|
|
def getShape(self) -> int:
|
|
return self._shape
|
|
|
|
def setSize(self, width: int, height: int):
|
|
self._width = width
|
|
self._height = height
|
|
|
|
def setStroke(self, width: int, color: int, dashWidth: float = 0, dashGap: float = 0):
|
|
self._stroke_width = width
|
|
self._stroke_color = color
|
|
|
|
def setCornerRadius(self, radius: float):
|
|
self._corner_radius = radius
|
|
self._corner_radii = None
|
|
|
|
def setCornerRadii(self, radii: List[float]):
|
|
self._corner_radii = radii
|
|
self._corner_radius = radii[0] if radii else 0
|
|
|
|
def setColors(self, colors: List[int]):
|
|
self._colors = list(colors)
|
|
self._color = None
|
|
|
|
def setColor(self, color: int):
|
|
self._color = color
|
|
self._colors = None
|
|
|
|
def setGradientType(self, gradientType: int):
|
|
self._gradient_type = gradientType
|
|
|
|
def setGradientCenter(self, x: float, y: float):
|
|
self._gradient_center_x = x
|
|
self._gradient_center_y = y
|
|
|
|
def setOrientation(self, orientation: int):
|
|
self._orientation = orientation
|
|
|
|
def setUseLevel(self, useLevel: bool):
|
|
self._use_level = useLevel
|
|
|
|
def getIntrinsicWidth(self) -> int:
|
|
return self._width if self._width > 0 else super().getIntrinsicWidth()
|
|
|
|
def getIntrinsicHeight(self) -> int:
|
|
return self._height if self._height > 0 else super().getIntrinsicHeight()
|
|
|
|
def draw(self, canvas: Canvas):
|
|
bounds = self._bounds
|
|
if HAS_PIL and hasattr(canvas, "_draw") and canvas._draw:
|
|
if self._color is not None:
|
|
c = self._color
|
|
fill_color = (Color.red(c), Color.green(c), Color.blue(c), Color.alpha(c))
|
|
elif self._colors:
|
|
c = self._colors[0]
|
|
fill_color = (Color.red(c), Color.green(c), Color.blue(c), Color.alpha(c))
|
|
else:
|
|
fill_color = (0, 0, 0, 255)
|
|
|
|
if self._shape == GradientDrawable.OVAL:
|
|
canvas._draw.ellipse([bounds.left, bounds.top, bounds.right, bounds.bottom],
|
|
fill=fill_color)
|
|
elif self._shape == GradientDrawable.RECTANGLE and self._corner_radius > 0:
|
|
canvas._draw.rounded_rectangle(
|
|
[bounds.left, bounds.top, bounds.right, bounds.bottom],
|
|
radius=self._corner_radius, fill=fill_color)
|
|
else:
|
|
canvas._draw.rectangle([bounds.left, bounds.top, bounds.right, bounds.bottom],
|
|
fill=fill_color)
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# LayerDrawable
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class LayerDrawable(Drawable):
|
|
"""LayerDrawable — 图层 Drawable"""
|
|
|
|
def __init__(self, layers: List[Drawable]):
|
|
super().__init__()
|
|
self._layers = list(layers)
|
|
self._ids = list(range(len(layers)))
|
|
self._layer_insets = [Rect(0, 0, 0, 0) for _ in layers]
|
|
|
|
def findDrawableByLayerId(self, id: int) -> Optional[Drawable]:
|
|
for i, lid in enumerate(self._ids):
|
|
if lid == id:
|
|
return self._layers[i]
|
|
return None
|
|
|
|
def setId(self, index: int, id: int):
|
|
if 0 <= index < len(self._ids):
|
|
self._ids[index] = id
|
|
|
|
def getNumberOfLayers(self) -> int:
|
|
return len(self._layers)
|
|
|
|
def getDrawable(self, index: int) -> Optional[Drawable]:
|
|
if 0 <= index < len(self._layers):
|
|
return self._layers[index]
|
|
return None
|
|
|
|
def setDrawableByLayerId(self, id: int, drawable: Drawable) -> bool:
|
|
for i, lid in enumerate(self._ids):
|
|
if lid == id:
|
|
self._layers[i] = drawable
|
|
return True
|
|
return False
|
|
|
|
def setLayerInset(self, index: int, l: int, t: int, r: int, b: int):
|
|
if 0 <= index < len(self._layer_insets):
|
|
self._layer_insets[index].set(l, t, r, b)
|
|
|
|
def setLayerInsetLeft(self, index: int, left: int):
|
|
if 0 <= index < len(self._layer_insets):
|
|
self._layer_insets[index].left = left
|
|
|
|
def setLayerInsetTop(self, index: int, top: int):
|
|
if 0 <= index < len(self._layer_insets):
|
|
self._layer_insets[index].top = top
|
|
|
|
def setLayerInsetRight(self, index: int, right: int):
|
|
if 0 <= index < len(self._layer_insets):
|
|
self._layer_insets[index].right = right
|
|
|
|
def setLayerInsetBottom(self, index: int, bottom: int):
|
|
if 0 <= index < len(self._layer_insets):
|
|
self._layer_insets[index].bottom = bottom
|
|
|
|
def draw(self, canvas: Canvas):
|
|
for i, layer in enumerate(self._layers):
|
|
ins = self._layer_insets[i] if i < len(self._layer_insets) else Rect(0, 0, 0, 0)
|
|
b = self._bounds
|
|
layer.setBounds(b.left + ins.left, b.top + ins.top,
|
|
b.right - ins.right, b.bottom - ins.bottom)
|
|
layer.draw(canvas)
|
|
|
|
def mutate(self) -> "LayerDrawable":
|
|
return self
|
|
|
|
def getOpacity(self) -> int:
|
|
return 0
|
|
|
|
def isStateful(self) -> bool:
|
|
return any(d.isStateful() for d in self._layers)
|
|
|
|
def setVisible(self, visible: bool, restart: bool = False) -> bool:
|
|
old = super().setVisible(visible, restart)
|
|
for d in self._layers:
|
|
d.setVisible(visible, restart)
|
|
return old
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# StateListDrawable
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class StateListDrawable(Drawable):
|
|
"""StateListDrawable — 状态列表 Drawable"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._state_sets: List[List[int]] = []
|
|
self._drawables: List[Drawable] = []
|
|
self._current_index = -1
|
|
|
|
def addState(self, stateSet: List[int], drawable: Drawable):
|
|
self._state_sets.append(stateSet)
|
|
self._drawables.append(drawable)
|
|
|
|
def selectDrawable(self, index: int) -> bool:
|
|
if 0 <= index < len(self._drawables):
|
|
self._current_index = index
|
|
return True
|
|
return False
|
|
|
|
def getCurrent(self) -> Drawable:
|
|
if 0 <= self._current_index < len(self._drawables):
|
|
return self._drawables[self._current_index]
|
|
if self._drawables:
|
|
return self._drawables[0]
|
|
return self
|
|
|
|
def draw(self, canvas: Canvas):
|
|
d = self.getCurrent()
|
|
d.setBounds(self._bounds)
|
|
d.draw(canvas)
|
|
|
|
def isStateful(self) -> bool:
|
|
return True
|
|
|
|
def setState(self, stateSet: List[int]) -> bool:
|
|
for i, ss in enumerate(self._state_sets):
|
|
if not ss or all(s in stateSet for s in ss):
|
|
self._current_index = i
|
|
return True
|
|
return False
|
|
|
|
def mutate(self) -> "StateListDrawable":
|
|
self._drawables = [d.mutate() for d in self._drawables]
|
|
return self
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# LevelListDrawable
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class LevelListDrawable(Drawable):
|
|
"""LevelListDrawable — 级别列表 Drawable"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._levels: List[Tuple[int, int, Drawable]] = []
|
|
self._current_index = -1
|
|
|
|
def addLevel(self, low: int, high: int, drawable: Drawable):
|
|
self._levels.append((low, high, drawable))
|
|
|
|
def onLevelChange(self, level: int) -> bool:
|
|
for i, (low, high, d) in enumerate(self._levels):
|
|
if low <= level <= high:
|
|
self._current_index = i
|
|
return True
|
|
return False
|
|
|
|
def getCurrent(self) -> Drawable:
|
|
if 0 <= self._current_index < len(self._levels):
|
|
return self._levels[self._current_index][2]
|
|
if self._levels:
|
|
return self._levels[0][2]
|
|
return self
|
|
|
|
def draw(self, canvas: Canvas):
|
|
d = self.getCurrent()
|
|
d.setBounds(self._bounds)
|
|
d.draw(canvas)
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# TransitionDrawable
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class TransitionDrawable(Drawable):
|
|
"""TransitionDrawable — 过渡 Drawable"""
|
|
|
|
def __init__(self, layers: List[Drawable]):
|
|
super().__init__()
|
|
self._layers = list(layers)
|
|
self._transition_progress = 0.0
|
|
self._reverse = False
|
|
|
|
def startTransition(self, durationMillis: int):
|
|
self._transition_progress = 0.0
|
|
self._reverse = False
|
|
|
|
def resetTransition(self):
|
|
self._transition_progress = 0.0
|
|
self._reverse = False
|
|
|
|
def reverse(self, durationMillis: int):
|
|
self._transition_progress = 1.0
|
|
self._reverse = True
|
|
|
|
def startReverse(self, durationMillis: int = 0):
|
|
self._transition_progress = 1.0
|
|
self._reverse = True
|
|
|
|
def draw(self, canvas: Canvas):
|
|
for layer in self._layers:
|
|
layer.setBounds(self._bounds)
|
|
layer.draw(canvas)
|
|
|
|
def getNumberOfLayers(self) -> int:
|
|
return len(self._layers)
|
|
|
|
def getDrawable(self, index: int) -> Optional[Drawable]:
|
|
if 0 <= index < len(self._layers):
|
|
return self._layers[index]
|
|
return None
|
|
|
|
def isCrossFadeEnabled(self) -> bool:
|
|
return True
|
|
|
|
def setCrossFadeEnabled(self, enabled: bool):
|
|
pass
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# RotateDrawable
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class RotateDrawable(Drawable):
|
|
"""RotateDrawable — 旋转 Drawable"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._drawable = None
|
|
self._from_degrees = 0.0
|
|
self._to_degrees = 360.0
|
|
self._pivot_x = 0.5
|
|
self._pivot_y = 0.5
|
|
self._current_degrees = 0.0
|
|
|
|
def setDrawable(self, drawable: Drawable):
|
|
self._drawable = drawable
|
|
|
|
def getDrawable(self) -> Optional[Drawable]:
|
|
return self._drawable
|
|
|
|
def setFromDegrees(self, fromDegrees: float):
|
|
self._from_degrees = fromDegrees
|
|
|
|
def setToDegrees(self, toDegrees: float):
|
|
self._to_degrees = toDegrees
|
|
|
|
def setPivotX(self, pivotX: float):
|
|
self._pivot_x = pivotX
|
|
|
|
def setPivotY(self, pivotY: float):
|
|
self._pivot_y = pivotY
|
|
|
|
def draw(self, canvas: Canvas):
|
|
if self._drawable:
|
|
self._drawable.setBounds(self._bounds)
|
|
self._drawable.draw(canvas)
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# ScaleDrawable
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class ScaleDrawable(Drawable):
|
|
"""ScaleDrawable — 缩放 Drawable"""
|
|
|
|
def __init__(self, drawable: Drawable = None, gravity: int = 0x33, scaleWidth: float = 0.0,
|
|
scaleHeight: float = 0.0):
|
|
super().__init__()
|
|
self._drawable = drawable
|
|
self._gravity = gravity
|
|
self._scale_width = scaleWidth
|
|
self._scale_height = scaleHeight
|
|
self._level = 0
|
|
|
|
def getDrawable(self) -> Optional[Drawable]:
|
|
return self._drawable
|
|
|
|
def setLevel(self, level: int) -> bool:
|
|
self._level = level
|
|
if self._drawable:
|
|
self._drawable.setLevel(level)
|
|
return True
|
|
|
|
def draw(self, canvas: Canvas):
|
|
if self._drawable:
|
|
self._drawable.setBounds(self._bounds)
|
|
self._drawable.draw(canvas)
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# ClipDrawable
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class ClipDrawable(Drawable):
|
|
"""ClipDrawable — 裁剪 Drawable"""
|
|
|
|
HORIZONTAL = 1
|
|
VERTICAL = 2
|
|
|
|
def __init__(self, drawable: Drawable = None, gravity: int = 0x33, orientation: int = HORIZONTAL):
|
|
super().__init__()
|
|
self._drawable = drawable
|
|
self._gravity = gravity
|
|
self._orientation = orientation
|
|
self._level = 0
|
|
|
|
def setLevel(self, level: int) -> bool:
|
|
self._level = level
|
|
if self._drawable:
|
|
self._drawable.setLevel(level)
|
|
return True
|
|
|
|
def draw(self, canvas: Canvas):
|
|
if self._drawable:
|
|
self._drawable.setBounds(self._bounds)
|
|
self._drawable.draw(canvas)
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# InsetDrawable
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class InsetDrawable(Drawable):
|
|
"""InsetDrawable — 内嵌 Drawable"""
|
|
|
|
def __init__(self, drawable: Drawable, inset: float):
|
|
super().__init__()
|
|
self._drawable = drawable
|
|
self._inset = int(inset)
|
|
self._inset_left = int(inset)
|
|
self._inset_top = int(inset)
|
|
self._inset_right = int(inset)
|
|
self._inset_bottom = int(inset)
|
|
|
|
def getDrawable(self) -> Optional[Drawable]:
|
|
return self._drawable
|
|
|
|
def getInset(self) -> int:
|
|
return self._inset
|
|
|
|
def draw(self, canvas: Canvas):
|
|
if self._drawable:
|
|
b = self._bounds
|
|
self._drawable.setBounds(
|
|
b.left + self._inset_left, b.top + self._inset_top,
|
|
b.right - self._inset_right, b.bottom - self._inset_bottom
|
|
)
|
|
self._drawable.draw(canvas)
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# ShapeDrawable
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class ShapeDrawable(Drawable):
|
|
"""ShapeDrawable — 形状 Drawable"""
|
|
|
|
def __init__(self, shape: "Shape" = None):
|
|
super().__init__()
|
|
self._shape = shape
|
|
self._paint = Paint()
|
|
self._intrinsic_width = -1
|
|
self._intrinsic_height = -1
|
|
|
|
def getShape(self):
|
|
return self._shape
|
|
|
|
def setShape(self, shape):
|
|
self._shape = shape
|
|
|
|
def getPaint(self) -> Paint:
|
|
return self._paint
|
|
|
|
def setIntrinsicWidth(self, width: int):
|
|
self._intrinsic_width = width
|
|
|
|
def setIntrinsicHeight(self, height: int):
|
|
self._intrinsic_height = height
|
|
|
|
def getIntrinsicWidth(self) -> int:
|
|
return self._intrinsic_width if self._intrinsic_width > 0 else super().getIntrinsicWidth()
|
|
|
|
def getIntrinsicHeight(self) -> int:
|
|
return self._intrinsic_height if self._intrinsic_height > 0 else super().getIntrinsicHeight()
|
|
|
|
def draw(self, canvas: Canvas):
|
|
if self._shape:
|
|
self._shape.draw(canvas, self._paint)
|
|
|
|
|
|
class Shape(Stub):
|
|
"""Shape — 形状基类"""
|
|
def draw(self, canvas: Canvas, paint: Paint):
|
|
pass
|
|
|
|
|
|
class ArcShape(Shape):
|
|
"""ArcShape — 弧形"""
|
|
def __init__(self, startAngle: float, sweepAngle: float):
|
|
super().__init__()
|
|
self._start_angle = startAngle
|
|
self._sweep_angle = sweepAngle
|
|
|
|
|
|
class OvalShape(Shape):
|
|
"""OvalShape — 椭圆"""
|
|
def draw(self, canvas: Canvas, paint: Paint):
|
|
if HAS_PIL and hasattr(canvas, "_draw"):
|
|
c = paint.getColor()
|
|
fill_color = (Color.red(c), Color.green(c), Color.blue(c), Color.alpha(c))
|
|
b = canvas.getClipBounds()
|
|
canvas._draw.ellipse([b.left, b.top, b.right, b.bottom], fill=fill_color)
|
|
|
|
|
|
class RectShape(Shape):
|
|
"""RectShape — 矩形"""
|
|
def draw(self, canvas: Canvas, paint: Paint):
|
|
if HAS_PIL and hasattr(canvas, "_draw"):
|
|
c = paint.getColor()
|
|
fill_color = (Color.red(c), Color.green(c), Color.blue(c), Color.alpha(c))
|
|
b = canvas.getClipBounds()
|
|
canvas._draw.rectangle([b.left, b.top, b.right, b.bottom], fill=fill_color)
|
|
|
|
|
|
class RoundRectShape(Shape):
|
|
"""RoundRectShape — 圆角矩形"""
|
|
def __init__(self, outerRadii: List[float], inset: RectF, innerRadii: List[float]):
|
|
super().__init__()
|
|
self._outer_radii = outerRadii
|
|
self._inset = inset
|
|
self._inner_radii = innerRadii
|
|
|
|
def draw(self, canvas: Canvas, paint: Paint):
|
|
if HAS_PIL and hasattr(canvas, "_draw"):
|
|
c = paint.getColor()
|
|
fill_color = (Color.red(c), Color.green(c), Color.blue(c), Color.alpha(c))
|
|
b = canvas.getClipBounds()
|
|
radius = self._outer_radii[0] if self._outer_radii else 0
|
|
canvas._draw.rounded_rectangle([b.left, b.top, b.right, b.bottom],
|
|
radius=radius, fill=fill_color)
|
|
|
|
|
|
class PaintDrawable(ShapeDrawable):
|
|
"""PaintDrawable — 画笔 Drawable (ShapeDrawable 子类)"""
|
|
|
|
def __init__(self, color: int = 0):
|
|
super().__init__(RectShape())
|
|
self.getPaint().setColor(color)
|
|
|
|
def setCornerRadius(self, radius: float):
|
|
self.setShape(RoundRectShape([radius, radius, radius, radius], None, None))
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# VectorDrawable
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class VectorDrawable(Drawable):
|
|
"""VectorDrawable — 矢量 Drawable"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._alpha = 255
|
|
|
|
def setAlpha(self, alpha: int):
|
|
self._alpha = alpha
|
|
|
|
def getAlpha(self) -> int:
|
|
return self._alpha
|
|
|
|
def draw(self, canvas: Canvas):
|
|
pass
|
|
|
|
@staticmethod
|
|
def createFromXml(res, parser) -> "VectorDrawable":
|
|
return VectorDrawable()
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# RippleDrawable
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class RippleDrawable(Drawable):
|
|
"""RippleDrawable — 水波纹 Drawable (API 21+)"""
|
|
|
|
def __init__(self, colorStates: ColorStateList, content: Drawable = None,
|
|
mask: Drawable = None):
|
|
super().__init__()
|
|
self._color_states = colorStates
|
|
self._content = content
|
|
self._mask = mask
|
|
self._radius = -1
|
|
|
|
def setRadius(self, radius: int):
|
|
self._radius = radius
|
|
|
|
def getRadius(self) -> int:
|
|
return self._radius
|
|
|
|
def draw(self, canvas: Canvas):
|
|
if self._content:
|
|
self._content.setBounds(self._bounds)
|
|
self._content.draw(canvas)
|
|
|
|
def setColor(self, color: ColorStateList):
|
|
self._color_states = color
|
|
|
|
def getColor(self) -> Optional[ColorStateList]:
|
|
return self._color_states
|
|
|
|
def mutate(self) -> "RippleDrawable":
|
|
return self
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# AnimatedVectorDrawable
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class AnimatedVectorDrawable(Drawable):
|
|
"""AnimatedVectorDrawable — 动画矢量 Drawable (API 21+)"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._running = False
|
|
|
|
def start(self):
|
|
self._running = True
|
|
|
|
def stop(self):
|
|
self._running = False
|
|
|
|
def isRunning(self) -> bool:
|
|
return self._running
|
|
|
|
def reset(self):
|
|
self._running = False
|
|
|
|
def draw(self, canvas: Canvas):
|
|
pass
|
|
|
|
@staticmethod
|
|
def createFromXml(res, parser) -> "AnimatedVectorDrawable":
|
|
return AnimatedVectorDrawable()
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# AnimationDrawable
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class AnimationDrawable(Drawable):
|
|
"""AnimationDrawable — 帧动画 Drawable"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._frames: List[Tuple[Drawable, int]] = []
|
|
self._current_frame = 0
|
|
self._oneshot = False
|
|
self._running = False
|
|
|
|
def addFrame(self, frame: Drawable, duration: int):
|
|
self._frames.append((frame, duration))
|
|
|
|
def getFrame(self, index: int) -> Optional[Drawable]:
|
|
if 0 <= index < len(self._frames):
|
|
return self._frames[index][0]
|
|
return None
|
|
|
|
def getNumberOfFrames(self) -> int:
|
|
return len(self._frames)
|
|
|
|
def getDuration(self, index: int) -> int:
|
|
if 0 <= index < len(self._frames):
|
|
return self._frames[index][1]
|
|
return 0
|
|
|
|
def setOneShot(self, oneshot: bool):
|
|
self._oneshot = oneshot
|
|
|
|
def isOneShot(self) -> bool:
|
|
return self._oneshot
|
|
|
|
def start(self):
|
|
self._running = True
|
|
|
|
def stop(self):
|
|
self._running = False
|
|
|
|
def isRunning(self) -> bool:
|
|
return self._running
|
|
|
|
def run(self):
|
|
pass
|
|
|
|
def selectDrawable(self, index: int) -> bool:
|
|
if 0 <= index < len(self._frames):
|
|
self._current_frame = index
|
|
return True
|
|
return False
|
|
|
|
def getCurrent(self) -> Drawable:
|
|
if self._frames:
|
|
return self._frames[self._current_frame][0]
|
|
return self
|
|
|
|
def draw(self, canvas: Canvas):
|
|
d = self.getCurrent()
|
|
d.setBounds(self._bounds)
|
|
d.draw(canvas)
|
|
|
|
def setVisible(self, visible: bool, restart: bool = False) -> bool:
|
|
old = super().setVisible(visible, restart)
|
|
if visible and restart:
|
|
self._current_frame = 0
|
|
return old
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# DrawableContainer
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class DrawableContainer(Drawable):
|
|
"""DrawableContainer — Drawable 容器"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._drawables: List[Drawable] = []
|
|
self._current_index = 0
|
|
|
|
def selectDrawable(self, index: int) -> bool:
|
|
if 0 <= index < len(self._drawables):
|
|
self._current_index = index
|
|
return True
|
|
return False
|
|
|
|
def getCurrent(self) -> Drawable:
|
|
if self._drawables:
|
|
return self._drawables[self._current_index]
|
|
return self
|
|
|
|
def draw(self, canvas: Canvas):
|
|
d = self.getCurrent()
|
|
d.setBounds(self._bounds)
|
|
d.draw(canvas)
|
|
|
|
def setAlpha(self, alpha: int):
|
|
for d in self._drawables:
|
|
d.setAlpha(alpha)
|
|
|
|
def setColorFilter(self, cf: Optional[ColorFilter]):
|
|
for d in self._drawables:
|
|
d.setColorFilter(cf)
|
|
|
|
def getOpacity(self) -> int:
|
|
return 0
|
|
|
|
def isStateful(self) -> bool:
|
|
return any(d.isStateful() for d in self._drawables)
|
|
|
|
def mutate(self) -> "DrawableContainer":
|
|
return self
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# __all__
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
__all__ = [
|
|
"Drawable", "BitmapDrawable", "ColorDrawable", "GradientDrawable",
|
|
"LayerDrawable", "StateListDrawable", "LevelListDrawable",
|
|
"TransitionDrawable", "RotateDrawable", "ScaleDrawable",
|
|
"ClipDrawable", "InsetDrawable", "ShapeDrawable", "PaintDrawable",
|
|
"VectorDrawable", "RippleDrawable", "AnimatedVectorDrawable",
|
|
"AnimationDrawable", "DrawableContainer", "DrawableConstantState",
|
|
"ColorStateList",
|
|
# Shapes
|
|
"Shape", "ArcShape", "OvalShape", "RectShape", "RoundRectShape",
|
|
] |