天天看點

weex解決scrollView嵌套webview高度問題,安卓自定義weex元件webview,

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