乐趣区

Flutter-新闻客户端-09-详情页展示分享远程真机调试

B 站视频

https://www.bilibili.com/vide…
https://www.bilibili.com/vide…

本节目标

  • 详情页技术方案比较
  • 载入 web 内容
  • 自动计算高度
  • 清除广告、推荐
  • 拦截请求
  • loading 状态显示
  • 分享插件
  • 远程 android 设备调试

详情展示

技术方案选择

分析工具 UI automator view

  • 文件位置

/Users/ducafecat/Library/Android/sdk/tools/bin/uiautomatorviewer

淘宝方案

混合方式

头条

混合方式

什么值得买

单一 webView

技术点分析

    1. webView 原生 混合方式
    1. 计算 web 页面高度
    1. 拦截请求,自定义指令
    1. 内存占用(尽量少的 dom 元素)

安装插件

  • webview_flutter

https://pub.flutter-io.cn/pac…

  • pubspec.yaml
dependencies:
  webview_flutter: ^0.3.20+2
  • ios/Runner/Info.plist
    <key>io.flutter.embedded_views_preview</key>
    <true/>

构建界面代码

  // 顶部导航
  Widget _buildAppBar() {return Container();
  }

  // 页标题
  Widget _buildPageTitle() {return Container();
  }

  // 页头部
  Widget _buildPageHeader() {return Container();
  }

  // web 内容
  Widget _buildWebView() {return Container();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(appBar: _buildAppBar(),
        body: SingleChildScrollView(
              child: Column(
                children: <Widget>[_buildPageTitle(),
                  Divider(height: 1),
                  _buildPageHeader(),
                  _buildWebView(),],
              ),
            ),
          );
  }

url 载入

  Widget _buildWebView() {
    return Container(
      height: _webViewHeight,
      child: WebView(
        initialUrl:
            '$SERVER_API_URL/news/content/${widget.item.id}', //widget.url,
        javascriptMode: JavascriptMode.unrestricted,
        onWebViewCreated: (WebViewController webViewController) async {_controller.complete(webViewController);
        },
        gestureNavigationEnabled: true,
      ),
    );
  }

计算高度

  • PX DP

    • https://blog.akanelee.me/2018…
  • 设备像素密度

    一个逻辑像素占用多少个实际像素

    • https://developer.mozilla.org…
    • https://api.flutter.dev/flutt…
  • 注册 js
  double _webViewHeight = 200;

        javascriptChannels: <JavascriptChannel>[_invokeJavascriptChannel(context),
        ].toSet(),

  // 注册 js 回调
  JavascriptChannel _invokeJavascriptChannel(BuildContext context) {
    return JavascriptChannel(
        name: 'Invoke',
        onMessageReceived: (JavascriptMessage message) {print(message.message);
          var webHeight = double.parse(message.message);
          if (webHeight != null) {setState(() {_webViewHeight = webHeight;});
          }
        });
  }
  • 回调
        onPageFinished: (String url) {_getWebViewHeight();
          setState(() {_isPageFinished = true;});
        },
  // 获取页面高度
  _getWebViewHeight() async {await (await _controller.future)?.evaluateJavascript('''
        try {// Invoke.postMessage([document.body.clientHeight,document.documentElement.clientHeight,document.documentElement.scrollHeight]);
          let scrollHeight = document.documentElement.scrollHeight;
          if (scrollHeight) {Invoke.postMessage(scrollHeight);
          }
        } catch {}
        ''');
  }

清除广告、推荐

  • https://cn.engadget.com/cn-20…
  • 删除广告
        onPageStarted: (String url) {Timer(Duration(seconds: 1), () {setState(() {_isPageFinished = true;});
           _removeAd();
           _getViewHeight();});
        },
  _removeWebViewAd() async {await (await _controller.future)?.evaluateJavascript('''
        try {function removeElement(elementName){let _element = document.getElementById(elementName);
            if(!_element) {_element = document.querySelector(elementName);
            }
            if(!_element) {return;}
            let _parentElement = _element.parentNode;
            if(_parentElement){_parentElement.removeChild(_element);
            }
          }

          removeElement('module-engadget-deeplink-top-ad');
          removeElement('module-engadget-deeplink-streams');
          removeElement('footer');
        } catch{}
        ''');
  }

拦截请求

  • 页面中 href
<div class="tags">
  <a href="/tag/chrome-os" class="tag">chrome os</a>
  <a href="/tag/chromebook" class="tag">chromebook</a>
  <a href="/tag/computer" class="tag">computer</a>
  <a href="/tag/gear" class="tag">gear</a>
  <a href="/tag/google" class="tag">google</a>
  <a href="/tag/laptop" class="tag">laptop</a>
  <a href="/tag/personal computing" class="tag">personal computing</a>
  <a href="/tag/personalcomputing" class="tag">personalcomputing</a>
  <a href="/tag/pixelbook-go" class="tag">pixelbook go</a>
</div>
  • navigation 拦截
        navigationDelegate: (NavigationRequest request) {if (request.url != '$SERVER_API_URL/news/content/${widget.item.id}') {toastInfo(msg: request.url);
            return NavigationDecision.prevent;
          }
          return NavigationDecision.navigate;
        },

loading 状态显示


  bool _isPageFinished = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(appBar: _buildAppBar(),
        body: Stack(
          children: <Widget>[
            SingleChildScrollView(
              child: Column(
                children: <Widget>[_buildPageTitle(),
                  Divider(height: 1),
                  _buildPageHeader(),
                  _buildWebView(),],
              ),
            ),
            _isPageFinished == true
                ? Container()
                : Align(
                    alignment: Alignment.center,
                    child: LoadingBouncingGrid.square(),),
          ],
        ));
  }

分享

安装插件

dependencies:
  share: ^0.6.4

代码

            onPressed: () {Share.share('${widget.item.title} ${widget.item.url}');
            },

真机调试

  • scrcpy

https://github.com/Genymobile…

资源

视频

  • b 站
  • 油管镜像

蓝湖设计稿(加微信给授权 ducafecat)

https://lanhuapp.com/url/lYuz1
密码: gSKl

蓝湖现在收费了,所以查看标记还请自己上传 xd 设计稿
商业设计稿文件不好直接分享, 可以加微信联系 ducafecat

YAPI 接口管理

http://yapi.demo.qunar.com/

代码

https://github.com/ducafecat/…

参考

https://pub.flutter-io.cn/pac…
https://pub.flutter-io.cn/pac…
https://pub.flutter-io.cn/pac…
https://github.com/Genymobile…

VSCode 插件

  • Flutter、Dart
  • Flutter Widget Snippets
  • Awesome Flutter Snippets
  • Paste JSON as Code
  • bloc
  • Code Spell Checker
退出移动版