React Native Native used as the sensor device as smooth

background

Alipay membership card page, there is a flip phone around, the light with the effect of moving gesture.

We have to achieve this effect, but we are in the cards where the RN page, then the RN can achieve this function?

Survey

Began to look a bit REACT-Native-Sensors , probably written like this

subscription = attitude.subscribe(({ x, y, z }) =>
    {
        let newTranslateX = y * screenWidth * 0.5 + screenWidth/2 - imgWidth/2;
        this.setState({
            translateX: newTranslateX
        });
    }
);
复制代码

This is --setState traditional way to refresh the page, between the final and Native JS is asynchronous communication through the bridge, so the final result is that will Caton.

How can we not through the bridge, directly to native updated view of what the answer is - the Using Native Driver for Animated ! ! !

Using Native Driver for Animated

What is Animated

Animated API allows animation to run smoothly, by binding Animated.Value operation to View, Animated.Value The props or styles, then Animated.timing () method, etc. Further update animation. More on Animated API can be seen here .

Animated default is to use JS driver driven FIG works as follows:

At this page update process:

[JS] The animation driver uses requestAnimationFrame to update Animated.Value [JS] Interpolate calculation [JS] Update Animated.View props
[JS→N] Serialized view update events
[N] The UIView or android.View is updated.

Animated.event

You can use Animated.event associated Animated.Value to one View event.

<ScrollView
  scrollEventThrottle={16}
  onScroll={Animated.event(
    [{ nativeEvent: { contentOffset: { y: this.state.animatedValue } } }]
  )}
>
  {content}
</ScrollView>
复制代码

useNativeDriver

RN document for a description of useNativeDriver as follows:

The Animated API is designed to be serializable. By using the native driver, we send everything about the animation to native before starting the animation, allowing native code to perform the animation on the UI thread without having to go through the bridge on every frame. Once the animation has started, the JS thread can be blocked without affecting the animation.

Use useNativeDriver rendering can be achieved in Native UI thread, onScroll after use is this:

<Animated.ScrollView // <-- Use the Animated ScrollView wrapper
  scrollEventThrottle={1} // <-- Use 1 here to make sure no events are ever missed
  onScroll={Animated.event(
    [{ nativeEvent: { contentOffset: { y: this.state.animatedValue } } }],
    { useNativeDriver: true } // <-- Add this
  )}
>
  {content}
</Animated.ScrollView>
复制代码

After using useNativeDriver, page updates will be no participation of the JS

[N] Native use CADisplayLink or android.view.Choreographer to update Animated.Value [N] Interpolate calculation
[N] Update Animated.View props
[N] The UIView or android.View is updated.

Now what we want to achieve, the actual needs of real-time data flip angle sensor, if there is a similar ScrollView of onScroll the event mapping out the most appropriate now to see how.

achieve

First look JS end, Animated API has a method createAnimatedComponent, Animated inside API are implemented using this function

const Animated = {
  View: AnimatedImplementation.createAnimatedComponent(View),
  Text: AnimatedImplementation.createAnimatedComponent(Text),
  Image: AnimatedImplementation.createAnimatedComponent(Image),
  ...
}
复制代码

Then look native, RCTScrollView is how to achieve the onScroll

RCTScrollEvent *scrollEvent = [[RCTScrollEvent alloc] initWithEventName:eventName
                                                                 reactTag:self.reactTag
                                                               scrollView:scrollView
                                                                 userData:userData
                                                            coalescingKey:_coalescingKey];
[_eventDispatcher sendEvent:scrollEvent];
复制代码

Here is a package RCTScrollEvent, in fact, is a subclass of RCTEvent, then be sure to use it in this way? Do not it? So using the original invocation tried it:

if (self.onMotionChange) {
    self.onMotionChange(data);
}
复制代码

We found that, ah, not surprisingly, not work. Then we look at onScroll final commissioning in native calls it:

)

So finally I had to call [RCTEventDispatcher sendEvent:] to trigger updates Native UI, so use this interface is a must. Then we follow RCTScrollEvent to achieve what RCTMotionEvent, the main body function code is:

- (NSDictionary *)body
{
    NSDictionary *body = @{
                           @"attitude":@{
                                   @"pitch":@(_motion.attitude.pitch),
                                   @"roll":@(_motion.attitude.roll),
                                   @"yaw":@(_motion.attitude.yaw),
                                   },
                           @"rotationRate":@{
                                   @"x":@(_motion.rotationRate.x),
                                   @"y":@(_motion.rotationRate.y),
                                   @"z":@(_motion.rotationRate.z)
                                   },
                           @"gravity":@{
                                   @"x":@(_motion.gravity.x),
                                   @"y":@(_motion.gravity.y),
                                   @"z":@(_motion.gravity.z)
                                   },
                           @"userAcceleration":@{
                                   @"x":@(_motion.userAcceleration.x),
                                   @"y":@(_motion.userAcceleration.y),
                                   @"z":@(_motion.userAcceleration.z)
                                   },
                           @"magneticField":@{
                                   @"field":@{
                                           @"x":@(_motion.magneticField.field.x),
                                           @"y":@(_motion.magneticField.field.y),
                                           @"z":@(_motion.magneticField.field.z)
                                           },
                                   @"accuracy":@(_motion.magneticField.accuracy)
                                   }
                           };
    
    return body;
}
复制代码

Finally, in the end use code JS

var interpolatedValue = this.state.roll.interpolate(...)

<AnimatedDeviceMotionView
  onDeviceMotionChange={
    Animated.event([{
      nativeEvent: {
        attitude: {
          roll: this.state.roll,
        }
      },
    }],
    {useNativeDriver: true},
    )
  }
/>

<Animated.Image style={{height: imgHeight, width: imgWidth, transform: [{translateX:interpolatedValue}]}} source={require('./image.png')} />
复制代码

Ultimately effect:

Continue to optimize

The above implementation is not a good thing, is the need to write a AnimatedMotionView in render useless, the connection is realized Animated.event and Animated.Value of. Then there is no way to get rid of this unwanted view, as a module of the same RN using our components it?

Animated.event do is associate the event and Animated.Value, then exactly how to achieve it?

First we look at node_modules/react-native/Libraries/Animated/src/AnimatedImplementation.jsthe createAnimatedComponentimplementation, which calls to attachNativeEventthis function, then calls to the native:

NativeAnimatedAPI.addAnimatedEventToView(viewTag, eventName, mapping);
复制代码

We look at the native code is how to achieve this function:

- (void)addAnimatedEventToView:(nonnull NSNumber *)viewTag
                     eventName:(nonnull NSString *)eventName
                  eventMapping:(NSDictionary<NSString *, id> *)eventMapping
{
  NSNumber *nodeTag = [RCTConvert NSNumber:eventMapping[@"animatedValueTag"]];
  RCTAnimatedNode *node = _animationNodes[nodeTag];
......
  NSArray<NSString *> *eventPath = [RCTConvert NSStringArray:eventMapping[@"nativeEventPath"]];

  RCTEventAnimation *driver =
    [[RCTEventAnimation alloc] initWithEventPath:eventPath valueNode:(RCTValueAnimatedNode *)node];

  NSString *key = [NSString stringWithFormat:@"%@%@", viewTag, eventName];
  if (_eventDrivers[key] != nil) {
    [_eventDrivers[key] addObject:driver];
  } else {
    NSMutableArray<RCTEventAnimation *> *drivers = [NSMutableArray new];
    [drivers addObject:driver];
    _eventDrivers[key] = drivers;
  }
}
复制代码

EventMapping information in the final construct a eventDriver, the driver eventually when we will be called to the native structure of RCTEvent call sendEvent:

- (void)handleAnimatedEvent:(id<RCTEvent>)event
{
  if (_eventDrivers.count == 0) {
    return;
  }

  NSString *key = [NSString stringWithFormat:@"%@%@", event.viewTag, event.eventName];
  NSMutableArray<RCTEventAnimation *> *driversForKey = _eventDrivers[key];
  if (driversForKey) {
    for (RCTEventAnimation *driver in driversForKey) {
      [driver updateWithEvent:event];
    }

    [self updateAnimations];
  }
}
复制代码

Etc., then the role of the viewTag and eventName, is connected into a key? What?

)

This identifies the RN in view of viewTag finally just turned into a unique string only, then we do not need this view is not possible, only a single viewTag can it?

Along the way, we look at this generation only viewTag. We look at the code JS loaded UIView's (RN version 0.45.1)

mountComponent: function(
  transaction,
  hostParent,
  hostContainerInfo,
  context,
) {
  var tag = ReactNativeTagHandles.allocateTag();

  this._rootNodeID = tag;
  this._hostParent = hostParent;
  this._hostContainerInfo = hostContainerInfo;
...
  UIManager.createView(
    tag,
    this.viewConfig.uiViewClassName,
    nativeTopRootTag,
    updatePayload,
  );
...
  return tag;
}
复制代码

AllocateTag we can use this method to generate ReactNativeTagHandles viewTag.

2019.02.25 Update: RN0.58.5, since no exposure allocateTag () method, so that a large number can be assigned as a workaround tag

So far, we can use the method AnimatedImplementation in attachNativeEvent to connect Animated.event and Animated.Value, and do not need to render useless the time to add a view.

Please detail the venue Github: github.com/rrd-fe/reac... , feel good Give me a star :)

Finally, we welcome the star we all lending large front-end team blog , all articles will be updated simultaneously to know almost columns and Nuggets account , week we will share a few high-quality large front-end technical articles.

Reference

facebook.github.io/react-nativ…

facebook.github.io/react-nativ…

medium.com/xebia/linki…

www.raizlabs.com/dev/2018/03…

www.jianshu.com/p/7aa301632…

Reproduced in: https: //juejin.im/post/5cfdc0995188252ed3172d34

Guess you like

Origin blog.csdn.net/weixin_33816946/article/details/91417207