本节目标
- 详情页技术方案比较
- 载入 web 内容
- 自动计算高度
- 清除广告、推荐
- 拦截请求
- loading 状态显示
- 分享插件
- 远程 android 设备调试
视频
代码
https://github.com/ducafecat/flutter_learn_news/releases/tag/v1.0.9
详情展示
技术方案选择
分析工具 UI automator view
/Users/ducafecat/Library/Android/sdk/tools/bin/uiautomatorviewer
淘宝方案
混合方式
头条
混合方式
什么值得买
单一 webView
技术点分析
- webView 原生 混合方式
- 计算 web 页面高度
- 拦截请求,自定义指令
- 内存占用(尽量少的 dom 元素)
安装插件
https://pub.flutter-io.cn/packages/webview_flutter
1 2
| dependencies: webview_flutter: ^0.3.20+2
|
1 2
| <key>io.flutter.embedded_views_preview</key> <true/>
|
构建界面代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| Widget _buildAppBar() { return Container(); }
Widget _buildPageTitle() { return Container(); }
Widget _buildPageHeader() { return Container(); }
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 载入
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| Widget _buildWebView() { return Container( height: _webViewHeight, child: WebView( initialUrl: '$SERVER_API_URL/news/content/${widget.item.id}', javascriptMode: JavascriptMode.unrestricted, onWebViewCreated: (WebViewController webViewController) async { _controller.complete(webViewController); }, gestureNavigationEnabled: true, ), ); }
|
计算高度
PX DP
设备像素密度
一个逻辑像素占用多少个实际像素
注册 js
1 2 3 4 5
| double _webViewHeight = 200;
javascriptChannels: <JavascriptChannel>[ _invokeJavascriptChannel(context), ].toSet(),
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
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; }); } }); }
|
1 2 3 4 5 6
| onPageFinished: (String url) { _getWebViewHeight(); setState(() { _isPageFinished = true; }); },
|
1 2 3 4 5 6 7 8 9 10 11 12
| _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 {} '''); }
|
清除广告、推荐
1 2 3 4 5 6 7 8 9
| onPageStarted: (String url) { Timer(Duration(seconds: 1), () { setState(() { _isPageFinished = true; }); _removeAd(); _getViewHeight(); }); },
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| _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{} '''); }
|
拦截请求
1 2 3 4 5 6 7 8 9 10 11
| <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>
|
1 2 3 4 5 6 7
| 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 状态显示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| 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(), ), ], )); }
|
分享
安装插件
1 2
| dependencies: share: ^0.6.4
|
代码
1 2 3
| onPressed: () { Share.share('${widget.item.title} ${widget.item.url}'); },
|
真机调试
https://github.com/Genymobile/scrcpy
资源
视频
蓝湖设计稿(加微信给授权 ducafecat)
https://lanhuapp.com/url/lYuz1
密码: gSKl
蓝湖现在收费了,所以查看标记还请自己上传 xd 设计稿
商业设计稿文件不好直接分享, 可以加微信联系 ducafecat
YAPI 接口管理
http://yapi.demo.qunar.com/
参考
https://pub.flutter-io.cn/packages/webview_flutter
https://pub.flutter-io.cn/packages/loading_animations
https://pub.flutter-io.cn/packages/share
https://github.com/Genymobile/scrcpy
VSCode 插件
© 猫哥
https://ducafecat.tech
邮箱 ducafecat@gmail.com / 微信 ducafecat / 留言板 disqus