上篇分析了负责分发touch的ReatRootView,这篇文章继续分析view的绘制。
react-native绘制view的思路是将js写的控件映射到native的控件,通过addView之类的函数将js的控件添加到reactRootView.
首先看一个我个人项目中的例子。ui长这样。
用ddms的hierarchy view抓一下看一下。
可以看到有ReactDrawerLayout ReactImageView ReactTextView等,这些都是java的控件。因此我们看到js写的界面
全都被解析成了native的对应控件。具体是怎么实现的呢?
答案在这个类UIManagerModule.java.
public UIManagerModule(ReactApplicationContext reactContext, List<ViewManager> viewManagerList) { super(reactContext); mViewManagers = new ViewManagerRegistry(viewManagerList); //各种控件的manager mEventDispatcher = new EventDispatcher(reactContext); //这个上篇文章见过 dispatch touch等事件的 mNativeViewHierarchyManager = new NativeViewHierarchyManager( mAnimationRegistry, mViewManagers ) ; //统一管理视图变化 mOperationsQueue = new UIViewOperationQueue(reactContext,this,mNativeViewHierarchyManager, mAnimationRegistry); //js发过来的operation统一处理 mNativeViewHierarchyOptimizer = new NativeViewHierarchyOptimizer(mOperationsQueue, mShadowNodeRegistry); DisplayMetrics displayMetrics = reactContext.getResources().getDisplayMetrics(); DisplayMetricsHolder.setDisplayMetrics(displayMetrics); mModuleConstants = UIManagerModuleConstantsHelper.createConstants(displayMetrics,viewManagerList); //rn封装的所有控件 都在这里 包括控件的属性比如width height textView具有text //控件可以执行的操作 比如drawLayout可以打开关闭等 reactContext.addLifecycleEventListener(this); } //当然这个类还涉及到js如何调用native代码技术 本文暂时不分析如何实现 只关注绘制部分代码
public int addMeasuredRootView ( final SizeMonitoringFrameLayout rootView) { final int tag = mNextRootViewTag ; mNextRootViewTag += ROOT_VIEW_TAG_INCREMENT ; // 改变view tag final ReactShadowNode rootCSSNode = new ReactShadowNode() ; //js主要操作cssnode来控制native组件 //设置cssnode rootCSSNode.setReactTag(tag) ; final ThemedReactContext themedRootContext = new ThemedReactContext(getReactApplicationContext() , rootView.getContext()) ; rootCSSNode.setThemedContext(themedRootContext) ; if (rootView.getLayoutParams() != null && rootView.getLayoutParams(). width > 0 && rootView.getLayoutParams(). height > 0 ) { rootCSSNode.setStyleWidth(rootView.getLayoutParams(). width ) ; rootCSSNode.setStyleHeight(rootView.getLayoutParams(). height ) ; } else { rootCSSNode.setStyleWidth(rootView.getWidth()) ; rootCSSNode.setStyleHeight(rootView.getHeight()) ; } rootCSSNode.setViewClassName( "Root" ) ; //设置cssnode rootView.setOnSizeChangedListener( new SizeMonitoringFrameLayout.OnSizeChangedListener() { @Override public void onSizeChanged ( final int width , final int height , int oldW , int oldH) { getReactApplicationContext().runOnNativeModulesQueueThread( new Runnable() { @Override public void run () { updateRootNodeSize( rootCSSNode , width , height ) ; //js层size改变的时候更新cssnode 进而更新native } }) ; } }) ; mShadowNodeRegistry .addRootNode(rootCSSNode) ; if (UiThreadUtil. isOnUiThread ()) { mNativeViewHierarchyManager .addRootView(tag , rootView , themedRootContext) ; } else { final Semaphore semaphore = new Semaphore( 0 ) ; //同步 getReactApplicationContext().runOnUiQueueThread( new Runnable() { @Override public void run () { mNativeViewHierarchyManager .addRootView( tag , rootView , themedRootContext ) ; semaphore .release() ; } }) ; try { SoftAssertions. assertCondition ( semaphore.tryAcquire( 5000 , TimeUnit. MILLISECONDS ) , "Timed out adding root view" ) ; } catch (InterruptedException e) { throw new RuntimeException(e) ; } } return tag ; }
我们看下这个函数在哪里被调用了。启动ctrl f搜索大法。 当当当当。 ReactInstanceManager.java private void attachMeasuredRootViewToInstance ( ReactRootView rootView , CatalystInstance catalystInstance) { UiThreadUtil. assertOnUiThread () ; rootView.removeAllViews() ; rootView.setId(View. NO_ID ) ; UIManagerModule uiManagerModule = catalystInstance.getNativeModule(UIManagerModule. class ) ; int rootTag = uiManagerModule.addMeasuredRootView(rootView) ; //xml文件必须要有一个reactRootView //将这个view注册到js中 @Nullable Bundle launchOptions = rootView.getLaunchOptions() ; WritableMap initialProps = launchOptions != null ? Arguments. fromBundle (launchOptions) : Arguments. createMap () ; String jsAppModuleName = rootView.getJSModuleName() ; WritableNativeMap appParams = new WritableNativeMap() ; appParams.putDouble( "rootTag" , rootTag) ; appParams.putMap( "initialProps" , initialProps) ; catalystInstance.getJSModule(AppRegistry. class).runApplication(jsAppModuleName, appParams); //第一篇文章我们就见过它 这个整个rn app启动的函数 //可以想象到的是 AppRegistry.js一定将这个传入的view 作为root 然后解析其他组件 解析好后通过一些函数动态 //加到这个rootview中 }
动态生成一个js对应的java view组件函数是这个。 @ReactMethod //这个annotation说明是java暴露给js的函数 public void createView ( int tag , String className , int rootViewTag , ReadableMap props) { ViewManager viewManager = mViewManagers .get(className) ; //拿到js层对应的java层控件的manager ReactShadowNode cssNode = viewManager.createShadowNodeInstance() ; //设置cssnode ReactShadowNode rootNode = mShadowNodeRegistry .getNode(rootViewTag) ; cssNode.setReactTag(tag) ; cssNode.setViewClassName(className) ; cssNode.setRootNode(rootNode) ; cssNode.setThemedContext(rootNode.getThemedContext()) ; //设置cssnode mShadowNodeRegistry .addNode(cssNode) ; CatalystStylesDiffMap styles = null; if (props != null ) { styles = new CatalystStylesDiffMap(props) ; cssNode.updateProperties(styles) ; } if (!cssNode.isVirtual()) { mNativeViewHierarchyOptimizer .handleCreateView(cssNode , rootViewTag , styles) ; //通过cssnode创建出控件 } }
NativeViewHierarchyOptimizer.java public void handleCreateView() mUIViewOperationQueue .enqueueCreateView(rootViewTag , tag , node.getViewClass() , initialProps) ;
UIViewOperationQueue.java public void enqueueCreateView ( int rootViewTagForContext , int viewReactTag , String viewClassName , @Nullable CatalystStylesDiffMap initialProps) { mOperations .add( new CreateViewOperation( rootViewTagForContext , //内部类 viewReactTag , viewClassName , initialProps)) ; }
public CreateViewOperation( int rootViewTagForContext , int tag , String className , @Nullable CatalystStylesDiffMap initialProps) { super (tag) ; mRootViewTagForContext = rootViewTagForContext ; mClassName = className ; mInitialProps = initialProps ; } @Override public void execute () { mNativeViewHierarchyManager .createView( mRootViewTagForContext , mTag , mClassName , mInitialProps ) ; }
NativeViewHierarchyManager.java public void createView ( int rootViewTagForContext , int tag , String className , @Nullable CatalystStylesDiffMap initialProps) { UiThreadUtil. assertOnUiThread () ; ViewManager viewManager = mViewManagers .get(className) ; View view = viewManager.createView( mRootViewsContext .get(rootViewTagForContext) , mJSResponderHandler ) ; //viewManager是一个虚基类 因此由具体的控件manager负责生成一个view mTagsToViews .put(tag , view) ; mTagsToViewManagers .put(tag , viewManager) ; //生成一个view后 存入mTagsToViews和mTagToViewManager需要时再取 view.setId(tag) ; //可以reuse if (initialProps != null ) { viewManager.updateProperties(view , initialProps) ; } } //因此UIManagerModule生成一个js对应的view是由NativeHierarchyManager调用相应的view的manager来实现的
相应的生成view后增加删除view的操作是由UIManagerModule暴露给js的函数manageChildren实现的。 UIManagerModule的managerChildren函数和上面的函数一样也是最终由NativeHierarchyManager调用view的manager来实现的。
ok~这篇文章就到这里啦~ 这篇文章是rn view系列的第二篇。由于view的内容非常多,本文重点关注了view的绘制部分。 通过上面的分析, 1 我们看到 rn管理view的类是UIManagerModule, 2 rn的ui必须有一个ReactRootView用来当作js view的根,由程序调用js的AppRegistry.runApplication这个入口前注册到js。 3 js解析js部分的控件hierarchy 并通过js的UIManagerModule暴露给js的createview addView removeView等接口操作native层的组件。 4 UIManagerModule完成生成 添加 删除js对应view的view的方法是,rn自己封装了一套React-native的组件,view及对应的manager。其中view统一命名成React开头,比如textView是ReactTextView,manager的命名规则也是一样。manager其实有点类似于builder设计模式,用来负责生成对应的view,比如ReactTextView对应的就是ReactTextViewManager。