下拉刷新组件与WebView内部子可滚动div冲突问题

文章讲述了在Android的WebView中处理可滚动Div与下拉刷新冲突的方案。通过使用evaluateJavascript监听Div的scrollTop值来控制是否允许下拉刷新。对于没有id的React组件,如Picker,可以通过添加隐藏div并改变其高度来同步Android端的状态,从而决定是否可以下拉刷新。

我的页面,分为2个div,2个div的高度加起来是100%占据整个webview页面。而最上面的div是一个可滚动的div(overflow: "auto")。往上滑动时没问题的,但是往下滑动就会出现问题 它会直接触发下拉刷新的问题
网上有如下解决方案,但是针对这个案例是无效的,因为本案例中webView.getScrollY始终为0(三个div构成了整个webview页面,自始至终这三个div位置是不会变化的,是固定的)

// 设置子视图是否允许滚动到顶部
    refreshLayout.setOnChildScrollUpCallback(new SwipeRefreshLayout.OnChildScrollUpCallback() {
       @Override
       public boolean canChildScrollUp(SwipeRefreshLayout parent, @Nullable View child) {
           return webView.getScrollY() > 0;
       }
    });

所以问题在于如何监听webview页面内子布局的滚动,以子布局的滚动来控制刷新动作。
可以利用webview的evaluateJavascript方法来监听特定元素,第一个参数script用于执行一段js脚本以获取指定元素的指定属性比如div的height,第二个参数resultCallback是一个回调函数,当取到第一个参数指定的属性时就会执行调用。

public void evaluateJavascript(String script, ValueCallback<String> resultCallback)

所以解决方案很明了了。只需要利用evaluateJavascript来监听可滚动的div的scrollTop值,当其为0时表示在顶端,此时下拉可以刷新。否则禁止下拉刷新。注意,这里需要和前端商量约定统一的要监听的元素的id,即下面的inner_scroll_box。

        wvContent?.setOnTouchListener { _, event ->
            when (event?.action) {
                MotionEvent.ACTION_MOVE -> {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                        wvContent?.evaluateJavascript(
                            "document.getElementById('inner_scroll_box').scrollTop"
                        ) { value ->
                            if (!TextUtils.isEmpty(value) && "null" != value) {
                                var doubleVa = value.toDoubleOrNull()
                                if (doubleVa != null) {
                                    if (doubleVa <= 0) {
                                        mWebviewScrollView?.setEnableRefresh(true);
                                    } else {
                                        mWebviewScrollView?.setEnableRefresh(false);
                                    }
                                }

                            }
                        };
                    }

                }
            }
            false
        }

对于没有id的React组件,比如antd mobile组件Picker,就需要添加一个隐藏的div,然后根据Picker的状态的改变来改变div的height,最后就可以在android端根据div的高度来决定是否可以下拉刷新了。

Picker

添加id为type_picker的隐藏div,在onVisibleChange中根据Picker列表的隐藏状态来改变div的高度。

<div>
                <Picker data={vms} cols={1} title={"资金类型"} value={this.state.value} extra={v2label[this.state.value]}
                    onChange={v => {if (v === this.state.value) {this.handleChange(v2type[v]);this.setState({ value: v })}}}
                    onVisibleChange={v => {if (v===true) {document.getElementById('type_picker').style.height="1px"} else {document.getElementById('type_picker').style.height="0px"}}}
                >
                    <CustomPickerButton/>
                </Picker>
                {/*添加此div是为了与android端进行特殊操作,让android端知道Picker隐藏状态*/}
                <div id={"type_picker"} style={{visibility: "none", height: 0}}></div>
            </div>

在android端根据id为type_picker的div的高度来决定是否可以下拉刷新。

webView.evaluateJavascript("document.getElementById(\"type_picker\").style.height", new ValueCallback<String>() {
                                @Override
                                public void onReceiveValue(String value) {
                                    if (!TextUtils.isEmpty(value) && !"null".equals(value)) {
                                        if (!"0px".equals(value.replace("\"", ""))) {
                                            refreshLayout.setEnabled(false);
                                        } else {
                                            refreshLayout.setEnabled(true);
                                        }
                                    }
                                }
                            });
请生成新代码 使得webview嵌在页面内可以跟随页面移动 <template> <view class="wapper"> <!-- <view class="bg-image"></view> --> <image class="bg-image" src="/static/home/bus/bg-image.png" mode="widthFix" style="width: 100%;" /> <!-- 自定义导航栏 --> <view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }"> <view class="navbar-content"> <!-- 左侧返回按钮 --> <view class="back-btn" @click="handleBack"> <image src="/static/home/bus/back-icon.png" mode="aspectFit" style="width: 48rpx;height: 48rpx;" /> </view> <!-- 中间标题 --> <text class="title">{{busLineBase.name}}</text> </view> </view> <view class="wapper-content" :style="{ paddingTop: statusBarHeight +50 + 'px' }"> <view class="line_info"> <view class="site"> <text class="from">{{busLineBase.startSn}}</text> <view class="arrow"> <text class="to">{{busLineBase.endSn}}</text> </view> </view> <view class="tit"> <view class="time_s"> <text class="time-text">{{busLineBase.firstTime}}</text> </view> <view class="time_e"> <text class="time-text">{{busLineBase.lastTime}}</text> </view> <text class="price">票价: {{busLineBase.price}}</text> </view> </view> <view class="distance-class"> <view class="station"> <view class="station-title"> <view class="station-name">候车站</view> <view class="station-content"> <view class="active">{{stationName}}</view> <view class="underline"></view> </view> </view> </view> <view class="distance" v-if="state==0 && travelOrder!=0"> <view class="time"> <text class="time_title">{{Math.ceil(travelTime/60)}}分钟</text> </view> <text class="arr">{{travelOrder}}站/ <text v-if="travelDistance<1000">{{numFilter(travelDistance)}}m</text> <text v-else>{{numFilter(travelDistance/1000)}}km</text> </text> </view> <view class="distance" v-else-if="state==0 && travelOrder==0 && isArrive==1"> <view class="time"> <view class="bus-arr">已到站</view> </view> </view> <view class="distance" v-else-if="state==0 &&isArrive==0"> <view class="time"> <view class="bus-coming">即将到站 {{travelDistance}}m</view> </view> </view> <view class="distance" v-else-if="state==-1"> <view class="time"> <view class="none">等待发车</view> </view> </view> <view class="distance" v-else> <view class="time"> <view class="none">暂无车辆信息</view> </view> </view> <!-- <view>已到站</view> --> </view> </view> <view class="site_list"> <view> <scroll-view class="wapper_sitelist" scroll-x="true" scroll-with-animation :scroll-into-view="targetId"> <view class="road"> <view class='part' v-for="(item, index) in siteList" @click="showActive(index, item.sn)"> <view :id="'id'+item.sid" class='name'> <view> <view class="dot_p" :class="{'dot_p_show': ind === index}"></view> <view class='dot_r' v-if="index!=siteList.length-1"></view> </view> <div :class="item.state==1?'road-bus':'road-bus-coming'" v-if="item.isHaveBus==0"></div> <view class='dot_d' :class="{'dot_d_show': ind === index}"></view> <view class='text' :class="{'text_show': ind === index}"> <view>{{index +1}}</view> <view class="text-dir"> {{item.sn}} </view> </view> </view> </view> </view> </scroll-view> <!-- <view> <web-view ref="webview" :webview-styles="webviewStyles" :src="src" :fullscreen="false"></web-view> </view> --> <!-- <view class="page-body"> <view class="page-section page-section-gap"> <map style="width: 100%; height: 250px;" :latitude="latitude" :longitude="longitude" :markers="covers"> </map> </view> </view> --> </view> </view> <view> <web-view ref="webview" :webview-styles="webviewStyles" :src="src" :fullscreen="false"></web-view> </view> <!-- <iframe ref="iframe" id="iframe" src="./static/map.html" width="100%" height="300px" frameborder="0" class="map"> --> </iframe> </view> </template> <script> import { getRealTimeBusLine, getRealTimeBusLineStation } from '@/request/bus.js' export default { data() { return { src: './static/web/map.html', timer: null, isArrive: 0, targetId: '', state: 0, stationName: '', travelDistance: 0, travelTime: 0, travelOrder: 0, ratio: '', ind: '', siteId: "", lineName: "二号线", siteName: "动漫园站", siteList: [], busLineBase: [], stateBus: [], time_s: '', time_e: '', from: '', busPosition: [0, 5, 11], to: '', isActive: false, lineId: '', siteNum: '0', haveBus: true, webviewStyles: { position: 'relative', top: '580px', // bottom: '30px', zindex: '9999', // marginTop: '850px', left: '20px', right: '20px', // height:'300px' // height: '500px', }, id: 0, // 使用 marker点击事件 需要填写id title: 'map', latitude: 39.90923, longitude: 116.397428, covers: [{ latitude: 39.909, longitude: 116.39742, iconPath: '../../../static/location.png' }, { latitude: 39.90, longitude: 116.39, iconPath: '../../../static/location.png' }] } }, onLoad(options) { // 获取路由参数 const { statusBarHeight } = uni.getSystemInfoSync() // const systemInfo = uni.getSystemInfoSync(); // this.ratio = (systemInfo.windowWidth / 750) / 0.5 console.log("options", options) this.statusBarHeight = statusBarHeight this.lineId = options.lineId this.lat = options.lat this.lng = options.lng this.lineName = options.lineName this.getLineDetail() if (this.timer) { clearInterval(this.timer); this.timer = null; } // this.timer = setInterval(() => { // console.log("30") // this.refresh() // }, 1000 * 30); // this.$nextTick(function() { // 仅 App 平台执行 // amap.initMap('amap', 'cd0bbe150380e1263de209abf425745b'); this.init() // }) }, methods: { // vue向iframe传递信息 init() { this.src = "../../../../hybrid/html/map.html" // var res = { // lineName:this.lineName // } // this.src = './static/map.html?data=' + encodeURIComponent(JSON.stringify({ // ...res // })) // var lineName = this.lineName // const iframeWindow = this.$refs.iframe.contentWindow; // this.dom = document.getElementById('iframe') // iframeWindow.postMessage({ // cmd: 'myVue', // params: { // info: 'Vue向iframe传递的消息', // } // }, '*') }, numFilter(value) { if (value != 0) { // 将数值转换为浮点数,并保留两位小数 let realVal = parseFloat(value).toFixed(2); return realVal; } }, async getLineDetail() { var res = await getRealTimeBusLine({ lineId: this.lineId, lat: this.lat, lng: this.lng }) if (res.code == 0) { console.log("1", res) this.siteList = res.data.busInfStop var ind = res.data.targetOrder this.ind = res.data.targetOrder - 1 this.stationName = this.siteList[ind - 1].sn this.state = res.data.state this.busLineBase = res.data.busLineBase this.targetOrder = res.data.targetOrder this.$nextTick(function() { this.targetId = 'id' + this.siteList[ind - 4].sid }); if (this.state == 0) { this.stateBus = res.data.stateBus var travelsList = this.stateBus.travels this.distance = this.findAllClosest(this.stateBus, this.targetOrder) if (this.distance.length != 0) { var travels = this.distance[0].travels this.travelDistance = travels[0].travelDistance this.travelOrder = travels[0].travelOrder this.travelTime = travels[0].travelTime this.siteList = this.processArrays(this.siteList, this.stateBus, 'order', 'state'); } } } }, async showActive(index, siteName) { this.ind = index this.stationName = siteName var res = await getRealTimeBusLineStation({ lineId: this.lineId, stationName: this.stationName }) if (res.code == 0) { this.siteList = res.data.busInfStop var ind = res.data.targetOrder this.stationName = this.siteList[ind - 1].sn this.state = res.data.state this.busLineBase = res.data.busLineBase this.ind = res.data.targetOrder - 1 this.targetOrder = res.data.targetOrder this.$nextTick(function() { this.targetId = 'id' + this.siteList[ind - 4].sid }); if (this.state == 0) { this.stateBus = res.data.stateBus var travelsList = this.stateBus.travels this.distance = this.findAllClosest(this.stateBus, this.targetOrder) if (this.distance.length != 0) { var travels = this.distance[0].travels this.travelDistance = travels[0].travelDistance this.travelOrder = travels[0].travelOrder this.travelTime = travels[0].travelTime this.isArrive = this.distance[0].state this.siteList = this.processArrays(this.siteList, this.stateBus, 'order', 'state'); } } } }, handleBack() { uni.navigateBack() }, async refresh() { console.log("刷新") var res = await getRealTimeBusLineStation({ lineId: this.lineId, stationName: this.stationName }) if (res.code == 0) { this.siteList = res.data.busInfStop var ind = res.data.targetOrder this.stationName = this.siteList[ind - 1].sn this.state = res.data.state this.busLineBase = res.data.busLineBase this.ind = res.data.targetOrder - 1 this.targetOrder = res.data.targetOrder this.$nextTick(function() { this.targetId = 'id' + this.siteList[ind - 4].sid }); if (this.state == 0) { this.stateBus = res.data.stateBus var travelsList = this.stateBus.travels this.distance = this.findAllClosest(this.stateBus, this .targetOrder) if (this.distance.length != 0) { var travels = this.distance[0].travels this.travelDistance = travels[0].travelDistance this.travelOrder = travels[0].travelOrder this.travelTime = travels[0].travelTime this.isArrive = this.distance[0].state this.siteList = this.processArrays(this.siteList, this .stateBus, 'order', 'state'); } } } }, // 找到距离最近车站的小车 findAllClosest(arr, target) { const validItems = arr.filter(item => item.order < target + 1); if (validItems.length === 0) return []; const distances = validItems.map(item => ({ item, distance: Math.abs(item.order - target) })); if (distances.length != 0) { const minDist = Math.min(...distances.map(d => d.distance)); return distances.filter(d => d.distance === minDist).map(d => d.item); } }, processArrays(arrayA, arrayB, key, valueKey) { // 创建arrayB的快速查找映射(基于指定键) const bMap = new Map(); arrayB.forEach(item => { if (item[key] !== undefined) { bMap.set(item[key], item); } }); // 遍历arrayA并添加属性 return arrayA.map(itemA => { // 浅拷贝避免修改原对象 const newItem = { ...itemA }; // 检查是否存在匹配元素 if (bMap.has(newItem[key])) { const matchedB = bMap.get(newItem[key]); // newItem.state = 0; newItem.isHaveBus = 0; // 添加arrayB中的属性值(如果存在) if (matchedB[valueKey] !== undefined) { newItem[valueKey] = matchedB[valueKey]; } } return newItem; }); } }, onPullDownRefresh() { console.log("下拉") this.refresh() setTimeout(() => { //结束下拉刷新 uni.stopPullDownRefresh(); }, 1000); }, onUnload() { console.log('页面即将卸载'); if (this.timer) { clearInterval(this.timer); this.timer = null; } }, } </script> <style> page { background-color: #f5f5f5; } </style> <style scoped> .bg-image { position: absolute; } .custom-navbar { position: absolute; top: 0; left: 0; width: 100%; z-index: 1000; /* 自定义背景色 */ /* 自定义阴影 */ } .navbar-content { height: 44px; /* 标准导航栏高度 */ display: flex; align-items: center; padding: 0 15px; } .title { flex: 1; text-align: center; font-size: 17px; font-weight: bold; color: #333; /* 标题颜色 */ } .back-btn { width: 48rpx; height: 48rpx; } .right-area { width: 40px; text-align: right; } .info_wapper { height: 156px; /* background: #36A3F9; */ /* background: url(@/static/home/bus/detail-bg.png) left no-repeat; */ padding: 15px; color: black; position: absolute; z-index: 9; width: 100%; } .line_info { position: relative; /* display: flex; */ } .site { font-size: 32rpx; color: black; margin-bottom: 10px; text-align: left; display: flex; margin-left: 30px; } .from { /* padding-left: 52rpx; */ color: black; } .arrow { background: url(@/static/home/bus/single.png) left no-repeat; background-size: 14px 5px; margin-left: 10px; color: black; } .to { padding-left: 26px; color: black; } .time_s { background: url(@/static/home/bus/start.png) left no-repeat; background-size: 18px 18px; justify-self: baseline; } .time_e { background: url(@/static/home/bus/end.png) left no-repeat; background-size: 18px 18px; margin-left: 15px; } .price { margin-left: 15px; } .tit { color: #fff; font-size: 26rpx; /* padding-left: 52rpx; */ margin-bottom: 10px; height: 21px; color: #333; display: flex; align-items: baseline; margin-left: 30px; } .distance-class { border-radius: 15px; /* box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1); */ background: url(@/static/home/bus/distance-bg.png) center no-repeat; background-size: cover; height: 150px; position: absolute; z-index: 9999; left: 20px; right: 20px; } .distance { color: #B7BABB; line-height: 20px; text-align: center; display: flex; flex-direction: column; align-items: center; position: relative; z-index: 9999; /* align-items: baseline; */ } .time { color: aquamarine; } .bus-arr { color: rgb(255, 164, 17); font-weight: bold; margin-top: 10px; } .bus-coming { color: rgb(55, 170, 126); font-weight: bold; line-height: 100rpx; } .station { margin-left: 20px; color: #353738; padding: 10px; } .station-title { display: flex; align-items: baseline; } .station-name { margin-right: 15px; } .station-content { padding-top: 10px; /* padding-left: 20rpx; */ position: relative; /* padding-right: 50rpx; */ /* 确保文字在下划线上方 */ z-index: 2; justify-content: center; display: flex; align-items: center; /* 文字在上层 */ } .underline { position: absolute; bottom: 2px; height: 3px; background: linear-gradient(to right, rgb(110, 207, 169), rgb(110, 224, 176)); /* 下划线颜色 */ border-radius: 3px; z-index: -1; width: 80%; align-items: center; justify-content: center; display: flex; /* margin-left: 30rpx; */ /* width: 60rpx; */ /* 下划线在文字下方 */ } .active { color: black; /* 激活状态文字颜色 */ font-weight: bold; /* font-size: 36rpx; */ } .time { float: left; width: 40%; font-size: 26rpx; /* height: 130rpx; */ line-height: 30px; } .time { font-size: 36rpx; color: #353738; } .time-text { margin-left: 25px; } .time_title { color: rgb(14, 170, 112); font-weight: bold; } .arr { float: left; /* width: 20%; */ font-size: 30rpx; color: rgb(14, 170, 112); } .site_list { /* margin: 15px; */ /* box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1); */ /* background-color: white; */ border-radius: 15px; margin-top: 140px; padding-left: 15px; padding-right: 15px; padding-top: 15px; padding-bottom: 10px; background: url(@/static/home/bus/site-bg.png) center no-repeat; position: absolute; z-index: 9999; left: 20px; right: 20px; background-size: cover; /* 让背景图片覆盖整个视口 */ } .site_list .wapper_sitelist { width: 100%; overflow-x: scroll; position: relative; -webkit-overflow-scrolling: touch; display: flex; /* margin-bottom: 200px; */ } .bus_road { background: url(@/static/home/bus/bus.png) no-repeat center; background-size: cover; width: 28px; height: 14px; position: absolute; z-index: 1; margin-top: 4px; left: 14px; /* margin-top: -40px; */ } .road-bus { background: url(@/static/home/bus/bus.png) no-repeat center; background-size: cover; width: 28px; height: 14px; position: absolute; z-index: 1; margin-top: -45px; margin-left: 10px; /* left: 14px; */ /* margin-top: -40px; */ } .road-bus-coming { background: url(@/static/home/bus/bus.png) no-repeat center; background-size: cover; width: 28px; height: 14px; position: absolute; z-index: 1; margin-top: -45px; margin-left: -40px; } .road { display: flex; } .wapper_sitelist::-webkit-scrollbar { display: none; } /deep/::-webkit-scrollbar { display: none; width: 0; height: 0; } .part { padding: 10px; display: flex; flex-direction: row; text-align: center; justify-content: center; padding-top: 20px; } .site_list .dot_r { margin-top: 10px; position: absolute; top: 23px; height: 3px; background: linear-gradient(to right, rgb(110, 207, 169), rgb(110, 224, 176)); /* 下划线颜色 */ width: 46px; border-radius: 3rpx; z-index: 1; margin-left: 4px; align-items: center; justify-content: center; display: flex; } .site_list .name { margin-top: 28px; display: flex; flex-direction: column; align-items: center; } .site_list .text { display: flex; text-align: center; margin-left: 5px; flex-direction: column; } .site_list .text_show { display: flex; text-align: center; margin-left: 5px; color: rgb(14, 170, 112); font-weight: bold; } .site_list .dot_show { background: url(@/static/home/bus/position_red.png) no-repeat center; background-size: 14px 18px; } .site_list .dot_d_show { border-radius: 50%; width: 16px; height: 16px; background-color: rgba(110, 207, 169, 0.5); position: absolute; z-index: 9999; top: 26px; margin-left: 8px; display: flex; align-items: center; justify-content: center; } .road-class { display: flex; } .dot_p { border-radius: 50%; width: 8px; height: 8px; background-color: rgb(110, 207, 169); position: absolute; z-index: 9999; top: 30px; display: flex; align-items: center; justify-content: center; /* margin-right: 2px; */ /* margin-left: 10rpx; */ } .dot_p_show { border-radius: 50%; width: 8px; height: 8px; background-color: rgb(14, 170, 112); position: absolute; z-index: 9999; top: 30px; display: flex; align-items: center; justify-content: center; } .text-dir { margin-top: 5px; writing-mode: vertical-rl; letter-spacing: 1px; } .none { color: #848484; font-size: 16px; margin-top: 15px; } .map { /* margin-top: 450px; */ position: absolute; z-index: 999; margin-top: 480px; /* height: 300px; */ } .web-class { /* top: 800px; */ /* height: 300px; */ /* position: absolute; z-index: 99999; */ width: 100%; margin-top: 500px; } .page-body { margin-top: 250px; padding-bottom: 20px; } </style>
07-08
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值