@nativescript-community/ui-refreshlayout 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/LICENSE +201 -0
- package/README.md +197 -2
- package/index-common.d.ts +41 -0
- package/index-common.js +59 -0
- package/index-common.js.map +1 -0
- package/index.android.d.ts +18 -0
- package/index.android.js +62 -0
- package/index.android.js.map +1 -0
- package/index.d.ts +35 -0
- package/index.ios.d.ts +49 -0
- package/index.ios.js +339 -0
- package/index.ios.js.map +1 -0
- package/package.json +49 -5
- package/platforms/android/AndroidManifest.xml +2 -0
- package/platforms/android/include.gradle +4 -0
- package/platforms/android/java/com/nativescript/ui/refreshlayout/RefreshLayout.java +284 -0
- package/platforms/android/native-api-usage.json +6 -0
- package/platforms/ios/native-api-usage.json +11 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
package com.nativescript.ui.refreshlayout;
|
|
2
|
+
|
|
3
|
+
import android.animation.Animator;
|
|
4
|
+
import android.animation.AnimatorListenerAdapter;
|
|
5
|
+
import android.animation.ValueAnimator;
|
|
6
|
+
import android.content.Context;
|
|
7
|
+
import android.graphics.Color;
|
|
8
|
+
import android.graphics.drawable.ShapeDrawable;
|
|
9
|
+
import android.graphics.drawable.shapes.OvalShape;
|
|
10
|
+
import android.util.TypedValue;
|
|
11
|
+
import android.view.MotionEvent;
|
|
12
|
+
import android.view.View;
|
|
13
|
+
import android.view.ViewConfiguration;
|
|
14
|
+
import android.view.animation.DecelerateInterpolator;
|
|
15
|
+
|
|
16
|
+
import androidx.core.view.ViewCompat;
|
|
17
|
+
import androidx.swiperefreshlayout.widget.CircularProgressDrawable;
|
|
18
|
+
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* SwipeRefreshLayout (pull from the top) which also supports a pull from the bottom.
|
|
22
|
+
* A horizontal drag is not intercepted, so a horizontal carousel can be inside (same as the CarouselFriendlySwipeRefreshLayout of the former ui-refreshlayout).
|
|
23
|
+
* Both follow the nested scrolling of the child (RecyclerView/CollectionView, NestedScrollView...): the top is the visual top, whatever the layout direction of the child.
|
|
24
|
+
*/
|
|
25
|
+
public class RefreshLayout extends SwipeRefreshLayout {
|
|
26
|
+
private static final float DRAG_RATE = 0.5f;
|
|
27
|
+
private static final int SPINNER_SIZE_DP = 40;
|
|
28
|
+
private static final int TRIGGER_DP = 80;
|
|
29
|
+
private static final int MAX_PULL_DP = 160;
|
|
30
|
+
|
|
31
|
+
private boolean mTopEnabled = true;
|
|
32
|
+
private boolean mBottomEnabled = false;
|
|
33
|
+
private boolean mBottomRefreshing = false;
|
|
34
|
+
private float mBottomPull = 0;
|
|
35
|
+
private final int mTrigger;
|
|
36
|
+
private final int mMaxPull;
|
|
37
|
+
private final int mSpinnerSize;
|
|
38
|
+
private View mTarget;
|
|
39
|
+
private ValueAnimator mAnimator;
|
|
40
|
+
|
|
41
|
+
private final CircularProgressDrawable mSpinner;
|
|
42
|
+
private final ShapeDrawable mSpinnerBackground;
|
|
43
|
+
private Runnable mBottomRefreshListener;
|
|
44
|
+
private final float mTouchSlop;
|
|
45
|
+
private float mPreviousX;
|
|
46
|
+
|
|
47
|
+
public RefreshLayout(Context context) {
|
|
48
|
+
super(context);
|
|
49
|
+
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
|
|
50
|
+
float density = context.getResources().getDisplayMetrics().density;
|
|
51
|
+
mTrigger = (int) (TRIGGER_DP * density);
|
|
52
|
+
mMaxPull = (int) (MAX_PULL_DP * density);
|
|
53
|
+
mSpinnerSize = (int) (SPINNER_SIZE_DP * density);
|
|
54
|
+
mSpinner = new CircularProgressDrawable(context);
|
|
55
|
+
mSpinner.setStyle(CircularProgressDrawable.DEFAULT);
|
|
56
|
+
mSpinnerBackground = new ShapeDrawable(new OvalShape());
|
|
57
|
+
TypedValue value = new TypedValue();
|
|
58
|
+
int background = context.getTheme().resolveAttribute(com.google.android.material.R.attr.colorSurfaceContainerHigh, value, true) ? value.data : Color.WHITE;
|
|
59
|
+
mSpinnerBackground.getPaint().setColor(background);
|
|
60
|
+
int primary = context.getTheme().resolveAttribute(androidx.appcompat.R.attr.colorPrimary, value, true) ? value.data : Color.BLACK;
|
|
61
|
+
mSpinner.setColorSchemeColors(primary);
|
|
62
|
+
setColorSchemeColors(primary);
|
|
63
|
+
setProgressBackgroundColorSchemeColor(background);
|
|
64
|
+
getOverlay().add(mSpinnerBackground);
|
|
65
|
+
getOverlay().add(mSpinner);
|
|
66
|
+
mSpinnerBackground.setAlpha(0);
|
|
67
|
+
mSpinner.setAlpha(0);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ---- configuration
|
|
71
|
+
|
|
72
|
+
public void setTopEnabled(boolean enabled) {
|
|
73
|
+
mTopEnabled = enabled;
|
|
74
|
+
setEnabled(enabled);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public void setBottomEnabled(boolean enabled) {
|
|
78
|
+
mBottomEnabled = enabled;
|
|
79
|
+
if (!enabled && (mBottomPull > 0 || mBottomRefreshing)) {
|
|
80
|
+
setBottomRefreshing(false);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
public boolean isBottomRefreshing() {
|
|
85
|
+
return mBottomRefreshing;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public void setBottomRefreshListener(Runnable listener) {
|
|
89
|
+
mBottomRefreshListener = listener;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public void setIndicatorColor(int color) {
|
|
93
|
+
mSpinner.setColorSchemeColors(color);
|
|
94
|
+
setColorSchemeColors(color);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
public void setIndicatorBackgroundColor(int color) {
|
|
98
|
+
mSpinnerBackground.getPaint().setColor(color);
|
|
99
|
+
setProgressBackgroundColorSchemeColor(color);
|
|
100
|
+
invalidate();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ---- bottom refresh state
|
|
104
|
+
|
|
105
|
+
public void setBottomRefreshing(boolean refreshing) {
|
|
106
|
+
if (refreshing == mBottomRefreshing) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
mBottomRefreshing = refreshing;
|
|
110
|
+
if (refreshing) {
|
|
111
|
+
// programmatic start: the spinner at its resting position
|
|
112
|
+
animatePullTo(mTrigger, false);
|
|
113
|
+
startSpinner();
|
|
114
|
+
} else {
|
|
115
|
+
mSpinner.stop();
|
|
116
|
+
animatePullTo(0, true);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private void startSpinner() {
|
|
121
|
+
mSpinner.setAlpha(255);
|
|
122
|
+
mSpinnerBackground.setAlpha(255);
|
|
123
|
+
mSpinner.setStartEndTrim(0f, 0.8f);
|
|
124
|
+
mSpinner.start();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private View getTarget() {
|
|
128
|
+
if (mTarget == null) {
|
|
129
|
+
for (int i = 0; i < getChildCount(); i++) {
|
|
130
|
+
View child = getChildAt(i);
|
|
131
|
+
// the circle of the SwipeRefreshLayout is a CircleImageView (an ImageView)
|
|
132
|
+
if (!(child instanceof android.widget.ImageView) && child.getVisibility() != GONE) {
|
|
133
|
+
mTarget = child;
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return mTarget;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@Override
|
|
142
|
+
public void onViewRemoved(View child) {
|
|
143
|
+
super.onViewRemoved(child);
|
|
144
|
+
if (child == mTarget) {
|
|
145
|
+
mTarget = null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private void applyPull(float pull) {
|
|
150
|
+
mBottomPull = pull;
|
|
151
|
+
View target = getTarget();
|
|
152
|
+
if (target != null) {
|
|
153
|
+
target.setTranslationY(-pull);
|
|
154
|
+
}
|
|
155
|
+
positionSpinner(pull);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private void positionSpinner(float pull) {
|
|
159
|
+
int width = getWidth();
|
|
160
|
+
int height = getHeight();
|
|
161
|
+
float progress = Math.min(1f, pull / mTrigger);
|
|
162
|
+
// centered in the space freed at the bottom
|
|
163
|
+
int centerY = (int) (height - pull / 2f);
|
|
164
|
+
int left = (width - mSpinnerSize) / 2;
|
|
165
|
+
int top = centerY - mSpinnerSize / 2;
|
|
166
|
+
mSpinnerBackground.setBounds(left, top, left + mSpinnerSize, top + mSpinnerSize);
|
|
167
|
+
int inset = mSpinnerSize / 5;
|
|
168
|
+
mSpinner.setBounds(left + inset, top + inset, left + mSpinnerSize - inset, top + mSpinnerSize - inset);
|
|
169
|
+
if (!mBottomRefreshing) {
|
|
170
|
+
int alpha = (int) (255 * Math.min(1f, progress * 1.5f));
|
|
171
|
+
mSpinnerBackground.setAlpha(alpha);
|
|
172
|
+
mSpinner.setAlpha(alpha);
|
|
173
|
+
mSpinner.setStartEndTrim(0f, 0.8f * progress);
|
|
174
|
+
mSpinner.setProgressRotation(progress * 0.5f);
|
|
175
|
+
mSpinner.setArrowEnabled(progress > 0.1f);
|
|
176
|
+
mSpinner.setArrowScale(progress);
|
|
177
|
+
}
|
|
178
|
+
invalidate();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private void animatePullTo(float to, final boolean hideSpinner) {
|
|
182
|
+
if (mAnimator != null) {
|
|
183
|
+
mAnimator.cancel();
|
|
184
|
+
}
|
|
185
|
+
mAnimator = ValueAnimator.ofFloat(mBottomPull, to);
|
|
186
|
+
mAnimator.setDuration(250);
|
|
187
|
+
mAnimator.setInterpolator(new DecelerateInterpolator());
|
|
188
|
+
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
|
189
|
+
@Override
|
|
190
|
+
public void onAnimationUpdate(ValueAnimator animation) {
|
|
191
|
+
applyPull((Float) animation.getAnimatedValue());
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
if (hideSpinner) {
|
|
195
|
+
mAnimator.addListener(new AnimatorListenerAdapter() {
|
|
196
|
+
@Override
|
|
197
|
+
public void onAnimationEnd(Animator animation) {
|
|
198
|
+
mSpinner.setAlpha(0);
|
|
199
|
+
mSpinnerBackground.setAlpha(0);
|
|
200
|
+
invalidate();
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
mAnimator.start();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
@Override
|
|
208
|
+
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
|
209
|
+
super.onLayout(changed, left, top, right, bottom);
|
|
210
|
+
if (mBottomPull > 0) {
|
|
211
|
+
positionSpinner(mBottomPull);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
@Override
|
|
216
|
+
public boolean onInterceptTouchEvent(MotionEvent event) {
|
|
217
|
+
switch (event.getAction()) {
|
|
218
|
+
case MotionEvent.ACTION_DOWN:
|
|
219
|
+
mPreviousX = event.getX();
|
|
220
|
+
break;
|
|
221
|
+
case MotionEvent.ACTION_MOVE:
|
|
222
|
+
if (Math.abs(event.getX() - mPreviousX) > mTouchSlop) {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
return super.onInterceptTouchEvent(event);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ---- nested scrolling
|
|
231
|
+
|
|
232
|
+
@Override
|
|
233
|
+
public boolean onStartNestedScroll(View child, View target, int axes, int type) {
|
|
234
|
+
boolean top = mTopEnabled && super.onStartNestedScroll(child, target, axes, type);
|
|
235
|
+
return top || (mBottomEnabled && (axes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0 && type == ViewCompat.TYPE_TOUCH);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
@Override
|
|
239
|
+
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed, int type) {
|
|
240
|
+
if (mTopEnabled) {
|
|
241
|
+
super.onNestedPreScroll(target, dx, dy, consumed, type);
|
|
242
|
+
}
|
|
243
|
+
// moving the finger back down releases the bottom pull
|
|
244
|
+
if (mBottomPull > 0 && !mBottomRefreshing && dy < 0 && type == ViewCompat.TYPE_TOUCH) {
|
|
245
|
+
float release = Math.min(mBottomPull, -dy / DRAG_RATE);
|
|
246
|
+
applyPull(mBottomPull - release);
|
|
247
|
+
consumed[1] += (int) (-release * DRAG_RATE);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
@Override
|
|
252
|
+
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type, int[] consumed) {
|
|
253
|
+
if (mTopEnabled) {
|
|
254
|
+
super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type, consumed);
|
|
255
|
+
}
|
|
256
|
+
if (mBottomEnabled && !mBottomRefreshing && type == ViewCompat.TYPE_TOUCH && dyUnconsumed > 0 && !target.canScrollVertically(1)) {
|
|
257
|
+
if (mAnimator != null) {
|
|
258
|
+
mAnimator.cancel();
|
|
259
|
+
}
|
|
260
|
+
applyPull(Math.min(mMaxPull, mBottomPull + dyUnconsumed * DRAG_RATE));
|
|
261
|
+
// the overscroll effect of the child (stretch/glow) must not take it
|
|
262
|
+
consumed[1] += dyUnconsumed;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
@Override
|
|
267
|
+
public void onStopNestedScroll(View target, int type) {
|
|
268
|
+
if (mTopEnabled) {
|
|
269
|
+
super.onStopNestedScroll(target, type);
|
|
270
|
+
}
|
|
271
|
+
if (mBottomPull > 0 && !mBottomRefreshing && type == ViewCompat.TYPE_TOUCH) {
|
|
272
|
+
if (mBottomPull >= mTrigger) {
|
|
273
|
+
mBottomRefreshing = true;
|
|
274
|
+
animatePullTo(mTrigger, false);
|
|
275
|
+
startSpinner();
|
|
276
|
+
if (mBottomRefreshListener != null) {
|
|
277
|
+
mBottomRefreshListener.run();
|
|
278
|
+
}
|
|
279
|
+
} else {
|
|
280
|
+
animatePullTo(0, true);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["../../src/ui-refreshlayout/index-common.ts","../../src/ui-refreshlayout/index.android.ts","../../src/ui-refreshlayout/index.d.ts","../../src/ui-refreshlayout/index.ios.ts","../../references.d.ts","../../tools/references.d.ts","../../src/references.d.ts"],"version":"5.8.3"}
|