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
技术点分析
-
- webView 原生 混合方式
-
- 计算 web 页面高度
-
- 拦截请求,自定义指令
-
- 内存占用(尽量少的 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