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;
}
}