@rafaelaugusto/jolt 0.1.0-beta.1 → 0.1.0-beta.11
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.
- package/README.md +385 -18
- package/dist/animate.d.ts +19 -3
- package/dist/animate.d.ts.map +1 -1
- package/dist/animate.js +186 -14
- package/dist/animate.js.map +1 -1
- package/dist/color.d.ts +11 -0
- package/dist/color.d.ts.map +1 -0
- package/dist/color.js +73 -0
- package/dist/color.js.map +1 -0
- package/dist/config.d.ts +31 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +73 -0
- package/dist/config.js.map +1 -0
- package/dist/decay.d.ts +11 -0
- package/dist/decay.d.ts.map +1 -0
- package/dist/decay.js +22 -0
- package/dist/decay.js.map +1 -0
- package/dist/decoration.d.ts +18 -0
- package/dist/decoration.d.ts.map +1 -0
- package/dist/decoration.js +155 -0
- package/dist/decoration.js.map +1 -0
- package/dist/drag-input.d.ts +15 -0
- package/dist/drag-input.d.ts.map +1 -0
- package/dist/drag-input.js +66 -0
- package/dist/drag-input.js.map +1 -0
- package/dist/drag-physics.d.ts +20 -0
- package/dist/drag-physics.d.ts.map +1 -0
- package/dist/drag-physics.js +75 -0
- package/dist/drag-physics.js.map +1 -0
- package/dist/drag.d.ts +58 -0
- package/dist/drag.d.ts.map +1 -0
- package/dist/drag.js +324 -0
- package/dist/drag.js.map +1 -0
- package/dist/driver/android-color.d.ts +20 -0
- package/dist/driver/android-color.d.ts.map +1 -0
- package/dist/driver/android-color.js +136 -0
- package/dist/driver/android-color.js.map +1 -0
- package/dist/driver/android-decoration.d.ts +22 -0
- package/dist/driver/android-decoration.d.ts.map +1 -0
- package/dist/driver/android-decoration.js +139 -0
- package/dist/driver/android-decoration.js.map +1 -0
- package/dist/driver/android-gradient.d.ts +5 -0
- package/dist/driver/android-gradient.d.ts.map +1 -0
- package/dist/driver/android-gradient.js +89 -0
- package/dist/driver/android-gradient.js.map +1 -0
- package/dist/driver/android-playback.d.ts +13 -0
- package/dist/driver/android-playback.d.ts.map +1 -0
- package/dist/driver/android-playback.js +141 -0
- package/dist/driver/android-playback.js.map +1 -0
- package/dist/driver/android.d.ts +1 -1
- package/dist/driver/android.d.ts.map +1 -1
- package/dist/driver/android.js +165 -13
- package/dist/driver/android.js.map +1 -1
- package/dist/driver/index.d.ts +25 -2
- package/dist/driver/index.d.ts.map +1 -1
- package/dist/driver/index.js.map +1 -1
- package/dist/driver/ios-color.d.ts +16 -0
- package/dist/driver/ios-color.d.ts.map +1 -0
- package/dist/driver/ios-color.js +392 -0
- package/dist/driver/ios-color.js.map +1 -0
- package/dist/driver/ios-decoration.d.ts +17 -0
- package/dist/driver/ios-decoration.d.ts.map +1 -0
- package/dist/driver/ios-decoration.js +271 -0
- package/dist/driver/ios-decoration.js.map +1 -0
- package/dist/driver/ios-gradient.d.ts +5 -0
- package/dist/driver/ios-gradient.d.ts.map +1 -0
- package/dist/driver/ios-gradient.js +109 -0
- package/dist/driver/ios-gradient.js.map +1 -0
- package/dist/driver/ios-layout.d.ts +9 -0
- package/dist/driver/ios-layout.d.ts.map +1 -0
- package/dist/driver/ios-layout.js +52 -0
- package/dist/driver/ios-layout.js.map +1 -0
- package/dist/driver/ios-playback.d.ts +10 -0
- package/dist/driver/ios-playback.d.ts.map +1 -0
- package/dist/driver/ios-playback.js +47 -0
- package/dist/driver/ios-playback.js.map +1 -0
- package/dist/driver/ios-shadow.d.ts +6 -0
- package/dist/driver/ios-shadow.d.ts.map +1 -0
- package/dist/driver/ios-shadow.js +52 -0
- package/dist/driver/ios-shadow.js.map +1 -0
- package/dist/driver/ios.d.ts.map +1 -1
- package/dist/driver/ios.js +163 -28
- package/dist/driver/ios.js.map +1 -1
- package/dist/engine.d.ts +75 -5
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +293 -23
- package/dist/engine.js.map +1 -1
- package/dist/gradient.d.ts +90 -0
- package/dist/gradient.d.ts.map +1 -0
- package/dist/gradient.js +247 -0
- package/dist/gradient.js.map +1 -0
- package/dist/haptics.d.ts +21 -0
- package/dist/haptics.d.ts.map +1 -0
- package/dist/haptics.js +120 -0
- package/dist/haptics.js.map +1 -0
- package/dist/image-effect-native.d.ts +26 -0
- package/dist/image-effect-native.d.ts.map +1 -0
- package/dist/image-effect-native.js +73 -0
- package/dist/image-effect-native.js.map +1 -0
- package/dist/image-effect.d.ts +50 -0
- package/dist/image-effect.d.ts.map +1 -0
- package/dist/image-effect.js +290 -0
- package/dist/image-effect.js.map +1 -0
- package/dist/image-owner.d.ts +3 -0
- package/dist/image-owner.d.ts.map +1 -0
- package/dist/image-owner.js +11 -0
- package/dist/image-owner.js.map +1 -0
- package/dist/index.d.ts +47 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -2
- package/dist/index.js.map +1 -1
- package/dist/interaction-surface.d.ts +12 -0
- package/dist/interaction-surface.d.ts.map +1 -0
- package/dist/interaction-surface.js +36 -0
- package/dist/interaction-surface.js.map +1 -0
- package/dist/items.d.ts +20 -0
- package/dist/items.d.ts.map +1 -0
- package/dist/items.js +109 -0
- package/dist/items.js.map +1 -0
- package/dist/keyboard.d.ts +41 -0
- package/dist/keyboard.d.ts.map +1 -0
- package/dist/keyboard.js +256 -0
- package/dist/keyboard.js.map +1 -0
- package/dist/label.d.ts +7 -0
- package/dist/label.d.ts.map +1 -0
- package/dist/label.js +60 -0
- package/dist/label.js.map +1 -0
- package/dist/layout.d.ts +13 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +348 -0
- package/dist/layout.js.map +1 -0
- package/dist/liquid-grid.d.ts +3 -0
- package/dist/liquid-grid.d.ts.map +1 -0
- package/dist/liquid-grid.js +24 -0
- package/dist/liquid-grid.js.map +1 -0
- package/dist/liquid.d.ts +18 -0
- package/dist/liquid.d.ts.map +1 -0
- package/dist/liquid.js +234 -0
- package/dist/liquid.js.map +1 -0
- package/dist/loader.d.ts +19 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +173 -0
- package/dist/loader.js.map +1 -0
- package/dist/motion-scope.d.ts +39 -0
- package/dist/motion-scope.d.ts.map +1 -0
- package/dist/motion-scope.js +238 -0
- package/dist/motion-scope.js.map +1 -0
- package/dist/motion-states.d.ts +43 -0
- package/dist/motion-states.d.ts.map +1 -0
- package/dist/motion-states.js +220 -0
- package/dist/motion-states.js.map +1 -0
- package/dist/particles.d.ts +39 -0
- package/dist/particles.d.ts.map +1 -0
- package/dist/particles.js +265 -0
- package/dist/particles.js.map +1 -0
- package/dist/path.d.ts +12 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/path.js +90 -0
- package/dist/path.js.map +1 -0
- package/dist/playback-group.d.ts +6 -0
- package/dist/playback-group.d.ts.map +1 -0
- package/dist/playback-group.js +141 -0
- package/dist/playback-group.js.map +1 -0
- package/dist/playback.d.ts +25 -0
- package/dist/playback.d.ts.map +1 -0
- package/dist/playback.js +35 -0
- package/dist/playback.js.map +1 -0
- package/dist/press.d.ts.map +1 -1
- package/dist/press.js +23 -7
- package/dist/press.js.map +1 -1
- package/dist/properties.d.ts +6 -3
- package/dist/properties.d.ts.map +1 -1
- package/dist/properties.js +26 -2
- package/dist/properties.js.map +1 -1
- package/dist/reveal.d.ts +25 -0
- package/dist/reveal.d.ts.map +1 -0
- package/dist/reveal.js +294 -0
- package/dist/reveal.js.map +1 -0
- package/dist/roll-diff.d.ts +22 -0
- package/dist/roll-diff.d.ts.map +1 -0
- package/dist/roll-diff.js +36 -0
- package/dist/roll-diff.js.map +1 -0
- package/dist/roll.d.ts +29 -0
- package/dist/roll.d.ts.map +1 -0
- package/dist/roll.js +442 -0
- package/dist/roll.js.map +1 -0
- package/dist/scroll-linked.d.ts +10 -0
- package/dist/scroll-linked.d.ts.map +1 -0
- package/dist/scroll-linked.js +58 -0
- package/dist/scroll-linked.js.map +1 -0
- package/dist/sequence.d.ts +10 -5
- package/dist/sequence.d.ts.map +1 -1
- package/dist/sequence.js +208 -14
- package/dist/sequence.js.map +1 -1
- package/dist/shimmer.d.ts +12 -0
- package/dist/shimmer.d.ts.map +1 -0
- package/dist/shimmer.js +161 -0
- package/dist/shimmer.js.map +1 -0
- package/dist/svg-path.d.ts +14 -0
- package/dist/svg-path.d.ts.map +1 -0
- package/dist/svg-path.js +281 -0
- package/dist/svg-path.js.map +1 -0
- package/dist/symbol-effect.d.ts +23 -0
- package/dist/symbol-effect.d.ts.map +1 -0
- package/dist/symbol-effect.js +187 -0
- package/dist/symbol-effect.js.map +1 -0
- package/dist/text-layout.d.ts +30 -0
- package/dist/text-layout.d.ts.map +1 -0
- package/dist/text-layout.js +185 -0
- package/dist/text-layout.js.map +1 -0
- package/dist/text.d.ts +17 -0
- package/dist/text.d.ts.map +1 -0
- package/dist/text.js +263 -0
- package/dist/text.js.map +1 -0
- package/dist/track.d.ts +3 -1
- package/dist/track.d.ts.map +1 -1
- package/dist/track.js +266 -13
- package/dist/track.js.map +1 -1
- package/dist/transition.d.ts +32 -1
- package/dist/transition.d.ts.map +1 -1
- package/dist/transition.js +58 -6
- package/dist/transition.js.map +1 -1
- package/dist/types.d.ts +48 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/vue/index.d.ts +95 -0
- package/dist/vue/index.d.ts.map +1 -0
- package/dist/vue/index.js +163 -0
- package/dist/vue/index.js.map +1 -0
- package/dist/zoom-native.d.ts +30 -0
- package/dist/zoom-native.d.ts.map +1 -0
- package/dist/zoom-native.js +56 -0
- package/dist/zoom-native.js.map +1 -0
- package/dist/zoomable.d.ts +48 -0
- package/dist/zoomable.d.ts.map +1 -0
- package/dist/zoomable.js +249 -0
- package/dist/zoomable.js.map +1 -0
- package/package.json +29 -5
- package/platforms/android/include.gradle +7 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/ColorTarget.java +102 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/DecorationTarget.java +154 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/GradientLayer.java +195 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/ImageEffect.java +288 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/KeyboardFollower.java +91 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/KeyboardObserver.java +97 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/NumberRoll.java +190 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/PhotoZoom.java +250 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/TextReveal.java +180 -0
- package/platforms/android/proguard-rules.pro +21 -0
- package/platforms/ios/build.xcconfig +1 -0
- package/platforms/ios/src/JoltImageEffect.h +25 -0
- package/platforms/ios/src/JoltImageEffect.mm +242 -0
- package/platforms/ios/src/JoltInteractiveAnimation.h +18 -0
- package/platforms/ios/src/JoltInteractiveAnimation.m +123 -0
- package/platforms/ios/src/JoltPhotoZoom.h +23 -0
- package/platforms/ios/src/JoltPhotoZoom.m +229 -0
- package/platforms/ios/src/module.modulemap +6 -0
- package/src/android-plugin.d.ts +184 -0
- package/src/animate.ts +249 -36
- package/src/color.ts +101 -0
- package/src/config.ts +84 -0
- package/src/decay.ts +45 -0
- package/src/decoration.ts +180 -0
- package/src/drag-input.ts +74 -0
- package/src/drag-physics.ts +110 -0
- package/src/drag.ts +395 -0
- package/src/driver/android-color.ts +170 -0
- package/src/driver/android-decoration.ts +182 -0
- package/src/driver/android-gradient.ts +109 -0
- package/src/driver/android-playback.ts +157 -0
- package/src/driver/android.ts +208 -20
- package/src/driver/index.ts +37 -2
- package/src/driver/ios-color.ts +441 -0
- package/src/driver/ios-decoration.ts +332 -0
- package/src/driver/ios-gradient.ts +114 -0
- package/src/driver/ios-layout.ts +63 -0
- package/src/driver/ios-playback.ts +51 -0
- package/src/driver/ios-shadow.ts +50 -0
- package/src/driver/ios.ts +193 -33
- package/src/engine.ts +321 -27
- package/src/gradient.ts +372 -0
- package/src/haptics.ts +148 -0
- package/src/image-effect-native.ts +129 -0
- package/src/image-effect.ts +336 -0
- package/src/image-owner.ts +12 -0
- package/src/index.ts +75 -4
- package/src/interaction-surface.ts +41 -0
- package/src/ios-plugin.d.ts +81 -0
- package/src/items.ts +140 -0
- package/src/keyboard.ts +291 -0
- package/src/label.ts +76 -0
- package/src/layout.ts +408 -0
- package/src/liquid-grid.ts +28 -0
- package/src/liquid.ts +287 -0
- package/src/loader.ts +202 -0
- package/src/motion-scope.ts +279 -0
- package/src/motion-states.ts +269 -0
- package/src/particles.ts +323 -0
- package/src/path.ts +124 -0
- package/src/playback-group.ts +143 -0
- package/src/playback.ts +62 -0
- package/src/press.ts +26 -10
- package/src/properties.ts +32 -6
- package/src/reveal.ts +374 -0
- package/src/roll-diff.ts +57 -0
- package/src/roll.ts +515 -0
- package/src/scroll-linked.ts +73 -0
- package/src/sequence.ts +204 -14
- package/src/shimmer.ts +180 -0
- package/src/svg-path.ts +355 -0
- package/src/symbol-effect.ts +226 -0
- package/src/text-layout.ts +228 -0
- package/src/text.ts +279 -0
- package/src/track.ts +347 -12
- package/src/transition.ts +93 -6
- package/src/types.ts +49 -1
- package/src/vue/index.ts +231 -0
- package/src/zoom-native.ts +124 -0
- package/src/zoomable.ts +330 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
package org.rafaelaugusto.jolt;
|
|
2
|
+
|
|
3
|
+
import android.animation.Animator;
|
|
4
|
+
import android.animation.AnimatorListenerAdapter;
|
|
5
|
+
import android.animation.ValueAnimator;
|
|
6
|
+
import android.graphics.Canvas;
|
|
7
|
+
import android.graphics.ColorFilter;
|
|
8
|
+
import android.graphics.LinearGradient;
|
|
9
|
+
import android.graphics.Matrix;
|
|
10
|
+
import android.graphics.Paint;
|
|
11
|
+
import android.graphics.PixelFormat;
|
|
12
|
+
import android.graphics.RadialGradient;
|
|
13
|
+
import android.graphics.Rect;
|
|
14
|
+
import android.graphics.RectF;
|
|
15
|
+
import android.graphics.Shader;
|
|
16
|
+
import android.graphics.SweepGradient;
|
|
17
|
+
import android.view.animation.LinearInterpolator;
|
|
18
|
+
|
|
19
|
+
/** Paints a linear, radial or conic gradient and plays sampled frames. All frame updates stay in native code. */
|
|
20
|
+
public final class GradientLayer extends android.graphics.drawable.Drawable {
|
|
21
|
+
public interface Listener {
|
|
22
|
+
void onEnd(boolean finished);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 0 = linear, 1 = radial, 2 = conic.
|
|
26
|
+
private final int type;
|
|
27
|
+
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
|
28
|
+
private final RectF area = new RectF();
|
|
29
|
+
private final Matrix matrix = new Matrix();
|
|
30
|
+
private int[] colors = new int[0];
|
|
31
|
+
private float[] stops = new float[0];
|
|
32
|
+
// angle, x, y, radius
|
|
33
|
+
private final float[] geometry = new float[4];
|
|
34
|
+
private float corner;
|
|
35
|
+
private ValueAnimator animator;
|
|
36
|
+
private Listener listener;
|
|
37
|
+
|
|
38
|
+
private float[] times;
|
|
39
|
+
private int[] frameColors;
|
|
40
|
+
private float[] frameStops;
|
|
41
|
+
private float[] frameGeometry;
|
|
42
|
+
|
|
43
|
+
public GradientLayer(int type) {
|
|
44
|
+
this.type = type;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public void setCorner(float radius) {
|
|
48
|
+
corner = radius;
|
|
49
|
+
invalidateSelf();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public void paint(int[] colors, float[] stops, float angle, float x, float y, float radius) {
|
|
53
|
+
cancel();
|
|
54
|
+
this.colors = colors;
|
|
55
|
+
this.stops = stops;
|
|
56
|
+
geometry[0] = angle;
|
|
57
|
+
geometry[1] = x;
|
|
58
|
+
geometry[2] = y;
|
|
59
|
+
geometry[3] = radius;
|
|
60
|
+
invalidateSelf();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Frames are flattened: `colors.length / times.length` colors and stops per frame, four geometry values per frame. */
|
|
64
|
+
public void play(float[] times, int[] colors, float[] stops, float[] geometry, long duration, long delay, int repeat, boolean reverse, Listener listener) {
|
|
65
|
+
cancel();
|
|
66
|
+
this.times = times;
|
|
67
|
+
frameColors = colors;
|
|
68
|
+
frameStops = stops;
|
|
69
|
+
frameGeometry = geometry;
|
|
70
|
+
this.listener = listener;
|
|
71
|
+
int count = colors.length / times.length;
|
|
72
|
+
this.colors = new int[count];
|
|
73
|
+
this.stops = new float[count];
|
|
74
|
+
apply(0);
|
|
75
|
+
final ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f);
|
|
76
|
+
animation.setDuration(Math.max(duration, 1));
|
|
77
|
+
animation.setStartDelay(delay);
|
|
78
|
+
animation.setInterpolator(new LinearInterpolator());
|
|
79
|
+
if (repeat != 0) {
|
|
80
|
+
animation.setRepeatCount(repeat < 0 ? ValueAnimator.INFINITE : repeat);
|
|
81
|
+
animation.setRepeatMode(reverse ? ValueAnimator.REVERSE : ValueAnimator.RESTART);
|
|
82
|
+
}
|
|
83
|
+
animation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
|
84
|
+
@Override
|
|
85
|
+
public void onAnimationUpdate(ValueAnimator updated) {
|
|
86
|
+
apply(updated.getAnimatedFraction());
|
|
87
|
+
invalidateSelf();
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
animation.addListener(new AnimatorListenerAdapter() {
|
|
91
|
+
private boolean cancelled;
|
|
92
|
+
|
|
93
|
+
@Override
|
|
94
|
+
public void onAnimationCancel(Animator ended) {
|
|
95
|
+
cancelled = true;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@Override
|
|
99
|
+
public void onAnimationEnd(Animator ended) {
|
|
100
|
+
if (animator != animation) return;
|
|
101
|
+
animator = null;
|
|
102
|
+
Listener done = GradientLayer.this.listener;
|
|
103
|
+
GradientLayer.this.listener = null;
|
|
104
|
+
if (done != null) done.onEnd(!cancelled);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
animator = animation;
|
|
108
|
+
animation.start();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
public void cancel() {
|
|
112
|
+
ValueAnimator running = animator;
|
|
113
|
+
animator = null;
|
|
114
|
+
listener = null;
|
|
115
|
+
if (running != null) running.cancel();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private void apply(float fraction) {
|
|
119
|
+
int frames = times.length;
|
|
120
|
+
int count = colors.length;
|
|
121
|
+
int i = 0;
|
|
122
|
+
while (i < frames - 2 && fraction > times[i + 1]) i++;
|
|
123
|
+
float span = times[i + 1] - times[i];
|
|
124
|
+
float t = span > 0 ? Math.max(0f, Math.min(1f, (fraction - times[i]) / span)) : 1f;
|
|
125
|
+
for (int c = 0; c < count; c++) {
|
|
126
|
+
colors[c] = lerpColor(frameColors[i * count + c], frameColors[(i + 1) * count + c], t);
|
|
127
|
+
stops[c] = lerp(frameStops[i * count + c], frameStops[(i + 1) * count + c], t);
|
|
128
|
+
}
|
|
129
|
+
for (int g = 0; g < 4; g++) geometry[g] = lerp(frameGeometry[i * 4 + g], frameGeometry[(i + 1) * 4 + g], t);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private static float lerp(float a, float b, float t) {
|
|
133
|
+
return a + (b - a) * t;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private static int lerpColor(int a, int b, float t) {
|
|
137
|
+
int alpha = Math.round(lerp((a >>> 24) & 0xff, (b >>> 24) & 0xff, t));
|
|
138
|
+
int red = Math.round(lerp((a >> 16) & 0xff, (b >> 16) & 0xff, t));
|
|
139
|
+
int green = Math.round(lerp((a >> 8) & 0xff, (b >> 8) & 0xff, t));
|
|
140
|
+
int blue = Math.round(lerp(a & 0xff, b & 0xff, t));
|
|
141
|
+
return (alpha << 24) | (red << 16) | (green << 8) | blue;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
@Override
|
|
145
|
+
public void draw(Canvas canvas) {
|
|
146
|
+
Rect bounds = getBounds();
|
|
147
|
+
float width = bounds.width();
|
|
148
|
+
float height = bounds.height();
|
|
149
|
+
if (width <= 0 || height <= 0 || colors.length < 2) return;
|
|
150
|
+
double radians = Math.toRadians(geometry[0]);
|
|
151
|
+
float sin = (float) Math.sin(radians);
|
|
152
|
+
float cos = (float) Math.cos(radians);
|
|
153
|
+
float left = bounds.left;
|
|
154
|
+
float top = bounds.top;
|
|
155
|
+
Shader shader;
|
|
156
|
+
if (type == 0) {
|
|
157
|
+
float half = (Math.abs(width * sin) + Math.abs(height * cos)) / 2f;
|
|
158
|
+
float cx = left + width / 2f;
|
|
159
|
+
float cy = top + height / 2f;
|
|
160
|
+
shader = new LinearGradient(cx - sin * half, cy + cos * half, cx + sin * half, cy - cos * half, colors, stops, Shader.TileMode.CLAMP);
|
|
161
|
+
} else if (type == 1) {
|
|
162
|
+
float radius = Math.max(geometry[3] * Math.min(width, height), 0.01f);
|
|
163
|
+
shader = new RadialGradient(left + geometry[1] * width, top + geometry[2] * height, radius, colors, stops, Shader.TileMode.CLAMP);
|
|
164
|
+
} else {
|
|
165
|
+
float cx = left + geometry[1] * width;
|
|
166
|
+
float cy = top + geometry[2] * height;
|
|
167
|
+
SweepGradient sweep = new SweepGradient(cx, cy, colors, stops);
|
|
168
|
+
// Turned so the first color starts at 12 o'clock and sweeps clockwise, as on iOS and in CSS.
|
|
169
|
+
matrix.setRotate(geometry[0] + 90f, cx, cy);
|
|
170
|
+
sweep.setLocalMatrix(matrix);
|
|
171
|
+
shader = sweep;
|
|
172
|
+
}
|
|
173
|
+
paint.setShader(shader);
|
|
174
|
+
area.set(bounds);
|
|
175
|
+
if (corner > 0) canvas.drawRoundRect(area, corner, corner, paint);
|
|
176
|
+
else canvas.drawRect(area, paint);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
@Override
|
|
180
|
+
public void setAlpha(int alpha) {
|
|
181
|
+
paint.setAlpha(alpha);
|
|
182
|
+
invalidateSelf();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
@Override
|
|
186
|
+
public void setColorFilter(ColorFilter filter) {
|
|
187
|
+
paint.setColorFilter(filter);
|
|
188
|
+
invalidateSelf();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
@Override
|
|
192
|
+
public int getOpacity() {
|
|
193
|
+
return PixelFormat.TRANSLUCENT;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
package org.rafaelaugusto.jolt;
|
|
2
|
+
|
|
3
|
+
import android.animation.Animator;
|
|
4
|
+
import android.animation.AnimatorListenerAdapter;
|
|
5
|
+
import android.animation.ValueAnimator;
|
|
6
|
+
import android.graphics.*;
|
|
7
|
+
import android.graphics.drawable.*;
|
|
8
|
+
import android.os.Build;
|
|
9
|
+
import android.view.MotionEvent;
|
|
10
|
+
import android.view.View;
|
|
11
|
+
import android.widget.ImageView;
|
|
12
|
+
|
|
13
|
+
/** Image-only GPU effects. All animation and pointer samples stay in Java. */
|
|
14
|
+
public final class ImageEffect {
|
|
15
|
+
public interface Listener { void onEnd(boolean completed, float progress); }
|
|
16
|
+
private static final String SHADER =
|
|
17
|
+
"uniform shader source; uniform shader destination; uniform float2 size;" +
|
|
18
|
+
"uniform float2 origin; uniform float2 displacement; uniform float progress;" +
|
|
19
|
+
"uniform float amplitude; uniform float radius; uniform float seed; uniform float mode;" +
|
|
20
|
+
"half4 main(float2 p) { float2 uv=p/size; float2 q=p;" +
|
|
21
|
+
"if(mode<1.5){ float2 v=p-origin; float d=length(v);" +
|
|
22
|
+
"float edge=clamp(min(min(p.x,size.x-p.x),min(p.y,size.y-p.y))/max(amplitude*2.,1.),0.,1.);" +
|
|
23
|
+
"if(mode<.5){float wave=d-progress*length(size);" +
|
|
24
|
+
"float a=sin(wave/max(radius*.12,1.))*exp(-pow(wave/max(radius,1.),2.))*amplitude*sin(progress*3.14159265);" +
|
|
25
|
+
"q-=v/max(d,1.)*a*edge;}else{q-=displacement*exp(-d*d/max(radius*radius,1.))*edge;}" +
|
|
26
|
+
"return source.eval(clamp(q,float2(.5),size-float2(.5)));}" +
|
|
27
|
+
"if(progress<=0.)return source.eval(p); if(progress>=1.)return destination.eval(p);" +
|
|
28
|
+
"float n=fract(sin(dot(floor(p/3.),float2(12.9898,78.233))+seed)*43758.5453);" +
|
|
29
|
+
"float a=smoothstep(n-.08,n+.08,progress*1.16-.08);" +
|
|
30
|
+
"return mix(source.eval(p),destination.eval(p),a);}";
|
|
31
|
+
|
|
32
|
+
private final ImageView view;
|
|
33
|
+
private final Listener listener;
|
|
34
|
+
private final int mode;
|
|
35
|
+
private final float amplitude, radius;
|
|
36
|
+
private final long duration;
|
|
37
|
+
private final Drawable original;
|
|
38
|
+
private final Bitmap originalBitmap;
|
|
39
|
+
private final ImageView.ScaleType originalScale;
|
|
40
|
+
private final View.OnTouchListener previousTouch;
|
|
41
|
+
private final boolean originalClickable;
|
|
42
|
+
private Bitmap source, destination, placeholderBitmap;
|
|
43
|
+
private RuntimeShader shader;
|
|
44
|
+
private EffectDrawable drawable;
|
|
45
|
+
private Drawable placeholder;
|
|
46
|
+
private ValueAnimator animator;
|
|
47
|
+
private boolean disposed, paused, suppressEnd, attached;
|
|
48
|
+
private float progress, goal = 1, x, y, dx, dy;
|
|
49
|
+
private int pointer = -1;
|
|
50
|
+
private float startX, startY, rawX, rawY;
|
|
51
|
+
private long frames, lastFrame, longestFrame;
|
|
52
|
+
private String failureReason="";
|
|
53
|
+
private final Path contentClip=new Path();
|
|
54
|
+
|
|
55
|
+
public ImageEffect(ImageView view, Bitmap from, Bitmap to, float[] geometry, int mode, float x, float y,
|
|
56
|
+
float amplitude, float radius, long duration, float seed,
|
|
57
|
+
View.OnTouchListener previousTouch, Listener listener) {
|
|
58
|
+
this.view=view; this.listener=listener; this.mode=mode;
|
|
59
|
+
this.amplitude=amplitude; this.radius=radius; this.duration=duration;
|
|
60
|
+
this.x=x; this.y=y; this.previousTouch=previousTouch;
|
|
61
|
+
original=view.getDrawable(); originalScale=view.getScaleType();
|
|
62
|
+
originalBitmap=from;
|
|
63
|
+
originalClickable=view.isClickable();
|
|
64
|
+
if(Build.VERSION.SDK_INT<33 || !view.isHardwareAccelerated() || from==null || original==null || original instanceof Animatable) {
|
|
65
|
+
failureReason="Requires API 33, hardware rendering and a static drawable ("+(original==null?"null":original.getClass().getName())+")";
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
int w=view.getWidth(), h=view.getHeight();
|
|
69
|
+
if(w<=0 || h<=0 || (long)w*h>4194304L) {failureReason="Image surface exceeds the pixel budget";return;}
|
|
70
|
+
try {
|
|
71
|
+
float left=view.getPaddingLeft(),top=view.getPaddingTop(),right=view.getPaddingRight(),bottom=view.getPaddingBottom();
|
|
72
|
+
float[] radii={
|
|
73
|
+
Math.max(0,geometry[2]-left),Math.max(0,geometry[2]-top),
|
|
74
|
+
Math.max(0,geometry[3]-right),Math.max(0,geometry[3]-top),
|
|
75
|
+
Math.max(0,geometry[4]-right),Math.max(0,geometry[4]-bottom),
|
|
76
|
+
Math.max(0,geometry[5]-left),Math.max(0,geometry[5]-bottom)
|
|
77
|
+
};
|
|
78
|
+
contentClip.addRoundRect(new RectF(left,top,w-right,h-bottom),radii,Path.Direction.CW);
|
|
79
|
+
source=raster(from,geometry[0],w,h);
|
|
80
|
+
if(mode==2) {
|
|
81
|
+
if(to==null) { releaseBitmaps(); return; }
|
|
82
|
+
destination=raster(to,geometry[1],w,h);
|
|
83
|
+
}
|
|
84
|
+
shader=new RuntimeShader(SHADER);
|
|
85
|
+
shader.setInputShader("source",new BitmapShader(source,Shader.TileMode.CLAMP,Shader.TileMode.CLAMP));
|
|
86
|
+
shader.setInputShader("destination",new BitmapShader(destination==null?source:destination,Shader.TileMode.CLAMP,Shader.TileMode.CLAMP));
|
|
87
|
+
shader.setFloatUniform("size",w,h); shader.setFloatUniform("mode",mode);
|
|
88
|
+
shader.setFloatUniform("amplitude",amplitude); shader.setFloatUniform("radius",radius);
|
|
89
|
+
shader.setFloatUniform("seed",seed);
|
|
90
|
+
drawable=new EffectDrawable(w,h);
|
|
91
|
+
drawable.setBounds(0,0,w,h);
|
|
92
|
+
update();
|
|
93
|
+
// NativeScript's ImageView rasterizes custom drawables on a software
|
|
94
|
+
// canvas. An overlay keeps RuntimeShader on the hardware canvas.
|
|
95
|
+
view.getOverlay().add(drawable);
|
|
96
|
+
attached=true;
|
|
97
|
+
// NativeScript's onDraw clips the canvas to its bitmap's mapped
|
|
98
|
+
// rectangle, including overlays. A full-content transparent alpha
|
|
99
|
+
// bitmap keeps that clip open in every stretch/rotation mode.
|
|
100
|
+
// Clearing the drawable leaves its cached bitmap shader invalid.
|
|
101
|
+
int pw=Math.max(1,w-view.getPaddingLeft()-view.getPaddingRight());
|
|
102
|
+
int ph=Math.max(1,h-view.getPaddingTop()-view.getPaddingBottom());
|
|
103
|
+
float angle=Math.abs(geometry[0]%180);
|
|
104
|
+
if(angle>45 && angle<135){int swap=pw;pw=ph;ph=swap;}
|
|
105
|
+
placeholderBitmap=Bitmap.createBitmap(pw,ph,Bitmap.Config.ALPHA_8);
|
|
106
|
+
view.setImageBitmap(placeholderBitmap);
|
|
107
|
+
placeholder=view.getDrawable();
|
|
108
|
+
if(mode==1) {
|
|
109
|
+
view.setClickable(true);
|
|
110
|
+
view.setOnTouchListener(this::touch);
|
|
111
|
+
}
|
|
112
|
+
} catch (RuntimeException | OutOfMemoryError error) {
|
|
113
|
+
failureReason=error.toString();
|
|
114
|
+
if(attached) restore(true);
|
|
115
|
+
releaseBitmaps(); shader=null; drawable=null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Match NativeScript's bitmap mapping without baking backgrounds/borders
|
|
120
|
+
* into the texture. The stationary inner corner mask is applied at draw time. */
|
|
121
|
+
private Bitmap raster(Bitmap bitmap,float rotation,int width,int height) {
|
|
122
|
+
Bitmap result=Bitmap.createBitmap(width,height,Bitmap.Config.ARGB_8888);
|
|
123
|
+
Canvas canvas=new Canvas(result);
|
|
124
|
+
float left=view.getPaddingLeft(),top=view.getPaddingTop();
|
|
125
|
+
float w=width-left-view.getPaddingRight(),h=height-top-view.getPaddingBottom();
|
|
126
|
+
float iw=bitmap.getWidth(),ih=bitmap.getHeight();
|
|
127
|
+
Matrix matrix=new Matrix();matrix.postRotate(rotation,iw/2,ih/2);
|
|
128
|
+
float angle=Math.abs(rotation%180);
|
|
129
|
+
if(angle>45 && angle<135){matrix.postTranslate((ih-iw)/2,(iw-ih)/2);float swap=iw;iw=ih;ih=swap;}
|
|
130
|
+
float sx=w/iw,sy=h/ih;
|
|
131
|
+
RectF visible=new RectF(left,top,left+w,top+h);
|
|
132
|
+
if(originalScale==ImageView.ScaleType.FIT_XY) matrix.postScale(sx,sy);
|
|
133
|
+
else if(originalScale==ImageView.ScaleType.MATRIX) visible.intersect(left,top,left+iw,top+ih);
|
|
134
|
+
else {
|
|
135
|
+
float scale=originalScale==ImageView.ScaleType.CENTER?1:originalScale==ImageView.ScaleType.CENTER_CROP?Math.max(sx,sy):Math.min(sx,sy);
|
|
136
|
+
matrix.postTranslate((w-iw)/2,(h-ih)/2);
|
|
137
|
+
matrix.postScale(scale,scale,w/2,h/2);
|
|
138
|
+
visible.intersect(left+(w-iw*scale)/2,top+(h-ih*scale)/2,left+(w+iw*scale)/2,top+(h+ih*scale)/2);
|
|
139
|
+
}
|
|
140
|
+
matrix.postTranslate(left,top);
|
|
141
|
+
BitmapShader pixels=new BitmapShader(bitmap,Shader.TileMode.CLAMP,Shader.TileMode.CLAMP);
|
|
142
|
+
pixels.setLocalMatrix(matrix);
|
|
143
|
+
Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG|Paint.FILTER_BITMAP_FLAG);
|
|
144
|
+
paint.setShader(pixels);paint.setColorFilter(view.getColorFilter());
|
|
145
|
+
canvas.drawRect(visible,paint);
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
public boolean isSupported() { return drawable!=null && !disposed; }
|
|
150
|
+
public String getFailureReason() { return failureReason; }
|
|
151
|
+
public float getProgress() { return progress; }
|
|
152
|
+
public long getFrames() { return frames; }
|
|
153
|
+
public long getLongestFrameNanos() { return longestFrame; }
|
|
154
|
+
public long getTextureBytes() { return source==null?0:(long)source.getAllocationByteCount()+(destination==null?0:destination.getAllocationByteCount())+(placeholderBitmap==null?0:placeholderBitmap.getAllocationByteCount()); }
|
|
155
|
+
public boolean isPaused() { return paused; }
|
|
156
|
+
|
|
157
|
+
public void play(float destinationProgress) {
|
|
158
|
+
if(disposed || drawable==null || mode==1) return;
|
|
159
|
+
goal=destinationProgress; paused=false;
|
|
160
|
+
animate(progress,goal,Math.max(1,(long)(duration*Math.abs(goal-progress))),false);
|
|
161
|
+
}
|
|
162
|
+
public void pause() {
|
|
163
|
+
paused=true;
|
|
164
|
+
if(animator!=null) animator.pause();
|
|
165
|
+
}
|
|
166
|
+
public void resume() {
|
|
167
|
+
if(disposed) return;
|
|
168
|
+
paused=false;
|
|
169
|
+
lastFrame=0;
|
|
170
|
+
if(animator!=null && animator.isPaused()) animator.resume();
|
|
171
|
+
}
|
|
172
|
+
public void seek(float value) {
|
|
173
|
+
if(disposed || mode==1) return;
|
|
174
|
+
boolean hold=paused;
|
|
175
|
+
cancelAnimator(); progress=value; update();
|
|
176
|
+
if(!hold) play(goal);
|
|
177
|
+
}
|
|
178
|
+
public void finish(float endpoint) {
|
|
179
|
+
if(disposed || mode==1) return;
|
|
180
|
+
cancelAnimator(); progress=endpoint; update();
|
|
181
|
+
listener.onEnd(true,progress);
|
|
182
|
+
}
|
|
183
|
+
public void stop() {
|
|
184
|
+
detach(true);
|
|
185
|
+
}
|
|
186
|
+
public void stopPreservingImage() {
|
|
187
|
+
detach(false);
|
|
188
|
+
}
|
|
189
|
+
private void detach(boolean restoreImage) {
|
|
190
|
+
if(disposed) return;
|
|
191
|
+
disposed=true; cancelAnimator();
|
|
192
|
+
restore(restoreImage); shader=null; drawable=null; releaseBitmaps();
|
|
193
|
+
}
|
|
194
|
+
private void restore(boolean restoreImage) {
|
|
195
|
+
if(attached && drawable!=null) view.getOverlay().remove(drawable);
|
|
196
|
+
if(restoreImage && attached && view.getDrawable()==placeholder) {
|
|
197
|
+
// NativeScript caches mBitmap separately from its drawable. Restoring
|
|
198
|
+
// only the drawable leaves the placeholder's geometry behind.
|
|
199
|
+
view.setImageBitmap(originalBitmap);
|
|
200
|
+
}
|
|
201
|
+
if(mode==1 && drawable!=null) {
|
|
202
|
+
view.setOnTouchListener(previousTouch); view.setClickable(originalClickable);
|
|
203
|
+
if(view.getParent()!=null) view.getParent().requestDisallowInterceptTouchEvent(false);
|
|
204
|
+
}
|
|
205
|
+
attached=false;
|
|
206
|
+
}
|
|
207
|
+
private void releaseBitmaps() {
|
|
208
|
+
// Draw commands may still reference them; let Android retire the backing storage safely.
|
|
209
|
+
source=null; destination=null; placeholderBitmap=null;
|
|
210
|
+
}
|
|
211
|
+
private void cancelAnimator() {
|
|
212
|
+
if(animator==null) return;
|
|
213
|
+
suppressEnd=true; animator.cancel(); animator=null; suppressEnd=false;
|
|
214
|
+
}
|
|
215
|
+
private void animate(float from,float to,long milliseconds,boolean settle) {
|
|
216
|
+
cancelAnimator();
|
|
217
|
+
lastFrame=0;
|
|
218
|
+
final float startDx=dx,startDy=dy;
|
|
219
|
+
animator=ValueAnimator.ofFloat(from,to);
|
|
220
|
+
animator.setDuration(milliseconds);
|
|
221
|
+
animator.setInterpolator(new android.view.animation.LinearInterpolator());
|
|
222
|
+
animator.addUpdateListener(a->{
|
|
223
|
+
float value=(float)a.getAnimatedValue();
|
|
224
|
+
if(settle) {float t=value; float decay=(float)(Math.exp(-8*t)*(1-t)); dx=startDx*decay; dy=startDy*decay;}
|
|
225
|
+
else progress=value;
|
|
226
|
+
update();
|
|
227
|
+
});
|
|
228
|
+
animator.addListener(new AnimatorListenerAdapter(){
|
|
229
|
+
@Override public void onAnimationEnd(Animator a){
|
|
230
|
+
if(suppressEnd || disposed) return;
|
|
231
|
+
animator=null;
|
|
232
|
+
if(settle) {dx=dy=0;update();}
|
|
233
|
+
else listener.onEnd(true,progress);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
animator.start();
|
|
237
|
+
}
|
|
238
|
+
private boolean touch(View target,MotionEvent event) {
|
|
239
|
+
if(disposed || paused) return false;
|
|
240
|
+
int action=event.getActionMasked();
|
|
241
|
+
if(action==MotionEvent.ACTION_DOWN) {
|
|
242
|
+
cancelAnimator(); pointer=event.getPointerId(0);
|
|
243
|
+
x=startX=event.getX(); y=startY=event.getY(); dx=dy=rawX=rawY=0;
|
|
244
|
+
update(); return true;
|
|
245
|
+
}
|
|
246
|
+
int index=event.findPointerIndex(pointer);
|
|
247
|
+
if(action==MotionEvent.ACTION_MOVE && index>=0) {
|
|
248
|
+
float vx=event.getX(index)-startX,vy=event.getY(index)-startY;
|
|
249
|
+
rawX=vx;rawY=vy;
|
|
250
|
+
float length=(float)Math.hypot(vx,vy);
|
|
251
|
+
float factor=length==0?0:(float)(amplitude*Math.tanh(length/Math.max(amplitude,1))/length);
|
|
252
|
+
dx=vx*factor; dy=vy*factor; update(); return true;
|
|
253
|
+
}
|
|
254
|
+
if(action==MotionEvent.ACTION_POINTER_UP && event.getPointerId(event.getActionIndex())==pointer) {
|
|
255
|
+
int next=event.getActionIndex()==0?1:0;
|
|
256
|
+
if(next<event.getPointerCount()) {
|
|
257
|
+
pointer=event.getPointerId(next); startX=event.getX(next)-rawX;startY=event.getY(next)-rawY;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if(action==MotionEvent.ACTION_UP || action==MotionEvent.ACTION_CANCEL) {
|
|
261
|
+
pointer=-1; animate(0,1,Math.min(600,duration),true);
|
|
262
|
+
if(action==MotionEvent.ACTION_UP) view.performClick();
|
|
263
|
+
}
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
private void update() {
|
|
267
|
+
if(shader==null || disposed) return;
|
|
268
|
+
shader.setFloatUniform("origin",x,y);
|
|
269
|
+
shader.setFloatUniform("displacement",dx,dy);
|
|
270
|
+
shader.setFloatUniform("progress",progress);
|
|
271
|
+
if(drawable!=null) drawable.invalidateSelf();
|
|
272
|
+
}
|
|
273
|
+
private final class EffectDrawable extends Drawable {
|
|
274
|
+
private final Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG);
|
|
275
|
+
private final int width,height;
|
|
276
|
+
EffectDrawable(int width,int height) {this.width=width;this.height=height;paint.setShader(shader);}
|
|
277
|
+
@Override public int getIntrinsicWidth(){return width;}
|
|
278
|
+
@Override public int getIntrinsicHeight(){return height;}
|
|
279
|
+
@Override public void draw(Canvas canvas){
|
|
280
|
+
long now=System.nanoTime(); if(lastFrame!=0 && animator!=null && !paused) longestFrame=Math.max(longestFrame,now-lastFrame);
|
|
281
|
+
lastFrame=now; frames++;
|
|
282
|
+
canvas.drawPath(contentClip,paint);
|
|
283
|
+
}
|
|
284
|
+
@Override public void setAlpha(int alpha){paint.setAlpha(alpha);invalidateSelf();}
|
|
285
|
+
@Override public void setColorFilter(ColorFilter filter){paint.setColorFilter(filter);invalidateSelf();}
|
|
286
|
+
@Override public int getOpacity(){return PixelFormat.TRANSLUCENT;}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
package org.rafaelaugusto.jolt;
|
|
2
|
+
|
|
3
|
+
import android.view.View;
|
|
4
|
+
import android.view.WindowInsets;
|
|
5
|
+
import android.view.WindowInsetsAnimation;
|
|
6
|
+
import java.util.List;
|
|
7
|
+
|
|
8
|
+
/** Keeps a view above the keyboard while the IME animates. All frame updates stay in native code. */
|
|
9
|
+
public final class KeyboardFollower extends WindowInsetsAnimation.Callback {
|
|
10
|
+
public interface Listener {
|
|
11
|
+
void onStart();
|
|
12
|
+
void onSettled(float translation);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
private final View view;
|
|
16
|
+
private final float offset;
|
|
17
|
+
private final Listener listener;
|
|
18
|
+
private float visualBottom;
|
|
19
|
+
private float from;
|
|
20
|
+
private float to;
|
|
21
|
+
private boolean tracking;
|
|
22
|
+
|
|
23
|
+
public KeyboardFollower(View view, float offset, Listener listener) {
|
|
24
|
+
super(DISPATCH_MODE_CONTINUE_ON_SUBTREE);
|
|
25
|
+
this.view = view;
|
|
26
|
+
this.offset = offset;
|
|
27
|
+
this.listener = listener;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public void attach() {
|
|
31
|
+
view.setWindowInsetsAnimationCallback(this);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public void detach() {
|
|
35
|
+
tracking = false;
|
|
36
|
+
view.setWindowInsetsAnimationCallback(null);
|
|
37
|
+
view.setTranslationY(0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private static boolean keyboard(WindowInsetsAnimation animation) {
|
|
41
|
+
return (animation.getTypeMask() & WindowInsets.Type.ime()) != 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private float bottom() {
|
|
45
|
+
int[] location = new int[2];
|
|
46
|
+
view.getLocationInWindow(location);
|
|
47
|
+
return location[1] + view.getHeight();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@Override
|
|
51
|
+
public void onPrepare(WindowInsetsAnimation animation) {
|
|
52
|
+
if (!keyboard(animation)) return;
|
|
53
|
+
visualBottom = bottom();
|
|
54
|
+
tracking = true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@Override
|
|
58
|
+
public WindowInsetsAnimation.Bounds onStart(WindowInsetsAnimation animation, WindowInsetsAnimation.Bounds bounds) {
|
|
59
|
+
if (!keyboard(animation) || !tracking) return bounds;
|
|
60
|
+
listener.onStart();
|
|
61
|
+
// The end state is laid out now; the view may already have moved with the layout.
|
|
62
|
+
float untranslated = bottom() - view.getTranslationY();
|
|
63
|
+
WindowInsets insets = view.getRootWindowInsets();
|
|
64
|
+
int height = insets == null ? 0 : insets.getInsets(WindowInsets.Type.ime()).bottom;
|
|
65
|
+
float top = view.getRootView().getHeight() - height;
|
|
66
|
+
to = -Math.max(0, untranslated + offset - top);
|
|
67
|
+
from = visualBottom - untranslated;
|
|
68
|
+
view.setTranslationY(from);
|
|
69
|
+
return bounds;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@Override
|
|
73
|
+
public WindowInsets onProgress(WindowInsets insets, List<WindowInsetsAnimation> running) {
|
|
74
|
+
if (!tracking) return insets;
|
|
75
|
+
for (WindowInsetsAnimation animation : running) {
|
|
76
|
+
if (keyboard(animation)) {
|
|
77
|
+
view.setTranslationY(from + (to - from) * animation.getInterpolatedFraction());
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return insets;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@Override
|
|
85
|
+
public void onEnd(WindowInsetsAnimation animation) {
|
|
86
|
+
if (!keyboard(animation) || !tracking) return;
|
|
87
|
+
tracking = false;
|
|
88
|
+
view.setTranslationY(to);
|
|
89
|
+
listener.onSettled(to);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
package org.rafaelaugusto.jolt;
|
|
2
|
+
|
|
3
|
+
import android.view.View;
|
|
4
|
+
import android.view.ViewTreeObserver;
|
|
5
|
+
import android.view.WindowInsets;
|
|
6
|
+
import android.view.WindowInsetsAnimation;
|
|
7
|
+
import android.view.animation.Interpolator;
|
|
8
|
+
import java.util.List;
|
|
9
|
+
|
|
10
|
+
/** Reports how much of a window the keyboard covers, and how it gets there. Moves nothing. */
|
|
11
|
+
public final class KeyboardObserver extends WindowInsetsAnimation.Callback
|
|
12
|
+
implements ViewTreeObserver.OnGlobalLayoutListener {
|
|
13
|
+
public interface Listener {
|
|
14
|
+
void onChange(int height, long duration, float[] curve);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
private static final int SAMPLES = 32;
|
|
18
|
+
|
|
19
|
+
private final View root;
|
|
20
|
+
private final Listener listener;
|
|
21
|
+
private int height;
|
|
22
|
+
private boolean animating;
|
|
23
|
+
|
|
24
|
+
public KeyboardObserver(View root, Listener listener) {
|
|
25
|
+
super(DISPATCH_MODE_CONTINUE_ON_SUBTREE);
|
|
26
|
+
this.root = root;
|
|
27
|
+
this.listener = listener;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public void attach() {
|
|
31
|
+
height = covered();
|
|
32
|
+
root.setWindowInsetsAnimationCallback(this);
|
|
33
|
+
root.getViewTreeObserver().addOnGlobalLayoutListener(this);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public void detach() {
|
|
37
|
+
root.setWindowInsetsAnimationCallback(null);
|
|
38
|
+
root.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public int height() {
|
|
42
|
+
return height;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private int covered() {
|
|
46
|
+
WindowInsets insets = root.getRootWindowInsets();
|
|
47
|
+
return insets == null ? 0 : insets.getInsets(WindowInsets.Type.ime()).bottom;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private static boolean keyboard(WindowInsetsAnimation animation) {
|
|
51
|
+
return (animation.getTypeMask() & WindowInsets.Type.ime()) != 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private static float[] sample(Interpolator interpolator) {
|
|
55
|
+
if (interpolator == null) return null;
|
|
56
|
+
float[] curve = new float[SAMPLES + 1];
|
|
57
|
+
for (int i = 0; i <= SAMPLES; i++) curve[i] = interpolator.getInterpolation(i / (float) SAMPLES);
|
|
58
|
+
return curve;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private void report(int next, long duration, float[] curve) {
|
|
62
|
+
if (next == height) return;
|
|
63
|
+
height = next;
|
|
64
|
+
listener.onChange(next, duration, curve);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@Override
|
|
68
|
+
public void onPrepare(WindowInsetsAnimation animation) {
|
|
69
|
+
// The layout for the end state comes before onStart; let onStart report it with its timing.
|
|
70
|
+
if (keyboard(animation)) animating = true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@Override
|
|
74
|
+
public WindowInsetsAnimation.Bounds onStart(WindowInsetsAnimation animation, WindowInsetsAnimation.Bounds bounds) {
|
|
75
|
+
if (!keyboard(animation)) return bounds;
|
|
76
|
+
// The end state is already in the root insets when the animation starts.
|
|
77
|
+
report(covered(), animation.getDurationMillis(), sample(animation.getInterpolator()));
|
|
78
|
+
return bounds;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@Override
|
|
82
|
+
public WindowInsets onProgress(WindowInsets insets, List<WindowInsetsAnimation> running) {
|
|
83
|
+
return insets;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@Override
|
|
87
|
+
public void onEnd(WindowInsetsAnimation animation) {
|
|
88
|
+
if (!keyboard(animation)) return;
|
|
89
|
+
animating = false;
|
|
90
|
+
report(covered(), 0, null);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@Override
|
|
94
|
+
public void onGlobalLayout() {
|
|
95
|
+
if (!animating) report(covered(), 0, null);
|
|
96
|
+
}
|
|
97
|
+
}
|