zkit-ui 2.0.3 → 2.0.5

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.
Files changed (63) hide show
  1. package/README.md +56 -0
  2. package/android/build.gradle +100 -0
  3. package/android/src/main/AndroidManifest.xml +1 -0
  4. package/android/src/main/java/cn/fontree/zkit/ui/preview/GaleriaModule.kt +43 -0
  5. package/android/src/main/java/cn/fontree/zkit/ui/preview/GaleriaView.kt +305 -0
  6. package/android/src/main/java/cn/fontree/zkit/ui/preview/GalleryLayoutSupport.java +450 -0
  7. package/android/src/main/java/cn/fontree/zkit/ui/preview/GalleryMediaItem.java +36 -0
  8. package/android/src/main/java/cn/fontree/zkit/ui/preview/GalleryPagerAdapter.java +140 -0
  9. package/android/src/main/java/cn/fontree/zkit/ui/preview/GallerySharedElementGeometry.java +25 -0
  10. package/android/src/main/java/cn/fontree/zkit/ui/preview/GallerySharedElementNames.java +39 -0
  11. package/android/src/main/java/cn/fontree/zkit/ui/preview/GallerySharedElementState.java +25 -0
  12. package/android/src/main/java/cn/fontree/zkit/ui/preview/GallerySourceViewRegistry.java +115 -0
  13. package/android/src/main/java/cn/fontree/zkit/ui/preview/GalleryTransitionSnapshotView.java +60 -0
  14. package/android/src/main/java/cn/fontree/zkit/ui/preview/GalleryVideoPlayerView.java +338 -0
  15. package/android/src/main/java/cn/fontree/zkit/ui/preview/GalleryViewerOverlayView.java +636 -0
  16. package/android/src/main/java/cn/fontree/zkit/ui/preview/GalleryViewerPageView.java +357 -0
  17. package/android/src/main/java/cn/fontree/zkit/ui/preview/GalleryViewerTransitionController.java +223 -0
  18. package/android/src/main/res/layout/galeria_player_view.xml +9 -0
  19. package/dist/all.d.ts +2 -0
  20. package/dist/all.d.ts.map +1 -1
  21. package/dist/all.js +24 -22
  22. package/dist/ui/NativeImagePreview/NativeImagePreview.d.ts +3 -0
  23. package/dist/ui/NativeImagePreview/NativeImagePreview.d.ts.map +1 -0
  24. package/dist/ui/NativeImagePreview/NativeImagePreview.js +99 -0
  25. package/dist/ui/NativeImagePreview/NativeImagePreview.web.d.ts +3 -0
  26. package/dist/ui/NativeImagePreview/NativeImagePreview.web.d.ts.map +1 -0
  27. package/dist/ui/NativeImagePreview/NativeImagePreview.web.js +56 -0
  28. package/dist/ui/NativeImagePreview/context.d.ts +14 -0
  29. package/dist/ui/NativeImagePreview/context.d.ts.map +1 -0
  30. package/dist/ui/NativeImagePreview/context.js +38 -0
  31. package/dist/ui/NativeImagePreview/index.d.ts +3 -0
  32. package/dist/ui/NativeImagePreview/index.d.ts.map +1 -0
  33. package/dist/ui/NativeImagePreview/index.js +5 -0
  34. package/dist/ui/NativeImagePreview/types.d.ts +66 -0
  35. package/dist/ui/NativeImagePreview/types.d.ts.map +1 -0
  36. package/dist/ui/NativeImagePreview/types.js +2 -0
  37. package/dist/ui/NativeImagePreview/utils.d.ts +8 -0
  38. package/dist/ui/NativeImagePreview/utils.d.ts.map +1 -0
  39. package/dist/ui/NativeImagePreview/utils.js +82 -0
  40. package/expo-module.config.json +11 -0
  41. package/ios/GaleriaModule.swift +46 -0
  42. package/ios/GaleriaView.swift +253 -0
  43. package/ios/GalleryViewer/GaleriaImageLoader.swift +73 -0
  44. package/ios/GalleryViewer/GaleriaImageViewInteraction.swift +245 -0
  45. package/ios/GalleryViewer/GaleriaLayoutSupport.swift +143 -0
  46. package/ios/GalleryViewer/GaleriaMedia.swift +28 -0
  47. package/ios/GalleryViewer/GaleriaSourceViewRegistry.swift +80 -0
  48. package/ios/GalleryViewer/GaleriaVideoPlayerView.swift +437 -0
  49. package/ios/GalleryViewer/GaleriaViewerConfiguration.swift +51 -0
  50. package/ios/GalleryViewer/GaleriaViewerController.swift +745 -0
  51. package/ios/GalleryViewer/GaleriaViewerPageView.swift +623 -0
  52. package/ios/GalleryViewer/GaleriaViewerTransitionCoordinator.swift +312 -0
  53. package/package.json +21 -13
  54. package/src/all.ts +18 -0
  55. package/src/services/ImagePreviewService/README.md +19 -0
  56. package/src/ui/NativeImagePreview/NativeImagePreview.tsx +146 -0
  57. package/src/ui/NativeImagePreview/NativeImagePreview.web.tsx +37 -0
  58. package/src/ui/NativeImagePreview/README.md +111 -0
  59. package/src/ui/NativeImagePreview/context.tsx +22 -0
  60. package/src/ui/NativeImagePreview/index.tsx +18 -0
  61. package/src/ui/NativeImagePreview/types.ts +92 -0
  62. package/src/ui/NativeImagePreview/utils.ts +107 -0
  63. package/zkit-ui.podspec +13 -1
@@ -0,0 +1,450 @@
1
+ package cn.fontree.zkit.ui.preview;
2
+
3
+ import android.graphics.Bitmap;
4
+ import android.graphics.Canvas;
5
+ import android.graphics.Matrix;
6
+ import android.graphics.Rect;
7
+ import android.graphics.RectF;
8
+ import android.graphics.drawable.BitmapDrawable;
9
+ import android.graphics.drawable.Drawable;
10
+ import android.view.View;
11
+ import android.widget.ImageView;
12
+
13
+ import androidx.annotation.NonNull;
14
+ import androidx.annotation.Nullable;
15
+
16
+ import com.github.chrisbanes.photoview.PhotoView;
17
+
18
+ public final class GalleryLayoutSupport {
19
+ private static final int MAX_TRANSITION_BITMAP_SIDE = 2048;
20
+
21
+ private GalleryLayoutSupport() {
22
+ }
23
+
24
+ @Nullable
25
+ public static GallerySharedElementState captureImageViewState(@Nullable ImageView imageView) {
26
+ return captureImageViewState(imageView, null);
27
+ }
28
+
29
+ @Nullable
30
+ public static GallerySharedElementState captureImageViewState(@Nullable ImageView imageView, @Nullable RectF clippingFrameInWindow) {
31
+ if (imageView == null) {
32
+ return null;
33
+ }
34
+ Drawable drawable = imageView.getDrawable();
35
+ if (drawable == null || imageView.getWidth() <= 0 || imageView.getHeight() <= 0) {
36
+ return null;
37
+ }
38
+
39
+ RectF imageBoundsInWindow = viewBoundsOnScreen(imageView);
40
+ RectF clippingFrame = clippingFrameInWindow != null
41
+ ? new RectF(clippingFrameInWindow)
42
+ : visibleBoundsOnScreen(imageView);
43
+ if (clippingFrame.isEmpty()) {
44
+ clippingFrame = new RectF(imageBoundsInWindow);
45
+ }
46
+
47
+ RectF contentFrameInView = resolveDisplayedContentFrame(imageView, drawable);
48
+ if (contentFrameInView == null || contentFrameInView.isEmpty()) {
49
+ return null;
50
+ }
51
+
52
+ Drawable transitionDrawable = transitionDrawableForImageView(imageView);
53
+ if (transitionDrawable == null) {
54
+ return null;
55
+ }
56
+
57
+ RectF contentFrameInWindow = new RectF(contentFrameInView);
58
+ int[] location = new int[2];
59
+ imageView.getLocationOnScreen(location);
60
+ contentFrameInWindow.offset(location[0], location[1]);
61
+
62
+ RectF visibleFrameInWindow = new RectF();
63
+ boolean intersects = visibleFrameInWindow.setIntersect(clippingFrame, contentFrameInWindow);
64
+ if (!intersects || visibleFrameInWindow.isEmpty()) {
65
+ visibleFrameInWindow = new RectF(clippingFrame);
66
+ }
67
+
68
+ RectF contentFrameInVisibleBounds = new RectF(
69
+ contentFrameInWindow.left - visibleFrameInWindow.left,
70
+ contentFrameInWindow.top - visibleFrameInWindow.top,
71
+ contentFrameInWindow.right - visibleFrameInWindow.left,
72
+ contentFrameInWindow.bottom - visibleFrameInWindow.top
73
+ );
74
+
75
+ return new GallerySharedElementState(
76
+ transitionDrawable,
77
+ new GallerySharedElementGeometry(visibleFrameInWindow, contentFrameInVisibleBounds)
78
+ );
79
+ }
80
+
81
+ @Nullable
82
+ public static GallerySharedElementGeometry captureImageViewGeometry(@Nullable ImageView imageView, @Nullable Drawable geometryDrawable) {
83
+ if (imageView == null || imageView.getWidth() <= 0 || imageView.getHeight() <= 0) {
84
+ return null;
85
+ }
86
+
87
+ RectF imageBoundsInWindow = viewBoundsOnScreen(imageView);
88
+ RectF clippingFrame = visibleBoundsOnScreen(imageView);
89
+ if (clippingFrame.isEmpty()) {
90
+ clippingFrame = new RectF(imageBoundsInWindow);
91
+ }
92
+
93
+ RectF contentFrameInView = null;
94
+ Drawable drawable = geometryDrawable != null ? geometryDrawable : imageView.getDrawable();
95
+ if (drawable != null) {
96
+ contentFrameInView = resolveDisplayedContentFrame(imageView, drawable);
97
+ }
98
+ if (contentFrameInView == null || contentFrameInView.isEmpty()) {
99
+ contentFrameInView = new RectF(0f, 0f, imageView.getWidth(), imageView.getHeight());
100
+ }
101
+
102
+ RectF contentFrameInWindow = new RectF(contentFrameInView);
103
+ int[] location = new int[2];
104
+ imageView.getLocationOnScreen(location);
105
+ contentFrameInWindow.offset(location[0], location[1]);
106
+
107
+ RectF visibleFrameInWindow = new RectF();
108
+ boolean intersects = visibleFrameInWindow.setIntersect(clippingFrame, contentFrameInWindow);
109
+ if (!intersects || visibleFrameInWindow.isEmpty()) {
110
+ visibleFrameInWindow = new RectF(clippingFrame);
111
+ }
112
+
113
+ RectF contentFrameInVisibleBounds = new RectF(
114
+ contentFrameInWindow.left - visibleFrameInWindow.left,
115
+ contentFrameInWindow.top - visibleFrameInWindow.top,
116
+ contentFrameInWindow.right - visibleFrameInWindow.left,
117
+ contentFrameInWindow.bottom - visibleFrameInWindow.top
118
+ );
119
+
120
+ return new GallerySharedElementGeometry(visibleFrameInWindow, contentFrameInVisibleBounds);
121
+ }
122
+
123
+ @NonNull
124
+ public static RectF viewBoundsOnScreen(@NonNull View view) {
125
+ int[] location = new int[2];
126
+ view.getLocationOnScreen(location);
127
+ return new RectF(
128
+ location[0],
129
+ location[1],
130
+ location[0] + view.getWidth(),
131
+ location[1] + view.getHeight()
132
+ );
133
+ }
134
+
135
+ @NonNull
136
+ public static RectF visibleBoundsOnScreen(@NonNull View view) {
137
+ Rect localVisibleRect = new Rect();
138
+ if (!view.getLocalVisibleRect(localVisibleRect) || localVisibleRect.isEmpty()) {
139
+ return viewBoundsOnScreen(view);
140
+ }
141
+
142
+ int[] location = new int[2];
143
+ view.getLocationOnScreen(location);
144
+ return new RectF(
145
+ location[0] + localVisibleRect.left,
146
+ location[1] + localVisibleRect.top,
147
+ location[0] + localVisibleRect.right,
148
+ location[1] + localVisibleRect.bottom
149
+ );
150
+ }
151
+
152
+ @Nullable
153
+ public static Drawable cloneDrawable(@Nullable Drawable drawable, @NonNull View view) {
154
+ if (drawable == null) {
155
+ return null;
156
+ }
157
+ Drawable.ConstantState constantState = drawable.getConstantState();
158
+ if (constantState == null) {
159
+ return drawable.mutate();
160
+ }
161
+ return constantState.newDrawable(view.getResources()).mutate();
162
+ }
163
+
164
+ @Nullable
165
+ public static Drawable transitionDrawableForImageView(@Nullable ImageView imageView) {
166
+ if (imageView == null) {
167
+ return null;
168
+ }
169
+ Drawable draweeActualDrawable = resolveDraweeActualDrawable(imageView);
170
+ if (draweeActualDrawable != null) {
171
+ Drawable clonedActualDrawable = cloneDrawableFromConstantState(draweeActualDrawable, imageView);
172
+ if (clonedActualDrawable != null) {
173
+ return clonedActualDrawable;
174
+ }
175
+ Drawable bitmapCopy = bitmapDrawableCopy(draweeActualDrawable, imageView);
176
+ if (bitmapCopy != null) {
177
+ return bitmapCopy;
178
+ }
179
+ }
180
+ if (isDraweeRootDrawable(imageView.getDrawable())) {
181
+ return null;
182
+ }
183
+ return cloneDrawable(imageView.getDrawable(), imageView);
184
+ }
185
+
186
+ @Nullable
187
+ private static Drawable cloneDrawableFromConstantState(@Nullable Drawable drawable, @NonNull View view) {
188
+ if (drawable == null) {
189
+ return null;
190
+ }
191
+ Drawable.ConstantState constantState = drawable.getConstantState();
192
+ if (constantState == null) {
193
+ return null;
194
+ }
195
+ return constantState.newDrawable(view.getResources()).mutate();
196
+ }
197
+
198
+ @Nullable
199
+ private static Drawable bitmapDrawableCopy(@NonNull Drawable drawable, @NonNull View view) {
200
+ int intrinsicWidth = drawable.getIntrinsicWidth();
201
+ int intrinsicHeight = drawable.getIntrinsicHeight();
202
+ if (intrinsicWidth <= 0 || intrinsicHeight <= 0) {
203
+ Rect bounds = drawable.getBounds();
204
+ intrinsicWidth = bounds.width();
205
+ intrinsicHeight = bounds.height();
206
+ }
207
+ if (intrinsicWidth <= 0 || intrinsicHeight <= 0) {
208
+ return null;
209
+ }
210
+
211
+ float scale = Math.min(
212
+ 1f,
213
+ MAX_TRANSITION_BITMAP_SIDE / Math.max((float) intrinsicWidth, (float) intrinsicHeight)
214
+ );
215
+ int bitmapWidth = Math.max(1, Math.round(intrinsicWidth * scale));
216
+ int bitmapHeight = Math.max(1, Math.round(intrinsicHeight * scale));
217
+
218
+ Bitmap bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
219
+ Canvas canvas = new Canvas(bitmap);
220
+ Rect oldBounds = new Rect(drawable.getBounds());
221
+ drawable.setBounds(0, 0, bitmapWidth, bitmapHeight);
222
+ drawable.draw(canvas);
223
+ drawable.setBounds(oldBounds);
224
+ return new BitmapDrawable(view.getResources(), bitmap);
225
+ }
226
+
227
+ @NonNull
228
+ public static GallerySharedElementGeometry defaultGeometryFor(@NonNull Drawable drawable, @NonNull RectF containerBounds) {
229
+ RectF visibleFrame = aspectFitRect(drawable, containerBounds);
230
+ return new GallerySharedElementGeometry(
231
+ visibleFrame,
232
+ new RectF(0f, 0f, visibleFrame.width(), visibleFrame.height())
233
+ );
234
+ }
235
+
236
+ @NonNull
237
+ public static GallerySharedElementGeometry defaultGeometryForAspectRatio(float width, float height, @NonNull RectF containerBounds) {
238
+ float safeWidth = Math.max(1f, width);
239
+ float safeHeight = Math.max(1f, height);
240
+ float widthScale = containerBounds.width() / safeWidth;
241
+ float heightScale = containerBounds.height() / safeHeight;
242
+ float scale = Math.min(widthScale, heightScale);
243
+ float targetWidth = safeWidth * scale;
244
+ float targetHeight = safeHeight * scale;
245
+ float left = containerBounds.left + (containerBounds.width() - targetWidth) * 0.5f;
246
+ float top = containerBounds.top + (containerBounds.height() - targetHeight) * 0.5f;
247
+ RectF visibleFrame = new RectF(left, top, left + targetWidth, top + targetHeight);
248
+ return new GallerySharedElementGeometry(
249
+ visibleFrame,
250
+ new RectF(0f, 0f, visibleFrame.width(), visibleFrame.height())
251
+ );
252
+ }
253
+
254
+ @NonNull
255
+ public static RectF aspectFitRect(@NonNull Drawable drawable, @NonNull RectF containerBounds) {
256
+ int intrinsicWidth = Math.max(1, drawable.getIntrinsicWidth());
257
+ int intrinsicHeight = Math.max(1, drawable.getIntrinsicHeight());
258
+ float widthScale = containerBounds.width() / intrinsicWidth;
259
+ float heightScale = containerBounds.height() / intrinsicHeight;
260
+ float scale = Math.min(widthScale, heightScale);
261
+ float width = intrinsicWidth * scale;
262
+ float height = intrinsicHeight * scale;
263
+ float left = containerBounds.left + (containerBounds.width() - width) * 0.5f;
264
+ float top = containerBounds.top + (containerBounds.height() - height) * 0.5f;
265
+ return new RectF(left, top, left + width, top + height);
266
+ }
267
+
268
+ @Nullable
269
+ private static RectF resolveDisplayedContentFrame(@NonNull ImageView imageView, @NonNull Drawable drawable) {
270
+ RectF viewBounds = new RectF(0f, 0f, imageView.getWidth(), imageView.getHeight());
271
+ if (viewBounds.isEmpty()) {
272
+ return null;
273
+ }
274
+
275
+ if (imageView instanceof PhotoView) {
276
+ RectF displayRect = ((PhotoView) imageView).getDisplayRect();
277
+ if (displayRect != null && !displayRect.isEmpty()) {
278
+ return new RectF(displayRect);
279
+ }
280
+ }
281
+
282
+ RectF draweeContentFrame = resolveDraweeActualContentFrame(imageView);
283
+ if (draweeContentFrame != null && !draweeContentFrame.isEmpty()) {
284
+ return draweeContentFrame;
285
+ }
286
+
287
+ int intrinsicWidth = drawable.getIntrinsicWidth();
288
+ int intrinsicHeight = drawable.getIntrinsicHeight();
289
+ if (intrinsicWidth <= 0 || intrinsicHeight <= 0) {
290
+ return new RectF(viewBounds);
291
+ }
292
+
293
+ ImageView.ScaleType scaleType = imageView.getScaleType();
294
+ if (scaleType == ImageView.ScaleType.MATRIX) {
295
+ RectF contentFrameInView = new RectF(0f, 0f, intrinsicWidth, intrinsicHeight);
296
+ Matrix imageMatrix = new Matrix(imageView.getImageMatrix());
297
+ imageMatrix.mapRect(contentFrameInView);
298
+ return contentFrameInView;
299
+ }
300
+
301
+ if (scaleType == ImageView.ScaleType.FIT_XY) {
302
+ return new RectF(viewBounds);
303
+ }
304
+
305
+ float widthScale = viewBounds.width() / intrinsicWidth;
306
+ float heightScale = viewBounds.height() / intrinsicHeight;
307
+ float scale;
308
+ float dx = 0f;
309
+ float dy = 0f;
310
+
311
+ switch (scaleType) {
312
+ case CENTER:
313
+ scale = 1f;
314
+ dx = (viewBounds.width() - intrinsicWidth) * 0.5f;
315
+ dy = (viewBounds.height() - intrinsicHeight) * 0.5f;
316
+ break;
317
+ case CENTER_CROP:
318
+ scale = Math.max(widthScale, heightScale);
319
+ dx = (viewBounds.width() - intrinsicWidth * scale) * 0.5f;
320
+ dy = (viewBounds.height() - intrinsicHeight * scale) * 0.5f;
321
+ break;
322
+ case CENTER_INSIDE:
323
+ scale = Math.min(1f, Math.min(widthScale, heightScale));
324
+ dx = (viewBounds.width() - intrinsicWidth * scale) * 0.5f;
325
+ dy = (viewBounds.height() - intrinsicHeight * scale) * 0.5f;
326
+ break;
327
+ case FIT_START:
328
+ scale = Math.min(widthScale, heightScale);
329
+ break;
330
+ case FIT_END:
331
+ scale = Math.min(widthScale, heightScale);
332
+ dx = viewBounds.width() - intrinsicWidth * scale;
333
+ dy = viewBounds.height() - intrinsicHeight * scale;
334
+ break;
335
+ case FIT_CENTER:
336
+ default:
337
+ scale = Math.min(widthScale, heightScale);
338
+ dx = (viewBounds.width() - intrinsicWidth * scale) * 0.5f;
339
+ dy = (viewBounds.height() - intrinsicHeight * scale) * 0.5f;
340
+ break;
341
+ }
342
+
343
+ float width = intrinsicWidth * scale;
344
+ float height = intrinsicHeight * scale;
345
+ return new RectF(dx, dy, dx + width, dy + height);
346
+ }
347
+
348
+ @Nullable
349
+ private static RectF resolveDraweeActualContentFrame(@NonNull ImageView imageView) {
350
+ try {
351
+ Object hierarchy = imageView.getClass().getMethod("getHierarchy").invoke(imageView);
352
+ if (hierarchy == null) {
353
+ return null;
354
+ }
355
+ RectF bounds = new RectF();
356
+ hierarchy.getClass().getMethod("getActualImageBounds", RectF.class).invoke(hierarchy, bounds);
357
+ if (bounds.width() <= 1f || bounds.height() <= 1f) {
358
+ return null;
359
+ }
360
+ return bounds;
361
+ } catch (Exception ignored) {
362
+ return null;
363
+ }
364
+ }
365
+
366
+ @Nullable
367
+ private static Drawable resolveDraweeActualDrawable(@NonNull ImageView imageView) {
368
+ try {
369
+ Object hierarchy = imageView.getClass().getMethod("getHierarchy").invoke(imageView);
370
+ if (hierarchy == null) {
371
+ return null;
372
+ }
373
+ Object hasImage = hierarchy.getClass().getMethod("hasImage").invoke(hierarchy);
374
+ if (!(hasImage instanceof Boolean) || !((Boolean) hasImage)) {
375
+ return null;
376
+ }
377
+ java.lang.reflect.Field actualImageWrapperField = hierarchy.getClass().getDeclaredField("mActualImageWrapper");
378
+ actualImageWrapperField.setAccessible(true);
379
+ Object actualImageWrapper = actualImageWrapperField.get(hierarchy);
380
+ if (actualImageWrapper == null) {
381
+ return null;
382
+ }
383
+ Object actualDrawable = actualImageWrapper.getClass().getMethod("getDrawable").invoke(actualImageWrapper);
384
+ if (actualDrawable instanceof Drawable) {
385
+ return (Drawable) actualDrawable;
386
+ }
387
+ } catch (Exception ignored) {
388
+ }
389
+
390
+ return findPositiveIntrinsicLeafDrawable(
391
+ imageView.getDrawable(),
392
+ new java.util.IdentityHashMap<>(),
393
+ 0
394
+ );
395
+ }
396
+
397
+ @Nullable
398
+ private static Drawable findPositiveIntrinsicLeafDrawable(
399
+ @Nullable Drawable drawable,
400
+ @NonNull java.util.IdentityHashMap<Drawable, Boolean> visited,
401
+ int depth
402
+ ) {
403
+ if (drawable == null || depth > 12 || visited.containsKey(drawable)) {
404
+ return null;
405
+ }
406
+ visited.put(drawable, true);
407
+
408
+ if (!isDraweeWrapperDrawable(drawable)
409
+ && drawable.getIntrinsicWidth() > 0
410
+ && drawable.getIntrinsicHeight() > 0) {
411
+ return drawable;
412
+ }
413
+
414
+ Drawable current = drawable.getCurrent();
415
+ if (current != null && current != drawable) {
416
+ Drawable found = findPositiveIntrinsicLeafDrawable(current, visited, depth + 1);
417
+ if (found != null) {
418
+ return found;
419
+ }
420
+ }
421
+
422
+ try {
423
+ Object count = drawable.getClass().getMethod("getNumberOfLayers").invoke(drawable);
424
+ if (count instanceof Integer) {
425
+ for (int i = ((Integer) count) - 1; i >= 0; i--) {
426
+ Object layer = drawable.getClass().getMethod("getDrawable", int.class).invoke(drawable, i);
427
+ if (layer instanceof Drawable) {
428
+ Drawable found = findPositiveIntrinsicLeafDrawable((Drawable) layer, visited, depth + 1);
429
+ if (found != null) {
430
+ return found;
431
+ }
432
+ }
433
+ }
434
+ }
435
+ } catch (Exception ignored) {
436
+ }
437
+
438
+ return null;
439
+ }
440
+
441
+ private static boolean isDraweeRootDrawable(@Nullable Drawable drawable) {
442
+ return drawable != null
443
+ && "com.facebook.drawee.generic.RootDrawable".equals(drawable.getClass().getName());
444
+ }
445
+
446
+ private static boolean isDraweeWrapperDrawable(@NonNull Drawable drawable) {
447
+ String className = drawable.getClass().getName();
448
+ return className.startsWith("com.facebook.drawee.");
449
+ }
450
+ }
@@ -0,0 +1,36 @@
1
+ package cn.fontree.zkit.ui.preview;
2
+
3
+ public class GalleryMediaItem {
4
+ public enum MediaType {
5
+ IMAGE,
6
+ VIDEO
7
+ }
8
+
9
+ private final String id;
10
+ private final MediaType mediaType;
11
+ private final String sourceUrl;
12
+ private final String thumbnailUrl;
13
+
14
+ public GalleryMediaItem(String id, MediaType mediaType, String sourceUrl, String thumbnailUrl) {
15
+ this.id = id;
16
+ this.mediaType = mediaType;
17
+ this.sourceUrl = sourceUrl;
18
+ this.thumbnailUrl = thumbnailUrl;
19
+ }
20
+
21
+ public String getId() {
22
+ return id;
23
+ }
24
+
25
+ public MediaType getMediaType() {
26
+ return mediaType;
27
+ }
28
+
29
+ public String getSourceUrl() {
30
+ return sourceUrl;
31
+ }
32
+
33
+ public String getThumbnailUrl() {
34
+ return thumbnailUrl;
35
+ }
36
+ }
@@ -0,0 +1,140 @@
1
+ package cn.fontree.zkit.ui.preview;
2
+
3
+ import android.view.ViewGroup;
4
+
5
+ import androidx.annotation.NonNull;
6
+ import androidx.annotation.Nullable;
7
+ import androidx.recyclerview.widget.RecyclerView;
8
+
9
+ import java.util.ArrayList;
10
+ import java.util.HashMap;
11
+ import java.util.List;
12
+ import java.util.Map;
13
+
14
+ public final class GalleryPagerAdapter extends RecyclerView.Adapter<GalleryPagerAdapter.PageViewHolder> {
15
+ public interface PageBindCallback {
16
+ void onPageBound(@NonNull GalleryViewerPageView pageView, int position);
17
+ }
18
+
19
+ private final List<GalleryMediaItem> items = new ArrayList<>();
20
+ private final Map<Integer, GalleryViewerPageView> attachedViews = new HashMap<>();
21
+ @Nullable
22
+ private final GalleryViewerPageView.Listener pageListener;
23
+ @Nullable
24
+ private final PageBindCallback pageBindCallback;
25
+ private int activeIndex = -1;
26
+
27
+ public GalleryPagerAdapter(
28
+ @Nullable GalleryViewerPageView.Listener pageListener,
29
+ @Nullable PageBindCallback pageBindCallback
30
+ ) {
31
+ this.pageListener = pageListener;
32
+ this.pageBindCallback = pageBindCallback;
33
+ }
34
+
35
+ public void setItems(@NonNull List<GalleryMediaItem> list) {
36
+ attachedViews.clear();
37
+ items.clear();
38
+ items.addAll(list);
39
+ notifyDataSetChanged();
40
+ }
41
+
42
+ public void setActiveIndex(int index) {
43
+ activeIndex = index;
44
+ for (Map.Entry<Integer, GalleryViewerPageView> entry : attachedViews.entrySet()) {
45
+ entry.getValue().setActive(entry.getKey() == index);
46
+ }
47
+ }
48
+
49
+ public void releaseAll() {
50
+ for (GalleryViewerPageView view : attachedViews.values()) {
51
+ view.release();
52
+ }
53
+ attachedViews.clear();
54
+ }
55
+
56
+ @Nullable
57
+ public GalleryViewerPageView findAttachedView(int position) {
58
+ GalleryViewerPageView pageView = attachedViews.get(position);
59
+ if (pageView == null) {
60
+ return null;
61
+ }
62
+ if (!pageView.isAttachedToWindow()) {
63
+ attachedViews.remove(position);
64
+ return null;
65
+ }
66
+ return pageView;
67
+ }
68
+
69
+ @Nullable
70
+ public GalleryViewerPageView findActiveView() {
71
+ return attachedViews.get(activeIndex);
72
+ }
73
+
74
+ @NonNull
75
+ @Override
76
+ public PageViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
77
+ GalleryViewerPageView pageView = new GalleryViewerPageView(parent.getContext());
78
+ pageView.setListener(pageListener);
79
+ return new PageViewHolder(pageView);
80
+ }
81
+
82
+ @Override
83
+ public void onBindViewHolder(@NonNull PageViewHolder holder, int position) {
84
+ holder.pageView.bind(items.get(position));
85
+ if (pageBindCallback != null) {
86
+ pageBindCallback.onPageBound(holder.pageView, position);
87
+ }
88
+ holder.pageView.setActive(position == activeIndex);
89
+ }
90
+
91
+ @Override
92
+ public void onViewAttachedToWindow(@NonNull PageViewHolder holder) {
93
+ super.onViewAttachedToWindow(holder);
94
+ removeAttachedViewKeys(holder.pageView);
95
+ int position = holder.getBindingAdapterPosition();
96
+ if (position == RecyclerView.NO_POSITION) {
97
+ return;
98
+ }
99
+ attachedViews.put(position, holder.pageView);
100
+ }
101
+
102
+ @Override
103
+ public void onViewDetachedFromWindow(@NonNull PageViewHolder holder) {
104
+ super.onViewDetachedFromWindow(holder);
105
+ removeAttachedViewKeys(holder.pageView);
106
+ }
107
+
108
+ @Override
109
+ public void onViewRecycled(@NonNull PageViewHolder holder) {
110
+ super.onViewRecycled(holder);
111
+ removeAttachedViewKeys(holder.pageView);
112
+ holder.pageView.release();
113
+ }
114
+
115
+ private void removeAttachedViewKeys(@NonNull GalleryViewerPageView pageView) {
116
+ List<Integer> keys = new ArrayList<>();
117
+ for (Map.Entry<Integer, GalleryViewerPageView> entry : attachedViews.entrySet()) {
118
+ if (entry.getValue() == pageView) {
119
+ keys.add(entry.getKey());
120
+ }
121
+ }
122
+ for (Integer key : keys) {
123
+ attachedViews.remove(key);
124
+ }
125
+ }
126
+
127
+ @Override
128
+ public int getItemCount() {
129
+ return items.size();
130
+ }
131
+
132
+ static final class PageViewHolder extends RecyclerView.ViewHolder {
133
+ final GalleryViewerPageView pageView;
134
+
135
+ PageViewHolder(@NonNull GalleryViewerPageView itemView) {
136
+ super(itemView);
137
+ this.pageView = itemView;
138
+ }
139
+ }
140
+ }
@@ -0,0 +1,25 @@
1
+ package cn.fontree.zkit.ui.preview;
2
+
3
+ import android.graphics.RectF;
4
+
5
+ import androidx.annotation.NonNull;
6
+
7
+ public final class GallerySharedElementGeometry {
8
+ private final RectF visibleFrameInWindow;
9
+ private final RectF contentFrameInVisibleBounds;
10
+
11
+ public GallerySharedElementGeometry(@NonNull RectF visibleFrameInWindow, @NonNull RectF contentFrameInVisibleBounds) {
12
+ this.visibleFrameInWindow = new RectF(visibleFrameInWindow);
13
+ this.contentFrameInVisibleBounds = new RectF(contentFrameInVisibleBounds);
14
+ }
15
+
16
+ @NonNull
17
+ public RectF getVisibleFrameInWindow() {
18
+ return new RectF(visibleFrameInWindow);
19
+ }
20
+
21
+ @NonNull
22
+ public RectF getContentFrameInVisibleBounds() {
23
+ return new RectF(contentFrameInVisibleBounds);
24
+ }
25
+ }
@@ -0,0 +1,39 @@
1
+ package cn.fontree.zkit.ui.preview;
2
+
3
+ import androidx.annotation.NonNull;
4
+ import androidx.annotation.Nullable;
5
+
6
+ public final class GallerySharedElementNames {
7
+ private static final String PREFIX = "zkit-native-image-preview:media:";
8
+
9
+ private GallerySharedElementNames() {
10
+ }
11
+
12
+ @NonNull
13
+ public static String forItem(@NonNull GalleryMediaItem item) {
14
+ return forItem(null, item);
15
+ }
16
+
17
+ @NonNull
18
+ public static String forItem(@Nullable String galleryId, @NonNull GalleryMediaItem item) {
19
+ return forId(galleryId, item.getId());
20
+ }
21
+
22
+ @NonNull
23
+ public static String forIndex(int index) {
24
+ return forIndex(null, index);
25
+ }
26
+
27
+ @NonNull
28
+ public static String forIndex(@Nullable String galleryId, int index) {
29
+ return forId(galleryId, String.valueOf(index));
30
+ }
31
+
32
+ @NonNull
33
+ private static String forId(@Nullable String galleryId, @NonNull String id) {
34
+ if (galleryId == null || galleryId.isEmpty()) {
35
+ return PREFIX + id;
36
+ }
37
+ return PREFIX + galleryId + ":" + id;
38
+ }
39
+ }