react-native-morph-card 0.1.11 → 0.1.13

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.
@@ -100,11 +100,64 @@ class MorphCardSourceView(context: Context) : ReactViewGroup(context) {
100
100
 
101
101
  // ── Snapshot ──
102
102
 
103
+ /**
104
+ * Capture a snapshot of the source card's children WITHOUT border radius clipping.
105
+ * Like iOS, we want the raw content (e.g. the full rectangular image), not what's
106
+ * visually clipped on screen. The border radius is applied separately during animation.
107
+ *
108
+ * This disables both Android's clipToOutline AND Fresco's RoundingParams (used by
109
+ * React Native's Image component) to capture the full rectangular content.
110
+ */
103
111
  private fun captureSnapshot(): Bitmap {
104
112
  val w = width
105
113
  val h = height
106
114
  val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
107
115
  val canvas = Canvas(bitmap)
116
+
117
+ // Track state to restore: (view, hadClipToOutline, savedRoundingParams)
118
+ data class ViewState(val view: View, val hadClip: Boolean, val roundingParams: Any?)
119
+ val savedStates = mutableListOf<ViewState>()
120
+
121
+ fun disableClipping(view: View) {
122
+ val hadClip = view.clipToOutline
123
+ var savedRounding: Any? = null
124
+
125
+ // Disable outline clipping
126
+ if (hadClip) {
127
+ view.clipToOutline = false
128
+ }
129
+
130
+ // Disable Fresco RoundingParams on DraweeView (React Native Image components).
131
+ // Uses reflection to avoid a compile-time dependency on Fresco.
132
+ try {
133
+ val getHierarchy = view.javaClass.getMethod("getHierarchy")
134
+ val hierarchy = getHierarchy.invoke(view)
135
+ if (hierarchy != null) {
136
+ val getRounding = hierarchy.javaClass.getMethod("getRoundingParams")
137
+ savedRounding = getRounding.invoke(hierarchy)
138
+ if (savedRounding != null) {
139
+ val roundingClass = Class.forName("com.facebook.drawee.generic.RoundingParams")
140
+ val setRounding = hierarchy.javaClass.getMethod("setRoundingParams", roundingClass)
141
+ setRounding.invoke(hierarchy, null)
142
+ }
143
+ }
144
+ } catch (_: Exception) {}
145
+
146
+ if (hadClip || savedRounding != null) {
147
+ savedStates.add(ViewState(view, hadClip, savedRounding))
148
+ }
149
+
150
+ if (view is ViewGroup) {
151
+ for (i in 0 until view.childCount) {
152
+ disableClipping(view.getChildAt(i))
153
+ }
154
+ }
155
+ }
156
+
157
+ for (i in 0 until childCount) {
158
+ disableClipping(getChildAt(i))
159
+ }
160
+
108
161
  for (i in 0 until childCount) {
109
162
  val child = getChildAt(i)
110
163
  if (child.visibility != VISIBLE) continue
@@ -113,6 +166,25 @@ class MorphCardSourceView(context: Context) : ReactViewGroup(context) {
113
166
  child.draw(canvas)
114
167
  canvas.restore()
115
168
  }
169
+
170
+ // Restore all view states
171
+ for (state in savedStates) {
172
+ if (state.hadClip) {
173
+ state.view.clipToOutline = true
174
+ }
175
+ if (state.roundingParams != null) {
176
+ try {
177
+ val getHierarchy = state.view.javaClass.getMethod("getHierarchy")
178
+ val hierarchy = getHierarchy.invoke(state.view)
179
+ if (hierarchy != null) {
180
+ val roundingClass = Class.forName("com.facebook.drawee.generic.RoundingParams")
181
+ val setRounding = hierarchy.javaClass.getMethod("setRoundingParams", roundingClass)
182
+ setRounding.invoke(hierarchy, state.roundingParams)
183
+ }
184
+ } catch (_: Exception) {}
185
+ }
186
+ }
187
+
116
188
  return bitmap
117
189
  }
118
190
 
@@ -502,8 +574,8 @@ class MorphCardSourceView(context: Context) : ReactViewGroup(context) {
502
574
  removeHierarchyListener()
503
575
 
504
576
  // The PixelCopy blocker bitmap contains the source card at its original position.
505
- // Clear the card area so the card wrapper animates without a duplicate underneath.
506
- // We keep the blocker in place to prevent the target screen from flashing.
577
+ // Replace the card area with the surrounding background color so the card wrapper
578
+ // animates without a duplicate underneath, and no transparent hole is visible.
507
579
  val blockerImg = wrapper.findViewWithTag<FrameLayout>("morphCardWrapper")?.let { cardW ->
508
580
  (0 until wrapper.childCount).map { wrapper.getChildAt(it) }.firstOrNull { it !== cardW }
509
581
  } as? ImageView
@@ -511,13 +583,22 @@ class MorphCardSourceView(context: Context) : ReactViewGroup(context) {
511
583
  val bmp = (blockerImg.drawable as? BitmapDrawable)?.bitmap
512
584
  if (bmp != null) {
513
585
  val clearCanvas = Canvas(bmp)
514
- val clearPaint = Paint(Paint.ANTI_ALIAS_FLAG)
515
- clearPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
516
- if (cardCornerRadiusPx > 0f) {
517
- clearCanvas.drawRoundRect(cardLeft, cardTop, cardLeft + cardWidth, cardTop + cardHeight, cardCornerRadiusPx, cardCornerRadiusPx, clearPaint)
586
+ // Sample the background color from a pixel just outside the card area
587
+ val sampleX = max(0, cardLeft.toInt() - 1)
588
+ val sampleY = min(bmp.height - 1, (cardTop + cardHeight / 2).toInt())
589
+ val bgColor = if (sampleX >= 0 && sampleX < bmp.width && sampleY >= 0 && sampleY < bmp.height) {
590
+ bmp.getPixel(sampleX, sampleY)
518
591
  } else {
519
- clearCanvas.drawRect(cardLeft, cardTop, cardLeft + cardWidth, cardTop + cardHeight, clearPaint)
592
+ Color.WHITE
520
593
  }
594
+ // First clear the card area to transparent
595
+ val clearPaint = Paint(Paint.ANTI_ALIAS_FLAG)
596
+ clearPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
597
+ clearCanvas.drawRect(cardLeft, cardTop, cardLeft + cardWidth, cardTop + cardHeight, clearPaint)
598
+ // Then fill with the sampled background color (no transparent hole)
599
+ val fillPaint = Paint(Paint.ANTI_ALIAS_FLAG)
600
+ fillPaint.color = bgColor
601
+ clearCanvas.drawRect(cardLeft, cardTop, cardLeft + cardWidth, cardTop + cardHeight, fillPaint)
521
602
  blockerImg.invalidate()
522
603
  }
523
604
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-morph-card",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "Native card-to-modal morph transition for React Native. iOS App Store-style expand animation.",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",