pythonnative 0.26.0__py3-none-any.whl → 0.28.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -21,12 +21,28 @@ applies those frames via per-child `MarginLayoutParams`.
21
21
 
22
22
  **Gestures** feed raw ``MotionEvent`` streams into the shared
23
23
  pure-Python [`GestureArbiter`][pythonnative.gestures.GestureArbiter],
24
- so gesture semantics match the desktop preview exactly.
24
+ so gesture semantics match the desktop preview exactly. Serialized
25
+ specs (including ``simultaneous`` / ``wait_for`` composition metadata
26
+ and ``fling`` configs) pass through untouched; this module only
27
+ schedules main-looper polls at the arbiter's ``next_deadline()`` so
28
+ long-press activation and multi-tap windows fire even when no
29
+ further touch events arrive.
25
30
 
26
31
  **Animations**: ``timing`` animations on transform/opacity/color props
27
32
  are driven natively by ``ObjectAnimator`` (Choreographer-paced, no
28
33
  Python per-frame work); springs and decay fall back to the Python
29
- ticker.
34
+ ticker, whose per-frame values arrive through
35
+ ``set_animated_property`` (floats for transforms and opacity,
36
+ interpolated "#AARRGGBB" strings for background and text colors that
37
+ re-bake through the border-drawable machinery instead of clobbering
38
+ it).
39
+
40
+ **Interaction props**: ``pointer_events`` maps onto the template's
41
+ ``PNFrameLayout`` touch interception when available (with a
42
+ Python-side fallback for older projects), ``hit_slop`` expands touch
43
+ targets via a parent ``TouchDelegate`` sized after layout,
44
+ ``z_index`` maps to ``View.setZ``, and per-corner radii extend the
45
+ ``GradientDrawable`` border path.
30
46
 
31
47
  This module is only imported on Android at runtime. Desktop tests
32
48
  inject a mock registry via
@@ -58,11 +74,20 @@ _SIDE_BORDER_COLOR_KEYS = (
58
74
  "border_right_color",
59
75
  "border_bottom_color",
60
76
  )
77
+ # Order matches GradientDrawable.setCornerRadii: top-left, top-right,
78
+ # bottom-right, bottom-left (each corner takes an x and a y radius).
79
+ _CORNER_RADIUS_KEYS = (
80
+ "border_top_left_radius",
81
+ "border_top_right_radius",
82
+ "border_bottom_right_radius",
83
+ "border_bottom_left_radius",
84
+ )
61
85
  _DRAWABLE_STYLE_KEYS = (
62
86
  "background_color",
63
87
  "border_radius",
64
88
  "border_width",
65
89
  "border_color",
90
+ *_CORNER_RADIUS_KEYS,
66
91
  *_SIDE_BORDER_WIDTH_KEYS,
67
92
  *_SIDE_BORDER_COLOR_KEYS,
68
93
  )
@@ -98,6 +123,38 @@ def _pn_runtime_class(class_name: str) -> Any:
98
123
  return jclass(f"{package}.{class_name}")
99
124
 
100
125
 
126
+ # Cached lookup for the template's PNFrameLayout container class.
127
+ # ``None`` means "not resolved yet"; ``False`` means "this generated
128
+ # project predates the class", so creation falls back to FrameLayout
129
+ # without retrying the (exception-throwing) lookup per view.
130
+ _pn_container_cls: Any = None
131
+
132
+
133
+ def _new_container() -> Any:
134
+ """Create a container view, preferring the template's ``PNFrameLayout``.
135
+
136
+ ``PNFrameLayout`` adds native ``pointer_events`` interception
137
+ (``onInterceptTouchEvent`` / ``dispatchTouchEvent``), which
138
+ Chaquopy can't express from Python. Older generated projects
139
+ without the class get a plain ``FrameLayout``; the
140
+ ``pointer_events`` prop then degrades to the Python-side
141
+ approximation in
142
+ [`_apply_pointer_events`][pythonnative.native_views.android._apply_pointer_events].
143
+ """
144
+ global _pn_container_cls
145
+ if _pn_container_cls is None:
146
+ try:
147
+ _pn_container_cls = _pn_runtime_class("PNFrameLayout")
148
+ except Exception:
149
+ _pn_container_cls = False
150
+ if _pn_container_cls is not False:
151
+ try:
152
+ return _pn_container_cls(_ctx())
153
+ except Exception:
154
+ diagnostics.swallowed("android._new_container")
155
+ return jclass("android.widget.FrameLayout")(_ctx())
156
+
157
+
101
158
  def _signed_color(value: int) -> int:
102
159
  """Clamp a Python color int into Java's signed 32-bit range."""
103
160
  value &= 0xFFFFFFFF
@@ -227,7 +284,7 @@ def _apply_border(view: Any, props: Dict[str, Any]) -> None:
227
284
  """
228
285
  if _apply_side_border(view, props):
229
286
  return
230
- has_border = any(k in props for k in ("border_radius", "border_width", "border_color"))
287
+ has_border = any(k in props for k in ("border_radius", "border_width", "border_color", *_CORNER_RADIUS_KEYS))
231
288
  has_bg = "background_color" in props and props["background_color"] is not None
232
289
  if not has_border and not has_bg:
233
290
  return
@@ -239,7 +296,21 @@ def _apply_border(view: Any, props: Dict[str, Any]) -> None:
239
296
  drawable.setColor(parse_color_int(props["background_color"]))
240
297
  except Exception:
241
298
  diagnostics.swallowed("android._apply_border")
242
- if "border_radius" in props and props["border_radius"] is not None:
299
+ if any(props.get(k) is not None for k in _CORNER_RADIUS_KEYS):
300
+ # Per-corner radii: unspecified corners inherit the uniform
301
+ # ``border_radius`` (else 0). ``setCornerRadii`` takes eight
302
+ # floats, an (x, y) pair per corner in _CORNER_RADIUS_KEYS
303
+ # order, each in pixels.
304
+ try:
305
+ base_radius = float(props.get("border_radius") or 0.0)
306
+ radii = []
307
+ for key in _CORNER_RADIUS_KEYS:
308
+ r = float(_dp(float(props[key] if props.get(key) is not None else base_radius)))
309
+ radii.extend([r, r])
310
+ drawable.setCornerRadii(radii)
311
+ except Exception:
312
+ diagnostics.swallowed("android._apply_border")
313
+ elif "border_radius" in props and props["border_radius"] is not None:
243
314
  try:
244
315
  drawable.setCornerRadius(float(_dp(float(props["border_radius"]))))
245
316
  except Exception:
@@ -269,6 +340,44 @@ def _apply_border(view: Any, props: Dict[str, Any]) -> None:
269
340
  diagnostics.swallowed("android._apply_border")
270
341
 
271
342
 
343
+ def _set_animated_background(view: Any, value: Any) -> None:
344
+ """Apply one animated ``background_color`` frame without losing borders.
345
+
346
+ Interpolations hand this module raw color values ("#AARRGGBB" or
347
+ "#RRGGBB" strings, or ints). Views whose background is a
348
+ border-rendering drawable (the uniform ``GradientDrawable`` or the
349
+ per-side ``PNBorderDrawable``) must keep their corner radii and
350
+ strokes while the fill animates, so the color routes through the
351
+ same [`_apply_border`][pythonnative.native_views.android._apply_border]
352
+ machinery ``update()`` uses. When the current background already is
353
+ a ``GradientDrawable``, its fill is mutated in place, so no
354
+ drawable is allocated per animation frame. Plain views take the
355
+ direct ``setBackgroundColor`` path.
356
+ """
357
+ state = _state_of(view)
358
+ visual = state.get("visual")
359
+ has_border = isinstance(visual, dict) and any(
360
+ visual.get(k) is not None for k in _DRAWABLE_STYLE_KEYS if k != "background_color"
361
+ )
362
+ if has_border:
363
+ visual = dict(visual)
364
+ visual["background_color"] = value
365
+ state["visual"] = visual
366
+ try:
367
+ GradientDrawable = jclass("android.graphics.drawable.GradientDrawable")
368
+ bg = view.getBackground()
369
+ if bg is not None and isinstance(bg, GradientDrawable):
370
+ bg.setColor(parse_color_int(value))
371
+ return
372
+ except Exception:
373
+ diagnostics.swallowed("android._set_animated_background")
374
+ # Per-side ``PNBorderDrawable`` backgrounds (immutable fill) and
375
+ # first frames before any drawable exists re-bake the full set.
376
+ _apply_border(view, visual)
377
+ return
378
+ view.setBackgroundColor(parse_color_int(value))
379
+
380
+
272
381
  def _apply_shadow(view: Any, props: Dict[str, Any]) -> None:
273
382
  """Apply shadow props via elevation plus tinted outline shadows.
274
383
 
@@ -417,6 +526,138 @@ def _apply_accessibility(view: Any, props: Dict[str, Any]) -> None:
417
526
  diagnostics.swallowed("android._apply_accessibility")
418
527
 
419
528
 
529
+ def _pointer_events_blocked(view: Any) -> bool:
530
+ """Whether ``pointer_events`` disables the view's own touch handling.
531
+
532
+ Consulted at the top of every touch-stream proxy (gesture, press)
533
+ so listeners installed at create time honor the prop without being
534
+ re-wired: ``"none"`` and ``"box_none"`` both mean the view itself
535
+ must not observe or consume the stream.
536
+ """
537
+ merged = _state_of(view).get("props") or {}
538
+ return merged.get("pointer_events") in ("none", "box_none")
539
+
540
+
541
+ def _apply_pointer_events(view: Any, value: Any) -> None:
542
+ """Apply the ``pointer_events`` prop (``None`` restores ``"auto"``).
543
+
544
+ Two cooperating layers implement the modes:
545
+
546
+ - **Native interception** (preferred): containers built by
547
+ [`_new_container`][pythonnative.native_views.android._new_container]
548
+ are the template's ``PNFrameLayout``, whose
549
+ ``setPointerEventsMode(String)`` drives real
550
+ ``dispatchTouchEvent`` / ``onInterceptTouchEvent`` overrides.
551
+ ``"none"`` declines the whole dispatch so touches pass through
552
+ to views underneath, ``"box_only"`` intercepts events away from
553
+ children, and ``"box_none"`` never intercepts. The call is
554
+ guarded so older generated projects without the class (and
555
+ non-container views) fall back cleanly.
556
+ - **Python-side muting**: ``setClickable`` toggles whether the
557
+ view claims events, and the touch-stream proxies consult
558
+ [`_pointer_events_blocked`][pythonnative.native_views.android._pointer_events_blocked]
559
+ so gesture/press wiring goes quiet without being torn down.
560
+ ``"auto"`` / removal restores the original clickability
561
+ captured before the first override.
562
+
563
+ On a plain ``FrameLayout`` (old template) the fallback keeps the
564
+ previous approximation: ``"none"`` silences the view itself but
565
+ interactive children keep receiving touches, and ``"box_only"``
566
+ only claims events no interactive child consumed. ``"box_none"``
567
+ is exact either way, since children need no help.
568
+ """
569
+ mode = value if value is not None else "auto"
570
+ state = _state_of(view)
571
+ try:
572
+ view.setPointerEventsMode(str(mode))
573
+ except Exception:
574
+ # Plain FrameLayout, a leaf widget, or an older template
575
+ # without PNFrameLayout; the Python-side fallback still runs.
576
+ pass
577
+ try:
578
+ if mode in ("none", "box_none"):
579
+ state.setdefault("pe_was_clickable", bool(view.isClickable()))
580
+ view.setClickable(False)
581
+ elif mode == "box_only":
582
+ state.setdefault("pe_was_clickable", bool(view.isClickable()))
583
+ view.setClickable(True)
584
+ elif "pe_was_clickable" in state:
585
+ view.setClickable(bool(state.pop("pe_was_clickable")))
586
+ except Exception:
587
+ diagnostics.swallowed("android._apply_pointer_events")
588
+
589
+
590
+ def _hit_slop_insets(value: Any) -> Optional[Tuple[float, float, float, float]]:
591
+ """Normalize a ``hit_slop`` prop into ``(top, left, bottom, right)`` points.
592
+
593
+ Accepts a uniform number or a dict with any of ``top`` / ``left``
594
+ / ``bottom`` / ``right``. Returns ``None`` for absent or
595
+ unparseable values.
596
+ """
597
+ if value is None:
598
+ return None
599
+ if isinstance(value, dict):
600
+ try:
601
+ return (
602
+ float(value.get("top") or 0.0),
603
+ float(value.get("left") or 0.0),
604
+ float(value.get("bottom") or 0.0),
605
+ float(value.get("right") or 0.0),
606
+ )
607
+ except Exception:
608
+ return None
609
+ try:
610
+ uniform = float(value)
611
+ except Exception:
612
+ return None
613
+ return (uniform, uniform, uniform, uniform)
614
+
615
+
616
+ def _update_hit_slop(view: Any) -> None:
617
+ """Extend the view's touch target via a ``TouchDelegate`` on its parent.
618
+
619
+ The standard Android mechanism: the parent forwards touches
620
+ landing inside an expanded ``Rect`` (the view's frame plus the
621
+ slop insets, in parent-relative pixels) to the view. Parents are
622
+ PythonNative-managed ``FrameLayout``s, so installing the delegate
623
+ is safe; a parent only holds one delegate, so among siblings the
624
+ last ``hit_slop`` applied wins (matching the platform limitation).
625
+
626
+ Requires a known frame, so
627
+ [`set_frame`][pythonnative.native_views.android.AndroidViewHandler.set_frame]
628
+ records the frame and re-invokes this; prop-only updates before
629
+ the first frame defer until layout runs.
630
+ """
631
+ state = _state_of(view)
632
+ frame = state.get("frame")
633
+ if frame is None:
634
+ return
635
+ merged = state.get("props") or {}
636
+ insets = _hit_slop_insets(merged.get("hit_slop"))
637
+ try:
638
+ parent = view.getParent()
639
+ if parent is None:
640
+ return
641
+ if insets is None:
642
+ if state.pop("hit_slop_active", None):
643
+ parent.setTouchDelegate(None)
644
+ return
645
+ top, left, bottom, right = insets
646
+ x, y, width, height = frame
647
+ Rect = jclass("android.graphics.Rect")
648
+ TouchDelegate = jclass("android.view.TouchDelegate")
649
+ rect = Rect(
650
+ _dp(x - left),
651
+ _dp(y - top),
652
+ _dp(x + width + right),
653
+ _dp(y + height + bottom),
654
+ )
655
+ parent.setTouchDelegate(TouchDelegate(rect, view))
656
+ state["hit_slop_active"] = True
657
+ except Exception:
658
+ diagnostics.swallowed("android._update_hit_slop")
659
+
660
+
420
661
  def _apply_common_visual(view: Any, props: Dict[str, Any]) -> None:
421
662
  """Apply visual properties shared across many handlers."""
422
663
  has_drawable_keys = any(k in props for k in _DRAWABLE_STYLE_KEYS)
@@ -440,6 +681,18 @@ def _apply_common_visual(view: Any, props: Dict[str, Any]) -> None:
440
681
  view.setAlpha(float(props["opacity"]))
441
682
  except Exception:
442
683
  diagnostics.swallowed("android._apply_common_visual")
684
+ if "z_index" in props:
685
+ # ``setZ`` takes pixels, so scale like other coordinates; a
686
+ # removal (``None``) resets the view to the default plane.
687
+ try:
688
+ z = props["z_index"]
689
+ view.setZ(float(_dp(float(z))) if z is not None else 0.0)
690
+ except Exception:
691
+ diagnostics.swallowed("android._apply_common_visual")
692
+ if "pointer_events" in props:
693
+ _apply_pointer_events(view, props["pointer_events"])
694
+ if "hit_slop" in props:
695
+ _update_hit_slop(view)
443
696
  _apply_shadow(view, props)
444
697
  _apply_transform(view, props)
445
698
  _apply_accessibility(view, props)
@@ -454,10 +707,14 @@ def _wire_gestures(view: Any, specs: Any) -> None:
454
707
  """Feed ``MotionEvent`` streams on ``view`` into a `GestureArbiter`.
455
708
 
456
709
  The arbiter emits ``(gesture_index, payload)`` pairs which are
457
- forwarded as ``gesture:<i>`` events for this view's tag. Long
458
- presses are polled with a main-looper ``Handler``. When a pan
459
- activates, the parent is asked not to intercept so an enclosing
460
- ScrollView can't steal the drag.
710
+ forwarded as ``gesture:<i>`` events for this view's tag. Specs
711
+ (including ``simultaneous`` / ``wait_for`` metadata and fling
712
+ configs) pass through to the arbiter untouched; all arbitration
713
+ and recognition live in ``pythonnative.gestures``. Time-based
714
+ deadlines (long press, multi-tap windows) are polled with a
715
+ main-looper ``Handler`` scheduled at the arbiter's
716
+ ``next_deadline()``. When a pan activates, the parent is asked
717
+ not to intercept so an enclosing ScrollView can't steal the drag.
461
718
  """
462
719
  state = _state_of(view)
463
720
  if not isinstance(specs, (list, tuple)) or not specs:
@@ -476,13 +733,27 @@ def _wire_gestures(view: Any, specs: Any) -> None:
476
733
 
477
734
 
478
735
  def _schedule_arbiter_poll(view: Any, state: Dict[str, Any]) -> None:
479
- """Schedule a main-looper poll at the arbiter's next deadline (long-press)."""
736
+ """Schedule a main-looper poll at the arbiter's next deadline.
737
+
738
+ Deadlines cover every time-based recognizer decision: long-press
739
+ activation and the multi-tap windows that flush an Exclusive
740
+ double-tap/single-tap buffer. Called after every pointer event
741
+ (not just DOWN) because a deadline can appear on UP, when no
742
+ further touch events arrive to drive the arbiter forward.
743
+
744
+ A pending poll at or before the requested deadline is reused
745
+ rather than stacking duplicate runnables; each poll reschedules
746
+ itself for the following deadline until none remains.
747
+ """
480
748
  arbiter = state.get("arbiter")
481
749
  if arbiter is None:
482
750
  return
483
751
  deadline = arbiter.next_deadline()
484
752
  if deadline is None:
485
753
  return
754
+ pending = state.get("poll_deadline")
755
+ if pending is not None and pending <= deadline + 0.001:
756
+ return
486
757
  delay_ms = max(1, int((deadline - time.monotonic()) * 1000.0))
487
758
  try:
488
759
  Handler = jclass("android.os.Handler")
@@ -492,9 +763,14 @@ def _schedule_arbiter_poll(view: Any, state: Dict[str, Any]) -> None:
492
763
  if handler is None:
493
764
  handler = Handler(Looper.getMainLooper())
494
765
  state["poll_handler"] = handler
766
+ token = state.get("poll_token", 0) + 1
767
+ state["poll_token"] = token
768
+ state["poll_deadline"] = deadline
495
769
 
496
770
  class _PollRunnable(dynamic_proxy(Runnable)):
497
771
  def run(self) -> None:
772
+ if state.get("poll_token") == token:
773
+ state["poll_deadline"] = None
498
774
  live = state.get("arbiter")
499
775
  if live is not None:
500
776
  live.poll(time.monotonic())
@@ -502,6 +778,7 @@ def _schedule_arbiter_poll(view: Any, state: Dict[str, Any]) -> None:
502
778
 
503
779
  handler.postDelayed(_PollRunnable(), delay_ms)
504
780
  except Exception:
781
+ state["poll_deadline"] = None
505
782
  diagnostics.swallowed("android._schedule_arbiter_poll")
506
783
 
507
784
 
@@ -512,6 +789,8 @@ def _bind_touch_stream(view: Any, state: Dict[str, Any]) -> None:
512
789
 
513
790
  class _GestureTouchProxy(dynamic_proxy(OnTouchListener)):
514
791
  def onTouch(self, v: Any, event: Any) -> bool:
792
+ if _pointer_events_blocked(v):
793
+ return False
515
794
  return bool(_feed_motion_event(v, state, event))
516
795
 
517
796
  view.setOnTouchListener(_GestureTouchProxy())
@@ -536,7 +815,6 @@ def _feed_motion_event(view: Any, state: Dict[str, Any], event: Any) -> bool:
536
815
  idx = int(event.getActionIndex())
537
816
  pid = int(event.getPointerId(idx))
538
817
  arbiter.pointer_down(pid, float(event.getX(idx)) / density, float(event.getY(idx)) / density, t)
539
- _schedule_arbiter_poll(view, state)
540
818
  elif action == 2: # MOVE
541
819
  for i in range(int(event.getPointerCount())):
542
820
  pid = int(event.getPointerId(i))
@@ -554,6 +832,10 @@ def _feed_motion_event(view: Any, state: Dict[str, Any], event: Any) -> bool:
554
832
  arbiter.pointer_up(pid, float(event.getX(idx)) / density, float(event.getY(idx)) / density, t)
555
833
  elif action == 3: # CANCEL
556
834
  arbiter.cancel(t)
835
+ # Every event can move the next time-based deadline (long-press
836
+ # activation, multi-tap windows opened on UP), so reschedule
837
+ # unconditionally; the scheduler dedups pending polls.
838
+ _schedule_arbiter_poll(view, state)
557
839
  return True
558
840
  except Exception:
559
841
  return True
@@ -770,6 +1052,13 @@ class AndroidViewHandler(ViewHandler):
770
1052
  except Exception:
771
1053
  diagnostics.swallowed("android.AndroidViewHandler.set_frame")
772
1054
  native_view.setLayoutParams(lp)
1055
+ # ``hit_slop`` needs the frame (and an attached parent, which
1056
+ # exists by the time frames are applied) to size its
1057
+ # TouchDelegate rect, so record the frame and recompute here.
1058
+ state = _state_of(native_view)
1059
+ state["frame"] = (x, y, width, height)
1060
+ if (state.get("props") or {}).get("hit_slop") is not None or state.get("hit_slop_active"):
1061
+ _update_hit_slop(native_view)
773
1062
  except Exception:
774
1063
  diagnostics.swallowed("android.AndroidViewHandler.set_frame")
775
1064
 
@@ -802,7 +1091,13 @@ class AndroidViewHandler(ViewHandler):
802
1091
  return (0.0, 0.0)
803
1092
 
804
1093
  def set_animated_property(self, native_view: Any, prop_name: str, value: Any) -> None:
805
- """Apply one Python-driven animation frame immediately."""
1094
+ """Apply one Python-driven animation frame immediately.
1095
+
1096
+ Handles float transform props (dp-scaled translations, scales,
1097
+ rotation degrees, opacity) and interpolated color frames for
1098
+ ``background_color`` / ``color`` ("#AARRGGBB" / "#RRGGBB"
1099
+ strings or ints). Unknown props are silently ignored.
1100
+ """
806
1101
  if native_view is None:
807
1102
  return
808
1103
  try:
@@ -822,7 +1117,12 @@ class AndroidViewHandler(ViewHandler):
822
1117
  elif prop_name == "rotate":
823
1118
  native_view.setRotation(float(value))
824
1119
  elif prop_name == "background_color":
825
- native_view.setBackgroundColor(parse_color_int(value))
1120
+ _set_animated_background(native_view, value)
1121
+ elif prop_name == "color":
1122
+ # Interpolations emit "#AARRGGBB" strings; parse_color_int
1123
+ # accepts strings and raw ints alike. Views without
1124
+ # setTextColor fall into the except below and skip.
1125
+ native_view.setTextColor(parse_color_int(value))
826
1126
  except Exception:
827
1127
  diagnostics.swallowed("android.AndroidViewHandler.set_animated_property")
828
1128
 
@@ -867,11 +1167,13 @@ class FlexContainerHandler(AndroidViewHandler):
867
1167
  All flex semantics (direction, alignment, distribution, padding)
868
1168
  are computed by the layout engine and applied via
869
1169
  [`set_frame`][pythonnative.native_views.android.AndroidViewHandler.set_frame].
870
- The container itself is just a positioning surface.
1170
+ The container itself is just a positioning surface (the template's
1171
+ ``PNFrameLayout`` when available, so ``pointer_events`` gets real
1172
+ touch interception).
871
1173
  """
872
1174
 
873
1175
  def _build(self, props: Dict[str, Any]) -> Any:
874
- return jclass("android.widget.FrameLayout")(_ctx())
1176
+ return _new_container()
875
1177
 
876
1178
  def insert_child(self, parent: Any, child: Any, index: int) -> None:
877
1179
  _insert_view(parent, child, index)
@@ -1959,7 +2261,7 @@ class SafeAreaViewHandler(FlexContainerHandler):
1959
2261
  """Safe-area container using FrameLayout with ``fitsSystemWindows``."""
1960
2262
 
1961
2263
  def _build(self, props: Dict[str, Any]) -> Any:
1962
- fl = jclass("android.widget.FrameLayout")(_ctx())
2264
+ fl = _new_container()
1963
2265
  fl.setFitsSystemWindows(True)
1964
2266
  return fl
1965
2267
 
@@ -2381,7 +2683,7 @@ class PressableHandler(FlexContainerHandler):
2381
2683
  _TAP_SLOP_DP = 12.0
2382
2684
 
2383
2685
  def _build(self, props: Dict[str, Any]) -> Any:
2384
- fl = jclass("android.widget.FrameLayout")(_ctx())
2686
+ fl = _new_container()
2385
2687
  fl.setClickable(True)
2386
2688
  fl.setFocusable(True)
2387
2689
  self._bind_press_stream(fl)
@@ -2410,6 +2712,8 @@ class PressableHandler(FlexContainerHandler):
2410
2712
 
2411
2713
  class _PressTouchProxy(dynamic_proxy(OnTouchListener)):
2412
2714
  def onTouch(self, view: Any, event: Any) -> bool:
2715
+ if _pointer_events_blocked(view):
2716
+ return False
2413
2717
  state = _state_of(view)
2414
2718
  _feed_motion_event(view, state, event)
2415
2719
  action = int(event.getActionMasked())