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
|
-
//
|
|
506
|
-
//
|
|
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
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
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
|
-
|
|
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.
|
|
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",
|