weex页面有scrollView嵌套web的页面,如果不给web指定高度就显示不出来。RN也有这个问题,查了下全部是通过原生开启一个不显示的webview提前加载一遍再将高度传给weex来解决,这种方式需要加载俩边资源拖慢了显示速度,而且浪费资源。
研究了下新方式,只加载一遍,用原生自定义的webview在加载完后在原生端修改高度。
同时使用自定义的webivew解决了有些特殊机型无法显示(如LG nexus5),以及webview的一系列设置,安全,js问题。
面临三个问题:
1,如何自定义weex组件?
2,如何通过源码找出修改weex控件宽高的方法?
3,如何得到webview的准确高度?
自定义weex控件:
继承WXComponent指定泛型。
public class WWebview extends WXComponent<DisplayX5Webview> {
}
在原生端application中注册组件后weex端就能直接引用,注意:不能有大写字母,weex引用时为蓝色方为成功;暗黄则是失败。
WXSDKEngine.registerComponent("mywebview", WWebview.class);
weex直接引用:
<mywebview
style={[styles.webViewContainer]}
/>
在初始化里返回weex显示的控件,这里可以返回任意布局,甚至动态添加子布局等。
@Override
protected DisplayX5Webview initComponentHostView(@NonNull Context context) {
webView = new DisplayX5Webview(context.getApplicationContext());
initWebView((Activity) context);//初始化
return webView;
}
约定weex端的标签属性source:(注意要小写)
@WXComponentProp(name = "source")
public void setSource(String source) {
mSource = source;
}
属性直接使用即可:
<mywebview
style={[styles.webViewContainer]}
source={`url`}
/>
加载weex时component的生命周期会先调用@WXComponentProp(name = "source"),再调用bindData()顾名思义就是加载数据的(小坑:如果weex端没有做判空,在网络请求下来之前先render一遍,后面网络数据返回以后再render就不会执行bindDta了)
@Override
public void bindData(WXComponent component) {
super.bindData(component);
if (!TextUtils.isEmpty(mSource)) {
webView.loadData(mSource, "text/html; charset=UTF-8", "UTF-8");
// webView.loadUrl(mSource);
}
}
到这里,如果如果在weex端指定高度就已经可以显示了。
在安卓端修改控件高度
修改高度很麻烦,无论如何设置LayoutParams都是没用的,下面大致看下源码里为什么会设置不进去。
Component的注册加载机制参考下:https://www.jianshu.com/p/53f69bfcbc50
直接看处理渲染的中心类WXDomHandler。dom,渲染处理都在此处。
@Override
public boolean handleMessage(Message msg) {
if (msg == null) {
return false;
}
int what = msg.what;
Object obj = msg.obj;
WXDomTask task = null;
if (obj != null && obj instanceof WXDomTask) {
task = (WXDomTask) obj;
Object action = ((WXDomTask) obj).args.get(0);
if (action != null && action instanceof TraceableAction) {
((TraceableAction) action).mDomQueueTime = SystemClock.uptimeMillis() - msg.getWhen();
}
}
if (!mHasBatch) {
mHasBatch = true;
if(what != WXDomHandler.MsgType.WX_DOM_BATCH) {
int delayTime = DELAY_TIME;
if(what == MsgType.WX_DOM_TRANSITION_BATCH){
delayTime = TRANSITION_DELAY_TIME;
}
mWXDomManager.sendEmptyMessageDelayed(WXDomHandler.MsgType.WX_DOM_BATCH, delayTime);
}
}
switch (what) {
case MsgType.WX_EXECUTE_ACTION:
mWXDomManager.executeAction(task.instanceId, (DOMAction) task.args.get(0), (boolean) task.args.get(1));
break;
case MsgType.WX_DOM_UPDATE_STYLE:
//keep this for direct native call
mWXDomManager.executeAction(task.instanceId, Actions.getUpdateStyle((String) task.args.get(0),
(JSONObject) task.args.get(1),
task.args.size() > 2 && (boolean) task.args.get(2)),false);
break;
case MsgType.WX_DOM_BATCH:
mWXDomManager.batch();
mHasBatch = false;
break;
case MsgType.WX_CONSUME_RENDER_TASKS:
mWXDomManager.consumeRenderTask(task.instanceId);
break;
default:
break;
}
return true;
}
很明显case MsgType.WX_DOM_UPDATE_STYLE:字面意思就是我们要找的。再看下具体做了什么来更新dom刷新ui:
case MsgType.WX_DOM_UPDATE_STYLE:
//keep this for direct native call
mWXDomManager.executeAction(task.instanceId, Actions.getUpdateStyle((String) task.args.get(0),
(JSONObject) task.args.get(1),
task.args.size() > 2 && (boolean) task.args.get(2)),false);
break;
需要三个参数:instanceId——WXComponent初始化自带,第二个参数getUpdateStyle,第三个不管
看第二个参数传入的方法:
public static DOMAction getUpdateStyle(String ref, JSONObject data, boolean byPesudo){
return new UpdateStyleAction(ref, data, byPesudo);
}
new 的DOMAction最终executeAction来执行执行代码依然在UpdateStyleAction中:
@Override
public void executeDom(DOMActionContext context) {
if (context.isDestory() || mData == null) {
return;
}
...
if (!mData.isEmpty()) {
domObject.updateStyle(mData, mIsCausedByPesudo);
domObject.applyStyle(mData);
if(!mData.isEmpty()) {
context.postRenderTask(this);
}
}
}
具体看updateStyle方法:
public void updateStyle(Map<String, Object> updates, boolean byPesudo) {
...
/**
* diff styles
* */
if(!diffUpdates(updates, getStyles())){
return;
}
if(mStyles == null) {
mStyles = new WXStyle();
}
mStyles.putAll(updates,byPesudo);
...
}
diffUpdates做了新老对比,无改变return。
mStyles.putAll(updates,byPesudo);这句可以看出最终还是追加修改的方式,所以只需要传入要修改的参数即可。
在WXComponent中有notifyNativeSizeChanged方法但是断点看mNeedLayoutOnAnimation一直为false。把这个方法拷贝出来用。
public void notifyNativeSizeChanged(int w, int h) {
Message message = Message.obtain();
WXDomTask task = new WXDomTask();
task.instanceId = getInstanceId();
if (task.args == null) {
task.args = new ArrayList<>();
}
JSONObject style = new JSONObject(2);
Spacing padding = getDomObject().getPadding();
Spacing border = getDomObject().getBorder();
int top = (int) (padding.get(Spacing.TOP) + border.get(Spacing.TOP));
int bottom = (int) (padding.get(Spacing.BOTTOM) + border.get(Spacing.BOTTOM));
float webW = WXViewUtils.getWebPxByWidth(w);
float webH = WXViewUtils.getWebPxByWidth(h) + top + bottom;
style.put("height", webH + 1);
style.put("width", webW + 1);
task.args.add(getRef());
task.args.add(style);
message.obj = task;
message.what = WXDomHandler.MsgType.WX_DOM_UPDATE_STYLE;
WXSDKManager.getInstance().getWXDomManager().sendMessage(message);
// ((WXDomObject) getDomObject()).setStyleHeight(h);
// ((WXDomObject) getDomObject()).setLayoutHeight(h);
//WXSDKManager.getInstance().getWXDomManager().postRenderTask(getInstanceId());
// Message msg = Message.obtain();
// msg.what = WXDomHandler.MsgType.WX_CONSUME_RENDER_TASKS;
// WXDomTask task = new WXDomTask();
// task.instanceId = getInstanceId();
// task.args = new ArrayList<>();
// task.args.add(null);
// msg.obj = task;
// WXSDKManager.getInstance().getWXDomManager().sendMessage(msg);
}
高度计算需要算上padding和border,不然会显示不全,获取方式源码都有提供。
综上发起更新style改变高度的方法如下:
private void reSetHeight() {
int contentHeight = webView.getContentHeight();
notifyNativeSizeChanged(screenWidth, WXViewUtils.dip2px(contentHeight));
}
获取webview内容高度最准确方式
webview获取高度是个小坑,
1,setWebViewClient和setWebChromeClient分别在有些机型上不回调。
2,onPageFinished执行完并不一定加载完
比较推荐自定义webview在onDraw中获取getContentHeight,因为不管webview设置的高度够不够,都会绘制完。
public class DisplayX5Webview extends X5WebView {
public interface DisplayFinish{
void After();
}
DisplayX5Webview.DisplayFinish df;
public void setDisplayListner(DisplayX5Webview.DisplayFinish df) {
this.df = df;
}
public DisplayX5Webview(Context context, AttributeSet attrs) {
super(context, attrs);
}
public DisplayX5Webview(Context context) {
super(context);
}
//onDraw表示显示完毕
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
df.After();
}
}
setDisplayListner回调中调用reSetHeight。以 if (oldContentHeight < contentHeight) 判断,只取大的高度值。
private void reSetHeight() {
int contentHeight = webView.getContentHeight();
if (oldContentHeight < contentHeight) {
notifyNativeSizeChanged(screenWidth, WXViewUtils.dip2px(contentHeight));
oldContentHeight = contentHeight;
}
}
网友评论