热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

ReactNative中如原生般流畅地使用设备传感器

背景支付宝的会员页的卡片,有一个左右翻转手机,光线随手势移动的效果。我们也要实现这种效果,但是我们的卡片是在RN页里的,那么

背景

支付宝的会员页的卡片,有一个左右翻转手机,光线随手势移动的效果。

我们也要实现这种效果,但是我们的卡片是在RN页里的,那么RN能否实现这样的功能呢?

调研

开始先看了一下react-native-sensors, 大概写法是这样

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

这还是传统的刷新页面的方式——setState,最终JS和Native之间是通过bridge进行异步通信,所以最后的结果就是会卡顿。

如何能不通过bridge,直接让native来更新view的呢 答案是有——Using Native Driver for Animated!!!

Using Native Driver for Animated

什么是Animated

Animated API能让动画流畅运行,通过绑定Animated.Value到View的styles或者props上,然后通过Animated.timing()等方法操作Animated.Value进而更新动画。更多关于Animated API可以看这里。

Animated默认是使用JS driver驱动的,工作方式如下图:

此时的页面更新流程为:

[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

可以使用Animated.event关联Animated.Value到某一个View的事件上。

>{content}

useNativeDriver

RN文档中关于useNativeDriver的说明如下:

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.

使用useNativeDriver可以实现渲染都在Native的UI线程,使用之后的onScroll是这样的:

true } // <-- Add this)}
>{content}

使用useNativeDriver之后&#xff0c;页面更新就没有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.

我们现在想要实现的效果&#xff0c;实际需要的是传感器的实时翻转角度数据&#xff0c;如果有一个类似ScrollView的onScroll的event映射出来是最合适的&#xff0c;现在就看如何实现。

实现

首先看JS端&#xff0c;Animated API有个createAnimatedComponent方法&#xff0c;Animated内部的API都是用这个函数实现的

const Animated &#61; {View: AnimatedImplementation.createAnimatedComponent(View),Text: AnimatedImplementation.createAnimatedComponent(Text),Image: AnimatedImplementation.createAnimatedComponent(Image),...
}

然后看native&#xff0c;RCTScrollView的onScroll是怎么实现的

RCTScrollEvent *scrollEvent &#61; [[RCTScrollEvent alloc] initWithEventName:eventNamereactTag:self.reactTagscrollView:scrollViewuserData:userDatacoalescingKey:_coalescingKey];
[_eventDispatcher sendEvent:scrollEvent];

这里是封装了一个RCTScrollEvent&#xff0c;其实是RCTEvent的一个子类&#xff0c;那么一定要用这种方式么&#xff1f;不用不可以么&#xff1f;所以使用原始的调用方式试了一下&#xff1a;

if (self.onMotionChange) {self.onMotionChange(data);
}

发现&#xff0c;嗯&#xff0c;不出意料地not work。那我们调试一下onScroll最后在native的调用吧&#xff1a;

)

所以最后还是要调用[RCTEventDispatcher sendEvent:]来触发Native UI的更新&#xff0c;所以使用这个接口是必须的。然后我们按照RCTScrollEvent来实现一下RCTMotionEvent&#xff0c;主体的body函数代码为&#xff1a;

- (NSDictionary *)body
{NSDictionary *body &#61; &#64;{&#64;"attitude":&#64;{&#64;"pitch":&#64;(_motion.attitude.pitch),&#64;"roll":&#64;(_motion.attitude.roll),&#64;"yaw":&#64;(_motion.attitude.yaw),},&#64;"rotationRate":&#64;{&#64;"x":&#64;(_motion.rotationRate.x),&#64;"y":&#64;(_motion.rotationRate.y),&#64;"z":&#64;(_motion.rotationRate.z)},&#64;"gravity":&#64;{&#64;"x":&#64;(_motion.gravity.x),&#64;"y":&#64;(_motion.gravity.y),&#64;"z":&#64;(_motion.gravity.z)},&#64;"userAcceleration":&#64;{&#64;"x":&#64;(_motion.userAcceleration.x),&#64;"y":&#64;(_motion.userAcceleration.y),&#64;"z":&#64;(_motion.userAcceleration.z)},&#64;"magneticField":&#64;{&#64;"field":&#64;{&#64;"x":&#64;(_motion.magneticField.field.x),&#64;"y":&#64;(_motion.magneticField.field.y),&#64;"z":&#64;(_motion.magneticField.field.z)},&#64;"accuracy":&#64;(_motion.magneticField.accuracy)}};return body;
}

最终&#xff0c;在JS端的使用代码为

var interpolatedValue &#61; this.state.roll.interpolate(...)true},)}
/>source&#61;{require(&#39;./image.png&#39;)} />

最终实现效果&#xff1a;

继续优化

上面的实现方式有一点不太好&#xff0c;就是需要在render中写一个无用的AnimatedMotionView&#xff0c;来实现Animated.event和Animated.Value的连接。那么有没有方法去掉这个无用的view&#xff0c;像一个RN的module一样使用我们的组件呢&#xff1f;

Animated.event做的事情就是将event和Animated.Value关联起来&#xff0c;那么具体是如何实现的呢&#xff1f;

首先我们看一下node_modules/react-native/Libraries/Animated/src/AnimatedImplementation.jscreateAnimatedComponent的实现&#xff0c;里面调用到attachNativeEvent这个函数&#xff0c;然后调用到native:

NativeAnimatedAPI.addAnimatedEventToView(viewTag, eventName, mapping);

我们看看native代码中这个函数是怎么实现的&#xff1a;

- (void)addAnimatedEventToView:(nonnull NSNumber *)viewTageventName:(nonnull NSString *)eventNameeventMapping:(NSDictionary *)eventMapping
{NSNumber *nodeTag &#61; [RCTConvert NSNumber:eventMapping[&#64;"animatedValueTag"]];RCTAnimatedNode *node &#61; _animationNodes[nodeTag];
......NSArray *eventPath &#61; [RCTConvert NSStringArray:eventMapping[&#64;"nativeEventPath"]];RCTEventAnimation *driver &#61;[[RCTEventAnimation alloc] initWithEventPath:eventPath valueNode:(RCTValueAnimatedNode *)node];NSString *key &#61; [NSString stringWithFormat:&#64;"%&#64;%&#64;", viewTag, eventName];if (_eventDrivers[key] !&#61; nil) {[_eventDrivers[key] addObject:driver];} else {NSMutableArray *drivers &#61; [NSMutableArray new];[drivers addObject:driver];_eventDrivers[key] &#61; drivers;}
}

eventMapping中的信息最终构造出一个eventDriver&#xff0c;这个driver最终会在我们native构造的RCTEvent调用sendEvent的时候调用到&#xff1a;

- (void)handleAnimatedEvent:(id)event
{if (_eventDrivers.count &#61;&#61; 0) {return;}NSString *key &#61; [NSString stringWithFormat:&#64;"%&#64;%&#64;", event.viewTag, event.eventName];NSMutableArray *driversForKey &#61; _eventDrivers[key];if (driversForKey) {for (RCTEventAnimation *driver in driversForKey) {[driver updateWithEvent:event];}[self updateAnimations];}
}

等等&#xff0c;那么那个viewTag和eventName的作用&#xff0c;就是连接起来变成了一个key&#xff1f;What?

)

这个标识RN中的view的viewTag最后只是变成一个唯一字符串而已&#xff0c;那么我们是不是可以不需要这个view&#xff0c;只需要一个唯一的viewTag就可以了呢&#xff1f;

顺着这个思路&#xff0c;我们再看看生成这个唯一的viewTag。我们看一下JS加载UIView的代码&#xff08;RN版本0.45.1&#xff09;

mountComponent: function(transaction,hostParent,hostContainerInfo,context,
) {var tag &#61; ReactNativeTagHandles.allocateTag();this._rootNodeID &#61; tag;this._hostParent &#61; hostParent;this._hostContainerInfo &#61; hostContainerInfo;
...UIManager.createView(tag,this.viewConfig.uiViewClassName,nativeTopRootTag,updatePayload,);
...return tag;
}

我们可以使用ReactNativeTagHandles的allocateTag方法来生成这个viewTag。

2019.02.25更新&#xff1a;在RN0.58.5中&#xff0c;由于没有暴露allocateTag()方法&#xff0c;所以只能赋给tag一个大数来作为workaround

到此为止&#xff0c;我们就可以使用AnimatedImplementation中的attachNativeEvent方法来连接Animated.event和Animated.Value了&#xff0c;不必需要在render的时候添加一个无用的view。

详细代码请移步Github: github.com/rrd-fe/reac…&#xff0c;觉得不错请给个star :)

最后&#xff0c;欢迎大家star我们的人人贷大前端团队博客&#xff0c;所有的文章还会同步更新到知乎专栏 和 掘金账号&#xff0c;我们每周都会分享几篇高质量的大前端技术文章。

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…

转:https://juejin.im/post/5cfdc0995188252ed3172d34



推荐阅读
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • Tkinter Frame容器grid布局并使用Scrollbar滚动原理
    本文介绍了如何使用Tkinter实现Frame容器的grid布局,并通过Scrollbar实现滚动效果。通过将Canvas作为父容器,使用滚动Canvas来滚动Frame,实现了在Frame中添加多个按钮,并通过Scrollbar进行滚动。同时,还介绍了更新Frame大小和绑定滚动按钮的方法,以及配置Scrollbar的相关参数。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • CentOS 7部署KVM虚拟化环境之一架构介绍
    本文介绍了CentOS 7部署KVM虚拟化环境的架构,详细解释了虚拟化技术的概念和原理,包括全虚拟化和半虚拟化。同时介绍了虚拟机的概念和虚拟化软件的作用。 ... [详细]
  • 本文详细介绍了Android中的坐标系以及与View相关的方法。首先介绍了Android坐标系和视图坐标系的概念,并通过图示进行了解释。接着提到了View的大小可以超过手机屏幕,并且只有在手机屏幕内才能看到。最后,作者表示将在后续文章中继续探讨与View相关的内容。 ... [详细]
  • 本文概述了JNI的原理以及常用方法。JNI提供了一种Java字节码调用C/C++的解决方案,但引用类型不能直接在Native层使用,需要进行类型转化。多维数组(包括二维数组)都是引用类型,需要使用jobjectArray类型来存取其值。此外,由于Java支持函数重载,根据函数名无法找到对应的JNI函数,因此介绍了JNI函数签名信息的解决方案。 ... [详细]
  • 近来有一个需求,是需要在androidjava基础库中插入一些log信息,完成这个工作需要的前置条件有编译好的android源码具体android源码如何编译,这 ... [详细]
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • mui框架offcanvas侧滑超出部分隐藏无法滚动如何解决
    web前端|js教程off-canvas,部分,超出web前端-js教程mui框架中off-canvas侧滑的一个缺点就是无法出现滚动条,因为它主要用途是设置类似于qq界面的那种格 ... [详细]
  • 涉及的知识点-ViewGroup的测量与布局-View的测量与布局-滑动冲突的处理-VelocityTracker滑动速率跟踪-Scroller实现弹性滑动-屏幕宽高的获取等实现步 ... [详细]
author-avatar
魑魅魍魉龌蹉尴尬
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有