@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,190 @@
|
|
|
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.Paint;
|
|
8
|
+
import android.text.SpannableStringBuilder;
|
|
9
|
+
import android.text.Spanned;
|
|
10
|
+
import android.text.method.TransformationMethod;
|
|
11
|
+
import android.text.style.ReplacementSpan;
|
|
12
|
+
import android.view.animation.LinearInterpolator;
|
|
13
|
+
import android.view.animation.PathInterpolator;
|
|
14
|
+
import android.widget.TextView;
|
|
15
|
+
|
|
16
|
+
/** Rolls the changed positions of a TextView with one native animator. */
|
|
17
|
+
public final class NumberRoll {
|
|
18
|
+
public interface Callback {
|
|
19
|
+
void onEnd(boolean finished);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private static final int MAX_POSITIONS = 256;
|
|
23
|
+
private final TextView view;
|
|
24
|
+
private final CharSequence original;
|
|
25
|
+
private final CharSequence shown;
|
|
26
|
+
private final TransformationMethod method;
|
|
27
|
+
private final DigitSpan[] spans;
|
|
28
|
+
private final int[] starts;
|
|
29
|
+
private final int[] ends;
|
|
30
|
+
private final float[] times;
|
|
31
|
+
private final float[] values;
|
|
32
|
+
private final long duration;
|
|
33
|
+
private final long delay;
|
|
34
|
+
private final long stagger;
|
|
35
|
+
private final long fade;
|
|
36
|
+
private final boolean tabular;
|
|
37
|
+
private final Callback callback;
|
|
38
|
+
private final PathInterpolator ease = new PathInterpolator(0f, 0f, 0.58f, 1f);
|
|
39
|
+
private ValueAnimator animator;
|
|
40
|
+
private CharSequence current;
|
|
41
|
+
private boolean removedMethod;
|
|
42
|
+
private boolean ended;
|
|
43
|
+
|
|
44
|
+
// Positions are ordered from the end of the text; each one starts `stagger` after the previous.
|
|
45
|
+
public NumberRoll(TextView view, int[] starts, int[] ends, String[] previous, int direction,
|
|
46
|
+
float[] times, float[] values, long duration, long delay, long stagger, float shift,
|
|
47
|
+
long fade, boolean tabular, Callback callback) {
|
|
48
|
+
this.view = view;
|
|
49
|
+
this.original = view.getText();
|
|
50
|
+
// NativeScript installs a TransformationMethod that rebuilds the displayed text from its
|
|
51
|
+
// own model, discarding spans set from here. Work on what is displayed and remove it while rolling.
|
|
52
|
+
this.method = view.getTransformationMethod();
|
|
53
|
+
CharSequence displayed = method != null && original != null ? method.getTransformation(original, view) : original;
|
|
54
|
+
this.shown = displayed != null ? displayed : "";
|
|
55
|
+
this.starts = starts;
|
|
56
|
+
this.ends = ends;
|
|
57
|
+
this.times = times;
|
|
58
|
+
this.values = values;
|
|
59
|
+
this.duration = duration;
|
|
60
|
+
this.delay = delay;
|
|
61
|
+
this.stagger = stagger;
|
|
62
|
+
this.fade = fade;
|
|
63
|
+
this.tabular = tabular;
|
|
64
|
+
this.callback = callback;
|
|
65
|
+
spans = new DigitSpan[starts.length];
|
|
66
|
+
for (int i = 0; i < starts.length; i++) {
|
|
67
|
+
String old = i < previous.length && previous[i] != null ? previous[i] : "";
|
|
68
|
+
spans[i] = new DigitSpan(old, direction, shift, values.length > 0 ? values[0] : shift);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
public boolean isSupported() {
|
|
73
|
+
if (original == null || starts.length == 0 || starts.length > MAX_POSITIONS) return false;
|
|
74
|
+
if (ends.length != starts.length || times.length < 2 || times.length != values.length) return false;
|
|
75
|
+
int length = shown.length();
|
|
76
|
+
for (int i = 0; i < starts.length; i++) {
|
|
77
|
+
if (starts[i] < 0 || ends[i] > length || starts[i] >= ends[i]) return false;
|
|
78
|
+
}
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
public void start() {
|
|
83
|
+
if (ended || animator != null) return;
|
|
84
|
+
if (tabular) view.setFontFeatureSettings("tnum");
|
|
85
|
+
SpannableStringBuilder spannable = new SpannableStringBuilder(shown);
|
|
86
|
+
for (int i = 0; i < spans.length; i++) {
|
|
87
|
+
spannable.setSpan(spans[i], starts[i], ends[i], Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
88
|
+
}
|
|
89
|
+
if (method != null) {
|
|
90
|
+
removedMethod = true;
|
|
91
|
+
view.setTransformationMethod(null);
|
|
92
|
+
}
|
|
93
|
+
view.setText(spannable);
|
|
94
|
+
current = view.getText();
|
|
95
|
+
final long total = delay + duration + stagger * (spans.length - 1);
|
|
96
|
+
animator = ValueAnimator.ofFloat(0f, 1f);
|
|
97
|
+
animator.setDuration(Math.max(total, 1));
|
|
98
|
+
animator.setInterpolator(new LinearInterpolator());
|
|
99
|
+
animator.addUpdateListener(animation -> update(animation.getAnimatedFraction() * total));
|
|
100
|
+
animator.addListener(new AnimatorListenerAdapter() {
|
|
101
|
+
@Override
|
|
102
|
+
public void onAnimationEnd(Animator animation) {
|
|
103
|
+
finish(true);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
animator.start();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
public void cancel() {
|
|
110
|
+
finish(false);
|
|
111
|
+
if (animator != null) animator.cancel();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private void update(float elapsed) {
|
|
115
|
+
for (int i = 0; i < spans.length; i++) {
|
|
116
|
+
float local = elapsed - delay - stagger * i;
|
|
117
|
+
spans[i].set(sample(local), alpha(local));
|
|
118
|
+
}
|
|
119
|
+
view.invalidate();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private float sample(float local) {
|
|
123
|
+
if (local <= 0) return values[0];
|
|
124
|
+
if (local >= duration) return values[values.length - 1];
|
|
125
|
+
float t = local / duration;
|
|
126
|
+
int i = 1;
|
|
127
|
+
while (i < times.length - 1 && times[i] < t) i++;
|
|
128
|
+
float span = times[i] - times[i - 1];
|
|
129
|
+
float fraction = span > 0 ? (t - times[i - 1]) / span : 1f;
|
|
130
|
+
return values[i - 1] + (values[i] - values[i - 1]) * fraction;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private float alpha(float local) {
|
|
134
|
+
if (local <= 0) return 0f;
|
|
135
|
+
if (fade <= 0 || local >= fade) return 1f;
|
|
136
|
+
return ease.getInterpolation(local / fade);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private void finish(boolean finished) {
|
|
140
|
+
if (ended) return;
|
|
141
|
+
ended = true;
|
|
142
|
+
if (current != null && view.getText() == current) view.setText(original);
|
|
143
|
+
if (removedMethod) view.setTransformationMethod(method);
|
|
144
|
+
callback.onEnd(finished);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Draws the new glyphs sliding in over the old ones sliding out, clipped to the line. */
|
|
148
|
+
static final class DigitSpan extends ReplacementSpan {
|
|
149
|
+
private final String previous;
|
|
150
|
+
private final int direction;
|
|
151
|
+
private final float shift;
|
|
152
|
+
private float offset;
|
|
153
|
+
private float alpha;
|
|
154
|
+
|
|
155
|
+
DigitSpan(String previous, int direction, float shift, float offset) {
|
|
156
|
+
this.previous = previous;
|
|
157
|
+
this.direction = direction;
|
|
158
|
+
this.shift = shift;
|
|
159
|
+
this.offset = offset;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
void set(float offset, float alpha) {
|
|
163
|
+
this.offset = offset;
|
|
164
|
+
this.alpha = alpha;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
@Override
|
|
168
|
+
public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
|
|
169
|
+
if (fm != null) paint.getFontMetricsInt(fm);
|
|
170
|
+
return Math.round(Math.max(paint.measureText(text, start, end), paint.measureText(previous)));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
@Override
|
|
174
|
+
public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y,
|
|
175
|
+
int bottom, Paint paint) {
|
|
176
|
+
int base = paint.getAlpha();
|
|
177
|
+
float width = getSize(paint, text, start, end, null);
|
|
178
|
+
canvas.save();
|
|
179
|
+
canvas.clipRect(x, top, x + width, bottom);
|
|
180
|
+
paint.setAlpha(Math.round(base * alpha));
|
|
181
|
+
canvas.drawText(text, start, end, x, y + direction * offset, paint);
|
|
182
|
+
if (!previous.isEmpty()) {
|
|
183
|
+
paint.setAlpha(Math.round(base * (1f - alpha)));
|
|
184
|
+
canvas.drawText(previous, x, y + direction * (offset - shift), paint);
|
|
185
|
+
}
|
|
186
|
+
paint.setAlpha(base);
|
|
187
|
+
canvas.restore();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
package org.rafaelaugusto.jolt;
|
|
2
|
+
|
|
3
|
+
import android.animation.ValueAnimator;
|
|
4
|
+
import android.graphics.RectF;
|
|
5
|
+
import android.view.*;
|
|
6
|
+
import android.widget.ImageView;
|
|
7
|
+
|
|
8
|
+
/** Focal zoom, bounds, pan and dismissal; no pointer/frame callback crosses to JS. */
|
|
9
|
+
public final class PhotoZoom implements View.OnTouchListener, View.OnLayoutChangeListener {
|
|
10
|
+
public interface Listener {
|
|
11
|
+
void onDismiss(float x,float y,float scale,float velocity,float progress);
|
|
12
|
+
void onSettled(float x,float y,float scale);
|
|
13
|
+
}
|
|
14
|
+
private final ImageView image;
|
|
15
|
+
private final View viewport,backdrop,chrome;
|
|
16
|
+
private final Listener listener;
|
|
17
|
+
private final View.OnTouchListener previousTouch,previousImageTouch;
|
|
18
|
+
private final ScaleGestureDetector pinch;
|
|
19
|
+
private final GestureDetector taps;
|
|
20
|
+
private final float minimum,maximum,doubleTap,density,slop;
|
|
21
|
+
private final float bitmapWidth,bitmapHeight;
|
|
22
|
+
private final boolean dismiss,originalClickable,originalImageClickable,originalClip,originalClipPadding;
|
|
23
|
+
private final float originalScaleX,originalScaleY,originalX,originalY,backdropAlpha,chromeAlpha;
|
|
24
|
+
private final RectF content=new RectF();
|
|
25
|
+
private float anchorX,anchorY,width,height,x,y,scale,progress;
|
|
26
|
+
private float lastX,lastY,downX,downY,lastFocusX,lastFocusY,vx,vy;
|
|
27
|
+
private float rawPanX,rawPanY,pinchFloor;
|
|
28
|
+
private int pointer=-1,mode; // 0 undecided, 1 pan, 2 dismiss, 3 handed to parent
|
|
29
|
+
private boolean disposed,pending,reduced,active=true;
|
|
30
|
+
private VelocityTracker velocity;
|
|
31
|
+
private ValueAnimator animation;
|
|
32
|
+
|
|
33
|
+
public PhotoZoom(ImageView image,View viewport,float bitmapWidth,float bitmapHeight,float minimum,float maximum,float doubleTap,
|
|
34
|
+
boolean dismiss,boolean reduced,View backdrop,View chrome,
|
|
35
|
+
View.OnTouchListener previousTouch,View.OnTouchListener previousImageTouch,Listener listener) {
|
|
36
|
+
this.image=image;this.viewport=viewport;this.minimum=minimum;this.maximum=maximum;
|
|
37
|
+
this.bitmapWidth=bitmapWidth;this.bitmapHeight=bitmapHeight;
|
|
38
|
+
this.doubleTap=doubleTap;this.dismiss=dismiss;this.reduced=reduced;
|
|
39
|
+
this.backdrop=backdrop;this.chrome=chrome;this.previousTouch=previousTouch;this.listener=listener;
|
|
40
|
+
this.previousImageTouch=previousImageTouch;
|
|
41
|
+
density=image.getResources().getDisplayMetrics().density;
|
|
42
|
+
slop=ViewConfiguration.get(image.getContext()).getScaledTouchSlop();
|
|
43
|
+
originalScaleX=image.getScaleX();originalScaleY=image.getScaleY();
|
|
44
|
+
originalX=image.getTranslationX();originalY=image.getTranslationY();
|
|
45
|
+
originalClickable=viewport.isClickable();
|
|
46
|
+
originalImageClickable=image.isClickable();
|
|
47
|
+
originalClip=!(viewport instanceof ViewGroup)||((ViewGroup)viewport).getClipChildren();
|
|
48
|
+
originalClipPadding=!(viewport instanceof ViewGroup)||((ViewGroup)viewport).getClipToPadding();
|
|
49
|
+
backdropAlpha=backdrop==null?1:backdrop.getAlpha();chromeAlpha=chrome==null?1:chrome.getAlpha();
|
|
50
|
+
scale=minimum;
|
|
51
|
+
pinch=new ScaleGestureDetector(image.getContext(),new ScaleGestureDetector.SimpleOnScaleGestureListener(){
|
|
52
|
+
@Override public boolean onScaleBegin(ScaleGestureDetector detector){
|
|
53
|
+
if(pending || disposed) return false;
|
|
54
|
+
cancelAnimation();mode=1;pinchFloor=Math.min(minimum,scale);
|
|
55
|
+
lastFocusX=detector.getFocusX();lastFocusY=detector.getFocusY();
|
|
56
|
+
disallow(true);return true;
|
|
57
|
+
}
|
|
58
|
+
@Override public boolean onScale(ScaleGestureDetector detector){
|
|
59
|
+
float next=clamp(scale*detector.getScaleFactor(),pinchFloor,maximum);
|
|
60
|
+
float fx=detector.getFocusX(),fy=detector.getFocusY();
|
|
61
|
+
x=fx-anchorX-(lastFocusX-anchorX-x)*next/scale;
|
|
62
|
+
y=fy-anchorY-(lastFocusY-anchorY-y)*next/scale;
|
|
63
|
+
scale=next;lastFocusX=fx;lastFocusY=fy;
|
|
64
|
+
progress=clamp((minimum-scale)/(minimum*.15f),0,1);
|
|
65
|
+
rebasePan();
|
|
66
|
+
apply();return true;
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
taps=new GestureDetector(image.getContext(),new GestureDetector.SimpleOnGestureListener(){
|
|
70
|
+
@Override public boolean onDown(MotionEvent e){return true;}
|
|
71
|
+
@Override public boolean onDoubleTap(MotionEvent e){
|
|
72
|
+
if(!pending) zoom(scale>minimum+.01f?minimum:Math.min(maximum,doubleTap),e.getX(),e.getY(),true);
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
@Override public boolean onSingleTapConfirmed(MotionEvent e){viewport.performClick();return true;}
|
|
76
|
+
});
|
|
77
|
+
viewport.setClickable(true);viewport.setOnTouchListener(this);
|
|
78
|
+
image.setClickable(true);image.setOnTouchListener((v,event)->{
|
|
79
|
+
MotionEvent local=MotionEvent.obtain(event);
|
|
80
|
+
local.transform(image.getMatrix());
|
|
81
|
+
local.offsetLocation(image.getLeft()-viewport.getScrollX(),image.getTop()-viewport.getScrollY());
|
|
82
|
+
boolean consumed=onTouch(viewport,local);local.recycle();return consumed;
|
|
83
|
+
});
|
|
84
|
+
if(viewport instanceof ViewGroup){((ViewGroup)viewport).setClipChildren(true);((ViewGroup)viewport).setClipToPadding(true);}
|
|
85
|
+
viewport.addOnLayoutChangeListener(this);image.addOnLayoutChangeListener(this);
|
|
86
|
+
measure();apply();
|
|
87
|
+
}
|
|
88
|
+
public float getX(){return x/density;}
|
|
89
|
+
public float getY(){return y/density;}
|
|
90
|
+
public float getScale(){return scale;}
|
|
91
|
+
public float getDismissProgress(){return progress;}
|
|
92
|
+
public boolean isDismissPending(){return pending;}
|
|
93
|
+
public void setReduced(boolean value){reduced=value;if(value){if(pending)reset(false);else settle(false);}}
|
|
94
|
+
public void setActive(boolean value){active=value;if(!value){cancelAnimation();endTouch();pending=false;settle(false);}}
|
|
95
|
+
public void zoom(float value,float focusX,float focusY,boolean animated){
|
|
96
|
+
if(disposed || pending) return;
|
|
97
|
+
cancelAnimation();
|
|
98
|
+
float next=clamp(value,minimum,maximum);
|
|
99
|
+
float tx=focusX-anchorX-(focusX-anchorX-x)*next/scale;
|
|
100
|
+
float ty=focusY-anchorY-(focusY-anchorY-y)*next/scale;
|
|
101
|
+
float[] bounds=bounds(next);
|
|
102
|
+
spring(clamp(tx,bounds[0],bounds[1]),clamp(ty,bounds[2],bounds[3]),next,0,0,animated,false);
|
|
103
|
+
}
|
|
104
|
+
public void reset(boolean animated){
|
|
105
|
+
if(disposed)return;
|
|
106
|
+
pending=false;mode=0;float[] b=bounds(minimum);
|
|
107
|
+
spring(clamp(0,b[0],b[1]),clamp(0,b[2],b[3]),minimum,0,0,animated,false);
|
|
108
|
+
}
|
|
109
|
+
public void requestDismiss(){
|
|
110
|
+
if(disposed || pending || !dismiss)return;
|
|
111
|
+
// Programmatic close normalizes zoom before emitting one route intent.
|
|
112
|
+
float[] b=bounds(minimum);
|
|
113
|
+
spring(clamp(0,b[0],b[1]),clamp(0,b[2],b[3]),minimum,0,0,true,true);
|
|
114
|
+
}
|
|
115
|
+
public void stop(){
|
|
116
|
+
if(disposed)return;
|
|
117
|
+
disposed=true;cancelAnimation();endTouch();
|
|
118
|
+
viewport.setOnTouchListener(previousTouch);viewport.setClickable(originalClickable);
|
|
119
|
+
image.setOnTouchListener(previousImageTouch);image.setClickable(originalImageClickable);
|
|
120
|
+
if(viewport instanceof ViewGroup){((ViewGroup)viewport).setClipChildren(originalClip);((ViewGroup)viewport).setClipToPadding(originalClipPadding);}
|
|
121
|
+
viewport.removeOnLayoutChangeListener(this);image.removeOnLayoutChangeListener(this);
|
|
122
|
+
image.setScaleX(originalScaleX);image.setScaleY(originalScaleY);
|
|
123
|
+
image.setTranslationX(originalX);image.setTranslationY(originalY);
|
|
124
|
+
if(backdrop!=null)backdrop.setAlpha(backdropAlpha);if(chrome!=null)chrome.setAlpha(chromeAlpha);
|
|
125
|
+
}
|
|
126
|
+
private void measure(){
|
|
127
|
+
width=viewport.getWidth();height=viewport.getHeight();
|
|
128
|
+
float iw=image.getWidth(),ih=image.getHeight();
|
|
129
|
+
float left=image.getPaddingLeft(),top=image.getPaddingTop();
|
|
130
|
+
float cw=iw-left-image.getPaddingRight(),ch=ih-top-image.getPaddingBottom();
|
|
131
|
+
float dw=Math.max(1,bitmapWidth),dh=Math.max(1,bitmapHeight);
|
|
132
|
+
ImageView.ScaleType fit=image.getScaleType();
|
|
133
|
+
float factor=fit==ImageView.ScaleType.CENTER_CROP?Math.max(cw/dw,ch/dh):Math.min(cw/dw,ch/dh);
|
|
134
|
+
if(fit==ImageView.ScaleType.FIT_XY){dw=cw;dh=ch;}
|
|
135
|
+
else if(fit!=ImageView.ScaleType.CENTER && fit!=ImageView.ScaleType.MATRIX){dw*=factor;dh*=factor;}
|
|
136
|
+
anchorX=image.getLeft()+image.getPivotX()+originalX;
|
|
137
|
+
anchorY=image.getTop()+image.getPivotY()+originalY;
|
|
138
|
+
// ImageView clips the raster before its outer transform is applied.
|
|
139
|
+
float ox=left+(fit==ImageView.ScaleType.MATRIX?0:(cw-dw)/2),oy=top+(fit==ImageView.ScaleType.MATRIX?0:(ch-dh)/2);
|
|
140
|
+
content.set((Math.max(left,ox)-image.getPivotX())*originalScaleX,
|
|
141
|
+
(Math.max(top,oy)-image.getPivotY())*originalScaleY,
|
|
142
|
+
(Math.min(left+cw,ox+dw)-image.getPivotX())*originalScaleX,
|
|
143
|
+
(Math.min(top+ch,oy+dh)-image.getPivotY())*originalScaleY);
|
|
144
|
+
}
|
|
145
|
+
private float[] bounds(float s){
|
|
146
|
+
float loX=width-anchorX-content.right*s,hiX=-anchorX-content.left*s;
|
|
147
|
+
float loY=height-anchorY-content.bottom*s,hiY=-anchorY-content.top*s;
|
|
148
|
+
if(loX>hiX)loX=hiX=(loX+hiX)/2;
|
|
149
|
+
if(loY>hiY)loY=hiY=(loY+hiY)/2;
|
|
150
|
+
return new float[]{loX,hiX,loY,hiY};
|
|
151
|
+
}
|
|
152
|
+
private void apply(){
|
|
153
|
+
image.setScaleX(originalScaleX*scale);image.setScaleY(originalScaleY*scale);
|
|
154
|
+
image.setTranslationX(originalX+x);image.setTranslationY(originalY+y);
|
|
155
|
+
if(backdrop!=null)backdrop.setAlpha(backdropAlpha*(1-progress));
|
|
156
|
+
if(chrome!=null)chrome.setAlpha(chromeAlpha*(1-progress));
|
|
157
|
+
}
|
|
158
|
+
private void settle(boolean animated){
|
|
159
|
+
if(disposed)return;
|
|
160
|
+
float s=clamp(scale,minimum,maximum),b[]=bounds(s);
|
|
161
|
+
float tx=clamp(x,b[0],b[1]),ty=clamp(y,b[2],b[3]);
|
|
162
|
+
if(mode==2){s=minimum;b=bounds(s);tx=clamp(0,b[0],b[1]);ty=clamp(0,b[2],b[3]);}
|
|
163
|
+
else {tx=clamp(tx+vx*.12f,b[0],b[1]);ty=clamp(ty+vy*.12f,b[2],b[3]);}
|
|
164
|
+
spring(tx,ty,s,vx,vy,animated,false);
|
|
165
|
+
}
|
|
166
|
+
private void cancelAnimation(){if(animation!=null){animation.cancel();animation=null;}}
|
|
167
|
+
private void spring(float tx,float ty,float targetScale,float velocityX,float velocityY,boolean animated,boolean close){
|
|
168
|
+
cancelAnimation();
|
|
169
|
+
final float sx=x,sy=y,ss=scale,sp=progress;
|
|
170
|
+
if(!animated || reduced){x=tx;y=ty;scale=targetScale;progress=0;apply();if(close)emitDismiss(0);else listener.onSettled(x/density,y/density,scale);return;}
|
|
171
|
+
animation=ValueAnimator.ofFloat(0,1);animation.setDuration(550);
|
|
172
|
+
animation.setInterpolator(new android.view.animation.LinearInterpolator());
|
|
173
|
+
animation.addUpdateListener(a->{
|
|
174
|
+
float fraction=(float)a.getAnimatedValue(),t=fraction*.55f;
|
|
175
|
+
float decay=(float)Math.exp(-16*t);
|
|
176
|
+
x=tx+((sx-tx)+(velocityX+16*(sx-tx))*t)*decay;
|
|
177
|
+
y=ty+((sy-ty)+(velocityY+16*(sy-ty))*t)*decay;
|
|
178
|
+
scale=targetScale+(ss-targetScale)*(1+16*t)*decay;
|
|
179
|
+
progress=sp*(1+16*t)*decay;
|
|
180
|
+
if(fraction>=1){x=tx;y=ty;scale=targetScale;progress=0;}
|
|
181
|
+
apply();
|
|
182
|
+
if(fraction>=1){animation=null;if(close)emitDismiss(0);else listener.onSettled(x/density,y/density,scale);}
|
|
183
|
+
});animation.start();
|
|
184
|
+
}
|
|
185
|
+
private void emitDismiss(float velocityY){
|
|
186
|
+
if(pending || disposed)return;
|
|
187
|
+
pending=true;listener.onDismiss(x/density,y/density,scale,velocityY/density,progress);
|
|
188
|
+
}
|
|
189
|
+
private void disallow(boolean value){if(viewport.getParent()!=null)viewport.getParent().requestDisallowInterceptTouchEvent(value);}
|
|
190
|
+
private void endTouch(){pointer=-1;if(velocity!=null){velocity.recycle();velocity=null;}disallow(false);}
|
|
191
|
+
@Override public boolean onTouch(View view,MotionEvent event){
|
|
192
|
+
if(disposed || !active || pending)return false;
|
|
193
|
+
int action=event.getActionMasked();
|
|
194
|
+
if(action==MotionEvent.ACTION_DOWN){
|
|
195
|
+
cancelAnimation();mode=0;vx=vy=0;
|
|
196
|
+
pointer=event.getPointerId(0);lastX=downX=event.getX();lastY=downY=event.getY();
|
|
197
|
+
rebasePan();
|
|
198
|
+
if(velocity!=null)velocity.recycle();velocity=VelocityTracker.obtain();disallow(true);
|
|
199
|
+
}
|
|
200
|
+
if(velocity!=null)velocity.addMovement(event);
|
|
201
|
+
pinch.onTouchEvent(event);taps.onTouchEvent(event);
|
|
202
|
+
int index=event.findPointerIndex(pointer);
|
|
203
|
+
if(action==MotionEvent.ACTION_MOVE && !pinch.isInProgress() && event.getPointerCount()==1 && index>=0){
|
|
204
|
+
float px=event.getX(index),py=event.getY(index),totalX=px-downX,totalY=py-downY;
|
|
205
|
+
if(mode==0 && Math.hypot(totalX,totalY)>slop){
|
|
206
|
+
if(scale<=minimum+.001f){
|
|
207
|
+
if(dismiss && Math.abs(totalY)>Math.abs(totalX)*1.2f)mode=2;
|
|
208
|
+
else {mode=3;disallow(false);return false;}
|
|
209
|
+
}else mode=1;
|
|
210
|
+
}
|
|
211
|
+
if(mode==1){
|
|
212
|
+
float[] b=bounds(scale);
|
|
213
|
+
rawPanX+=px-lastX;rawPanY+=py-lastY;
|
|
214
|
+
x=rubber(rawPanX,b[0],b[1],width);y=rubber(rawPanY,b[2],b[3],height);
|
|
215
|
+
}else if(mode==2){
|
|
216
|
+
x+= (px-lastX)*.4f;y+=py-lastY;
|
|
217
|
+
progress=clamp(Math.abs(y)/Math.max(height*.5f,1),0,1);
|
|
218
|
+
scale=minimum*(1-progress*.15f);
|
|
219
|
+
}
|
|
220
|
+
lastX=px;lastY=py;apply();
|
|
221
|
+
}
|
|
222
|
+
if(action==MotionEvent.ACTION_POINTER_UP){
|
|
223
|
+
int next=event.getActionIndex()==0?1:0;
|
|
224
|
+
if(next<event.getPointerCount()){pointer=event.getPointerId(next);lastX=downX=event.getX(next);lastY=downY=event.getY(next);rebasePan();}
|
|
225
|
+
}
|
|
226
|
+
if(action==MotionEvent.ACTION_UP || action==MotionEvent.ACTION_CANCEL){
|
|
227
|
+
if(velocity!=null){velocity.computeCurrentVelocity(1000);vx=velocity.getXVelocity(pointer);vy=velocity.getYVelocity(pointer);}
|
|
228
|
+
boolean commit=action==MotionEvent.ACTION_UP && mode==2 && (progress>.35f || (Math.abs(vy)>1000*density && y*vy>0));
|
|
229
|
+
boolean wasAnimation=animation!=null;
|
|
230
|
+
endTouch();
|
|
231
|
+
if(commit)emitDismiss(vy);
|
|
232
|
+
else if(mode!=3 && !wasAnimation)settle(true);
|
|
233
|
+
}
|
|
234
|
+
return mode!=3;
|
|
235
|
+
}
|
|
236
|
+
@Override public void onLayoutChange(View v,int l,int t,int r,int b,int ol,int ot,int or,int ob){
|
|
237
|
+
if(disposed || (l==ol&&t==ot&&r==or&&b==ob))return;
|
|
238
|
+
cancelAnimation();pending=false;measure();vx=vy=0;mode=0;settle(false);
|
|
239
|
+
}
|
|
240
|
+
private static float clamp(float v,float lo,float hi){return Math.max(lo,Math.min(hi,v));}
|
|
241
|
+
private void rebasePan(){float[] b=bounds(scale);rawPanX=unrubber(x,b[0],b[1],width);rawPanY=unrubber(y,b[2],b[3],height);}
|
|
242
|
+
private static float unrubber(float v,float lo,float hi,float dimension){
|
|
243
|
+
float edge=clamp(v,lo,hi),d=v-edge,size=Math.max(1,dimension);
|
|
244
|
+
return edge+d/(.55f*Math.max(.001f,1-Math.abs(d)/size));
|
|
245
|
+
}
|
|
246
|
+
private static float rubber(float value,float lo,float hi,float dimension){
|
|
247
|
+
float edge=clamp(value,lo,hi),d=value-edge;
|
|
248
|
+
return edge+Math.signum(d)*dimension*(1-1/(Math.abs(d)*.55f/Math.max(1,dimension)+1));
|
|
249
|
+
}
|
|
250
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
package org.rafaelaugusto.jolt;
|
|
2
|
+
|
|
3
|
+
import android.animation.Animator;
|
|
4
|
+
import android.animation.AnimatorListenerAdapter;
|
|
5
|
+
import android.animation.ValueAnimator;
|
|
6
|
+
import android.text.SpannableStringBuilder;
|
|
7
|
+
import android.text.Spanned;
|
|
8
|
+
import android.text.TextPaint;
|
|
9
|
+
import android.text.method.TransformationMethod;
|
|
10
|
+
import android.text.style.CharacterStyle;
|
|
11
|
+
import android.text.style.UpdateAppearance;
|
|
12
|
+
import android.view.animation.Interpolator;
|
|
13
|
+
import android.view.animation.LinearInterpolator;
|
|
14
|
+
import android.view.animation.PathInterpolator;
|
|
15
|
+
import android.widget.TextView;
|
|
16
|
+
import java.text.BreakIterator;
|
|
17
|
+
import java.util.ArrayList;
|
|
18
|
+
import java.util.List;
|
|
19
|
+
|
|
20
|
+
/** Fades a TextView's text in piece by piece. Layout, truncation and the real text stay as they are. */
|
|
21
|
+
public final class TextReveal {
|
|
22
|
+
public interface Callback {
|
|
23
|
+
void onEnd(boolean finished);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private static final int MAX_PIECES = 256;
|
|
27
|
+
private static final Interpolator EASE_OUT = new PathInterpolator(0f, 0f, 0.58f, 1f);
|
|
28
|
+
private final TextView view;
|
|
29
|
+
private final long duration;
|
|
30
|
+
private final long stagger;
|
|
31
|
+
private final long delay;
|
|
32
|
+
private final float offset;
|
|
33
|
+
private final Callback callback;
|
|
34
|
+
private final CharSequence original;
|
|
35
|
+
private final CharSequence shown;
|
|
36
|
+
private final TransformationMethod method;
|
|
37
|
+
private final Piece[] pieces;
|
|
38
|
+
private ValueAnimator animator;
|
|
39
|
+
private boolean settled;
|
|
40
|
+
|
|
41
|
+
public TextReveal(TextView view, boolean byWord, long duration, long stagger, long delay, float offset, Callback callback) {
|
|
42
|
+
this.view = view;
|
|
43
|
+
this.duration = Math.max(duration, 1);
|
|
44
|
+
this.stagger = stagger;
|
|
45
|
+
this.delay = delay;
|
|
46
|
+
this.offset = offset;
|
|
47
|
+
this.callback = callback;
|
|
48
|
+
CharSequence text = view.getText();
|
|
49
|
+
this.original = text == null ? "" : text;
|
|
50
|
+
// NativeScript's transformation method rebuilds the displayed text from its own model,
|
|
51
|
+
// so pieces are measured on what is drawn and the spans go on that text.
|
|
52
|
+
this.method = view.getTransformationMethod();
|
|
53
|
+
CharSequence displayed = method != null ? method.getTransformation(original, view) : original;
|
|
54
|
+
this.shown = displayed == null ? "" : displayed;
|
|
55
|
+
this.pieces = split(shown.toString(), byWord);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public boolean isSupported() {
|
|
59
|
+
return pieces.length <= MAX_PIECES;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public void start() {
|
|
63
|
+
if (settled || animator != null) return;
|
|
64
|
+
if (pieces.length == 0) {
|
|
65
|
+
settle(true);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
SpannableStringBuilder spannable = new SpannableStringBuilder(shown);
|
|
69
|
+
for (Piece piece : pieces) {
|
|
70
|
+
piece.alpha = 0f;
|
|
71
|
+
piece.shift = offset;
|
|
72
|
+
spannable.setSpan(piece, piece.start, piece.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
73
|
+
}
|
|
74
|
+
if (method != null) view.setTransformationMethod(null);
|
|
75
|
+
view.setText(spannable);
|
|
76
|
+
final long total = delay + duration + (pieces.length - 1) * stagger;
|
|
77
|
+
animator = ValueAnimator.ofFloat(0f, 1f);
|
|
78
|
+
animator.setDuration(total);
|
|
79
|
+
animator.setInterpolator(new LinearInterpolator());
|
|
80
|
+
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
|
81
|
+
@Override
|
|
82
|
+
public void onAnimationUpdate(ValueAnimator animation) {
|
|
83
|
+
update(animation.getAnimatedFraction() * total);
|
|
84
|
+
view.invalidate();
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
animator.addListener(new AnimatorListenerAdapter() {
|
|
88
|
+
@Override
|
|
89
|
+
public void onAnimationEnd(Animator animation) {
|
|
90
|
+
settle(true);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
animator.start();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
public void cancel() {
|
|
97
|
+
settle(false);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private void update(float time) {
|
|
101
|
+
for (int i = 0; i < pieces.length; i++) {
|
|
102
|
+
float local = (time - delay - i * stagger) / duration;
|
|
103
|
+
float eased = local <= 0f ? 0f : local >= 1f ? 1f : EASE_OUT.getInterpolation(local);
|
|
104
|
+
pieces[i].alpha = eased;
|
|
105
|
+
pieces[i].shift = offset * (1f - eased);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private void settle(boolean finished) {
|
|
110
|
+
if (settled) return;
|
|
111
|
+
settled = true;
|
|
112
|
+
ValueAnimator running = animator;
|
|
113
|
+
animator = null;
|
|
114
|
+
if (running != null) running.cancel();
|
|
115
|
+
CharSequence current = view.getText();
|
|
116
|
+
// An app change already replaced the spans; its text wins.
|
|
117
|
+
if (current instanceof Spanned && ((Spanned) current).getSpans(0, current.length(), Piece.class).length > 0) {
|
|
118
|
+
view.setText(original);
|
|
119
|
+
}
|
|
120
|
+
if (method != null && view.getTransformationMethod() == null) view.setTransformationMethod(method);
|
|
121
|
+
callback.onEnd(finished);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private static Piece[] split(String text, boolean byWord) {
|
|
125
|
+
List<Piece> pieces = new ArrayList<>();
|
|
126
|
+
BreakIterator iterator = byWord ? BreakIterator.getWordInstance() : BreakIterator.getCharacterInstance();
|
|
127
|
+
iterator.setText(text);
|
|
128
|
+
int start = iterator.first();
|
|
129
|
+
for (int end = iterator.next(); end != BreakIterator.DONE; start = end, end = iterator.next()) {
|
|
130
|
+
if (byWord ? isWord(text, start, end) : !isBlank(text, start, end)) pieces.add(new Piece(start, end));
|
|
131
|
+
}
|
|
132
|
+
if (byWord) {
|
|
133
|
+
// Spaces and punctuation stay with the word before them, so every visible mark is revealed.
|
|
134
|
+
for (int i = 0; i < pieces.size(); i++) {
|
|
135
|
+
Piece piece = pieces.get(i);
|
|
136
|
+
if (i == 0) piece.start = 0;
|
|
137
|
+
piece.end = i + 1 < pieces.size() ? pieces.get(i + 1).start : text.length();
|
|
138
|
+
}
|
|
139
|
+
if (pieces.isEmpty() && !isBlank(text, 0, text.length())) pieces.add(new Piece(0, text.length()));
|
|
140
|
+
}
|
|
141
|
+
return pieces.toArray(new Piece[0]);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private static boolean isWord(String text, int start, int end) {
|
|
145
|
+
for (int i = start; i < end; ) {
|
|
146
|
+
int point = text.codePointAt(i);
|
|
147
|
+
if (Character.isLetterOrDigit(point)) return true;
|
|
148
|
+
i += Character.charCount(point);
|
|
149
|
+
}
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private static boolean isBlank(String text, int start, int end) {
|
|
154
|
+
for (int i = start; i < end; ) {
|
|
155
|
+
int point = text.codePointAt(i);
|
|
156
|
+
if (!Character.isWhitespace(point) && !Character.isSpaceChar(point)) return false;
|
|
157
|
+
i += Character.charCount(point);
|
|
158
|
+
}
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Only changes how its range is drawn, so the TextView's layout never sees it. */
|
|
163
|
+
private static final class Piece extends CharacterStyle implements UpdateAppearance {
|
|
164
|
+
int start;
|
|
165
|
+
int end;
|
|
166
|
+
float alpha;
|
|
167
|
+
float shift;
|
|
168
|
+
|
|
169
|
+
Piece(int start, int end) {
|
|
170
|
+
this.start = start;
|
|
171
|
+
this.end = end;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
@Override
|
|
175
|
+
public void updateDrawState(TextPaint paint) {
|
|
176
|
+
paint.setAlpha(Math.round(paint.getAlpha() * alpha));
|
|
177
|
+
paint.baselineShift += Math.round(shift);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
-keep class org.rafaelaugusto.jolt.ColorTarget { *; }
|
|
2
|
+
-keep class org.nativescript.widgets.BorderDrawable { *; }
|
|
3
|
+
-keepnames class org.nativescript.widgets.BoxShadowDrawable
|
|
4
|
+
-keepclassmembers class org.nativescript.widgets.BoxShadowDrawable {
|
|
5
|
+
public android.graphics.drawable.Drawable getWrappedDrawable();
|
|
6
|
+
}
|
|
7
|
+
-keep class org.rafaelaugusto.jolt.DecorationTarget { *; }
|
|
8
|
+
-keep class org.rafaelaugusto.jolt.KeyboardFollower { *; }
|
|
9
|
+
-keep class org.rafaelaugusto.jolt.KeyboardFollower$* { *; }
|
|
10
|
+
-keep class org.rafaelaugusto.jolt.KeyboardObserver { *; }
|
|
11
|
+
-keep class org.rafaelaugusto.jolt.KeyboardObserver$* { *; }
|
|
12
|
+
-keep class org.rafaelaugusto.jolt.TextReveal { *; }
|
|
13
|
+
-keep class org.rafaelaugusto.jolt.TextReveal$* { *; }
|
|
14
|
+
-keep class org.rafaelaugusto.jolt.NumberRoll { *; }
|
|
15
|
+
-keep class org.rafaelaugusto.jolt.NumberRoll$* { *; }
|
|
16
|
+
-keep class org.rafaelaugusto.jolt.ImageEffect { *; }
|
|
17
|
+
-keep class org.rafaelaugusto.jolt.ImageEffect$* { *; }
|
|
18
|
+
-keep class org.rafaelaugusto.jolt.PhotoZoom { *; }
|
|
19
|
+
-keep class org.rafaelaugusto.jolt.PhotoZoom$* { *; }
|
|
20
|
+
-keep class org.rafaelaugusto.jolt.GradientLayer { *; }
|
|
21
|
+
-keep class org.rafaelaugusto.jolt.GradientLayer$* { *; }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
OTHER_LDFLAGS = $(inherited) -framework Metal -framework MetalKit
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#import <UIKit/UIKit.h>
|
|
2
|
+
|
|
3
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
4
|
+
@interface JoltImageEffect : NSObject
|
|
5
|
+
+ (nullable instancetype)createWithView:(UIImageView *)view
|
|
6
|
+
destination:(nullable UIImage *)destination
|
|
7
|
+
mode:(NSInteger)mode
|
|
8
|
+
originX:(double)x originY:(double)y
|
|
9
|
+
amplitude:(double)amplitude radius:(double)radius
|
|
10
|
+
duration:(double)duration seed:(double)seed
|
|
11
|
+
listener:(void (^)(BOOL, double))listener;
|
|
12
|
+
@property(nonatomic, readonly) double progress;
|
|
13
|
+
@property(nonatomic, readonly) BOOL paused;
|
|
14
|
+
@property(nonatomic, readonly) NSUInteger frames;
|
|
15
|
+
@property(nonatomic, readonly) double longestFrame;
|
|
16
|
+
@property(nonatomic, readonly) NSUInteger textureBytes;
|
|
17
|
+
- (void)play:(double)endpoint;
|
|
18
|
+
- (void)pause;
|
|
19
|
+
- (void)resume;
|
|
20
|
+
- (void)seek:(double)progress;
|
|
21
|
+
- (void)finish:(double)endpoint;
|
|
22
|
+
- (void)stop;
|
|
23
|
+
- (void)stopPreservingImage;
|
|
24
|
+
@end
|
|
25
|
+
NS_ASSUME_NONNULL_END
|