flutter:如何使用官方 webview_flutter 包进行下拉刷新 flutter webview

Hon*_*one 2 webview flutter

我想在 flutter web 视图中添加复习内容

 Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
          title: Stack(
        children: <Widget>[
          Container(
            child: Center(
              child: Text(_title),
            ),
          ),
        ],
      )),
      body: SafeArea(
          child: WebView(
              key: _key,
              javascriptMode: JavascriptMode.unrestricted,
              initialUrl: _url)),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

cef*_*aci 5

@peter-koltai:非常感谢!我真的很欣赏你的解决方案,高度工作正常,即使高度来得有点晚(页面内容可见,但滚动高度不存在),但还有其他问题。(抱歉我不能投票给你)

\n

问题SingleChildScrollView

\n
    \n
  • SingleChildScrollView始终具有页面的绝对高度,例如,如果文本框没有从一开始就展开(javascript),则滚动高度超过页面高度。
  • \n
  • 获取WebView整个滚动区域的高度,但不知道显示大小,因此如果出现底部或顶部模式表,它们不会在屏幕的视图区域中正确渲染,而是在滚动区域的绝对完整高度中渲染,所以你必须上下滚动例如 6000px。
  • \n
  • 如果您进一步浏览而不刷新页面,滚动位置将保留在您之前绝对页面高度中离开的位置。
  • \n
\n

完整代码:

\n

所以@shalin-shah的解决方案给了我这个很好的工作解决方案:\n如果你从页面的top=0开始,然后显示RefreshIndicator直到,我会计算向下拖动的距离(>屏幕高度的20%)onPageFinished

\n

webview.dart:\n如果RefreshIndicator达到Completer向下拖动的距离,则获取一个并开始旋转重新加载,如果页面加载完成则完成。

\n
import \'package:flutter/material.dart\';\nimport \'dart:async\';\nimport \'dart:io\';\n\nimport \'package:flutter/foundation.dart\';\nimport \'package:flutter/gestures.dart\';\nimport \'package:flutter_web_refresh/pull_to_refresh.dart\';\nimport \'package:webview_flutter/webview_flutter.dart\';\n\nclass MyWebViewWidget extends StatefulWidget {\n  final String initialUrl;\n\n  const MyWebViewWidget({\n    Key? key,\n    required this.initialUrl,\n  }) : super(key: key);\n\n  @override\n  State<MyWebViewWidget> createState() => _MyWebViewWidgetState();\n}\n\nclass _MyWebViewWidgetState extends State<MyWebViewWidget> with WidgetsBindingObserver {\n\n  late WebViewController _controller;\n\n  // Drag to refresh helpers\n  final DragGesturePullToRefresh pullToRefresh = DragGesturePullToRefresh();\n  final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey = GlobalKey<RefreshIndicatorState>();\n\n  @override\n  void initState() {\n    super.initState();\n\n    WidgetsBinding.instance!.addObserver(this);\n    if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();\n  }\n\n  @override\n  void dispose() {\n    // remove listener\n    WidgetsBinding.instance!.removeObserver(this);\n    super.dispose();\n  }\n\n  @override\n  void didChangeMetrics() {\n    // on portrait / landscape or other change, recalculate height\n    pullToRefresh.setRefreshDistance(MediaQuery.of(context).size.height);\n  }\n\n  @override\n  Widget build(context) {\n    return RefreshIndicator(\n      key: _refreshIndicatorKey,\n      onRefresh: () {\n        Completer<void> completer = pullToRefresh.refresh();\n        _controller.reload();\n        return completer.future;\n      },\n      child: WebView(\n        initialUrl: widget.initialUrl,\n        javascriptMode: JavascriptMode.unrestricted,\n        zoomEnabled: true,\n        gestureNavigationEnabled: true,\n        gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{\n          pullToRefresh.dragGestureRecognizer(_refreshIndicatorKey),\n        },\n        onWebViewCreated: (WebViewController webViewController) {\n          _controller = webViewController;\n          pullToRefresh.setController(_controller);\n        },\n        onPageStarted: (String url) { pullToRefresh.started(); },\n        onPageFinished: (finish) {    pullToRefresh.finished(); },\n        onWebResourceError: (error) {\n          debugPrint(\n              \'MyWebViewWidget:onWebResourceError(): ${error.description}\');\n          pullToRefresh.finished();\n        },\n      ),\n    );\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

pull_to_refresh.dart:\n从页面top=0开始拖动并始终向下后,计算移动距离,当超过屏幕尺寸的20%时RefreshIndicator show()调用。

\n
import \'package:flutter/material.dart\';\nimport \'dart:async\';\n\nimport \'package:flutter/foundation.dart\';\nimport \'package:flutter/gestures.dart\';\nimport \'package:webview_flutter/webview_flutter.dart\';\n\n// Fixed issue: https://github.com/flutter/flutter/issues/39389\nclass AllowVerticalDragGestureRecognizer extends VerticalDragGestureRecognizer {\n  @override\n  //override rejectGesture here\n  void rejectGesture(int pointer) {\n    acceptGesture(pointer);\n  }\n}\n\nclass DragGesturePullToRefresh {\n  static const double EXCEEDS_LOADING_TIME = 3000;\n  static const double REFRESH_DISTANCE_MIN = .2;\n\n  late WebViewController _controller;\n\n  // loading\n  Completer<void> completer = Completer<void>();\n  int msLoading = 0;\n  bool isLoading = true;\n\n  // drag\n  bool dragStarted = false;\n  double dragDistance = 0;\n  double refreshDistance = 200;\n\n  Factory<OneSequenceGestureRecognizer> dragGestureRecognizer(final GlobalKey<RefreshIndicatorState> refreshIndicatorKey) {\n    return Factory<OneSequenceGestureRecognizer>(() => AllowVerticalDragGestureRecognizer()\n    // Got the original idea from https://stackoverflow.com/users/15862916/shalin-shah:\n    // https://stackoverflow.com/questions/57656045/pull-down-to-refresh-webview-page-in-flutter\n      ..onDown = (DragDownDetails dragDownDetails) {\n        // if the page is still loading don\'t allow refreshing again\n        if (!isLoading ||\n            (msLoading > 0 && (DateTime.now().millisecondsSinceEpoch - msLoading) > EXCEEDS_LOADING_TIME)) {\n          _controller.getScrollY().then((scrollYPos) {\n            if (scrollYPos == 0) {\n              dragStarted = true;\n              dragDistance = 0;\n            }\n          });\n        }\n      }\n      ..onUpdate = (DragUpdateDetails dragUpdateDetails) {\n        calculateDrag(refreshIndicatorKey, dragUpdateDetails.delta.dy);\n      }\n      ..onEnd = (DragEndDetails dragEndDetails) { clearDrag(); }\n      ..onCancel = () { clearDrag(); });\n  }\n\n  void setController(WebViewController controller){ _controller = controller; }\n  void setRefreshDistance(double height){ refreshDistance = height * REFRESH_DISTANCE_MIN; }\n\n  Completer<void> refresh() {\n    if (!completer.isCompleted) {\n      completer.complete();\n    }\n    completer = Completer<void>();\n    started();\n    return completer;\n  }\n\n  void started() {\n    msLoading = DateTime.now().millisecondsSinceEpoch;\n    isLoading = true;\n  }\n\n  void finished() {\n    msLoading = 0;\n    isLoading = false;\n    // hide the RefreshIndicator\n    if (!completer.isCompleted) {\n      completer.complete();\n    }\n  }\n\n  void clearDrag() {\n    dragStarted = false;\n    dragDistance = 0;\n  }\n\n  void calculateDrag(final GlobalKey<RefreshIndicatorState> refreshIndicatorKey, double dy) async {\n    if (dragStarted && dy >= 0) {\n      dragDistance += dy;\n      // Show the RefreshIndicator\n      if (dragDistance > refreshDistance) {\n        debugPrint(\n            \'DragGesturePullToRefresh:refreshPage(): $dragDistance > $refreshDistance\');\n        clearDrag();\n        unawaited(refreshIndicatorKey.currentState?.show());\n      }\n    /*\n      The web page scrolling is not blocked, when you start to drag down from the top position of\n      the page to start the refresh process, e.g. like in the chrome browser. So the refresh process\n      is stopped if you start to drag down from the page top position and then up before reaching\n      the distance to start the refresh process.\n    */\n    } else {\n      clearDrag();\n    }\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

此修复对于手势事件flutter webview VerticalDragGestureRecognizer 没有回调但只有 onDown 和 onCancel很有帮助。

\n

完整的代码也在github上。

\n

gif,我不可以发图……

\n

与 SingleChildScrollView 或 chrome 浏览器的差异

\n

=> 已修复:转到更新

\n
    \n
  • RefreshIndicator通过向下拖动直到达到启动刷新过程的距离,不会显示初始动画。(可以不同方式添加)
  • \n
  • 当您开始从页面顶部位置向下拖动以开始刷新过程时,网页滚动不会被阻止,例如在 chrome 浏览器中。因此,如果您开始从页面顶部位置向下拖动,然后在到达开始刷新过程的距离之前向上拖动,刷新过程就会停止。检查我的解决方案和评论refreshPage()中的方法。pull_to_refresh.dart
  • \n
\n

我发现差异无关 \xe2\x80\x8d\xe2\x99\x80\xef\xb8\x8f 因为这些问题破坏了浏览体验。

\n

更新

\n

我更改了使用ScrollNotificationwhich在设置RefreshIndicator时解释正确。FixedScrollMetrics所以我们有原始动画,例如SingleChildScrollViewchrome 浏览器。

\n

github

\n

完整代码:

\n

webview.dart

\n
import \'package:flutter/material.dart\';\nimport \'dart:io\';\n\nimport \'package:flutter/foundation.dart\';\nimport \'package:flutter_web_refresh/pull_to_refresh.dart\';\nimport \'package:webview_flutter/webview_flutter.dart\';\n\nclass MyWebViewWidget extends StatefulWidget {\n  final String initialUrl;\n\n  const MyWebViewWidget({\n    Key? key,\n    required this.initialUrl,\n  }) : super(key: key);\n\n  @override\n  State<MyWebViewWidget> createState() => _MyWebViewWidgetState();\n}\n\nclass _MyWebViewWidgetState extends State<MyWebViewWidget>\n    with WidgetsBindingObserver {\n\n  late WebViewController _controller;\n  late DragGesturePullToRefresh dragGesturePullToRefresh;\n\n  @override\n  void initState() {\n    super.initState();\n\n    dragGesturePullToRefresh = DragGesturePullToRefresh();\n    WidgetsBinding.instance!.addObserver(this);\n    if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();\n  }\n\n  @override\n  void dispose() {\n    // remove listener\n    WidgetsBinding.instance!.removeObserver(this);\n    super.dispose();\n  }\n\n  @override\n  void didChangeMetrics() {\n    // on portrait / landscape or other change, recalculate height\n    dragGesturePullToRefresh.setHeight(MediaQuery.of(context).size.height);\n  }\n\n  @override\n  Widget build(context) {\n    return\n        // NotificationListener(\n        // onNotification: (scrollNotification) {\n        //  debugPrint(\'MyWebViewWidget:NotificationListener(): $scrollNotification\');\n        //  return true;\n        // }, child:\n      RefreshIndicator(\n        onRefresh: () => dragGesturePullToRefresh.refresh(),\n        child: Builder(\n          builder: (context) => WebView(\n            initialUrl: widget.initialUrl,\n            javascriptMode: JavascriptMode.unrestricted,\n            zoomEnabled: true,\n            gestureNavigationEnabled: true,\n            gestureRecognizers: {Factory(() => dragGesturePullToRefresh)},\n            onWebViewCreated: (WebViewController webViewController) {\n              _controller = webViewController;\n              dragGesturePullToRefresh\n                  .setContext(context)\n                  .setController(_controller);\n            },\n            onPageStarted: (String url) { dragGesturePullToRefresh.started(); },\n            onPageFinished: (finish) {    dragGesturePullToRefresh.finished();},\n            onWebResourceError: (error) {\n              debugPrint(\n                  \'MyWebViewWidget:onWebResourceError(): ${error.description}\');\n              dragGesturePullToRefresh.finished();\n            },\n          ),\n        ),\n      );\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

pull_to_refresh.dart

\n
import \'package:flutter/cupertino.dart\';\nimport \'package:flutter/material.dart\';\nimport \'dart:async\';\n\nimport \'package:flutter/gestures.dart\';\nimport \'package:webview_flutter/webview_flutter.dart\';\n\n// Fixed issue: https://github.com/flutter/flutter/issues/39389\nclass DragGesturePullToRefresh extends VerticalDragGestureRecognizer {\n  static const double EXCEEDS_LOADING_TIME = 3000;\n\n  late BuildContext _context;\n  late WebViewController _controller;\n\n  // loading\n  Completer<void> completer = Completer<void>();\n  int msLoading = 0;\n  bool isLoading = true;\n\n  // drag\n  double height = 200;\n  bool dragStarted = false;\n  double dragDistance = 0;\n\n  @override\n  //override rejectGesture here\n  void rejectGesture(int pointer) {\n    acceptGesture(pointer);\n  }\n\n  void _clearDrag() {\n    dragStarted = false;\n    dragDistance = 0;\n  }\n\n  DragGesturePullToRefresh setContext(BuildContext context) { _context = context; return this; }\n  DragGesturePullToRefresh setController(WebViewController controller) { _controller = controller; return this; }\n\n  void setHeight(double height) { this.height = height; }\n\n  Future refresh() {\n    if (!completer.isCompleted) {\n      completer.complete();\n    }\n    completer = Completer<void>();\n    started();\n    _controller.reload();\n    return completer.future;\n  }\n\n  void started() {\n    msLoading = DateTime.now().millisecondsSinceEpoch;\n    isLoading = true;\n  }\n\n  void finished() {\n    msLoading = 0;\n    isLoading = false;\n    // hide the RefreshIndicator\n    if (!completer.isCompleted) {\n      completer.complete();\n    }\n  }\n\n  FixedScrollMetrics _getMetrics(double minScrollExtent, double maxScrollExtent,\n      double pixels, double viewportDimension, AxisDirection axisDirection) {\n    return FixedScrollMetrics(\n        minScrollExtent: minScrollExtent,\n        maxScrollExtent: maxScrollExtent,\n        pixels: pixels,\n        viewportDimension: viewportDimension,\n        axisDirection: axisDirection);\n  }\n\n  DragGesturePullToRefresh() {\n    onStart = (DragStartDetails dragDetails) {\n      // debugPrint(\'MyWebViewWidget:onStart(): $dragDetails\');\n      if (!isLoading ||\n          (msLoading > 0 && (DateTime.now().millisecondsSinceEpoch - msLoading) > EXCEEDS_LOADING_TIME)) {\n        _controller.getScrollY().then((scrollYPos) {\n          if (scrollYPos == 0) {\n            dragStarted = true;\n            dragDistance = 0;\n            ScrollStartNotification(\n                    metrics: _getMetrics(0, height, 0, height, AxisDirection.down),\n                    dragDetails: dragDetails,\n                    context: _context)\n                .dispatch(_context);\n          }\n        });\n      }\n    };\n    onUpdate = (DragUpdateDetails dragDetails) {\n      if (dragStarted) {\n        double dy = dragDetails.delta.dy;\n        dragDistance += dy;\n        ScrollUpdateNotification(\n                metrics: _getMetrics(\n                    dy > 0 ? 0 : dragDistance, height,\n                    dy > 0 ? (-1) * dy : dragDistance, height,\n                    dragDistance < 0 ? AxisDirection.up : AxisDirection.down),\n                context: _context,\n                scrollDelta: (-1) * dy)\n            .dispatch(_context);\n        if (dragDistance < 0) {\n          _clearDrag();\n        }\n      }\n    };\n    onEnd = (DragEndDetails dragDetails) {\n      ScrollEndNotification(\n              metrics: _getMetrics(0, height, dragDistance, height, AxisDirection.down),\n              context: _context)\n          .dispatch(_context);\n      _clearDrag();\n    };\n    onCancel = () {\n      ScrollUpdateNotification(\n              metrics: _getMetrics(0, height, 1, height, AxisDirection.up),\n              context: _context,\n              scrollDelta: 0)\n          .dispatch(_context);\n      _clearDrag();\n    };\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n