この記事は、現状のFlutterSDKで中でAndroid側で発生している問題を回避するだけのものです
課題
・iOSはWebViewのGestureを使用したやり方の記事が散見される
・AndroidはWebView+PullToRefreshがうまく動作しないバグがある
私が行った対応
iOS
WebView+pullToRefresh
android
Stackの中にWebViewとPullToRefreshを別々に入れる
class AllowVerticalDragGestureRecognizer extends VerticalDragGestureRecognizer {
@override
void rejectGesture(int pointer) {
acceptGesture(pointer);
}
}
// ignore: must_be_immutable
class WebviewPage extends StatelessWidget {
WebviewPage({Key? key, required this.initialUrl}) : super(key: key);
final String initialUrl;
WebViewController? controller;
final refreshController = RefreshController();
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
// Androidの戻るボタンをwebviewのbackに反映させる
if (await controller?.canGoBack() ?? false) {
controller!.goBack();
}
return false;
},
child: Scaffold(
body: Platform.isIOS ? _iosWidget : _androidWidget,
),
);
}
Widget get _iosWidget {
return SmartRefresher(
onRefresh: () async {
await controller?.reload();
refreshController.refreshCompleted();
},
controller: refreshController,
header: Platform.isIOS
? CustomHeader(
builder: (_, __) {
return const CupertinoActivityIndicator(
color: Colors.white,
);
},
)
: const MaterialClassicHeader(),
child: _customWebView(),
);
}
Widget get _androidWidget {
return Stack(alignment: AlignmentDirectional.topCenter, children: [
_customWebView(),
Positioned(
top: 0,
left: 0,
right: 0,
height: 80,
child: IgnorePointer(
ignoring: true,
child: SmartRefresher(
onRefresh: () async {
await controller?.reload();
Future.delayed(const Duration(milliseconds: 500)).then((_) {
refreshController.refreshCompleted();
});
},
controller: refreshController,
header: Platform.isIOS
? CustomHeader(
builder: (_, __) {
return const CupertinoActivityIndicator(
color: Colors.white,
);
},
)
: const MaterialClassicHeader(),
child: Container(
height: 0,
),
),
),
),
]);
}
Widget _customWebView() {
final gesture = <Factory<AllowVerticalDragGestureRecognizer>>{};
gesture.add(Factory<AllowVerticalDragGestureRecognizer>(
() => Platform.isIOS ? _iosGesture : _androidGesture,
));
return WebView(
userAgent: Const.userAgent,
backgroundColor: Colors.black,
javascriptMode: JavascriptMode.unrestricted,
gestureRecognizers: gesture,
onWebViewCreated: (controller) {
this.controller = controller;
},
);
}
AllowVerticalDragGestureRecognizer get _iosGesture {
final gesture = AllowVerticalDragGestureRecognizer();
bool refreshFlag = false;
gesture.onDown = (_) {
controller!.getScrollY().then(
(value) {
if (value == 0) {
refreshFlag = true;
}
},
);
};
gesture.onEnd = (_) {
if (refreshFlag) {
refreshFlag = false;
controller!.getScrollY().then((value) {
if (value < 0) {
refreshController.requestRefresh();
}
});
}
};
return gesture;
}
AllowVerticalDragGestureRecognizer get _androidGesture {
final gesture = AllowVerticalDragGestureRecognizer();
bool refreshFlag = false;
gesture.onStart = (startDetails) {
controller!.getScrollY().then(
(value) {
if (value == 0) {
refreshFlag = true;
}
},
);
};
gesture.onEnd = (DragEndDetails dragEndDetails) {
if (refreshFlag) {
refreshFlag = false;
controller!.getScrollY().then((value) {
if (dragEndDetails.primaryVelocity == 0) {
return;
}
if (value == 0) {
refreshController.requestRefresh();
}
});
}
};
return gesture;
}
}