@@ -7,6 +7,7 @@ import androidx.compose.animation.core.Animatable
77import androidx.compose.animation.core.AnimationSpec
88import androidx.compose.animation.core.Easing
99import androidx.compose.animation.core.LinearEasing
10+ import androidx.compose.animation.core.LinearOutSlowInEasing
1011import androidx.compose.animation.core.RepeatMode
1112import androidx.compose.animation.core.animateFloat
1213import androidx.compose.animation.core.infiniteRepeatable
@@ -41,11 +42,13 @@ import androidx.compose.ui.graphics.Color
4142import androidx.compose.ui.graphics.StrokeCap
4243import androidx.compose.ui.graphics.drawscope.DrawScope
4344import androidx.compose.ui.graphics.drawscope.Stroke
45+ import androidx.compose.ui.hapticfeedback.HapticFeedbackType
4446import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
4547import androidx.compose.ui.input.nestedscroll.NestedScrollSource
4648import androidx.compose.ui.input.nestedscroll.nestedScroll
4749import androidx.compose.ui.input.pointer.pointerInput
4850import androidx.compose.ui.platform.LocalDensity
51+ import androidx.compose.ui.platform.LocalHapticFeedback
4952import androidx.compose.ui.text.TextStyle
5053import androidx.compose.ui.text.font.FontWeight
5154import androidx.compose.ui.unit.Dp
@@ -68,7 +71,8 @@ import kotlin.time.TimeSource
6871 * @param pullToRefreshState pullToRefreshState
6972 * @param color The color of the refresh indicator.
7073 * @param circleSize The size of the refresh indicator circle.
71- * @param textStyle The style of the refresh text.
74+ * @param refreshTexts The texts to show when refreshing.
75+ * @param refreshTextStyle The style of the refresh text.
7276 * @param onRefresh The callback to be called when the refresh is triggered.
7377 * @param content the content to be shown when the [PullToRefresh] is expanded.
7478 *
@@ -79,7 +83,8 @@ fun PullToRefresh(
7983 pullToRefreshState : PullToRefreshState ,
8084 color : Color = PullToRefreshDefaults .color,
8185 circleSize : Dp = PullToRefreshDefaults .circleSize,
82- textStyle : TextStyle = PullToRefreshDefaults .textStyle,
86+ refreshTexts : List <String > = PullToRefreshDefaults .refreshTexts,
87+ refreshTextStyle : TextStyle = PullToRefreshDefaults .refreshTextStyle,
8388 onRefresh : () -> Unit = {},
8489 content : @Composable () -> Unit
8590) {
@@ -98,6 +103,8 @@ fun PullToRefresh(
98103 if (event.changes.all { ! it.pressed }) {
99104 pullToRefreshState.onPointerRelease()
100105 continue
106+ } else {
107+ pullToRefreshState.resetPointerReleased()
101108 }
102109 }
103110 }
@@ -117,38 +124,65 @@ fun PullToRefresh(
117124 pullToRefreshState = pullToRefreshState,
118125 circleSize = circleSize,
119126 color = color,
120- textStyle = textStyle
127+ refreshTexts = refreshTexts,
128+ refreshTextStyle = refreshTextStyle
121129 )
122130 content()
123131 }
124132 }
125133}
126134
127135/* *
128- * 刷新头部
136+ * Refresh header
129137 *
130- * @param modifier 修饰符
131- * @param pullToRefreshState 下拉刷新状态
132- * @param circleSize 指示器圆圈大小
133- * @param color 指示器颜色
134- * @param textStyle 刷新文本样式
138+ * @param modifier The modifier to be applied to the [RefreshHeader]
139+ * @param pullToRefreshState The state of the pull-to-refresh
140+ * @param circleSize The size of the refresh indicator circle
141+ * @param color The color of the refresh indicator
142+ * @param refreshTexts The texts to show when refreshing
143+ * @param refreshTextStyle The style of the refresh text
135144 */
136145@Composable
137146fun RefreshHeader (
138147 modifier : Modifier = Modifier ,
139148 pullToRefreshState : PullToRefreshState ,
140149 circleSize : Dp ,
141150 color : Color ,
142- textStyle : TextStyle
151+ refreshTexts : List <String >,
152+ refreshTextStyle : TextStyle
143153) {
154+ val hapticFeedback = LocalHapticFeedback .current
155+ val density = LocalDensity .current
156+
144157 val dragOffset = pullToRefreshState.dragOffsetAnimatable.value
145158 val thresholdOffset = pullToRefreshState.refreshThresholdOffset
146159 val maxDrag = pullToRefreshState.maxDragDistancePx
147- val density = LocalDensity .current
148160 val pullProgress = pullToRefreshState.pullProgress
149161 val rotation by animateRotation()
150162 val refreshCompleteAnimProgress = pullToRefreshState.refreshCompleteAnimProgress
151163
164+ LaunchedEffect (pullToRefreshState.refreshState) {
165+ if (pullToRefreshState.refreshState == RefreshState .ThresholdReached ) {
166+ hapticFeedback.performHapticFeedback(HapticFeedbackType .GestureThresholdActivate )
167+ }
168+ }
169+
170+ val refreshText = when (pullToRefreshState.refreshState) {
171+ RefreshState .Idle -> " "
172+ RefreshState .Pulling -> if (pullToRefreshState.pullProgress > 0.5 ) refreshTexts[0 ] else " "
173+ RefreshState .ThresholdReached -> refreshTexts[1 ]
174+ RefreshState .Refreshing -> refreshTexts[2 ]
175+ RefreshState .RefreshComplete -> refreshTexts[3 ]
176+ }
177+
178+ val textAlpha = when (pullToRefreshState.refreshState) {
179+ RefreshState .Pulling -> {
180+ if (pullToRefreshState.pullProgress > 0.5f ) (pullToRefreshState.pullProgress - 0.5f ) * 2 else 0f
181+ }
182+
183+ else -> 1f
184+ }
185+
152186 val headerHeight = with (density) {
153187 when (pullToRefreshState.refreshState) {
154188 RefreshState .Idle -> 0 .dp
@@ -216,31 +250,15 @@ fun RefreshHeader(
216250 }
217251 }
218252
219- val refreshText = when (pullToRefreshState.refreshState) {
220- RefreshState .Idle -> " "
221- RefreshState .Pulling -> if (pullToRefreshState.pullProgress > 0.5 ) " 下拉刷新" else " "
222- RefreshState .ThresholdReached -> " 松开刷新"
223- RefreshState .Refreshing -> " 正在刷新"
224- RefreshState .RefreshComplete -> " 刷新完成"
225- }
226-
227- val textAlpha = when (pullToRefreshState.refreshState) {
228- RefreshState .Pulling -> {
229- if (pullToRefreshState.pullProgress > 0.5f ) (pullToRefreshState.pullProgress - 0.5f ) * 2 else 0f
230- }
231-
232- else -> 1f
233- }
234-
235253 AnimatedVisibility (
236254 visible = pullToRefreshState.refreshState != RefreshState .Idle
237255 ) {
238256 Text (
239257 text = refreshText,
240- style = textStyle ,
258+ style = refreshTextStyle ,
241259 color = color,
242260 modifier = Modifier
243- .padding(vertical = 6 .dp)
261+ .padding(top = 12 .dp)
244262 .alpha(textAlpha)
245263 )
246264 }
@@ -271,7 +289,7 @@ private fun RefreshContent(
271289}
272290
273291/* *
274- * Create rotation animation state
292+ * Animate the rotation angle
275293 *
276294 * @return rotation angle state
277295 */
@@ -395,7 +413,7 @@ private fun DrawScope.drawRefreshingState(
395413}
396414
397415/* *
398- * Draw the circle that shrinks to the refresh complete state
416+ * Draw the circle that expands to the refresh complete state
399417 */
400418private fun DrawScope.drawRefreshCompleteState (
401419 center : Offset ,
@@ -436,17 +454,15 @@ sealed class RefreshState {
436454}
437455
438456/* *
439- * Remember the PullToRefreshState state object
457+ * Remember the [ PullToRefreshState] state object
440458 *
441- * @return PullToRefreshState state object
459+ * @return [ PullToRefreshState] state object
442460 */
443461@Composable
444462fun rememberPullToRefreshState (): PullToRefreshState {
445463 val coroutineScope = rememberCoroutineScope()
446- val density = LocalDensity .current
447- val screenHeightDp = getWindowSize().height
448-
449- val maxDragDistancePx = with (density) { (screenHeightDp.dp * maxDragRatio).toPx() }
464+ val screenHeight = getWindowSize().height
465+ val maxDragDistancePx = screenHeight * maxDragRatio
450466 val refreshThresholdOffset = maxDragDistancePx * thresholdRatio
451467
452468 return remember {
@@ -459,9 +475,9 @@ fun rememberPullToRefreshState(): PullToRefreshState {
459475}
460476
461477/* *
462- * PullToRefreshState
478+ * The PullToRefreshState class
463479 *
464- * @param coroutineScope CoroutineScope
480+ * @param coroutineScope Coroutine scope
465481 * @param maxDragDistancePx Maximum drag distance
466482 * @param refreshThresholdOffset Refresh threshold offset
467483 */
@@ -523,7 +539,7 @@ class PullToRefreshState(
523539 )
524540 }
525541
526- private fun resetPointerReleased () {
542+ internal fun resetPointerReleased () {
527543 pointerReleased = false
528544 }
529545
@@ -631,8 +647,8 @@ class PullToRefreshState(
631647 animateDragOffset(
632648 targetValue = refreshThresholdOffset,
633649 animationSpec = tween(
634- durationMillis = 100 ,
635- easing = LinearEasing
650+ durationMillis = 200 ,
651+ easing = LinearOutSlowInEasing
636652 )
637653 )
638654 rawDragOffset = refreshThresholdOffset
@@ -661,12 +677,13 @@ internal const val maxDragRatio = 1 / 5f
661677internal const val thresholdRatio = 1 / 4f
662678
663679/* *
664- * PullToRefreshDefaults
680+ * The default values of the [PullToRefresh] component.
665681 */
666682object PullToRefreshDefaults {
667683 val color: Color = Color .Gray
668- val circleSize: Dp = 24 .dp
669- val textStyle = TextStyle (
684+ val circleSize: Dp = 20 .dp
685+ val refreshTexts = listOf (" Pull down to refresh" , " Release to refresh" , " Refreshing..." , " Refreshed successfully" )
686+ val refreshTextStyle = TextStyle (
670687 fontSize = 14 .sp,
671688 fontWeight = FontWeight .Bold ,
672689 color = color
0 commit comments