react-native-refresh-list2 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/LICENSE +20 -0
- package/README.md +67 -0
- package/lib/module/Loading/Loading.js +19 -0
- package/lib/module/Loading/Loading.js.map +1 -0
- package/lib/module/Loading/index.js +5 -0
- package/lib/module/Loading/index.js.map +1 -0
- package/lib/module/RefreshControl/BottomContainer.js +88 -0
- package/lib/module/RefreshControl/BottomContainer.js.map +1 -0
- package/lib/module/RefreshControl/NormalControl.js +94 -0
- package/lib/module/RefreshControl/NormalControl.js.map +1 -0
- package/lib/module/RefreshControl/RefreshContainer.js +53 -0
- package/lib/module/RefreshControl/RefreshContainer.js.map +1 -0
- package/lib/module/RefreshControl/RefreshContext.js +32 -0
- package/lib/module/RefreshControl/RefreshContext.js.map +1 -0
- package/lib/module/RefreshControl/RefreshFlatList.js +291 -0
- package/lib/module/RefreshControl/RefreshFlatList.js.map +1 -0
- package/lib/module/RefreshControl/RefreshScrollView.js +279 -0
- package/lib/module/RefreshControl/RefreshScrollView.js.map +1 -0
- package/lib/module/RefreshControl/index.js +7 -0
- package/lib/module/RefreshControl/index.js.map +1 -0
- package/lib/module/RefreshControl/type.js +39 -0
- package/lib/module/RefreshControl/type.js.map +1 -0
- package/lib/module/icon/CommentIcon.js +32 -0
- package/lib/module/icon/CommentIcon.js.map +1 -0
- package/lib/module/icon/Icon.js +25 -0
- package/lib/module/icon/Icon.js.map +1 -0
- package/lib/module/icon/MoreIcon.js +40 -0
- package/lib/module/icon/MoreIcon.js.map +1 -0
- package/lib/module/icon/Praise.js +23 -0
- package/lib/module/icon/Praise.js.map +1 -0
- package/lib/module/icon/SearchIcon.js +35 -0
- package/lib/module/icon/SearchIcon.js.map +1 -0
- package/lib/module/icon/index.js +5 -0
- package/lib/module/icon/index.js.map +1 -0
- package/lib/module/icon/library.js +20 -0
- package/lib/module/icon/library.js.map +1 -0
- package/lib/module/index.js +4 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/Loading/Loading.d.ts +9 -0
- package/lib/typescript/src/Loading/Loading.d.ts.map +1 -0
- package/lib/typescript/src/Loading/index.d.ts +3 -0
- package/lib/typescript/src/Loading/index.d.ts.map +1 -0
- package/lib/typescript/src/RefreshControl/BottomContainer.d.ts +17 -0
- package/lib/typescript/src/RefreshControl/BottomContainer.d.ts.map +1 -0
- package/lib/typescript/src/RefreshControl/NormalControl.d.ts +20 -0
- package/lib/typescript/src/RefreshControl/NormalControl.d.ts.map +1 -0
- package/lib/typescript/src/RefreshControl/RefreshContainer.d.ts +17 -0
- package/lib/typescript/src/RefreshControl/RefreshContainer.d.ts.map +1 -0
- package/lib/typescript/src/RefreshControl/RefreshContext.d.ts +9 -0
- package/lib/typescript/src/RefreshControl/RefreshContext.d.ts.map +1 -0
- package/lib/typescript/src/RefreshControl/RefreshFlatList.d.ts +20 -0
- package/lib/typescript/src/RefreshControl/RefreshFlatList.d.ts.map +1 -0
- package/lib/typescript/src/RefreshControl/RefreshScrollView.d.ts +20 -0
- package/lib/typescript/src/RefreshControl/RefreshScrollView.d.ts.map +1 -0
- package/lib/typescript/src/RefreshControl/index.d.ts +5 -0
- package/lib/typescript/src/RefreshControl/index.d.ts.map +1 -0
- package/lib/typescript/src/RefreshControl/type.d.ts +70 -0
- package/lib/typescript/src/RefreshControl/type.d.ts.map +1 -0
- package/lib/typescript/src/icon/CommentIcon.d.ts +3 -0
- package/lib/typescript/src/icon/CommentIcon.d.ts.map +1 -0
- package/lib/typescript/src/icon/Icon.d.ts +10 -0
- package/lib/typescript/src/icon/Icon.d.ts.map +1 -0
- package/lib/typescript/src/icon/MoreIcon.d.ts +3 -0
- package/lib/typescript/src/icon/MoreIcon.d.ts.map +1 -0
- package/lib/typescript/src/icon/Praise.d.ts +9 -0
- package/lib/typescript/src/icon/Praise.d.ts.map +1 -0
- package/lib/typescript/src/icon/SearchIcon.d.ts +9 -0
- package/lib/typescript/src/icon/SearchIcon.d.ts.map +1 -0
- package/lib/typescript/src/icon/index.d.ts +3 -0
- package/lib/typescript/src/icon/index.d.ts.map +1 -0
- package/lib/typescript/src/icon/library.d.ts +17 -0
- package/lib/typescript/src/icon/library.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +2 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/package.json +168 -0
- package/src/Loading/Loading.tsx +15 -0
- package/src/Loading/index.tsx +5 -0
- package/src/RefreshControl/BottomContainer.tsx +112 -0
- package/src/RefreshControl/NormalControl.tsx +118 -0
- package/src/RefreshControl/RefreshContainer.tsx +74 -0
- package/src/RefreshControl/RefreshContext.tsx +30 -0
- package/src/RefreshControl/RefreshFlatList.tsx +372 -0
- package/src/RefreshControl/RefreshScrollView.tsx +359 -0
- package/src/RefreshControl/index.tsx +5 -0
- package/src/RefreshControl/type.ts +74 -0
- package/src/icon/CommentIcon.tsx +29 -0
- package/src/icon/Icon.tsx +26 -0
- package/src/icon/MoreIcon.tsx +38 -0
- package/src/icon/Praise.tsx +21 -0
- package/src/icon/SearchIcon.tsx +36 -0
- package/src/icon/index.tsx +3 -0
- package/src/icon/library.ts +30 -0
- package/src/index.tsx +1 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
// RefreshFlatList.tsx
|
|
2
|
+
|
|
3
|
+
import React, { useCallback, useEffect, useRef } from 'react';
|
|
4
|
+
import { Dimensions, Platform, type ScrollViewProps } from 'react-native';
|
|
5
|
+
import { Gesture, GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
6
|
+
import Animated, {
|
|
7
|
+
runOnJS,
|
|
8
|
+
interpolate,
|
|
9
|
+
useAnimatedScrollHandler,
|
|
10
|
+
withTiming,
|
|
11
|
+
useAnimatedProps,
|
|
12
|
+
useAnimatedStyle,
|
|
13
|
+
useSharedValue,
|
|
14
|
+
useDerivedValue,
|
|
15
|
+
withSequence,
|
|
16
|
+
withDelay,
|
|
17
|
+
Easing,
|
|
18
|
+
useAnimatedReaction, type SharedValue,
|
|
19
|
+
} from 'react-native-reanimated';
|
|
20
|
+
import { RefreshContainerContext, RefreshStatus } from './type';
|
|
21
|
+
import RefreshContainer from './RefreshContainer';
|
|
22
|
+
// 假设您不需要单独的 BottomContainer 组件,而是将加载更多内容放在 ListFooterComponent 中
|
|
23
|
+
|
|
24
|
+
// ------------------- 常量定义 -------------------
|
|
25
|
+
const { height } = Dimensions.get('window');
|
|
26
|
+
|
|
27
|
+
const MAX_SCROLL_VELOCITY_Y = 20;
|
|
28
|
+
const MIN_SCROLL_VELOCITY_Y = 0.5;
|
|
29
|
+
const DEFAULT_TRIGGLE_HEIGHT = 100;
|
|
30
|
+
const RESET_TIMING_EASING = Easing.bezier(0.33, 1, 0.68, 1);
|
|
31
|
+
const MAX_BOUNCE_DISTANCE = 100
|
|
32
|
+
|
|
33
|
+
// ------------------- Props 定义 -------------------
|
|
34
|
+
interface RefreshFlatListProps extends ScrollViewProps {
|
|
35
|
+
refreshing: boolean;
|
|
36
|
+
refreshComponent: () => React.ReactNode;
|
|
37
|
+
onRefresh: () => void;
|
|
38
|
+
onScroll?: (event: any) => void;
|
|
39
|
+
handleOnLoadMore?: () => void;
|
|
40
|
+
triggleHeight?: number;
|
|
41
|
+
canOffset?: boolean;
|
|
42
|
+
bounces?: boolean;
|
|
43
|
+
offsetY?: number
|
|
44
|
+
children: React.ReactNode;
|
|
45
|
+
scrollViewParentTransitionY?: SharedValue<number>;
|
|
46
|
+
scrollParentRef?: any,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const RefreshScrollList = (props: RefreshFlatListProps) => {
|
|
50
|
+
const {
|
|
51
|
+
refreshing,
|
|
52
|
+
onRefresh,
|
|
53
|
+
refreshComponent,
|
|
54
|
+
triggleHeight = DEFAULT_TRIGGLE_HEIGHT,
|
|
55
|
+
canOffset = true,
|
|
56
|
+
bounces = true,
|
|
57
|
+
onScroll: onScrollHandler,
|
|
58
|
+
children,
|
|
59
|
+
offsetY = 0,
|
|
60
|
+
scrollViewParentTransitionY,
|
|
61
|
+
scrollParentRef,
|
|
62
|
+
...restProps
|
|
63
|
+
} = props;
|
|
64
|
+
|
|
65
|
+
// ------------------- Shared Values 和 Refs -------------------
|
|
66
|
+
const panRef = useRef<any>(null);
|
|
67
|
+
const internalScrollRef = useRef<any>(null);
|
|
68
|
+
const scrollRef = scrollParentRef || internalScrollRef;
|
|
69
|
+
|
|
70
|
+
// @ts-ignore
|
|
71
|
+
const nativeRef: any = useRef();
|
|
72
|
+
const nativeGesture = Gesture.Native().withRef(nativeRef);
|
|
73
|
+
const internalScrollViewParentTransitionY = useSharedValue(0);
|
|
74
|
+
const scrollViewTransitionY = scrollViewParentTransitionY || internalScrollViewParentTransitionY;
|
|
75
|
+
const refreshTransitionY = useSharedValue(0);
|
|
76
|
+
const offset = useSharedValue(0);
|
|
77
|
+
const scrollBounse = useSharedValue(false);
|
|
78
|
+
const refreshStatus = useSharedValue<RefreshStatus>(RefreshStatus.Idle);
|
|
79
|
+
const scrollViewTotalHeight = useSharedValue(0);
|
|
80
|
+
const scrollBeginTop = useSharedValue(0)
|
|
81
|
+
|
|
82
|
+
// ------------------- Derived Values -------------------
|
|
83
|
+
const direction = useDerivedValue(() => {
|
|
84
|
+
return refreshTransitionY.value > 0 ? 1 : -1;
|
|
85
|
+
}, [refreshTransitionY]);
|
|
86
|
+
|
|
87
|
+
const canRefresh = useDerivedValue(() => {
|
|
88
|
+
const marginTop = scrollViewTransitionY.value;
|
|
89
|
+
// 核心:判断是否触顶 (下拉) 或触底 (上拉/加载更多)
|
|
90
|
+
return marginTop < 1
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
useAnimatedReaction(
|
|
94
|
+
() => refreshStatus.value,
|
|
95
|
+
() => {
|
|
96
|
+
}
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
// ------------------- Refreshing Side Effect -------------------
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
if (refreshing) {
|
|
102
|
+
if (direction.value === 1) {
|
|
103
|
+
refreshStatus.value = RefreshStatus.Holding;
|
|
104
|
+
// 刷新中保持指示器在触发高度
|
|
105
|
+
refreshTransitionY.value = withTiming(triggleHeight * direction.value);
|
|
106
|
+
} else if (refreshStatus.value === RefreshStatus.Idle) {
|
|
107
|
+
// scrollRef.current?.scrollTo({
|
|
108
|
+
// y: 0,
|
|
109
|
+
// animated: false,
|
|
110
|
+
// });
|
|
111
|
+
refreshStatus.value = RefreshStatus.Holding;
|
|
112
|
+
refreshTransitionY.value = withTiming(triggleHeight);
|
|
113
|
+
}
|
|
114
|
+
} else if (refreshStatus.value !== RefreshStatus.Idle) {
|
|
115
|
+
if (direction.value === 1) {
|
|
116
|
+
// 下拉刷新完成,收起指示器
|
|
117
|
+
refreshStatus.value = RefreshStatus.Done;
|
|
118
|
+
refreshTransitionY.value = withDelay(
|
|
119
|
+
500,
|
|
120
|
+
withTiming(
|
|
121
|
+
0,
|
|
122
|
+
{
|
|
123
|
+
easing: RESET_TIMING_EASING,
|
|
124
|
+
},
|
|
125
|
+
() => {
|
|
126
|
+
refreshStatus.value = RefreshStatus.Idle;
|
|
127
|
+
}
|
|
128
|
+
)
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}, [refreshing, direction.value, refreshStatus, refreshTransitionY, triggleHeight]);
|
|
133
|
+
|
|
134
|
+
const handleOnRefresh = useCallback(() => {
|
|
135
|
+
if (refreshing) return;
|
|
136
|
+
onRefresh && onRefresh();
|
|
137
|
+
}, [refreshing, onRefresh]);
|
|
138
|
+
|
|
139
|
+
// ------------------- Scroll Handler -------------------
|
|
140
|
+
const onScroll = useAnimatedScrollHandler<{
|
|
141
|
+
scrollBeginTime: number;
|
|
142
|
+
scrollBeginY: number;
|
|
143
|
+
}>({
|
|
144
|
+
onBeginDrag: (event, context) => {
|
|
145
|
+
context.scrollBeginTime = new Date().valueOf();
|
|
146
|
+
context.scrollBeginY = event.contentOffset.y;
|
|
147
|
+
},
|
|
148
|
+
onScroll: (event, context) => {
|
|
149
|
+
onScrollHandler && runOnJS(onScrollHandler)({
|
|
150
|
+
nativeEvent: event,
|
|
151
|
+
});
|
|
152
|
+
const { scrollBeginY, scrollBeginTime } = context;
|
|
153
|
+
scrollViewTransitionY.value = event.contentOffset.y;
|
|
154
|
+
// 滚动边界反弹动画逻辑
|
|
155
|
+
const marginTop = scrollViewTransitionY.value;
|
|
156
|
+
|
|
157
|
+
if (marginTop === 0 && !scrollBounse.value) {
|
|
158
|
+
|
|
159
|
+
const bounceDirection = marginTop === 0 ? 1 : -1;
|
|
160
|
+
const endTime = new Date().valueOf();
|
|
161
|
+
const velocityY = Math.min(
|
|
162
|
+
Math.abs(
|
|
163
|
+
(scrollViewTransitionY.value - scrollBeginY) /
|
|
164
|
+
(endTime - scrollBeginTime)
|
|
165
|
+
),
|
|
166
|
+
MAX_SCROLL_VELOCITY_Y
|
|
167
|
+
);
|
|
168
|
+
if (!bounces || velocityY < MIN_SCROLL_VELOCITY_Y) return;
|
|
169
|
+
|
|
170
|
+
const ratio = (Math.PI / 2 / MAX_SCROLL_VELOCITY_Y) * velocityY;
|
|
171
|
+
const bounceDistance = (height / 4) * Math.sin(ratio);
|
|
172
|
+
const duration = 100 + 100 * Math.sin(ratio * 2);
|
|
173
|
+
|
|
174
|
+
scrollBounse.value = true;
|
|
175
|
+
refreshTransitionY.value = withSequence(
|
|
176
|
+
withTiming(bounceDistance * bounceDirection, { duration }),
|
|
177
|
+
withTiming(
|
|
178
|
+
0,
|
|
179
|
+
{
|
|
180
|
+
duration,
|
|
181
|
+
easing: RESET_TIMING_EASING,
|
|
182
|
+
},
|
|
183
|
+
() => {
|
|
184
|
+
scrollBounse.value = false;
|
|
185
|
+
}
|
|
186
|
+
)
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
const scrollToTopJS = useCallback(() => {
|
|
194
|
+
// 检查 ref 是否存在
|
|
195
|
+
if (scrollRef.current) {
|
|
196
|
+
(scrollRef.current as any).scrollTo({
|
|
197
|
+
y: 0,
|
|
198
|
+
animated: false,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}, [scrollRef]);
|
|
202
|
+
|
|
203
|
+
// ------------------- Pan Gesture -------------------
|
|
204
|
+
const panGesture = Gesture.Pan()
|
|
205
|
+
.withRef(panRef)
|
|
206
|
+
.activeOffsetY(10)
|
|
207
|
+
.simultaneousWithExternalGesture(nativeRef)
|
|
208
|
+
// 移除 activeOffsetY 或设置为极小值,让优先级完全由 requireExternalGestureToFail 决定
|
|
209
|
+
// 关键:与原生滚动同时进行,以便在触顶时接管
|
|
210
|
+
.onBegin(() => {
|
|
211
|
+
offset.value = refreshTransitionY.value;
|
|
212
|
+
scrollBeginTop.value = scrollViewTransitionY.value;
|
|
213
|
+
})
|
|
214
|
+
.onUpdate(({ translationY }) => {
|
|
215
|
+
const shouldAllowModify = canRefresh.value || (refreshStatus.value !== RefreshStatus.Idle && (translationY - scrollBeginTop.value) >= 0);
|
|
216
|
+
if (!shouldAllowModify) {
|
|
217
|
+
refreshTransitionY.value = 0;
|
|
218
|
+
// 如果不在触顶区域且未处于刷新状态,则不修改位移
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (Platform.OS !== 'ios') runOnJS(scrollToTopJS)()
|
|
223
|
+
|
|
224
|
+
let nextY =
|
|
225
|
+
offset.value +
|
|
226
|
+
interpolate(
|
|
227
|
+
(translationY - scrollBeginTop.value),
|
|
228
|
+
[0, height],
|
|
229
|
+
[0, height / 2],
|
|
230
|
+
// 确保超过 height 时不会失控,但这里通常不需要
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
refreshTransitionY.value = Math.min(nextY, MAX_BOUNCE_DISTANCE);
|
|
234
|
+
|
|
235
|
+
if (!refreshing) {
|
|
236
|
+
if (Math.abs(refreshTransitionY.value) >= triggleHeight) {
|
|
237
|
+
refreshStatus.value = RefreshStatus.Reached;
|
|
238
|
+
} else {
|
|
239
|
+
refreshStatus.value = RefreshStatus.Pulling;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
})
|
|
243
|
+
.onEnd(() => {
|
|
244
|
+
if (refreshing) {
|
|
245
|
+
if (refreshTransitionY.value >= triggleHeight * direction.value) {
|
|
246
|
+
refreshTransitionY.value = withTiming(
|
|
247
|
+
triggleHeight * direction.value
|
|
248
|
+
);
|
|
249
|
+
} else {
|
|
250
|
+
``
|
|
251
|
+
refreshTransitionY.value = withTiming(0);
|
|
252
|
+
}
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (Math.abs(refreshTransitionY.value) >= triggleHeight) {
|
|
256
|
+
if (refreshTransitionY.value > 0) {
|
|
257
|
+
runOnJS(handleOnRefresh)();
|
|
258
|
+
}
|
|
259
|
+
} else {
|
|
260
|
+
refreshTransitionY.value = withTiming(
|
|
261
|
+
0,
|
|
262
|
+
{
|
|
263
|
+
easing: RESET_TIMING_EASING,
|
|
264
|
+
},
|
|
265
|
+
() => {
|
|
266
|
+
refreshStatus.value = RefreshStatus.Idle;
|
|
267
|
+
}
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
// ------------------- Animated Styles and Props -------------------
|
|
274
|
+
const animatedStyle = useAnimatedStyle(() => {
|
|
275
|
+
// 🌟 核心修复:这个 style 将应用于外部 Animated.View,实现整个列表的平移
|
|
276
|
+
return {
|
|
277
|
+
transform: [
|
|
278
|
+
{
|
|
279
|
+
translateY: canOffset ? refreshTransitionY.value : 0,
|
|
280
|
+
},
|
|
281
|
+
],
|
|
282
|
+
};
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
const animatedScrollEnabled = useDerivedValue(() => {
|
|
286
|
+
if (Platform.OS === 'ios') return true
|
|
287
|
+
return refreshTransitionY.value <= 0;
|
|
288
|
+
}, [refreshTransitionY]);
|
|
289
|
+
|
|
290
|
+
const animatedProps = useAnimatedProps(() => {
|
|
291
|
+
const top = -refreshTransitionY.value;
|
|
292
|
+
return {
|
|
293
|
+
scrollEnabled: animatedScrollEnabled.value,
|
|
294
|
+
scrollIndicatorInsets: {
|
|
295
|
+
top: top - 1,
|
|
296
|
+
left: 0,
|
|
297
|
+
bottom: 0,
|
|
298
|
+
right: 0,
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
const derivedTransitionY = useDerivedValue(() => {
|
|
304
|
+
return refreshTransitionY.value + offsetY;
|
|
305
|
+
}, [offsetY]); // 依赖 offsetY,如果 offsetY 变化,重新计算
|
|
306
|
+
|
|
307
|
+
// ------------------- JSX 渲染 -------------------
|
|
308
|
+
return (
|
|
309
|
+
<GestureHandlerRootView style={{
|
|
310
|
+
flex: 1
|
|
311
|
+
}}>
|
|
312
|
+
<RefreshContainerContext.Provider
|
|
313
|
+
value={{
|
|
314
|
+
transitionY: refreshTransitionY,
|
|
315
|
+
scrollBounse,
|
|
316
|
+
triggleHeight,
|
|
317
|
+
refreshing,
|
|
318
|
+
refreshStatus,
|
|
319
|
+
direction,
|
|
320
|
+
canRefresh,
|
|
321
|
+
}}
|
|
322
|
+
>
|
|
323
|
+
<GestureDetector gesture={panGesture}>
|
|
324
|
+
{/* 🌟 核心修复:使用 Animated.View 包裹 FlatList 并应用 animatedStyle */}
|
|
325
|
+
<GestureDetector gesture={nativeGesture}>
|
|
326
|
+
{/* @ts-ignore */}
|
|
327
|
+
<Animated.ScrollView
|
|
328
|
+
ref={scrollRef}
|
|
329
|
+
bounces={false}
|
|
330
|
+
overScrollMode={'never'}
|
|
331
|
+
scrollEventThrottle={16}
|
|
332
|
+
onScroll={onScroll}
|
|
333
|
+
animatedProps={animatedProps}
|
|
334
|
+
// 移除 style={animatedStyle}
|
|
335
|
+
onContentSizeChange={(_: number, h: number) => {
|
|
336
|
+
scrollViewTotalHeight.value = h;
|
|
337
|
+
}}
|
|
338
|
+
{...restProps}
|
|
339
|
+
style={[{ flex: 1 }, animatedStyle]} // 合并样式
|
|
340
|
+
>
|
|
341
|
+
{children}
|
|
342
|
+
</Animated.ScrollView>
|
|
343
|
+
</GestureDetector>
|
|
344
|
+
</GestureDetector>
|
|
345
|
+
{/* RefreshContainer 必须在列表之上,使用绝对定位 */}
|
|
346
|
+
<RefreshContainer
|
|
347
|
+
offsetTop={offsetY}
|
|
348
|
+
refreshStatus={refreshStatus}
|
|
349
|
+
transitionY={derivedTransitionY}
|
|
350
|
+
triggleHeight={triggleHeight + (offsetY)}
|
|
351
|
+
>
|
|
352
|
+
{refreshComponent && refreshComponent()}
|
|
353
|
+
</RefreshContainer>
|
|
354
|
+
</RefreshContainerContext.Provider>
|
|
355
|
+
</GestureHandlerRootView>
|
|
356
|
+
);
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
export default RefreshScrollList;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
import { type SharedValue, type DerivedValue } from 'react-native-reanimated';
|
|
3
|
+
|
|
4
|
+
interface SkeletonContextProps {
|
|
5
|
+
/**
|
|
6
|
+
* Refresh Container transitionY
|
|
7
|
+
*/
|
|
8
|
+
transitionY: SharedValue<number>;
|
|
9
|
+
/**
|
|
10
|
+
* ScrollView scroll to the top with velocity. It can continue scroll a little, scrollBounse is a marker to mark the progress
|
|
11
|
+
* If True, scrollView is bounceing, and refresh animation will not triggle
|
|
12
|
+
*/
|
|
13
|
+
scrollBounse: SharedValue<boolean>;
|
|
14
|
+
/**
|
|
15
|
+
* Only transitionY bigger than triggleHeight, refresh animation will triggle
|
|
16
|
+
*/
|
|
17
|
+
triggleHeight: number;
|
|
18
|
+
/**
|
|
19
|
+
* current RefreshContainer is refreshing
|
|
20
|
+
*/
|
|
21
|
+
refreshing: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* RefreshStatus by transitionY
|
|
24
|
+
* When transitionY.value reach some point, it will change
|
|
25
|
+
*/
|
|
26
|
+
refreshStatus: SharedValue<RefreshStatus>;
|
|
27
|
+
/**
|
|
28
|
+
* ScrollView pulling direction
|
|
29
|
+
* 1: down
|
|
30
|
+
* -1: up
|
|
31
|
+
* */
|
|
32
|
+
direction: DerivedValue<1 | -1>;
|
|
33
|
+
/**
|
|
34
|
+
* inside scrollView wheather reach boundary
|
|
35
|
+
*/
|
|
36
|
+
canRefresh: DerivedValue<boolean>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const RefreshContainerContext = createContext<SkeletonContextProps>(
|
|
40
|
+
{} as SkeletonContextProps
|
|
41
|
+
);
|
|
42
|
+
export const useRefreshScroll = () => useContext(RefreshContainerContext);
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Once Refresh LifeCycle:
|
|
46
|
+
* Idle -> Pulling -> Idle: Not reach triggleHeight, fail to refresh
|
|
47
|
+
* Idle -> Pulling -> Reached -> Holding -> Done -> Idle: A compelete refresh
|
|
48
|
+
*/
|
|
49
|
+
export enum RefreshStatus {
|
|
50
|
+
/**
|
|
51
|
+
* Refresh normal status
|
|
52
|
+
*/
|
|
53
|
+
Idle,
|
|
54
|
+
/**
|
|
55
|
+
* Refresh is pulling down, and not reach triggleHeight
|
|
56
|
+
*/
|
|
57
|
+
Pulling,
|
|
58
|
+
/**
|
|
59
|
+
* Refresh is pulling down continue, and reached triggleHeight
|
|
60
|
+
*/
|
|
61
|
+
Reached,
|
|
62
|
+
/**
|
|
63
|
+
* Refresh is Refreshing
|
|
64
|
+
*/
|
|
65
|
+
Holding,
|
|
66
|
+
/**
|
|
67
|
+
* Refresh is done
|
|
68
|
+
*/
|
|
69
|
+
Done,
|
|
70
|
+
/**
|
|
71
|
+
* ScrollView Auto Load More
|
|
72
|
+
*/
|
|
73
|
+
AutoLoad,
|
|
74
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import Svg, {Defs, ClipPath, Rect, G, Path} from "react-native-svg";
|
|
2
|
+
/* SVGR has dropped some elements not supported by react-native-svg: style */
|
|
3
|
+
const SVGComponent = (props: any) => (
|
|
4
|
+
<Svg
|
|
5
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
6
|
+
xmlnsXlink="http://www.w3.org/1999/xlink"
|
|
7
|
+
width={14}
|
|
8
|
+
height={14}
|
|
9
|
+
viewBox="0 0 14 14"
|
|
10
|
+
{...props}
|
|
11
|
+
>
|
|
12
|
+
<Defs>
|
|
13
|
+
<ClipPath id="a">
|
|
14
|
+
<Rect
|
|
15
|
+
width={14}
|
|
16
|
+
height={14}
|
|
17
|
+
transform="translate(126.35 43.8)"
|
|
18
|
+
/>
|
|
19
|
+
</ClipPath>
|
|
20
|
+
</Defs>
|
|
21
|
+
<G className="b" transform="translate(-126.35 -43.8)">
|
|
22
|
+
<Path
|
|
23
|
+
d="M42.26.643H33.389a1.949,1.949,0,0,0-1.948,1.948V8.929a1.948,1.948,0,0,0,1.948,1.947h2.1a.139.139,0,0,1,.106.046l1.168,1.17a1.532,1.532,0,0,0,1.058.423,1.464,1.464,0,0,0,1.039-.425l1.167-1.167a.137.137,0,0,1,.107-.047h2.1A1.991,1.991,0,0,0,44.2,8.929V2.591A1.95,1.95,0,0,0,42.26.643M37.507,11.325,36.34,10.159a1.176,1.176,0,0,0-.877-.376h-2.1a.856.856,0,0,1-.854-.854V2.591a.856.856,0,0,1,.854-.854H42.24a.856.856,0,0,1,.854.854V8.929a.856.856,0,0,1-.854.854H40.09a1.271,1.271,0,0,0-.874.374l-1.167,1.168a.373.373,0,0,1-.527.016l-.015-.016"
|
|
24
|
+
transform="translate(95.559 44.357)"
|
|
25
|
+
/>
|
|
26
|
+
</G>
|
|
27
|
+
</Svg>
|
|
28
|
+
);
|
|
29
|
+
export default SVGComponent;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import Svg, { Path } from 'react-native-svg';
|
|
3
|
+
import { Library } from './library';
|
|
4
|
+
|
|
5
|
+
interface IconProps {
|
|
6
|
+
name: keyof typeof Library;
|
|
7
|
+
size?: number;
|
|
8
|
+
color?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const Icon: React.FC<IconProps> = (props) => {
|
|
12
|
+
const { name, size = 20, color = 'black' } = props;
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<Svg
|
|
16
|
+
testID="MAUI-ICON-ID"
|
|
17
|
+
width={size}
|
|
18
|
+
height={size}
|
|
19
|
+
viewBox="0 0 1024 1024"
|
|
20
|
+
>
|
|
21
|
+
<Path d={Library[name]} fill={color} />
|
|
22
|
+
</Svg>
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default Icon;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import Svg, {Defs, ClipPath, Rect, G, Path} from "react-native-svg";
|
|
2
|
+
/* SVGR has dropped some elements not supported by react-native-svg: style */
|
|
3
|
+
const SVGComponent = (props: any) => (
|
|
4
|
+
<Svg
|
|
5
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
6
|
+
xmlnsXlink="http://www.w3.org/1999/xlink"
|
|
7
|
+
width={16}
|
|
8
|
+
height={16}
|
|
9
|
+
viewBox="0 0 16 16"
|
|
10
|
+
{...props}
|
|
11
|
+
>
|
|
12
|
+
<Defs>
|
|
13
|
+
<ClipPath id="a">
|
|
14
|
+
<Rect
|
|
15
|
+
width={16}
|
|
16
|
+
height={16}
|
|
17
|
+
transform="translate(314 477)"
|
|
18
|
+
/>
|
|
19
|
+
</ClipPath>
|
|
20
|
+
</Defs>
|
|
21
|
+
<G className="b" transform="translate(-314 -477)">
|
|
22
|
+
<G transform="translate(225.396 64.866)">
|
|
23
|
+
<Path
|
|
24
|
+
d="M746.662,419.69a1.556,1.556,0,1,0,1.556-1.556A1.556,1.556,0,0,0,746.662,419.69Z"
|
|
25
|
+
transform="translate(-646.169)"
|
|
26
|
+
/>
|
|
27
|
+
<Path
|
|
28
|
+
d="M89.6,419.69a1.556,1.556,0,1,0,1.556-1.556A1.556,1.556,0,0,0,89.6,419.69Z"
|
|
29
|
+
/>
|
|
30
|
+
<Path
|
|
31
|
+
d="M418.133,419.69a1.556,1.556,0,1,0,1.556-1.556A1.555,1.555,0,0,0,418.133,419.69Z"
|
|
32
|
+
transform="translate(-323.084)"
|
|
33
|
+
/>
|
|
34
|
+
</G>
|
|
35
|
+
</G>
|
|
36
|
+
</Svg>
|
|
37
|
+
);
|
|
38
|
+
export default SVGComponent;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Svg, Path } from 'react-native-svg';
|
|
3
|
+
|
|
4
|
+
interface CartIconProps {
|
|
5
|
+
width?: number;
|
|
6
|
+
height?: number;
|
|
7
|
+
fill?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const CartIcon: React.FC<CartIconProps> = ({ width = 14, height = 14, fill = '#666' }) => {
|
|
11
|
+
return (
|
|
12
|
+
<Svg width={width} height={height} viewBox="0 0 14 14" fill="none">
|
|
13
|
+
<Path
|
|
14
|
+
d="M11.666 4.347H8.061V4.17l.137-1.064a3.2 3.2 0 00-.276-1.868 2.055 2.055 0 00-1.596-1.246l-.2-.034a1.4 1.4 0 00-1.263.176 1.348 1.348 0 00-.177.185l-.024.117a1.38 1.38 0 00-.276.427.342.342 0 00-.073.5 1.345 1.345 0 00-.1.244.608.608 0 00-.036.249V2.861A2.419 2.419 0 011.881 5.446H1.023a1.012 1.012 0 00-.745.318A1.027 1.027 0 000 6.537l.275 5.218A1.072 1.072 0 001.33 12.774h8.961a1.216 1.216 0 001.226-1.049l1.2-5.977a1.261 1.261 0 00-1.054-1.4zM5.408 1.356a.328.328 0 01.322-.256.32.32 0 01.061 0l.2.034a1.017 1.017 0 01.773.606 1.964 1.964 0 01.2 1.254l-.13 1.069v.172A1.243 1.243 0 008.061 5.461h3.462a.209.209 0 01.119.177l-1.2 5.944c0 .052-.088.113-.146.113H3.449L3.075 6.312A3.557 3.557 0 005.408 2.827zM2.371 11.342v.034a.3.3 0 01-.318.319H1.538a.346.346 0 01-.319-.32L1.082 6.81A.319.319 0 011.4 6.492h.344a.375.375 0 01.318.319z"
|
|
15
|
+
fill={fill}
|
|
16
|
+
/>
|
|
17
|
+
</Svg>
|
|
18
|
+
);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default CartIcon;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Svg, Circle, Line } from 'react-native-svg';
|
|
3
|
+
|
|
4
|
+
interface SearchIconProps {
|
|
5
|
+
width?: number;
|
|
6
|
+
height?: number;
|
|
7
|
+
fill?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const SearchIcon: React.FC<SearchIconProps> = ({ width = 20, height = 20, fill = '#111' }) => {
|
|
11
|
+
return (
|
|
12
|
+
<Svg width={width} height={height} viewBox="0 0 20 20" fill="none">
|
|
13
|
+
{/* 圆环 */}
|
|
14
|
+
<Circle
|
|
15
|
+
cx={9.5}
|
|
16
|
+
cy={9.5}
|
|
17
|
+
r={6}
|
|
18
|
+
stroke={fill}
|
|
19
|
+
strokeWidth={1.5}
|
|
20
|
+
fill="none"
|
|
21
|
+
/>
|
|
22
|
+
{/* 手柄 */}
|
|
23
|
+
<Line
|
|
24
|
+
x1={13.74}
|
|
25
|
+
y1={13.74}
|
|
26
|
+
x2={16.85}
|
|
27
|
+
y2={16.85}
|
|
28
|
+
stroke={fill}
|
|
29
|
+
strokeWidth={1.5}
|
|
30
|
+
strokeLinecap="round"
|
|
31
|
+
/>
|
|
32
|
+
</Svg>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export default SearchIcon;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// svg from https://www.iconfont.cn/collections/detail?spm=a313x.7781069.0.da5a778a4&cid=19238
|
|
2
|
+
export const Library = {
|
|
3
|
+
'arrow-left':
|
|
4
|
+
'M641.28 278.613333l-45.226667-45.226666-278.634666 278.762666 278.613333 278.485334 45.248-45.269334-233.365333-233.237333z',
|
|
5
|
+
'arrow-right':
|
|
6
|
+
'M593.450667 512.128L360.064 278.613333l45.290667-45.226666 278.613333 278.762666L405.333333 790.613333l-45.226666-45.269333z',
|
|
7
|
+
'arrow-up':
|
|
8
|
+
'M500.8 461.909333L267.306667 695.296l-45.226667-45.269333 278.741333-278.613334L779.306667 650.026667l-45.248 45.226666z',
|
|
9
|
+
'arrow-down':
|
|
10
|
+
'M500.8 604.779L267.307 371.392l-45.227 45.27 278.741 278.613L779.307 416.66l-45.248-45.248z',
|
|
11
|
+
'plus':
|
|
12
|
+
'M544 213.333333v266.666667H810.666667v64H544V810.666667h-64V544H213.333333v-64h266.666667V213.333333z',
|
|
13
|
+
'minus': 'M810.666667 480v64H213.333333v-64z',
|
|
14
|
+
'code':
|
|
15
|
+
'M541.141333 268.864l61.717334 16.938667-132.394667 482.474666-61.717333-16.938666 132.394666-482.474667zM329.002667 298.666667l44.885333 45.610666-175.36 172.586667 175.04 167.573333-44.266667 46.229334L106.666667 517.504 329.002667 298.666667z m355.882666 0l222.336 218.837333L684.586667 730.666667l-44.266667-46.229334 175.018667-167.573333L640 344.277333 684.885333 298.666667z',
|
|
16
|
+
'check':
|
|
17
|
+
'M235.946667 472.938667l-45.226667 45.312 210.090667 209.514666 432.362666-427.690666-45.013333-45.482667-387.157333 382.976z',
|
|
18
|
+
'sorting':
|
|
19
|
+
'M438.634667 192v644.202667l-0.810667 5.781333c-1.152 7.210667-2.304 8.704-8.64 17.002667l-7.168 3.925333c-15.914667 8.490667-18.282667 7.168-38.4-3.925333L149.333333 624.533333l45.269334-45.226666 180.032 180.138666V192h64z m216.96 10.005333l231.04 231.146667-45.269334 45.248L661.333333 298.261333V865.706667h-64V226.133333a34.133333 34.133333 0 0 1 58.282667-24.128z',
|
|
20
|
+
'favorites':
|
|
21
|
+
'M484.267 272.021l6.634 6.72c5.974 5.974 13.014 12.843 21.099 20.63l9.195-8.918c7.253-7.04 13.44-13.184 18.56-18.432a193.28 193.28 0 0 1 277.44 0c75.904 77.526 76.629 202.795 2.133 281.195L512 853.333 204.672 553.237c-74.475-78.421-73.77-203.69 2.133-281.216a193.28 193.28 0 0 1 277.44 0z m293.162 232.15c46.272-53.76 44.182-136.15-5.973-187.371a129.28 129.28 0 0 0-185.984 0l-15.125 15.104a1687.253 1687.253 0 0 1-4.395 4.31L512 388.18l-49.28-47.445-13.227-12.928-10.965-11.008a129.28 129.28 0 0 0-186.005 0c-51.456 52.565-52.31 137.963-2.198 191.573L512 763.883l261.675-255.531 3.754-4.181z',
|
|
22
|
+
'favorites-fill':
|
|
23
|
+
'M484.266667 272.021333l6.634666 6.72c5.973333 5.973333 13.013333 12.842667 21.098667 20.629334l9.194667-8.917334c7.253333-7.04 13.44-13.184 18.56-18.432a193.28 193.28 0 0 1 277.44 0c75.904 77.525333 76.629333 202.794667 2.133333 281.194667L512 853.333333 204.672 553.237333c-74.474667-78.421333-73.770667-203.690667 2.133333-281.216a193.28 193.28 0 0 1 277.44 0z',
|
|
24
|
+
'search':
|
|
25
|
+
'M469.333 192c153.174 0 277.334 124.16 277.334 277.333 0 68.054-24.534 130.411-65.216 178.688L846.336 818.24l-48.341 49.877L630.4 695.125a276.053 276.053 0 0 1-161.067 51.542C316.16 746.667 192 622.507 192 469.333S316.16 192 469.333 192z m0 64C351.51 256 256 351.51 256 469.333s95.51 213.334 213.333 213.334 213.334-95.51 213.334-213.334S587.157 256 469.333 256z',
|
|
26
|
+
'arrow-line-up':
|
|
27
|
+
'M478.3 927.5V175.2L259 394.5c-10.7 10.7-28.1 10.7-38.7 0l-6.5-6.5c-10.7-10.7-10.7-28.1 0-38.7L481.6 81.4c4.5-9.2 13.4-16 23.9-17.6 7.1-1.5 14.7-0.1 21 4 4 2.4 7.5 5.6 10.1 9.4l0.5 0.5c2.6 2.6 4.6 5.6 5.9 8.8l266.7 266.7c10.7 10.7 10.7 28.1 0 38.7l-6.5 6.5c-10.7 10.7-28.1 10.7-38.7 0l-222.3-222v751.1c0 17.6-14.4 32-32 32-17.5 0-31.9-14.4-31.9-32z',
|
|
28
|
+
'arrow-line-down':
|
|
29
|
+
'M478.3 95.2v752.3L259 628.2c-10.7-10.7-28.1-10.7-38.7 0l-6.5 6.5c-10.7 10.7-10.7 28.1 0 38.7l267.9 267.9c4.5 9.2 13.4 16 23.9 17.6 7.1 1.5 14.7 0.1 21-4 4-2.4 7.5-5.6 10.1-9.4l0.5-0.5c2.6-2.6 4.6-5.6 5.9-8.8l266.7-266.7c10.7-10.7 10.7-28.1 0-38.7l-6.5-6.5c-10.7-10.7-28.1-10.7-38.7 0l-222.4 222V95.2c0-17.6-14.4-32-32-32-17.5 0-31.9 14.4-31.9 32z',
|
|
30
|
+
};
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './RefreshControl';
|