React Native封装原生UI组件
2017-03-03 16:11
543 查看
我的博客原文地址
在React Native开发过程中,有时我们想要使用原生的一个UI组件或者是JS比较难以实现的动画效果时,我们可以在React Naitve应用程序中封装和植入已有的原生组件。
比如开源项目Lottie在Android上能够非常简单的实现一些复杂的动画效果,如果我们想在JS中也实现这样的效果呢?很简单,我们可以自己构建一个原生UI组件。
接下来就以此为例来进行介绍。Lottie官方已经提供了React Native版本的Lottie了,但这里我们仅仅是做为例子来介绍。
官方中文文档
先看一下效果图:
除了
使用
注意: 在ReactJS里,修改一个属性会引发一次对设置方法的调用。有一种修改情况是,移除掉之前设置的属性。在这种情况下设置方法也一样会被调用,并且“默认”值会被作为参数提供(对于基础类型来说可以通过
在
这里方法名可以随意定,但是
在js文件可以通过
或者是在
LogoSmall.json 文件可以从LottieAndroid的源码中获取,点击这里,放在 src/main/assets 即可。
在React Native开发过程中,有时我们想要使用原生的一个UI组件或者是JS比较难以实现的动画效果时,我们可以在React Naitve应用程序中封装和植入已有的原生组件。
比如开源项目Lottie在Android上能够非常简单的实现一些复杂的动画效果,如果我们想在JS中也实现这样的效果呢?很简单,我们可以自己构建一个原生UI组件。
接下来就以此为例来进行介绍。Lottie官方已经提供了React Native版本的Lottie了,但这里我们仅仅是做为例子来介绍。
官方中文文档
先看一下效果图:
封装组件
创建ViewManager的子类
创建LottieViewManager类,并实现
getName()和
createViewInstance方法:
public class LottieViewManager extends SimpleViewManager<LottieAnimationView> { private static final String REACT_CLASS = "LottieAnimationView"; @Override public String getName() { return REACT_CLASS; } @Override protected LottieAnimationView createViewInstance(ThemedReactContext reactContext) { return new LottieAnimationView(reactContext); } }
导出属性的设置方法
要导出给JavaScript使用的属性,需要申明带有@ReactProp(或
@ReactPropGroup)注解的设置方法。方法的第一个参数是要修改属性的视图实例,第二个参数是要设置的属性值。方法的返回值类型必须为
void,而且访问控制必须被声明为
public。JavaScript所得知的属性类型会由该方法第二个参数的类型来自动决定。支持的类型有:
boolean,
int,
float,
double,
String,
Boolean,
Integer,
ReadableArray,
ReadableMap。
@ReactProp注解必须包含一个字符串类型的参数
name。这个参数指定了对应属性在JavaScript端的名字。
除了
name,
@ReactProp注解还接受这些可选的参数:
defaultBoolean,
defaultInt,
defaultFloat。这些参数必须是对应的基础类型的值(也就是
boolean,
int,
float),这些值会被传递给
setter方法,以免JavaScript端某些情况下在组件中移除了对应的属性。注意这个”默认”值只对基本类型生效,对于其他的类型而言,当对应的属性删除时,
null会作为默认值提供给方法。
使用
@ReactPropGroup来注解的设置方法和
@ReactProp不同。请参见
@ReactPropGroup注解类源代码中的文档来获取更多详情。
注意: 在ReactJS里,修改一个属性会引发一次对设置方法的调用。有一种修改情况是,移除掉之前设置的属性。在这种情况下设置方法也一样会被调用,并且“默认”值会被作为参数提供(对于基础类型来说可以通过
defaultBoolean、
defaultFloat等
@ReactProp的属性提供,而对于复杂类型来说参数则会设置为
null)
在
LottieViewManager中创建以下方法:
@ReactProp(name = "sourceName") public void setSourceName(LottieAnimationView view, String name) { view.setAnimation(name); } @ReactProp(name = "progress", defaultFloat = 0f) public void setProgress(LottieAnimationView view, float progress) { view.setProgress(progress); } @ReactProp(name = "loop") public void setLoop(LottieAnimationView view, boolean loop) { view.loop(loop); }
这里方法名可以随意定,但是
@ReactProp(name = "loop")一定要和JS里面调用的属性值对应。
导出一些命令
在LottieViewManager中添加以下方法:
private static final int COMMAND_PLAY = 1; private static final int COMMAND_RESET = 2; @Override public Map<String, Integer> getCommandsMap() { return MapBuilder.of( "play", COMMAND_PLAY, "reset", COMMAND_RESET ); } @Override public void receiveCommand(final LottieAnimationView view, int commandId, @Nullable ReadableArray args) { switch (commandId) { case COMMAND_PLAY: { new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { if (ViewCompat.isAttachedToWindow(view)) { view.setProgress(0f); view.playAnimation(); } } }); } break; case COMMAND_RESET: { new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { if (ViewCompat.isAttachedToWindow(view)) { view.cancelAnimation(); view.setProgress(0f); } } }); } break; } }
在js文件可以通过
this.runCommand('play')来调用。
注册ViewManager
实现ReactPackage的子类
LottiePackage,这和原生模块的注册方法类似,唯一的区别是我们把它放到
createViewManagers方法里:
public class LottiePackage 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 LottieViewManager() ); } }
添加组件
在Application的
getPackages()方法中添加模块:
@Override protected List<ReactPackage> getPackages() { return Arrays.<ReactPackage>asList( new MainReactPackage(), new LottiePackage() ); }
或者是在
Activity的
onCreate中:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mReactRootView = new ReactRootView(this); mReactInstanceManager = ReactInstanceManager.builder() .setApplication(getApplication()) .setBundleAssetName("index.android.bundle") .setJSMainModuleName("index.android") .addPackage(new MainReactPackage()) .addPackage(new LottiePackage()) .setUseDeveloperSupport(BuildConfig.DEBUG) .setInitialLifecycleState(LifecycleState.RESUMED) .build(); mReactRootView.startReactApplication(mReactInstanceManager, "HelloWorld", null); setContentView(mReactRootView); }
实现对应的JavaScript模块
创建Lottie.jsimport React, { PropTypes } from 'react'; import { requireNativeComponent, View, UIManager, findNodeHandle, ReactNative, Platform } from 'react-native'; /* var LottieView = { name: 'LottieView', defaultProps: { progress: 0, loop: true, }, propTypes: { sourceName :PropTypes.string, progress: PropTypes.number, loop: PropTypes.bool, ...View.propTypes // 包含默认的View的属性 }, }; module.exports = requireNativeComponent('LottieAnimationView', LottieView); */ const Lottie = requireNativeComponent('LottieAnimationView', LottieView); class LottieView extends React.Component { constructor(props) { super(props); } play() { this.runCommand('play'); } reset() { this.runCommand('reset'); } runCommand(name, args = []) { return Platform.select({ android: () => UIManager.dispatchViewManagerCommand( this.getHandle(), UIManager.LottieAnimationView.Commands[name], args ), ios: () => LottieViewManager[name](this.getHandle(), ...args), })(); } getHandle() { return findNodeHandle(this.refs.lottieView); } render() { return <Lottie ref="lottieView" {...this.props} loop={true} />; } } module.exports = LottieView;
UIManager.dispatchViewManagerCommand把调用命令分发到Native端对应的组件类型的ViewManager,再通过ViewManager调用View组件实例的对应方法,这部分后面再介绍。
调用组件
在index.android.js里面调用:import LottieView from './Lottie' …… onClicked(){ this.refs.lottie.play(); } render() { return ( <TouchableWithoutFeedback onPress={() => this.onClicked()}> <View style={styles.lottieContainer} > <LottieView ref="lottie" style={styles.lottie} sourceName='LogoSmall.json' loop={true}> </LottieView> </View> </TouchableWithoutFeedback> ) }
LogoSmall.json 文件可以从LottieAndroid的源码中获取,点击这里,放在 src/main/assets 即可。
相关文章推荐
- React Native之原生UI组件封装---适配Android
- React Native之原生UI组件封装---适配Android
- Android React Native使用原生UI组件
- React-Native踩坑之路:react-native原生组件封装(iOS)
- react native学习笔记25——Android原生模块的封装与调用
- React Native使用指南-原生UI组件
- React Native 原生UI组件的基本使用
- React Native调用原生UI组件
- React-Native 自封装原生模块
- react-native封装原生下拉刷新组件
- 【react-native-0.31-iOS】封装原生组件并调用(02)
- react-native 在android封装原生listView
- React-Native开发之原生模块封装(Android)升级版
- [置顶] React-Native开发之原生模块封装(Android)升级版
- React Native封装Android原生控件
- React-native桥接Android原生开发详解
- 最新iOS原生项目集成React-Native
- React Native嵌入Android原生项目中
- React Native 集成原生极光JMessage 踩坑