|
| 1 | +import 'package:flare_flutter/flare_actor.dart'; |
1 | 2 | import 'package:flutter/cupertino.dart' as IOS; |
2 | 3 | import 'package:flutter/material.dart'; |
3 | 4 | import 'package:flutter_spinkit/flutter_spinkit.dart'; |
4 | 5 | import 'package:gsy_github_app_flutter/common/style/gsy_style.dart'; |
5 | 6 | import 'package:gsy_github_app_flutter/common/utils/common_utils.dart'; |
| 7 | +import 'dart:math'; |
| 8 | + |
| 9 | +const double iosRefreshHeight = 140; |
| 10 | +const double iosRefreshIndicatorExtent = 100; |
6 | 11 |
|
7 | 12 | ///通用下上刷新控件 |
8 | 13 | class GSYPullLoadWidget extends StatefulWidget { |
@@ -172,14 +177,18 @@ class _GSYPullLoadWidgetState extends State<GSYPullLoadWidget> { |
172 | 177 | ///用ios模式的下拉刷新 |
173 | 178 | return CustomScrollView( |
174 | 179 | controller: _scrollController, |
| 180 | + |
175 | 181 | ///回弹效果 |
176 | | - physics: const BouncingScrollPhysics( |
| 182 | + physics: const CustomBouncingScrollPhysics( |
177 | 183 | parent: AlwaysScrollableScrollPhysics(), |
178 | 184 | ), |
179 | 185 | slivers: <Widget>[ |
180 | 186 | ///控制显示刷新的 CupertinoSliverRefreshControl |
181 | 187 | IOS.CupertinoSliverRefreshControl( |
| 188 | + refreshIndicatorExtent: iosRefreshIndicatorExtent, |
| 189 | + refreshTriggerPullDistance: iosRefreshHeight, |
182 | 190 | onRefresh: handleRefresh, |
| 191 | + builder: buildSimpleRefreshIndicator, |
183 | 192 | ), |
184 | 193 | SliverSafeArea( |
185 | 194 | sliver: SliverList( |
@@ -276,6 +285,28 @@ class _GSYPullLoadWidgetState extends State<GSYPullLoadWidget> { |
276 | 285 | ), |
277 | 286 | ); |
278 | 287 | } |
| 288 | + |
| 289 | + Widget buildSimpleRefreshIndicator( |
| 290 | + BuildContext context, |
| 291 | + IOS.RefreshIndicatorMode refreshState, |
| 292 | + double pulledExtent, |
| 293 | + double refreshTriggerPullDistance, |
| 294 | + double refreshIndicatorExtent, |
| 295 | + ) { |
| 296 | + return Align( |
| 297 | + alignment: Alignment.bottomCenter, |
| 298 | + child: new Container( |
| 299 | + color: Colors.black, |
| 300 | + width: MediaQuery.of(context).size.width, |
| 301 | + ///动态大小处理 |
| 302 | + height: pulledExtent > iosRefreshHeight ? pulledExtent : iosRefreshHeight, |
| 303 | + child: FlareActor("static/file/loading_world_now.flr", |
| 304 | + alignment: Alignment.topCenter, |
| 305 | + fit: BoxFit.cover, |
| 306 | + animation: "Earth Moving"), |
| 307 | + ), |
| 308 | + ); |
| 309 | + } |
279 | 310 | } |
280 | 311 |
|
281 | 312 | class GSYPullLoadWidgetControl extends ChangeNotifier { |
@@ -322,3 +353,96 @@ class GSYPullLoadWidgetControl extends ChangeNotifier { |
322 | 353 | ///是否加载中 |
323 | 354 | bool isLoading = false; |
324 | 355 | } |
| 356 | + |
| 357 | +///自定义弹性滑动模块 |
| 358 | +class CustomBouncingScrollPhysics extends ScrollPhysics { |
| 359 | + const CustomBouncingScrollPhysics({ScrollPhysics parent}) |
| 360 | + : super(parent: parent); |
| 361 | + |
| 362 | + @override |
| 363 | + CustomBouncingScrollPhysics applyTo(ScrollPhysics ancestor) { |
| 364 | + return CustomBouncingScrollPhysics(parent: buildParent(ancestor)); |
| 365 | + } |
| 366 | + |
| 367 | + double frictionFactor(double overscrollFraction) => |
| 368 | + 0.52 * pow(1 - overscrollFraction, 2); |
| 369 | + |
| 370 | + @override |
| 371 | + double applyPhysicsToUserOffset(ScrollMetrics position, double offset) { |
| 372 | + assert(offset != 0.0); |
| 373 | + assert(position.minScrollExtent <= position.maxScrollExtent); |
| 374 | + if (!position.outOfRange) return offset; |
| 375 | + |
| 376 | + /// -2 是因为有时候会只到 iosRefreshHeight - 1 |
| 377 | + /// 会导致触发重新又从0开始的问题 |
| 378 | + if (position.pixels.abs() >= (iosRefreshHeight - 2)) { |
| 379 | + return 0; |
| 380 | + } |
| 381 | + final double overscrollPastStart = |
| 382 | + max(position.minScrollExtent - position.pixels, 0.0); |
| 383 | + final double overscrollPastEnd = |
| 384 | + max(position.pixels - position.maxScrollExtent, 0.0); |
| 385 | + final double overscrollPast = max(overscrollPastStart, overscrollPastEnd); |
| 386 | + |
| 387 | + final bool easing = (overscrollPastStart > 0.0 && offset < 0.0) || |
| 388 | + (overscrollPastEnd > 0.0 && offset > 0.0); |
| 389 | + |
| 390 | + final double friction = easing |
| 391 | + ? frictionFactor( |
| 392 | + (overscrollPast - offset.abs()) / position.viewportDimension) |
| 393 | + : frictionFactor(overscrollPast / position.viewportDimension); |
| 394 | + |
| 395 | + |
| 396 | + |
| 397 | + final double direction = offset.sign; |
| 398 | + |
| 399 | + print("easing $easing overscrollPastStart $overscrollPastStart"); |
| 400 | + |
| 401 | + return direction * _applyFriction(overscrollPast, offset.abs(), friction); |
| 402 | + } |
| 403 | + |
| 404 | + static double _applyFriction( |
| 405 | + double extentOutside, double absDelta, double gamma) { |
| 406 | + assert(absDelta > 0); |
| 407 | + double total = 0.0; |
| 408 | + if (extentOutside > 0) { |
| 409 | + final double deltaToLimit = extentOutside / gamma; |
| 410 | + if (absDelta < deltaToLimit) return absDelta * gamma; |
| 411 | + total += extentOutside; |
| 412 | + absDelta -= deltaToLimit; |
| 413 | + } |
| 414 | + return total + absDelta; |
| 415 | + } |
| 416 | + |
| 417 | + @override |
| 418 | + double applyBoundaryConditions(ScrollMetrics position, double value) => 0.0; |
| 419 | + |
| 420 | + @override |
| 421 | + Simulation createBallisticSimulation( |
| 422 | + ScrollMetrics position, double velocity) { |
| 423 | + final Tolerance tolerance = this.tolerance; |
| 424 | + if (velocity.abs() >= tolerance.velocity || position.outOfRange) { |
| 425 | + return BouncingScrollSimulation( |
| 426 | + spring: spring, |
| 427 | + position: position.pixels, |
| 428 | + velocity: velocity * 0.91, |
| 429 | + leadingExtent: position.minScrollExtent, |
| 430 | + trailingExtent: position.maxScrollExtent, |
| 431 | + tolerance: tolerance, |
| 432 | + ); |
| 433 | + } |
| 434 | + return null; |
| 435 | + } |
| 436 | + |
| 437 | + @override |
| 438 | + double get minFlingVelocity => 50.0 * 2.0; |
| 439 | + |
| 440 | + @override |
| 441 | + double carriedMomentum(double existingVelocity) { |
| 442 | + return existingVelocity.sign * |
| 443 | + min(0.000816 * pow(existingVelocity.abs(), 1.967).toDouble(), 40000.0); |
| 444 | + } |
| 445 | + |
| 446 | + @override |
| 447 | + double get dragStartDistanceMotionThreshold => 3.5; |
| 448 | +} |
0 commit comments