美文网首页
React Native findNodeHandle直接操作

React Native findNodeHandle直接操作

作者: 罗坤_23333 | 来源:发表于2020-03-13 11:42 被阅读0次

    用法

    • JS端通过ref直接操作组件内方法

      import { findNodeHandle } from 'react-native'
      
      <ScrollView 
        ref={ref => {
          this._scrollview = ref;
        }}>
      </ScrollView>
      
      // 滚动到指定位置
      this._scrollview.scrollTo(x, y, animated)
      
    • 直接操作原生模块内的方法
      通过findNodeHandle获取_nativeTag并赋值给 this._handle

      import { NativeModules, findNodeHandle } from 'react-native'
      
      <ScrollView 
        ref={ref => {
          this._handle = findNodeHandle(ref)
        }}>
      </ScrollView>
      
      // 滚动到指定位置
      NativeModules.ScrollViewModule.scrollTo(this._handle, x, y, animated)
      

    示例代码

    这里以<ScrollView>组件封装设计为例,介绍如何一步一步实现原生模块

    index.js

    import { NativeModules, findNodeHandle } from 'react-native'
    const ScrollViewClass = NativeModuls.ScrollViewModule
    
    class ScrollView extends React.Component {
      _setScrollViewRef=(ref)=>{
        this._scrollViewRef = findNodeHandle(ref)
      }
    
      scrollTo=({x, y, animated})=>{
         // 这里有两种方式实现🎉🌟🎉🌟🎉🌟
         // 详见下面代码
      }
    
      render(){
        return (
           <ScrollViewClass {...props} ref={this._setScrollViewRef}>
        )
      }
    }
    

    方式一:UIManager的Command模式

    JavaScript

    import { UIManager } from 'react-native'
    
    scrollTo=()=>{
    
      UIManager.dispatchViewManagerCommand(
        this._setScrollViewRef,  // 告诉原生需要定位到的组件
        UIManager.RCTScrollView.Commands.scrollTo,  // 原生getCommandsMap提前定义好的命令
        [x, y, animated],  // 携带的参数ReadableArray
      )
    
    }
    

    获取命令常量这里建议使用 UIManager.RCTXXX.Commands.*
    RN0.60+版本可以用UIManager.getViewManagerConfig('RCTXXX').Commands但不兼容老版本

    1.1 Android实现过程

    1.1.1 ScrollViewManager.class

    继承GroupManager.class/ViewManager.class,在这里重写getCommandsMap()receiveCommand()方法

    class ScrollViewManager  extends GroupManager {
    
      public static final String REACT_CLASS = "RCTScrollView";
    
      public static final int COMMAND_SCROLL_TO = 1;
      public static final int COMMAND_SCROLL_TO_END = 2;
    
      @Override
      public String getName() {
        return REACT_CLASS;
      }
    
      /**
       * 重写getCommandsMap
       * 
       * @return map 返回为receiveCommand注册的命令集合
       **/  
      @Override
      public Map<String,Integer> getCommandsMap() {
        Map<String, Integer> map = new HashMap<>();
        map.put("scrollTo", COMMAND_SCROLL_TO);
        map.put("scrollToEnd", COMMAND_SCROLL_TO_END);
        return map;
      }
    
      /**
       * 重写receiveCommand
       *
       * @param root 当前接收命令的ViewManager实例
       * @param commandId 命令id,与getCommandMap内定义的对应
       * @param args 可选参数
       **/
      @Override
      public void receiveCommand(
        ScrollViewManager root,
        int commandId, 
        @Nullable ReadableArray args) {
        switch (commandId) {
          case COMMAND_SCROLL_TO:{
              root.scrollTo(
                    (int)args.getDouble(0),
                    (int)args.getDouble(1),
                    args.getBoolean(2))
          }
          case COMMAND_SCROLL_TO_END:{
              boolean animated = args.getBoolean(0);
              root.scrollToEnd(animated)
          }
        }
      }
    }
    

    1.2 iOS实现过程

    1.2.1 RCTScrollViewManageer.h

    // .h
    #import <React/RCTViewManager.h>
    
    @interface RCTScrollViewManager : RCTViewManager
    
    @end
    

    1.2.1 RCTScrollViewManageer.m

    也是使用addUIBlock固定写法。
    不过和android不同,iOS可以直接在ViewManager中使用RCT_EXPORT_METHOD(),并且指定方法也会自动注册到Commands,不像android需要手动重写getCommandsMap

    // .m
    #import "RCTScrollViewManager.h"
    
    @implementation RCTScrollViewManager
    
    RCT_EXPORT_MODULE()
    
    //注册scrollTo方法
    RCT_EXPORT_METHOD(scrollTo:(nonnull NSNumber *)reactTag
                      offsetX:(CGFloat)x
                      offsetY:(CGFloat)y
                      animated:(BOOL)animated)
    {
       [ self.bridge.uiManager addUIBlock:
         ^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry)
         {
            //通过tag获取到当前view实例
            UIView *view = viewRegistry[reactTag];
            // 调用ios系统组件自带方法
            if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) { //加保护判断是不是scrollview
               [(id<RCTScrollableProtocol>)view scrollToOffset:(CGPoint){x, y} animated:animated];
            }
         }
    
       ]
    }
    
    

    方式二: 原生模块与原生UI组件结合使用

    JavaScript直接调用ReactMethod / RCT_EXPORT_METHOD 原生方法

    import { NativeModules } from 'react-native'
    
    scrollTo=()=>{
    
      NativeModules.ScrollViewModule.scrollTo(this._setScrollViewRef, x, y, animated)
    
    }
    

    2.1 Android实现过程

    2.1.1 ScrollViewManager.class

    和上面1.1.1一样,但不再重写getCommandsMap()receiveCommand()方法

    2.1.2 ScrollViewModule.class

    需要额外声明ScrollViewModule类继承ReactContextBaseJavaModule.class
    在这里创建scrollTo()ReactMethod方法

    public class ScrollViewModule extends ReactContextBaseJavaModule {
        @Override
        public String getName() {
            return "ScrollViewModule";
        }
    
        public ScrollViewModule(ReactApplicationContext reactContext) {
            super(reactContext);
        }
    
        /**
         * 创建scrollTo方法
         *
         * @param viewTag  由findNodeHandle创建
         *        the view tag of the parent view
         *
         * @param x
         * @param y
         * @param animated
         **/
        @ReactMethod
        public void scrollTo(final int viewTag, int x, int y, boolean animated) {
            final ReactApplicationContext context = getReactApplicationContext();
    
            //固定写法:通过拓展uiManager实现定位ViewManager
            UIManagerModule uiManager = context.getNativeModule(UIManagerModule.class);
            uiManager.addUIBlock(new UIBlock() {
    
                @Override
                public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
                    final ScrollView scrollview;
    
                    try {
                        scrollview = (ScrollView) nativeViewHierarchyManager.resolveView(viewTag);
                        // 调用自带的scrollTo方法
                        scrollview.scrollTo(x, y, animated);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
    

    2.1.2 ScrollViewPackage.class

    ReactPackage中同时注册createNativeModulescreateViewManagers

    public class ScrollViewPackage implements ReactPackage {
    
        @Override
        public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
            return Arrays.<NativeModule>asList(
                    new ScrollViewModule(reactContext) // <- add here
            );
        }
    
        // Deprecated as of RN 0.47.0
        public List<Class<? extends JavaScriptModule>> createJSModules() {
            return Collections.emptyList();
        }
    
        @Override
        public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
            List<ViewManager> modules = new ArrayList<>();
            modules.add(new ScrollViewManager(reactContext)); // <- add here
            return modules;
        }
    }
    
    

    2.2 iOS实现过程

    1.2

    2.3 iOS的RCT_EXPORT_METHOD宏和android的@ReactMethod注释区别

    // iOS native
    RCT_EXPORT_METHOD(scrollTo, ...)
    RCT_EXPORT_METHOD(scrollToEnd, ...)
    
    // android native
    @ReactMethod
    public void scrollTo(int viewTag ...)
    
    // index.js
    console.log(UIManager.RCTScrollView)
    console.log(NativeModules.ScrollViewManager)
    
    JavaScript打印结果 iOS android
    UIManager.RCTScrollView.Commands {scrollTo: 1, scrollToEnd: 2} undefined
    NativeModules.ScrollViewManager {scrollTo: fn(), scrollToEnd: fn() } {scrollTo: fn(), scrollToEnd: fn() }

    iOS会同时自动注册到Commands和NativeModule方法集合中,而
    android是用@ReactMethodgetCommandMap()分开注册的。

    其他:measure文档补完

    老版本:

    import {
      ...
      findNodeHandle,
    } from 'react-native';
    
    var RCTUIManager = require('NativeModules').UIManager;
    
    var view = this.refs['yourRef']; // Where view is a ref obtained through <View ref='ref'/>
    RCTUIManager.measure(findNodeHandle(view), (fx, fy, width, height, px, py) => {
      console.log('Component width is: ' + width)
      console.log('Component height is: ' + height)
      console.log('X offset to frame: ' + fx)
      console.log('Y offset to frame: ' + fy)
      console.log('X offset to page: ' + px)
      console.log('Y offset to page: ' + py)
    })
    

    现可直接使用

    this.refs['yourRef'].measure(findNodeHandle(view), (fx, fy, width, height, px, py) => {
      console.log('Component width is: ' + width)
      console.log('Component height is: ' + height)
      console.log('X offset to frame: ' + fx)
      console.log('Y offset to frame: ' + fy)
      console.log('X offset to page: ' + px)
      console.log('Y offset to page: ' + py)
    })
    

    Reference

    相关文章

      网友评论

          本文标题:React Native findNodeHandle直接操作

          本文链接:https://www.haomeiwen.com/subject/lkwxictx.html