react-native-morph-card 0.1.14 → 0.2.0
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.
|
@@ -114,55 +114,86 @@ class MorphCardSourceView(context: Context) : ReactViewGroup(context) {
|
|
|
114
114
|
val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
|
|
115
115
|
val canvas = Canvas(bitmap)
|
|
116
116
|
|
|
117
|
-
// Track
|
|
118
|
-
data class
|
|
119
|
-
val
|
|
117
|
+
// Track Fresco views to restore rounding after capture
|
|
118
|
+
data class FrescoState(val view: View, val hierarchy: Any, val roundingParams: Any)
|
|
119
|
+
val frescoStates = mutableListOf<FrescoState>()
|
|
120
|
+
// Track views whose clipToOutline was disabled
|
|
121
|
+
val clippedViews = mutableListOf<View>()
|
|
120
122
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
var savedRounding: Any? = null
|
|
124
|
-
|
|
125
|
-
Log.d(TAG, "captureSnapshot: visiting ${view.javaClass.name} clipToOutline=$hadClip size=${view.width}x${view.height}")
|
|
123
|
+
// Map of Fresco views to their hierarchy (for drawing drawable directly)
|
|
124
|
+
val frescoViews = mutableMapOf<View, Any>()
|
|
126
125
|
|
|
126
|
+
fun prepareView(view: View) {
|
|
127
127
|
// Disable outline clipping
|
|
128
|
-
if (
|
|
128
|
+
if (view.clipToOutline) {
|
|
129
|
+
clippedViews.add(view)
|
|
129
130
|
view.clipToOutline = false
|
|
130
131
|
}
|
|
131
132
|
|
|
132
|
-
//
|
|
133
|
-
// Uses reflection to avoid a compile-time dependency on Fresco.
|
|
133
|
+
// Detect Fresco DraweeView and disable rounding
|
|
134
134
|
try {
|
|
135
135
|
val getHierarchy = view.javaClass.getMethod("getHierarchy")
|
|
136
136
|
val hierarchy = getHierarchy.invoke(view)
|
|
137
137
|
if (hierarchy != null) {
|
|
138
|
-
Log.d(TAG, "captureSnapshot: found hierarchy ${hierarchy.javaClass.name}")
|
|
139
138
|
val getRounding = hierarchy.javaClass.getMethod("getRoundingParams")
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (savedRounding != null) {
|
|
139
|
+
val rounding = getRounding.invoke(hierarchy)
|
|
140
|
+
if (rounding != null) {
|
|
143
141
|
val roundingClass = Class.forName("com.facebook.drawee.generic.RoundingParams")
|
|
144
142
|
val setRounding = hierarchy.javaClass.getMethod("setRoundingParams", roundingClass)
|
|
145
143
|
setRounding.invoke(hierarchy, null)
|
|
146
|
-
|
|
144
|
+
frescoStates.add(FrescoState(view, hierarchy, rounding))
|
|
145
|
+
frescoViews[view] = hierarchy
|
|
146
|
+
Log.d(TAG, "captureSnapshot: disabled Fresco rounding on ${view.width}x${view.height}")
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
|
-
} catch (
|
|
150
|
-
Log.d(TAG, "captureSnapshot: reflection skip for ${view.javaClass.simpleName}: ${e.javaClass.simpleName}")
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
if (hadClip || savedRounding != null) {
|
|
154
|
-
savedStates.add(ViewState(view, hadClip, savedRounding))
|
|
155
|
-
}
|
|
149
|
+
} catch (_: Exception) {}
|
|
156
150
|
|
|
157
151
|
if (view is ViewGroup) {
|
|
158
152
|
for (i in 0 until view.childCount) {
|
|
159
|
-
|
|
153
|
+
prepareView(view.getChildAt(i))
|
|
160
154
|
}
|
|
161
155
|
}
|
|
162
156
|
}
|
|
163
157
|
|
|
164
158
|
for (i in 0 until childCount) {
|
|
165
|
-
|
|
159
|
+
prepareView(getChildAt(i))
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Draw children. For Fresco views, draw the top-level drawable directly
|
|
163
|
+
// (since the internal drawable is rebuilt when roundingParams changes).
|
|
164
|
+
fun drawView(view: View, c: Canvas) {
|
|
165
|
+
val hierarchy = frescoViews[view]
|
|
166
|
+
if (hierarchy != null) {
|
|
167
|
+
try {
|
|
168
|
+
val getTopDrawable = hierarchy.javaClass.getMethod("getTopLevelDrawable")
|
|
169
|
+
val drawable = getTopDrawable.invoke(hierarchy) as? android.graphics.drawable.Drawable
|
|
170
|
+
if (drawable != null) {
|
|
171
|
+
drawable.setBounds(0, 0, view.width, view.height)
|
|
172
|
+
drawable.invalidateSelf()
|
|
173
|
+
drawable.draw(c)
|
|
174
|
+
Log.d(TAG, "captureSnapshot: drew Fresco drawable directly ${view.width}x${view.height}")
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
} catch (_: Exception) {}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (view is ViewGroup) {
|
|
181
|
+
// Draw background
|
|
182
|
+
view.background?.let { bg ->
|
|
183
|
+
bg.setBounds(0, 0, view.width, view.height)
|
|
184
|
+
bg.draw(c)
|
|
185
|
+
}
|
|
186
|
+
for (i in 0 until view.childCount) {
|
|
187
|
+
val child = view.getChildAt(i)
|
|
188
|
+
if (child.visibility != VISIBLE) continue
|
|
189
|
+
c.save()
|
|
190
|
+
c.translate(child.left.toFloat(), child.top.toFloat())
|
|
191
|
+
drawView(child, c)
|
|
192
|
+
c.restore()
|
|
193
|
+
}
|
|
194
|
+
} else {
|
|
195
|
+
view.draw(c)
|
|
196
|
+
}
|
|
166
197
|
}
|
|
167
198
|
|
|
168
199
|
for (i in 0 until childCount) {
|
|
@@ -170,26 +201,21 @@ class MorphCardSourceView(context: Context) : ReactViewGroup(context) {
|
|
|
170
201
|
if (child.visibility != VISIBLE) continue
|
|
171
202
|
canvas.save()
|
|
172
203
|
canvas.translate(child.left.toFloat(), child.top.toFloat())
|
|
173
|
-
child
|
|
204
|
+
drawView(child, canvas)
|
|
174
205
|
canvas.restore()
|
|
175
206
|
}
|
|
176
207
|
|
|
177
|
-
// Restore
|
|
178
|
-
for (
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
val setRounding = hierarchy.javaClass.getMethod("setRoundingParams", roundingClass)
|
|
189
|
-
setRounding.invoke(hierarchy, state.roundingParams)
|
|
190
|
-
}
|
|
191
|
-
} catch (_: Exception) {}
|
|
192
|
-
}
|
|
208
|
+
// Restore clipToOutline
|
|
209
|
+
for (view in clippedViews) {
|
|
210
|
+
view.clipToOutline = true
|
|
211
|
+
}
|
|
212
|
+
// Restore Fresco rounding params
|
|
213
|
+
for (state in frescoStates) {
|
|
214
|
+
try {
|
|
215
|
+
val roundingClass = Class.forName("com.facebook.drawee.generic.RoundingParams")
|
|
216
|
+
val setRounding = state.hierarchy.javaClass.getMethod("setRoundingParams", roundingClass)
|
|
217
|
+
setRounding.invoke(state.hierarchy, state.roundingParams)
|
|
218
|
+
} catch (_: Exception) {}
|
|
193
219
|
}
|
|
194
220
|
|
|
195
221
|
return bitmap
|
|
@@ -712,7 +738,7 @@ class MorphCardSourceView(context: Context) : ReactViewGroup(context) {
|
|
|
712
738
|
this@MorphCardSourceView.alpha = 1f
|
|
713
739
|
|
|
714
740
|
transferSnapshotToTarget(decorView, wrapper, targetView,
|
|
715
|
-
targetWidthPx, targetHeightPx, targetCornerRadiusPx
|
|
741
|
+
targetWidthPx, targetHeightPx, targetCornerRadiusPx)
|
|
716
742
|
|
|
717
743
|
promise.resolve(true)
|
|
718
744
|
}
|
|
@@ -733,8 +759,7 @@ class MorphCardSourceView(context: Context) : ReactViewGroup(context) {
|
|
|
733
759
|
targetView: View?,
|
|
734
760
|
targetWidthPx: Float,
|
|
735
761
|
targetHeightPx: Float,
|
|
736
|
-
cornerRadius: Float
|
|
737
|
-
fadeDuration: Long = 100
|
|
762
|
+
cornerRadius: Float
|
|
738
763
|
) {
|
|
739
764
|
val target = targetView as? MorphCardTargetView
|
|
740
765
|
if (target == null) {
|
|
@@ -777,19 +802,9 @@ class MorphCardSourceView(context: Context) : ReactViewGroup(context) {
|
|
|
777
802
|
Log.d(TAG, "transferSnapshot: handed snapshot to MorphCardTargetView")
|
|
778
803
|
}
|
|
779
804
|
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
overlay.alpha = anim.animatedValue as Float
|
|
784
|
-
}
|
|
785
|
-
fadeOut.addListener(object : android.animation.AnimatorListenerAdapter() {
|
|
786
|
-
override fun onAnimationEnd(animation: android.animation.Animator) {
|
|
787
|
-
decorView.removeView(overlay)
|
|
788
|
-
overlayContainer = null
|
|
789
|
-
Log.d(TAG, "transferSnapshot: overlay fade-out complete")
|
|
790
|
-
}
|
|
791
|
-
})
|
|
792
|
-
fadeOut.start()
|
|
805
|
+
decorView.removeView(overlay)
|
|
806
|
+
overlayContainer = null
|
|
807
|
+
Log.d(TAG, "transferSnapshot: overlay removed")
|
|
793
808
|
}
|
|
794
809
|
|
|
795
810
|
// ══════════════════════════════════════════════════════════════
|
|
@@ -128,25 +128,54 @@ static CGRect imageFrameForScaleMode(UIViewContentMode mode,
|
|
|
128
128
|
#pragma mark - Snapshot helper
|
|
129
129
|
|
|
130
130
|
- (UIImage *)captureSnapshot {
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
|
|
131
|
+
// Temporarily disable clipsToBounds + cornerRadius on all descendants so we
|
|
132
|
+
// capture full rectangular content (like iOS does for the source layer itself).
|
|
133
|
+
// This mirrors the Android fix that disables Fresco's RoundingParams.
|
|
134
|
+
NSMutableArray<NSDictionary *> *clippedViews = [NSMutableArray array];
|
|
135
|
+
__block void (^prepareView)(UIView *);
|
|
136
|
+
prepareView = ^(UIView *view) {
|
|
137
|
+
if (view.clipsToBounds && view.layer.cornerRadius > 0) {
|
|
138
|
+
[clippedViews addObject:@{
|
|
139
|
+
@"view": view,
|
|
140
|
+
@"radius": @(view.layer.cornerRadius)
|
|
141
|
+
}];
|
|
142
|
+
view.layer.cornerRadius = 0;
|
|
143
|
+
view.clipsToBounds = NO;
|
|
144
|
+
}
|
|
145
|
+
for (UIView *child in view.subviews) {
|
|
146
|
+
prepareView(child);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
for (UIView *child in self.subviews) {
|
|
150
|
+
prepareView(child);
|
|
151
|
+
}
|
|
152
|
+
|
|
135
153
|
UIGraphicsImageRendererFormat *format =
|
|
136
154
|
[UIGraphicsImageRendererFormat defaultFormat];
|
|
137
155
|
format.opaque = NO;
|
|
138
156
|
CGSize size = self.bounds.size;
|
|
139
157
|
UIGraphicsImageRenderer *renderer =
|
|
140
158
|
[[UIGraphicsImageRenderer alloc] initWithSize:size format:format];
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
159
|
+
UIImage *image =
|
|
160
|
+
[renderer imageWithActions:^(UIGraphicsImageRendererContext *ctx) {
|
|
161
|
+
for (UIView *child in self.subviews) {
|
|
162
|
+
CGContextSaveGState(ctx.CGContext);
|
|
163
|
+
CGContextTranslateCTM(ctx.CGContext, child.frame.origin.x,
|
|
164
|
+
child.frame.origin.y);
|
|
165
|
+
[child drawViewHierarchyInRect:(CGRect){CGPointZero, child.frame.size}
|
|
166
|
+
afterScreenUpdates:NO];
|
|
167
|
+
CGContextRestoreGState(ctx.CGContext);
|
|
168
|
+
}
|
|
169
|
+
}];
|
|
170
|
+
|
|
171
|
+
// Restore clipping
|
|
172
|
+
for (NSDictionary *entry in clippedViews) {
|
|
173
|
+
UIView *view = entry[@"view"];
|
|
174
|
+
view.layer.cornerRadius = [entry[@"radius"] floatValue];
|
|
175
|
+
view.clipsToBounds = YES;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return image;
|
|
150
179
|
}
|
|
151
180
|
|
|
152
181
|
#pragma mark - Expand
|
|
@@ -258,9 +287,7 @@ static CGRect imageFrameForScaleMode(UIViewContentMode mode,
|
|
|
258
287
|
UIView *ts = self->_targetScreenContainer;
|
|
259
288
|
if (ts) {
|
|
260
289
|
[UIView animateWithDuration:dur * 0.5
|
|
261
|
-
animations:^{
|
|
262
|
-
ts.alpha = 1;
|
|
263
|
-
}
|
|
290
|
+
animations:^{ ts.alpha = 1; }
|
|
264
291
|
completion:nil];
|
|
265
292
|
}
|
|
266
293
|
});
|
|
@@ -284,14 +311,8 @@ static CGRect imageFrameForScaleMode(UIViewContentMode mode,
|
|
|
284
311
|
self.alpha = 1;
|
|
285
312
|
UIView *ts = self->_targetScreenContainer;
|
|
286
313
|
if (ts) { ts.alpha = 1; }
|
|
287
|
-
[
|
|
288
|
-
|
|
289
|
-
wrapper.alpha = 0;
|
|
290
|
-
}
|
|
291
|
-
completion:^(BOOL finished) {
|
|
292
|
-
[wrapper removeFromSuperview];
|
|
293
|
-
self->_wrapperView = nil;
|
|
294
|
-
}];
|
|
314
|
+
[wrapper removeFromSuperview];
|
|
315
|
+
self->_wrapperView = nil;
|
|
295
316
|
resolve(@(YES));
|
|
296
317
|
}];
|
|
297
318
|
|
|
@@ -345,9 +366,7 @@ static CGRect imageFrameForScaleMode(UIViewContentMode mode,
|
|
|
345
366
|
UIView *ts = self->_targetScreenContainer;
|
|
346
367
|
if (ts) {
|
|
347
368
|
[UIView animateWithDuration:dur * 0.5
|
|
348
|
-
animations:^{
|
|
349
|
-
ts.alpha = 1;
|
|
350
|
-
}
|
|
369
|
+
animations:^{ ts.alpha = 1; }
|
|
351
370
|
completion:nil];
|
|
352
371
|
}
|
|
353
372
|
});
|
|
@@ -365,15 +384,9 @@ static CGRect imageFrameForScaleMode(UIViewContentMode mode,
|
|
|
365
384
|
self.alpha = 1;
|
|
366
385
|
UIView *ts = self->_targetScreenContainer;
|
|
367
386
|
if (ts) { ts.alpha = 1; }
|
|
368
|
-
[
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
}
|
|
372
|
-
completion:^(BOOL finished) {
|
|
373
|
-
[container removeFromSuperview];
|
|
374
|
-
self->_wrapperView = nil;
|
|
375
|
-
self->_snapshot = nil;
|
|
376
|
-
}];
|
|
387
|
+
[container removeFromSuperview];
|
|
388
|
+
self->_wrapperView = nil;
|
|
389
|
+
self->_snapshot = nil;
|
|
377
390
|
resolve(@(YES));
|
|
378
391
|
}];
|
|
379
392
|
|
package/package.json
CHANGED