@onekeyfe/react-native-bottom-tabs 6.7.0-alpha.400 → 6.7.0-alpha.402

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.
@@ -1,29 +1,27 @@
1
1
  package com.onekey.bottomtabs
2
2
 
3
- import android.graphics.BlendMode
4
3
  import android.graphics.Canvas
5
4
  import android.graphics.Color
6
- import android.graphics.LinearGradient
7
5
  import android.graphics.Paint
6
+ import android.graphics.Path
8
7
  import android.graphics.RenderEffect
9
8
  import android.graphics.RenderNode
10
9
  import android.graphics.Shader
11
10
  import androidx.annotation.RequiresApi
12
11
  import kotlin.math.ceil
13
- import kotlin.math.max
14
12
 
15
- /** A GPU-only backdrop. The source callback must draw scenes, never this overlay. */
13
+ /** A GPU-only backdrop confined to the tab bar bounds. The source callback must draw scenes, never this overlay. */
16
14
  @RequiresApi(31)
17
15
  internal class BottomBarBlurRenderer(private val density: Float) {
18
16
  private val node = RenderNode("TabBarBackdrop")
19
17
  private val tintPaint = Paint()
20
- private val maskPaint = Paint().apply { blendMode = BlendMode.DST_IN }
21
- private var lastWidth = 0
22
- private var lastBarTop = -1
18
+ private val clipPath = Path()
19
+ private var lastLeft = 0
20
+ private var lastTop = -1
21
+ private var lastRight = 0
23
22
  private var lastBottom = 0
23
+ private var lastCornerRadius = -1f
24
24
  private var lastTint = Color.TRANSPARENT
25
- private var fadeTop = 0f
26
- private var sampleTop = 0f
27
25
 
28
26
  init {
29
27
  node.setRenderEffect(RenderEffect.createBlurEffect(
@@ -35,51 +33,73 @@ internal class BottomBarBlurRenderer(private val density: Float) {
35
33
 
36
34
  fun draw(
37
35
  canvas: Canvas,
38
- width: Int,
39
- barTop: Int,
36
+ left: Int,
37
+ top: Int,
38
+ right: Int,
40
39
  bottom: Int,
40
+ cornerRadius: Float,
41
41
  tint: Int,
42
42
  drawScenes: (Canvas) -> Unit,
43
43
  ) {
44
- if (!canvas.isHardwareAccelerated || width <= 0 || bottom <= barTop) return
44
+ val width = right - left
45
+ val height = bottom - top
46
+ if (!canvas.isHardwareAccelerated || width <= 0 || height <= 0) return
45
47
 
46
- if (width != lastWidth || barTop != lastBarTop || bottom != lastBottom || tint != lastTint) {
47
- fadeTop = max(0f, barTop - 24f * density)
48
- sampleTop = max(0f, fadeTop - 40f * density)
49
- node.setPosition(0, 0, ceil(width / SCALE).toInt(), ceil((bottom - sampleTop) / SCALE).toInt())
50
- maskPaint.shader = LinearGradient(
51
- 0f, fadeTop, 0f, barTop.toFloat(),
52
- Color.TRANSPARENT, Color.WHITE, Shader.TileMode.CLAMP,
53
- )
48
+ if (
49
+ left != lastLeft ||
50
+ top != lastTop ||
51
+ right != lastRight ||
52
+ bottom != lastBottom ||
53
+ cornerRadius != lastCornerRadius ||
54
+ tint != lastTint
55
+ ) {
56
+ node.setPosition(0, 0, ceil(width / SCALE).toInt(), ceil(height / SCALE).toInt())
57
+ clipPath.reset()
58
+ if (cornerRadius > 0f) {
59
+ clipPath.addRoundRect(
60
+ left.toFloat(),
61
+ top.toFloat(),
62
+ right.toFloat(),
63
+ bottom.toFloat(),
64
+ cornerRadius,
65
+ cornerRadius,
66
+ Path.Direction.CW,
67
+ )
68
+ }
54
69
  val surface = (tint and 0x00ffffff) or (minOf(Color.alpha(tint), 199) shl 24)
55
70
  tintPaint.color = surface
56
- lastWidth = width
57
- lastBarTop = barTop
71
+ lastLeft = left
72
+ lastTop = top
73
+ lastRight = right
58
74
  lastBottom = bottom
75
+ lastCornerRadius = cornerRadius
59
76
  lastTint = tint
60
77
  }
61
78
 
62
79
  val recording = node.beginRecording()
63
80
  try {
64
- // Reuse child View display lists. No bitmap capture or full-screen blur.
81
+ // Reuse child View display lists. Only sample the scene region directly behind the bar.
65
82
  recording.scale(1f / SCALE, 1f / SCALE)
66
- recording.translate(0f, -sampleTop)
83
+ recording.translate(-left.toFloat(), -top.toFloat())
67
84
  recording.drawColor(tint or (0xff shl 24))
68
85
  drawScenes(recording)
69
86
  } finally {
70
87
  node.endRecording()
71
88
  }
72
89
 
73
- val layer = canvas.saveLayer(0f, fadeTop, width.toFloat(), bottom.toFloat(), null)
90
+ val saveCount = canvas.save()
91
+ if (cornerRadius > 0f) {
92
+ canvas.clipPath(clipPath)
93
+ } else {
94
+ canvas.clipRect(left, top, right, bottom)
95
+ }
74
96
  val transform = canvas.save()
75
- canvas.translate(0f, sampleTop)
97
+ canvas.translate(left.toFloat(), top.toFloat())
76
98
  canvas.scale(SCALE, SCALE)
77
99
  canvas.drawRenderNode(node)
78
100
  canvas.restoreToCount(transform)
79
- canvas.drawRect(0f, fadeTop, width.toFloat(), bottom.toFloat(), tintPaint)
80
- // Fade the combined blur and tint, keeping the transition free of a hard seam.
81
- canvas.drawRect(0f, fadeTop, width.toFloat(), bottom.toFloat(), maskPaint)
82
- canvas.restoreToCount(layer)
101
+ canvas.drawRect(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat(), tintPaint)
102
+ canvas.restoreToCount(saveCount)
83
103
  }
84
104
 
85
105
  fun release() {
@@ -154,7 +154,14 @@ class BottomTabsView(context: Context) : LinearLayout(context) {
154
154
  private val floatingRoot = object : FrameLayout(context) {
155
155
  override fun drawChild(canvas: Canvas, child: View, drawingTime: Long): Boolean {
156
156
  if (child === floatingBar && usesBottomBlur) {
157
- drawBlurBackdrop(canvas, width, floatingBar.top, height) { recording ->
157
+ drawBlurBackdrop(
158
+ canvas,
159
+ floatingBar.left,
160
+ floatingBar.top,
161
+ floatingBar.right,
162
+ floatingBar.bottom,
163
+ floatingBar.height / 2f,
164
+ ) { recording ->
158
165
  super.drawChild(recording, layoutHolder, drawingTime)
159
166
  }
160
167
  }
@@ -994,18 +1001,31 @@ class BottomTabsView(context: Context) : LinearLayout(context) {
994
1001
  }
995
1002
 
996
1003
  private fun drawBlurBackdrop(
997
- canvas: Canvas, width: Int, barTop: Int, bottom: Int, drawScenes: (Canvas) -> Unit,
1004
+ canvas: Canvas,
1005
+ left: Int,
1006
+ top: Int,
1007
+ right: Int,
1008
+ bottom: Int,
1009
+ cornerRadius: Float,
1010
+ drawScenes: (Canvas) -> Unit,
998
1011
  ) {
999
1012
  if (!usesBottomBlur || !canvas.isHardwareAccelerated || Build.VERSION.SDK_INT < 31) return
1000
1013
  val blur = bottomBarBlur ?: BottomBarBlurRenderer(resources.displayMetrics.density).also {
1001
1014
  bottomBarBlur = it
1002
1015
  }
1003
- blur.draw(canvas, width, barTop, bottom, blurSurfaceColor, drawScenes)
1016
+ blur.draw(canvas, left, top, right, bottom, cornerRadius, blurSurfaceColor, drawScenes)
1004
1017
  }
1005
1018
 
1006
1019
  override fun drawChild(canvas: Canvas, child: View, drawingTime: Long): Boolean {
1007
1020
  if (child === bottomNavigation && androidTabBarStyle == AndroidTabBarStyle.DEFAULT) {
1008
- drawBlurBackdrop(canvas, width, bottomNavigation.top, bottomNavigation.bottom) { recording ->
1021
+ drawBlurBackdrop(
1022
+ canvas,
1023
+ bottomNavigation.left,
1024
+ bottomNavigation.top,
1025
+ bottomNavigation.right,
1026
+ bottomNavigation.bottom,
1027
+ 0f,
1028
+ ) { recording ->
1009
1029
  super.drawChild(recording, layoutHolder, drawingTime)
1010
1030
  }
1011
1031
  }
package/docs/SPEC.md CHANGED
@@ -172,10 +172,10 @@ bottom gap, or 0 while hidden.
172
172
 
173
173
  Floating appearance with background blur disabled: the bar surface is `barTintColor` (or the theme background)
174
174
  at 95% opacity (lightened slightly for dark colors), with
175
- a hairline rim and an outline shadow. When background blur is enabled (see below), the bottom strip extends from
176
- 24 dp above the capsule to the bottom of the scene, including the gap beneath
177
- the capsule. The existing capsule keeps its rim/shadow and uses a 12 percent
178
- surface fill over the blurred strip; its lens and navigation controls remain
175
+ a hairline rim and an outline shadow. When background blur is enabled (see below), the live blur and translucent
176
+ surface tint are confined and clipped to the capsule bounds; the region above,
177
+ beside, and beneath the capsule remains unblurred. The existing capsule keeps its rim/shadow and uses a 12 percent
178
+ surface fill over the blurred capsule backdrop; its lens and navigation controls remain
179
179
  sharp. The selected tab sits on a capsule pill inset 4 dp
180
180
  (`activeIndicatorColor` when provided). The Material ripple is disabled; the
181
181
  lens provides press feedback.
@@ -211,7 +211,7 @@ coverage. Complete consumer-app and physical-device acceptance remain pending;
211
211
  API 31-35 and visual dark-mode acceptance have not been reverified for this port.
212
212
 
213
213
  `androidBottomBlurEnabled?: boolean` controls the native Android tab bar's live
214
- background blur, translucent surface tint, and 24 dp fade into the page above it.
214
+ background blur and translucent surface tint behind the bar.
215
215
  Omitting it (including `undefined`) MUST enable the effect only on Android 16
216
216
  (API 36) and newer. `false` MUST restore the existing appearance and layout for the current
217
217
  `androidTabBarStyle`: docked scenes reserve bar height; floating scenes retain
@@ -229,19 +229,22 @@ report zero bar height. Toggling, resizing, theme changes, and system/keyboard
229
229
  insets MUST refresh geometry and appearance without remounting scenes.
230
230
 
231
231
  The module owns the GPU resources on the UI thread. It MUST sample only the
232
- scene layer, excluding the tab bar and its own output. It MUST use hardware
233
- RenderNode/RenderEffect rendering of a bottom strip at one-quarter resolution
234
- in each dimension, with a 20 dp blur radius and 40 dp sampling margin above the
235
- fade. It MUST NOT capture bitmaps, read pixels back to the CPU, emit scroll
236
- events to JS, or run a recurring frame callback for this effect. Rendering MUST
237
- follow the normal view invalidation cycle, using the scene's existing display
238
- lists. The translucent tint MUST follow `tabBarStyle.backgroundColor` when set,
239
- otherwise the docked Material surface color or floating theme background,
240
- with opacity capped at 78 percent.
241
-
242
- Only one blur RenderNode and cached paints/shaders MAY be retained per mounted
243
- tab view. Its offscreen dimensions MUST be bounded by one-quarter of the view
244
- width and the smaller of the view height or reported bar footprint plus 64 dp (rounded up).
232
+ scene layer directly behind the active tab bar bounds (the docked bar rectangle
233
+ in `default` style or the capsule bounds in `floating` style), excluding the
234
+ tab bar and its own output, and clip the blurred backdrop and tint to those
235
+ bounds (including the capsule corner radius in `floating` style) with no blur
236
+ or fade bleeding above or outside the bar. It MUST use hardware
237
+ RenderNode/RenderEffect rendering of the bar bounds at one-quarter resolution
238
+ in each dimension, with a 20 dp blur radius. It MUST NOT capture bitmaps, read
239
+ pixels back to the CPU, emit scroll events to JS, or run a recurring frame
240
+ callback for this effect. Rendering MUST follow the normal view invalidation
241
+ cycle, using the scene's existing display lists. The translucent tint MUST
242
+ follow `tabBarStyle.backgroundColor` when set, otherwise the docked Material
243
+ surface color or floating theme background, with opacity capped at 78 percent.
244
+
245
+ Only one blur RenderNode and cached paints/paths MAY be retained per mounted
246
+ tab view. Its offscreen dimensions MUST be bounded by one-quarter of the bar
247
+ width and one-quarter of the bar height (rounded up).
245
248
  Disabling, hiding, detaching, and dropping the native view MUST discard the
246
249
  display list; no scene snapshots, disk cache, timers, or background workers are
247
250
  owned. SurfaceView content is outside the normal View display list and is not
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/react-native-bottom-tabs",
3
- "version": "6.7.0-alpha.400",
3
+ "version": "6.7.0-alpha.402",
4
4
  "description": "Native bottom tabs (tab bar + scene container) for React Native on iOS and Android",
5
5
  "source": "./src/index.tsx",
6
6
  "main": "./lib/module/index.js",