官网的例子以ReactNative项目来说明,没有关于ReactNative嵌入到android是如何调用android原生组件的例子,没办法只能参考ReactNative源代码来自己调用。本文以调用android组件ExpandableListView为例。
1、创建调用原生组件模块ReactExpandableListViewManager。
package com.example.test.widget.reactnative.expandableListView;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.widget.ExpandableListView;
import com.example.test.adapter.ExpandableListAdapter;
import com.example.test.bean.AppUpdate;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.annotations.ReactProp;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import rx.Observable;
import rx.Observer;
import rx.Subscriber;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
/**
* Created by Springever on 2017/5/18.
*/
@ReactModule(name = ReactExpandableListViewManager.REACT_CLASS)
public class ReactExpandableListViewManager extends SimpleViewManager implements ExpandableListAdapter.Callback {
public static final String REACT_CLASS = "RCTExpandableListView";//和ReactNative的js组件名字一致
private ExpandableListView expandableListView;
private ExpandableListAdapter mUpdateAdapter;
private Activity activity;
private static final String NAME_ENTITIES = "entities";
private static String PREF_IGNORE = "ignore";
private static final String JSON_UPAPPITEMS = "upappitems";
private static final String JSON_IGNOREAPPITEMS = "ignoreappitems";
public List mUpdates = new ArrayList();
public List mIgnores = new ArrayList();
public ReactExpandableListViewManager(){
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
protected ExpandableListView createViewInstance(ThemedReactContext reactContext) {
expandableListView =new ExpandableListView(reactContext);
mUpdateAdapter = new ExpandableListAdapter(reactContext);
mUpdateAdapter.registerCallback(this);//注册回调函数
expandableListView.setAdapter(mUpdateAdapter);
expandableListView.setCacheColorHint(Color.TRANSPARENT);//点击时候不会变黑
expandableListView.setGroupIndicator(null);//去掉左边图标
expandableListView.expandGroup(ExpandableListAdapter.GROUP_UPDATE);//触发展开
expandableListView.expandGroup(ExpandableListAdapter.GROUP_IGNORE);//触发展开
activity = reactContext.getCurrentActivity();
showData();
return expandableListView;
}
@ReactProp(name = "layoutWidth")
public void setLayoutWidth(ExpandableListView view, int layoutWidth) {
}
@ReactProp(name = "layoutHeight")
public void setLayoutHeight(ExpandableListView view, int layoutHeight) {
}
public void showData() {
final Thread t = Thread.currentThread();
Observable.create(new Observable.OnSubscribe() {
@Override
public void call(Subscriber super JSONObject> subscriber) {
byte[] bytes = readFromAsset(activity, "preload/update.json");
JSONObject jsonObj = null;
if (bytes != null) {
try {
jsonObj = new JSONObject(new String(bytes));
} catch (JSONException e) {
e.printStackTrace();
}
}
subscriber.onNext(jsonObj);
subscriber.onCompleted();
}
})
.subscribeOn(Schedulers.io()) // 指定 subscribe() 发生在 IO 线程
.observeOn(AndroidSchedulers.mainThread()) // 指定 Subscriber 的回调发生在主线程
.subscribe(new Observer() {
@Override
public void onNext(JSONObject jsonObj) {
JSONObject entities = jsonObj.optJSONObject(NAME_ENTITIES);
if (entities != null) {
try {
readFromJSON(entities);
//上次忽略更新的应用
SharedPreferences pref = activity.getSharedPreferences(PREF_IGNORE, 0);
Set ignoreSet = pref.getAll().keySet();
List update = new ArrayList();
List ignore = new ArrayList();
if (mUpdates != null) {
for (AppUpdate au : mUpdates) {
//比较本地应用
//int status = getXXX(au.mPackageName, au.mVersionCode, au.mVersion);
//if (status != STATUS_INSTALLED_OLD_VERSION)
// continue;
if (ignoreSet.contains(au.mPackageName)) {
ignore.add(au);
} else {
update.add(au);
}
}
}
mUpdateAdapter.setData(update, ignore);
} catch (JSONException e) {
e.printStackTrace();
}
}
}
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
});
/*
JSONObject jsonObj = null;
try {
byte[] bytes = readFromAsset(this, "preload/update.json");
if (bytes != null) {
jsonObj = new JSONObject(new String(bytes));
} else {
}
JSONObject entities = jsonObj.optJSONObject(NAME_ENTITIES);
if (entities != null) {
readFromJSON(entities);
//上次忽略更新的应用
SharedPreferences pref = getSharedPreferences(PREF_IGNORE, 0);
Set ignoreSet = pref.getAll().keySet();
List update = new ArrayList();
List ignore = new ArrayList();
if (mUpdates != null) {
for (AppUpdate au : mUpdates) {
//比较本地应用
//int status = getXXX(au.mPackageName, au.mVersionCode, au.mVersion);
//if (status != STATUS_INSTALLED_OLD_VERSION)
// continue;
if (ignoreSet.contains(au.mPackageName)) {
ignore.add(au);
} else {
update.add(au);
}
}
}
mUpdateAdapter.setData(update, ignore);
}
} catch (Exception e) {
}
*/
}
public static byte[] readFromAsset(Context context, String fileName) {
byte[] ret = null;
InputStream instream = null;
try {
instream = context.getAssets().open(fileName);
byte[] buffer = new byte[8192];
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int len = -1;
while ((len = instream.read(buffer)) >= 0)
baos.write(buffer, 0, len);
baos.flush();
ret = baos.toByteArray();
baos.close();
} catch (IOException e) {
} finally {
try {
if (instream != null)
instream.close();
} catch (IOException e) {
}
}
return ret;
}
public void readFromJSON(JSONObject jsonObj) throws JSONException {
mUpdates.clear();
Object upAppItemObj = jsonObj.opt(JSON_UPAPPITEMS);
if (upAppItemObj != null) {
// 兼容两种更新接口数据
if (upAppItemObj instanceof JSONArray) {
parseUpdateArrayData(mUpdates, (JSONArray) upAppItemObj);
} else if (upAppItemObj instanceof JSONObject) {
int objCount = ((JSONObject) upAppItemObj).length();
parseUpdateObjData(mUpdates, (JSONObject) upAppItemObj, objCount);
} else {
// Can't resolve upappitems, do nothing.
}
}
mIgnores.clear();
Object ignoreAppItemObj = jsonObj.opt(JSON_IGNOREAPPITEMS);
if (ignoreAppItemObj != null) {
if (ignoreAppItemObj instanceof JSONArray) {
parseUpdateArrayData(mIgnores, (JSONArray) ignoreAppItemObj);
} else if (ignoreAppItemObj instanceof JSONObject) {
int objCount = ((JSONObject) ignoreAppItemObj).length();
parseUpdateObjData(mIgnores, (JSONObject) ignoreAppItemObj, objCount);
} else {
// Can't resolve ignoreappitems, do nothing.
}
}
}
private void parseUpdateObjData(List outList, JSONObject jsonObj, int objCount) {
int length = objCount;
if (jsonObj == null || objCount <= 0)
return;
for (int pos = 0; pos < length; ++pos) {
JSONObject updateObj = jsonObj.optJSONObject(String.valueOf(pos));
if (updateObj == null)
continue;
try {
AppUpdate update = new AppUpdate();
update.readFromJSON(updateObj);
outList.add(update);
} catch (JSONException e) {
continue;
}
}
}
private void parseUpdateArrayData(List outList, JSONArray jsonObj) {
JSONArray updateArray = jsonObj;
int length = 0;
if (updateArray != null && (length = updateArray.length()) > 0) {
for (int pos = 0; pos < length; ++pos) {
JSONObject updateObj = updateArray.optJSONObject(pos);
if (updateObj == null)
continue;
try {
AppUpdate update = new AppUpdate();
update.readFromJSON(updateObj);
outList.add(update);
} catch (JSONException e) {
continue;
}
}
}
}
public JSONObject generateJSONObject() throws JSONException {
JSONObject ret = new JSONObject();
JSONArray array = new JSONArray();
for (AppUpdate update : mUpdates) {
if (update == null)
continue;
JSONObject updateObj = update.generateJSONObject();
array.put(updateObj);
}
ret.put(JSON_UPAPPITEMS, array);
array = new JSONArray();
for (AppUpdate update : mIgnores) {
if (update == null)
continue;
JSONObject updateObj = update.generateJSONObject();
array.put(updateObj);
}
ret.put(JSON_IGNOREAPPITEMS, array);
return ret;
}
@Override
public void onUpdate(ExpandableListAdapter.UpdateInfoHolder updateInfo) {
}
@Override
public void onIgnore(AppUpdate item) {
}
@Override
public void onRemoveIgnore(AppUpdate item) {
}
}
核心东西是继承SimpleViewManager
REACT_CLASS可以说是组件对外发布的名称(ReactNative的js通过这个名字可以找到这个组件)。
@ReactProp(name = "layoutWidth")
public void setLayoutWidth(ExpandableListView view, int layoutWidth) {
}
这个方法是ReactNative的js会传递layoutWidth属性过来(而且是数字类型),这是固定写法。
2、将ViewManager类注册到ReactPackage
package com.example.test.widget.reactnative.expandableListView;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Created by Springever on 2017/5/18.
*/
public class ExpandableReactPackage implements ReactPackage {
@Override
public List createNativeModules(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List> createJSModules() {
return Collections.emptyList();
}
@Override
public List createViewManagers(ReactApplicationContext reactContext) {
return Arrays.asList(
new ReactExpandableListViewManager()
);
}
}
3、将ReactPackage添加到application中(在ReactNative的启动Activity中添加)
addPackage(new ExpandableReactPackage())
package com.example.test.activity;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.view.KeyEvent;
import com.example.test.BuildConfig;
import com.example.test.widget.reactnative.expandableListView.ExpandableReactPackage;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactRootView;
import com.facebook.react.common.LifecycleState;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.shell.MainReactPackage;
/**
* Created by Springever on 2017/5/2.
*/
public class ReactNativeActivity extends Activity implements DefaultHardwareBackBtnHandler {
private ReactRootView mReactRootView;
private ReactInstanceManager mReactInstanceManager;
private final static int OVERLAY_PERMISSION_REQ_CODE=1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(this)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE);
}
}
mReactRootView = new ReactRootView(this);
mReactInstanceManager = ReactInstanceManager.builder()
.setApplication(getApplication())
.setBundleAssetName("index.android.bundle")
.setJSMainModuleName("index.android")
.addPackage(new MainReactPackage())
.addPackage(new ExpandableReactPackage())
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build();
// 注意这里的HelloWorld必须对应“index.android.js”中的
// “AppRegistry.registerComponent()”的第一个参数
mReactRootView.startReactApplication(mReactInstanceManager, "ReactNativeActivity", null);
setContentView(mReactRootView);
}
@Override
public void invokeDefaultOnBackPressed() {
super.onBackPressed();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == OVERLAY_PERMISSION_REQ_CODE) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(this)) {
// SYSTEM_ALERT_WINDOW permission not granted...
}
}
}
}
@Override
public void onBackPressed() {
if (mReactInstanceManager != null) {
mReactInstanceManager.onBackPressed();
} else {
super.onBackPressed();
}
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) {
mReactInstanceManager.showDevOptionsDialog();
return true;
}
return super.onKeyUp(keyCode, event);
}
@Override
protected void onPause() {
super.onPause();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostPause(this);
}
}
@Override
protected void onResume() {
super.onResume();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostResume(this, this);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostDestroy();
}
}
}
4、编写ReactNative的ExpandableListView.js
RCTExpandableListView与ViewManager类中的REACT_CLASS一致'use strict';
import { PropTypes } from 'react';
import { requireNativeComponent, View } from 'react-native';
var iFace = {
name: 'ExpandableTextView',
/*
propTypes: {
src: PropTypes.string,
borderRadius: PropTypes.number,
resizeMode: PropTypes.oneOf(['cover', 'contain', 'stretch']),
...View.propTypes // 包含默认的View的属性
},
*/
propTypes: {
layoutWidth: PropTypes.number,
layoutHeight: PropTypes.number,
...View.propTypes
},
};
module.exports = requireNativeComponent('RCTExpandableListView', iFace);
5、ReactNative的index.anroid.js调用组件
Dimensions获得屏幕大小
var ExpandableListView = require('./android/lib/ExpandableListView');
const {width, height} = Dimensions.get('window');
class SubScreen extends React.Component {
static navigationOptions = {
tabBarLabel: 'SubScreen',
tabBarIcon: ({ tintColor }) => (
),
};
render() {
height=height-300;
return (
Alert测试
ExpandableListView测试
);
}
}
最后由于ReactNative的自定义的顶层容器改写了requestLayout(),导致重写等不能上传到顶层容器RootViewImpl,这样的后果是导致类似ListView的setData、notifyDataSetChanged方法失效,解决办法是在自定组件中重写requestLayout方法,先调用父类requestLayout,而后手动触发measure计算方法、layout布局方法。代码如下:
public class ReactExpandableListView extends ExpandableListView {
public ReactExpandableListView(Context context) {
super(context);
}
public ReactExpandableListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ReactExpandableListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public ReactExpandableListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
private final Runnable measureAndLayout = new Runnable() {
@Override
public void run() {
measure(
MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY));
layout(getLeft(), getTop(), getRight(), getBottom());
}
};
@Override
public void requestLayout() {
super.requestLayout();
// The spinner relies on a measure + layout pass happening after it calls requestLayout().
// Without this, the widget never actually changes the selection and doesn't call the
// appropriate listeners. Since we override onLayout in our ViewGroups, a layout pass never
// happens after a call to requestLayout, so we simulate one here.
post(measureAndLayout);
}
}
这样,我们需要用ReactExpandableListView替代ExpandableListView。
最后ReactNative的js如何调用原生模块(比如某个方法),可以参考官网http://reactnative.cn/docs/0.44/native-modules-android.html#content ,这次官网写的是对的。
具体代码可以参考git:https://github.com/Springever/Test