目前我有一个需求,就是开发一个新闻APP,类似于头条,有新闻的展示,用户的评论和点赞、收藏等等,这个后端接口已经设计好了,这个功能主要通过访问url使用webview来实现该功能,一言不合就开始撸代码了:
- 加载flutter webview插件
- 定制化webview的显示界面
- 对官方插件的二次定制
- 完成对url的加载监听
1. 加载flutter webview插件
flutter_webview_plugin: ^0.3.0+2
2. 定制化webview的显示界面
本页面主要由三部分组成,最上面那部分是flutter的原生页面,中间部分是webview页面,最下方是由bottomNavigationBar组件实现,如代码所示。
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
title: Text('网页详情'),
leading: IconButton(
icon:Icon(Icons.arrow_back_ios,size:20) ,
onPressed: (){
_webviewReference.hide();
String url = 'http://www.appshuo.club/appview/p/';
MyRouter.pushNoParams(context, MyRouter.homePage);
},),
bottom: PreferredSize(
child: _progressBar(lineProgress, context),
preferredSize: Size.fromHeight(0.1),
)
),
body: SafeArea(
child: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.all(10),
child: Column(
children: [
Padding(
padding: EdgeInsets.only(bottom: 10),
child:Row(
children: [
CircleAvatar(
radius: 20.0,
backgroundImage: NetworkImage(widget.article.user.photo),
backgroundColor: Colors.white,
),
Padding(
padding: const EdgeInsets.only(left: 10.0),
child: Text(widget.article.user.nickname),
),
Padding(
padding: const EdgeInsets.only(left: 15.0),
child: Text('阅读',style: TextStyle(color: Colors.black38,fontSize: 13.0)),
),
Padding(
padding: const EdgeInsets.only(left: 3.0),
child: Text('${widget.article.readcount}',style: TextStyle(color: Colors.black38,fontSize: 13.0)),
),
Expanded(
child: GestureDetector(
child: Padding(
padding: EdgeInsets.only(left: ScreenUtils.screenW()/2-50,right: 10.0),
child: new SizedBox(
width: 50,
child: FlatButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(0.0),
side: BorderSide(color: Colors.green)),
color: Colors.white,
textColor: Colors.green,
padding: EdgeInsets.all(8.0),
onPressed: () {},
child: Text(
"+ 关注",
style: TextStyle(
fontSize: 14.0,
),
),
),
),
),
onTap: () {
MyRouter.pushNoParams(context,MyRouter.userPage);
},
),
)
],
)),
Container(
height: ScreenUtils.screenH()-ScreenUtils.padTopH() - 120.0 - 40.0,
child: MyFlutterWebViewPage(url),
),
],
),
)),
),
bottomNavigationBar: BottomAppBar(
child: talkFeild(),
),
);
}
Widget talkFeild(){
return Row(
children: <Widget>[
Expanded(
child: Container(
margin: EdgeInsets.only(left: 20.0,top:5.0,bottom: 5.0),
width: MediaQuery.of(context).size.width,
alignment: AlignmentDirectional.center,
height: this.h,
decoration: BoxDecoration(
color: Color.fromARGB(255, 237, 236, 237),
borderRadius: BorderRadius.circular(24.0)),
child: TextField(
// onSubmitted: onSubmitted,
onTap: (){
_webviewReference.hide();
MyRouter.push(context, MyRouter.writeCommentPage, {'articleId': widget.article.id, 'article': widget.article});
},
cursorColor: Color.fromARGB(255, 0, 189, 96),
decoration: InputDecoration(
contentPadding: const EdgeInsets.only(left: 10.0,bottom:12.0),
border: InputBorder.none,
hintText: '说两句儿...',
hintStyle: TextStyle(
fontSize: 15, color: Color.fromARGB(255, 192, 191, 191)),
),
style: TextStyle(fontSize: 15),
),
),
),
GestureDetector(
child: Padding(
padding: EdgeInsets.only(left: 20.0),
child: IconText(context, widget.article.commentCount == 0 ? '评论' : widget.article.commentCount, Constant.ASSETS_IMG +
'ic_notification_tv_calendar_comments.png'),
),
onTap: () {
_webviewReference.hide();
MyRouter.push(context, MyRouter.commentList, {'articleId': widget.article.id, 'article': widget.article});
},
),
GestureDetector(
child: Padding(
padding: EdgeInsets.only(left: 20.0, right: 20.0),
child: IconText(context, widget.article.praiseCount == 0 ? '赞' : widget.article.praiseCount, Constant.ASSETS_IMG + 'ic_vote.png'),
),
onTap: () {
Navigator.pop(context);
},
)
],
);
}
Row textField() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
Expanded(
child: new TextField(
decoration: InputDecoration(
hintText: '期待您的评论...',
border: null,
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.blue[300]),
),
),
keyboardType: TextInputType.text,
maxLength: 250,
maxLines: 1,
),
),
IconButton(
icon: Icon(Icons.send),
onPressed: () {
_webviewReference.show();
Navigator.of(context).pop();
},
)
],
);
}
TextStyle getStyle(Color color, double fontSize, {bool bold = false}) {
return TextStyle(
color: color,
fontSize: fontSize,
fontWeight: bold ? FontWeight.bold : FontWeight.normal);
}
}
3. 对官方插件的二次定制
先通过notifyRect()在界面上面一个矩形,然后使用_webviewReference.launch方法在指定的矩形中加载url
import 'dart:async';
import 'package:duo_zui/Util/screen_utils.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';
class MyFlutterWebViewPage extends StatefulWidget {
final String url;
bool hidden = false;
MyFlutterWebViewPage(this.url, {Key key}) : super(key: key);
@override
_WebViewWidgetState createState() => _WebViewWidgetState();
}
class _WebViewWidgetState extends State<MyFlutterWebViewPage> {
final webviewReference = FlutterWebviewPlugin();
bool _closed = false;
Rect _rect;
bool needFullScreen = false;
Timer _resizeTimer;
StreamSubscription<WebViewStateChanged> _onStateChanged;
@override
void initState() {
super.initState();
webviewReference.close();
if (widget.hidden) {
_onStateChanged =
webviewReference.onStateChanged.listen((WebViewStateChanged state) {
if (state.type == WebViewState.finishLoad) {
webviewReference.show();
}
});
}
}
@override
void dispose() {
super.dispose();
webviewReference.close();
webviewReference.dispose();
if (widget.hidden) {
_onStateChanged.cancel();
}
}
@override
Widget build(BuildContext context) {
print('build widget.url=\\\\${widget.url}');
return _WebviewPlaceholder(onRectChanged: (Rect value) {
if (_rect == null || _closed) {
if(_rect != value){
_rect = value;
}
print('_webviewReference.launch');
webviewReference.launch(widget.url,
withJavascript: true,
withLocalStorage: true,
scrollBar: true,
rect: getRect());
} else {
print('_webviewReference.launch else');
// if (_rect != value) {
// _rect = value;
// }
// webviewReference.reloadUrl(widget.url);
if (_rect != value) {
_rect = value;
_resizeTimer?.cancel();
_resizeTimer = Timer(const Duration(milliseconds: 250), () {
// avoid resizing to fast when build is called multiple time
webviewReference.resize(_rect);
});
}
}
}, child: const Center(child: const CircularProgressIndicator()),);
}
getRect() {
if(needFullScreen){
return null;
}else{
return Rect.fromLTRB(0.0, ScreenUtils.padTopH() + 120.0,
ScreenUtils.screenW(), ScreenUtils.screenH() - 40.0);
}
}
}
class _WebviewPlaceholder extends SingleChildRenderObjectWidget {
const _WebviewPlaceholder({
Key key,
@required this.onRectChanged,
Widget child,
}) : super(key: key, child: child);
final ValueChanged<Rect> onRectChanged;
@override
RenderObject createRenderObject(BuildContext context) {
return _WebviewPlaceholderRender(
onRectChanged: onRectChanged,
);
}
@override
void updateRenderObject(
BuildContext context, _WebviewPlaceholderRender renderObject) {
renderObject..onRectChanged = onRectChanged;
}
}
class _WebviewPlaceholderRender extends RenderProxyBox {
_WebviewPlaceholderRender({
RenderBox child,
ValueChanged<Rect> onRectChanged,
}) : _callback = onRectChanged,
super(child);
ValueChanged<Rect> _callback;
Rect _rect;
Rect get rect => _rect;
set onRectChanged(ValueChanged<Rect> callback) {
if (callback != _callback) {
_callback = callback;
notifyRect();
}
}
void notifyRect() {
if (_callback != null && _rect != null) {
_callback(_rect);
}
}
@override
void paint(PaintingContext context, Offset offset) {
super.paint(context, offset);
final rect = offset & size;
if (_rect != rect) {
_rect = rect;
notifyRect();
}
}
}
4. 完成对url的加载监听
最后来实现加载url的进度条:
_progressBar(double progress, BuildContext context) {
return Container(
child: LinearProgressIndicator(
backgroundColor: Colors.blueAccent.withOpacity(0),
value: progress == 1.0 ? 0 : progress,
valueColor: new AlwaysStoppedAnimation<Color>(Colors.lightBlue),
),
height: 1,
);
}
整个定制的过程就写完了,在网上定制webview的资料还是很少见的,通常都是整个页面的加载,以上是其中的一个方案,希望可以帮助到大家,以后我会持续更新我的app进度,将我遇到的难点和我的方案分享给大家。
最后我将我的疑问抛出来,希望哪位大佬看到我的疑问,可以为我答疑解惑,共同进步,1、flutter的webview的页面都是会覆盖flutter的原生Widget,导致我的评论框要评论的时候都显示不出来,我实现的方案是跳转到另一个页面,但是感觉不是友好,2、flutter的webview如何在文章后追加flutter原生的widget,比如我的评论列表,以及以下推荐新闻,我现在的实现方案是将评论列表放到了网页中。
本文含有隐藏内容,请 开通VIP 后查看