標籤:
Android React Native 已經將幾個常用的原生組件進行了封裝,比如 ScrollView 和 TextInput,但是並不是所有系統的原始組件都被封裝了,因此有的時候我們不得不自己動手封裝一下,從而能夠使用那些React Native沒有為我們封裝的原生組件,比如WebView,官方並沒有提供Android端的實現,那麼我們現在就動手封裝一下WebView。
之前寫過一篇文章Android React Native使用原生模組,而使用原生UI組件的方法和使用原生模組的方法十分類似。
首先,我需要繼承SimpleViewManager這個泛型類,和原生模組類似,需要重寫getName()方法,將UI組件名稱暴露給javascript層,接著需要重寫createViewInstance方法,在裡面返回我們需要使用的原生UI組件的執行個體,這裡就是WebView。然後就是暴露一些必要屬性給javascript層,為了簡單起見,我們這裡只暴露兩個屬性,一個是url,一個是html,一旦javascript層設定了url,就會載入一個網頁,而一旦設定了html,則會去載入這段html,而屬性的暴露是使用註解,將註解設定在對應的set方法上,之後再set方法中處理UI的更新,比如一旦設定了url,在setUrl裡面就要載入網頁。最終我們的ViewManager就是這樣子的
public class ReactWebViewManager extends SimpleViewManager<WebView> { public static final String REACT_CLASS = "RCTWebView"; @Override public String getName() { return REACT_CLASS; } @Override protected WebView createViewInstance(ThemedReactContext reactContext) { WebView webView= new WebView(reactContext); webView.setWebViewClient(new WebViewClient(){ @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { view.loadUrl(url); return true; } }); return webView; } @ReactProp(name = "url") public void setUrl(WebView view,@Nullable String url) { Log.e("TAG", "setUrl"); view.loadUrl(url); } @ReactProp(name = "html") public void setHtml(WebView view,@Nullable String html) { Log.e("TAG", "setHtml"); view.loadData(html, "text/html; charset=utf-8", "UTF-8"); }}
和原生模組一樣,原生UI組件也需要進行註冊,實現ReactPackage介面,進行WebView的註冊。
public class AppReactPackage implements ReactPackage { @Override public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) { return Collections.emptyList();; } @Override public List<Class<? extends JavaScriptModule>> createJSModules() { return Collections.emptyList(); } @Override public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { return Arrays.<ViewManager>asList( new ReactWebViewManager()); }}
將這個ReactPackage添加到ReactInstanceManager執行個體中去
.addPackage(new AppReactPackage())
然後在javascript層建立一個WebView.js檔案。輸入下面的內容
‘use strict‘;var { requireNativeComponent,PropTypes } = require(‘react-native‘);var iface = { name: ‘WebView‘, propTypes: { url: PropTypes.string, html: PropTypes.string, },};module.exports = requireNativeComponent(‘RCTWebView‘, iface);
可以看到,我們只是在裡面指定了屬性的類型。
到目前為止,你已經可以使用這個WebView組件了。
var WebView=require(‘./WebView‘);render: function() { return ( <View style={styles.container}> <WebView url="https://www.baidu.com" style={{width:200,height:400}}></WebView> </View> ); },
這裡只是簡單載入了一下百度首頁,有一點需要特別注意,就是組件的寬度高度一定要設定,否則你會看不到這個組件。最終效果如下。
這還只是最基礎的將原始UI組件顯示出來,而更為常見的卻是事件,比如我們需要在javascript層處理這個WebView的滾動事件,這時候又要怎麼做呢。
這時候我們就需要繼承WebView,重寫對應的事件,然後將事件傳遞給javascript層了
public class RTCWebView extends WebView{ public RTCWebView(Context context) { super(context); } public RTCWebView(Context context, AttributeSet attrs) { super(context, attrs); } public RTCWebView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); Log.e("TAG","onScrollChanged"); WritableMap event = Arguments.createMap(); event.putInt("ScrollX", l); event.putInt("ScrollY", t); ReactContext reactContext = (ReactContext)getContext(); reactContext.getJSModule(RCTEventEmitter.class).receiveEvent( getId(), "topChange", event); }}
我們重寫了滾動時回調的onScrollChanged方法,構造了一個WritableMap 對象,將ScrollX和ScrollY傳入,然後調用reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(getId(), “topChange”, event);將事件發生到javascript層,注意topChange對應著javascript層的onChange方法,這個映射關係在UIManagerModuleConstants類中。
然後我們需要修改ReactWebViewManager 中的createViewInstance方法,在裡面返回我們實現的子類,就像這樣子
protected WebView createViewInstance(ThemedReactContext reactContext) { WebView webView= new RTCWebView(reactContext); webView.setWebViewClient(new WebViewClient(){ @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { view.loadUrl(url); return true; } }); return webView; }
而javascript層也需要進行一定程度的改造,最終的代碼如下
‘use strict‘;var React = require(‘react-native‘);var { requireNativeComponent, PropTypes} = React;class WebView extends React.Component { constructor() { super(); this._onChange = this._onChange.bind(this); } _onChange(event: Event) { if (!this.props.onScrollChange) { return; } this.props.onScrollChange({ScrollX:event.nativeEvent.ScrollX,ScrollY:event.nativeEvent.ScrollY}); } render() { return <RCTWebView {...this.props} onChange={this._onChange} />; }}WebView.propTypes = { url: PropTypes.string, html: PropTypes.string, onScrollChange: PropTypes.func,};var RCTWebView = requireNativeComponent(‘RCTWebView‘, WebView,{ nativeOnly: {onChange: true}});module.exports = WebView
不要問我為什麼是這樣子的,因為官方文檔上就是在這麼寫的,你只需要複製代碼,進行修改即可,詳見文檔Native UI Components
這裡需要注意的就是function.bind(this)的文法了,有興趣的自己去網上搜,這塊我也講不清楚,畢竟沒怎麼學過javascript和React,怕誤人子弟。在onChange函數中,我們進行判斷,如果屬性onScrollChange沒有設定,就直接return,否則就調用設定的onScrollChange屬性值(該值是一個函數類型),將Java層傳入的兩個參數傳到該函數中去,{ScrollX:event.nativeEvent.ScrollX,ScrollY:event.nativeEvent.ScrollY}
然後我們來進行調用
var WebView=require(‘./WebView‘);render: function() {return (<View style={styles.container}><WebView onScrollChange={this.onWebViewScroll} url="https://www.baidu.com" style={{width:200,height:400}}></WebView></View>);},onWebViewScroll:function(event){ console.log(event);},
這時候等待WebView載入處理,你再上下滑動,就會看到控制台的輸出,如下
以上就是使用原生UI組件的全部流程,可以看出React Native官方已經為我們做了很好的封裝,我們只需要編寫少量的代碼,就可以使用原生UI組件了。
Android React Native使用原生UI組件