├── android ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── jianghe │ │ └── keyevent │ │ ├── KeyEventPackage.java │ │ └── KeyEventModule.java └── build.gradle ├── package.json ├── .npmignore ├── LICENSE.md ├── keyevent.js └── README.md /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.1" 6 | 7 | defaultConfig { 8 | minSdkVersion 16 9 | targetSdkVersion 22 10 | versionCode 1 11 | versionName "1.0" 12 | ndk { 13 | abiFilters "armeabi-v7a", "x86" 14 | } 15 | } 16 | } 17 | 18 | dependencies { 19 | compile 'com.facebook.react:react-native:+' 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-common-keyevent", 3 | "version": "0.0.3", 4 | "main": "keyevent.js", 5 | "description": "为使RN能满足TV端遥控器需求,获取Android原生键盘事件封装的RN键盘事件类", 6 | "keywords": [ 7 | "react-native", 8 | "keyevent", 9 | "TV", 10 | "RN", 11 | "android" 12 | ], 13 | "author": "jianghe <573748150jh@163.com> (github.com/372623460jh)", 14 | "license": "ISC", 15 | "peerDependencies": { 16 | "react-native": ">=0.30" 17 | }, 18 | "rnpm": { 19 | "android": { 20 | "packageInstance": "new KeyEventPackage()" 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | 25 | # Android/IJ 26 | # 27 | *.iml 28 | .idea 29 | .gradle 30 | local.properties 31 | 32 | # node.js 33 | # 34 | node_modules/ 35 | npm-debug.log 36 | 37 | # BUCK 38 | buck-out/ 39 | \.buckd/ 40 | android/app/libs 41 | android/keystores/debug.keystore 42 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | BSD License 2 | 3 | Copyright (c) 2017 JiangHe. 4 | 5 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 6 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 7 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 8 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 9 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 10 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 11 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 12 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 13 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 14 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /android/src/main/java/com/jianghe/keyevent/KeyEventPackage.java: -------------------------------------------------------------------------------- 1 | package com.jianghe.keyevent; 2 | 3 | import com.facebook.react.ReactPackage; 4 | import com.facebook.react.bridge.JavaScriptModule; 5 | import com.facebook.react.bridge.NativeModule; 6 | import com.facebook.react.bridge.ReactApplicationContext; 7 | import com.facebook.react.uimanager.ViewManager; 8 | 9 | import java.util.Arrays; 10 | import java.util.Collections; 11 | import java.util.List; 12 | 13 | /** 14 | * KeyEventPackage实现ReactPackage用于引用是添加到eactInstanceManager中addPackage方法中 15 | * @author jiangHe 16 | * @date 2017-6-19 17 | * @version 1.0.0 18 | */ 19 | public class KeyEventPackage implements ReactPackage { 20 | 21 | public KeyEventPackage() { } 22 | 23 | @Override 24 | public List createNativeModules(ReactApplicationContext reactContext) { 25 | return Arrays.asList( 26 | KeyEventModule.initKeyEventModule(reactContext) 27 | ); 28 | } 29 | 30 | @Override 31 | public List> createJSModules() { 32 | return Collections.emptyList(); 33 | } 34 | 35 | @Override 36 | public List createViewManagers(ReactApplicationContext reactContext) { 37 | return Collections.emptyList(); 38 | } 39 | } -------------------------------------------------------------------------------- /android/src/main/java/com/jianghe/keyevent/KeyEventModule.java: -------------------------------------------------------------------------------- 1 | package com.jianghe.keyevent; 2 | 3 | import com.facebook.react.bridge.ReactApplicationContext; 4 | import com.facebook.react.bridge.ReactContext; 5 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 6 | import com.facebook.react.modules.core.DeviceEventManagerModule; 7 | 8 | /** 9 | * KeyEventModule继承ReactContextBaseJavaModule用于实现原生模块方法 10 | * @author jiangHe 11 | * @date 2017-6-19 12 | * @version 1.0.0 13 | */ 14 | public class KeyEventModule extends ReactContextBaseJavaModule { 15 | 16 | //react的上下文对象 17 | private ReactContext reactContext; 18 | 19 | //react的事件传递类DeviceEventEmitter对象 20 | //通过DeviceEventEmitter.emit方法可向RN发送消息 21 | private DeviceEventManagerModule.RCTDeviceEventEmitter deviceEventEmitter = null; 22 | 23 | //该类的实例化对象 24 | private static KeyEventModule keyEventModule = null; 25 | 26 | //构造方法初始化ReactContext对象 27 | protected KeyEventModule(ReactApplicationContext reactContext) { 28 | super(reactContext); 29 | this.reactContext = reactContext; 30 | } 31 | 32 | //返回模块名 33 | @Override 34 | public String getName() { 35 | return "KeyEventModule"; 36 | } 37 | 38 | //静态方法实例化本类返回实例化对象 39 | public static KeyEventModule initKeyEventModule(ReactApplicationContext reactContext) { 40 | keyEventModule = new KeyEventModule(reactContext); 41 | return keyEventModule; 42 | } 43 | 44 | //获取实例化对象的静态方法 45 | public static KeyEventModule getkeyEventModule() { 46 | return keyEventModule; 47 | } 48 | 49 | //提供给Activity 键盘按下调用的方法 50 | public void onKeyDownEvent(int keyCode) { 51 | if (deviceEventEmitter == null) { 52 | //通过反射实例化DeviceEventEmitter 53 | deviceEventEmitter = reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class); 54 | } 55 | //发送消息给RN 56 | deviceEventEmitter.emit("KeyDown", keyCode); 57 | }; 58 | 59 | //提供给Activity 键盘抬起调用的方法 60 | public void onKeyUpEvent(int keyCode) { 61 | if (deviceEventEmitter == null) { 62 | //通过反射实例化DeviceEventEmitter 63 | deviceEventEmitter = reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class); 64 | } 65 | //发送消息给RN 66 | deviceEventEmitter.emit("KeyUp", keyCode); 67 | }; 68 | 69 | } 70 | -------------------------------------------------------------------------------- /keyevent.js: -------------------------------------------------------------------------------- 1 | import {DeviceEventEmitter} from 'react-native'; 2 | 3 | /** 4 | * 键盘事件类 5 | * 6 | * 该类封装了键盘的3个主要是键 7 | * onkeyDown 键按下 8 | * onKeyUp 键抬起 9 | * onKeyPress 键单击 10 | * 每个KeyController组件中都有一个唯一的KeyEvent对象 11 | * updateState方法:用于更新当前组件是否处于激活状态 12 | * 处于激活状态的组件下的键盘事件才生效 13 | * 14 | * @param obj 构造方法入参初始化值 15 | * { 16 | * state:boolean,//当前对象初始化时是否处于激活状态 17 | * onKeyDown:[Object function],//键盘按下的回调方法 18 | * onKeyUp:[Object function],//键盘抬起的回调方法 19 | * onKeyPress:[Object function],//键盘单击的回调方法 20 | * } 21 | */ 22 | function KeyEvent(obj) { 23 | 24 | //该事件对象的唯一标识 25 | this._KeyBaseId = this._getId(); 26 | //该实例化对象keydown事件 27 | this.onKeyDown = DeviceEventEmitter.addListener(this._KeyBaseId + 'KeyDown', obj.onKeyDown); 28 | //该实例化对象onKeyUp事件 29 | this.onKeyUp = DeviceEventEmitter.addListener(this._KeyBaseId + 'KeyUp', obj.onKeyUp); 30 | //该实例化对象onKeyPress事件 31 | this.onKeyPress = DeviceEventEmitter.addListener(this._KeyBaseId + 'KeyPress', obj.onKeyPress); 32 | //当前该组件的激活状态 33 | this.updateState(obj.state || false); 34 | } 35 | KeyEvent.prototype = { 36 | construct: KeyEvent, 37 | //获取不重复随机数ID的方法 38 | _getId: function () { 39 | return "jh" + Date.now().toString(36) + Math.random().toString(36).substr(3); 40 | }, 41 | //修改当前激活状态的方法 42 | updateState: function (isActive) { 43 | this.state = isActive; 44 | //修改静态属性改变处于激活状态的KeyController组件ID 45 | this.state ? KeyEvent.nowActiveClass = this._KeyBaseId : null; 46 | } 47 | }; 48 | 49 | /** 50 | * 工厂模式实例化对象的构建方法 51 | */ 52 | KeyEvent.initKeyEvent = function (obj) { 53 | return new KeyEvent(obj); 54 | }; 55 | 56 | /** 57 | * 当前处于激活状态的KeyController组件ID,""为没有当前没有处于激活状态的组件 58 | * 该静态属性用于标注当前处于激活状态的实例化组件的唯一ID 59 | * 组件只有在激活状态下才能响应键盘事件,当有键盘事件发生时将通过这一属性对键盘事件进行分发, 60 | * 分发到处于激活状态的实例组件对象上。 61 | * @type {string} 62 | */ 63 | KeyEvent.nowActiveClass = ""; 64 | 65 | /** 66 | * 按键栈用于记录按键被按下的时间,来触发press事件 67 | * @type {{}} 68 | */ 69 | KeyEvent.keyCodeStack = {}; 70 | 71 | /** 72 | * 用于移除对Android按键的监听 73 | */ 74 | KeyEvent.removeListenAndroid = function () { 75 | KeyEvent.keyDownEventAndroid ? KeyEvent.keyDownEventAndroid.remove() : null; 76 | KeyEvent.keyUpEventAndroid ? KeyEvent.keyUpEventAndroid.remove() : null; 77 | }; 78 | 79 | //Android原生keyDown的消息监听。Android原生通过DeviceEventEmitter来传递消息 80 | KeyEvent.keyDownEventAndroid = DeviceEventEmitter.addListener('KeyDown', function (keyCode) { 81 | if (KeyEvent.nowActiveClass) { 82 | //记录键按下时间 83 | KeyEvent.keyCodeStack[keyCode + ""] = new Date().getTime(); 84 | //分发消息给具体的实例化对象 85 | DeviceEventEmitter.emit(KeyEvent.nowActiveClass + 'KeyDown', keyCode); 86 | } 87 | }); 88 | 89 | //Android原生keyUp的消息监听。Android原生通过DeviceEventEmitter来传递消息 90 | KeyEvent.keyUpEventAndroid = DeviceEventEmitter.addListener('KeyUp', function (keyCode) { 91 | if (KeyEvent.nowActiveClass) { 92 | //分发消息给具体的实例化对象 93 | DeviceEventEmitter.emit(KeyEvent.nowActiveClass + 'KeyUp', keyCode); 94 | //当按键按下抬起间隔<2000ms >30ms时触发press事件 95 | if (KeyEvent.keyCodeStack[keyCode + ""]) { 96 | let dTime = new Date().getTime() - KeyEvent.keyCodeStack[keyCode + ""]; 97 | if (dTime > 30 && dTime < 2000) { 98 | //触发Press事件 99 | KeyEvent.keyPressEventAndroid(keyCode); 100 | } 101 | } 102 | //重置按键按下时间 103 | KeyEvent.keyCodeStack[keyCode + ""] = 0; 104 | } 105 | }); 106 | 107 | //通过原生Android,Dowm,Up事件加工的keyPress的事件。 108 | KeyEvent.keyPressEventAndroid = function (keyCode) { 109 | if (KeyEvent.nowActiveClass) { 110 | //分发消息给具体的实例化对象 111 | DeviceEventEmitter.emit(KeyEvent.nowActiveClass + 'KeyPress', keyCode); 112 | } 113 | }; 114 | 115 | export default KeyEvent; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## react-native-common-keyevent 2 | 3 | 该组件用于`React-Native`中获取`Android`原生层面的键盘响应事件。 4 | 5 | - 目前开发者本人的主要使用场景是Android-TV端获取遥控器的按键事件。 6 | 7 | - GitHub地址:[https://github.com/372623460jh/react-native-common-keyevent](https://github.com/372623460jh/react-native-common-keyevent). 8 | 9 | ### 安装 10 | 11 | #### 使用npm 12 | 13 | $ npm install react-native-common-keyevent --save 14 | 15 | 16 | 17 | ### Android端环境配置 18 | 19 | 20 | 1. `android/setting.gradle`文件增加以下代码 21 | 22 | ... 23 | include ':react-native-common-keyevent' 24 | project(':react-native-common-keyevent').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-common-keyevent/android') 25 | 26 | 27 | 2. `android/app/build.gradle`文件增加以下代码 28 | 29 | ... 30 | dependencies { 31 | ... 32 | compile project(':react-native-common-keyevent') 33 | } 34 | 35 | 36 | 3. android代码中修改(Application方式或Activity方式二选一) 37 | 38 | - Application方式注册模块 39 | 40 | 41 | 42 | import import com.jianghe.keyevent.KeyEventPackage; //--> 导入模块包 43 | 44 | public class MainApplication extends Application implements ReactApplication { 45 | ...... 46 | @Override 47 | protected List getPackages() { 48 | return Arrays.asList( 49 | new MainReactPackage(), 50 | new KeyEventPackage() //-->增加组件的ReactPackage文件 51 | ); 52 | } 53 | ...... 54 | } 55 | 56 | - Activity方式注册模块 57 | 58 | 59 | 60 | import com.jianghe.keyevent.*; //--> 导入模块包 61 | 62 | public class MyActivity extends Activity implements DefaultHardwareBackBtnHandler { 63 | 64 | private ReactRootView mReactRootView; 65 | private ReactInstanceManager mReactInstanceManager; 66 | 67 | @Override 68 | protected void onCreate(Bundle savedInstanceState) { 69 | super.onCreate(savedInstanceState); 70 | mReactRootView = new ReactRootView(this); 71 | mReactInstanceManager = ReactInstanceManager.builder() 72 | .setApplication(getApplication()) 73 | .setBundleAssetName("index.android.bundle") 74 | .setJSMainModuleName("index.android") 75 | .addPackage(new MainReactPackage()) 76 | .addPackage(new KeyEventPackage()) //-->增加组件的ReactPackage文件 77 | .setUseDeveloperSupport(true) 78 | .setInitialLifecycleState(LifecycleState.RESUMED) 79 | .build(); 80 | mReactRootView.startReactApplication(mReactInstanceManager, "HelloWorld", null); 81 | setContentView(mReactRootView); 82 | } 83 | 84 | } 85 | 86 | 87 | 4. Activity中增加按键监听并传递给RN的方法 88 | 89 | import android.view.KeyEvent; // -->引入Android键盘事件包 90 | import com.jianghe.keyevent.KeyEventModule; // -->引入该组件模块包 91 | 92 | @Override 93 | public boolean onKeyDown(int keyCode, KeyEvent event) { 94 | //利用RN的DeviceEventEmitter对象将按键消息发送给js 95 | KeyEventModule.getkeyEventModule().onKeyDownEvent(keyCode); 96 | super.onKeyDown(keyCode, event); 97 | return true; 98 | } 99 | 100 | @Override 101 | public boolean onKeyUp(int keyCode, KeyEvent event) { 102 | //利用RN的DeviceEventEmitter对象将按键消息发送给js 103 | KeyEventModule.getkeyEventModule().onKeyUpEvent(keyCode); 104 | super.onKeyUp(keyCode, event); 105 | return true; 106 | } 107 | 108 | 109 | ### React-Native中使用 110 | 111 | 在任何你想要使用按键监听的地方引入该组件模块 112 | 113 | import KeyEvent from 'react-native-common-keyevent'; 114 | 115 | 1. 在组件中申明3个事件响应方法 116 | 117 | keydown: function (keycode) { 118 | console.log("按下" + keycode); 119 | }, 120 | keyup: function (keycode) { 121 | console.log("抬起" + keycode); 122 | }, 123 | keypress: function (keycode) { 124 | console.log("点击" + keycode); 125 | }, 126 | 127 | 1. 在componentDidMount生命周期方法中初始化KeyEvent对象 128 | 129 | this.ke = KeyEvent.initKeyEvent({ 130 | state: true,//当前对象初始化时是否处于激活状态 131 | onKeyDown: this.keydown,//键盘按下的回调方法 132 | onKeyUp: this.keyup,//键盘抬起的回调方法 133 | onKeyPress: this.keypress,//键盘单击的回调方法 134 | }) 135 | 136 | 137 | ## 注意 138 | 139 | RN中的多个页面都会被渲染到一个Activity中,这样存在的问题就是RN中多个页面响应的其实都是1个Activity的键盘事件。 140 | 那么会出现以下问题: 141 | 142 | 1.现在有2个页面A.js , B.js 现在A中的回车事件是 `Alert.alert('A');` B中的上事件是 `Alert.alert('B');` 143 | 144 | 当前的焦点页在A上,回车打出的是A;现在通过`react-navigation`跳转到B页面,这时回车依然会弹出A,这不是我们预期的结果。所以在`KeyEvent`对象初始化的时候传入了一个`state`属性,这个属性就是用来控制当前`KeyEvent`对象是否生效的。 145 | 上面情况的解决方案: 146 | 147 | A现在通过`react-navigation`跳转到B页面是使用`this.ke.updateState(false)`来将A页面的激活状态调整为`false`这样Android原生的按键事件就会分配到激活的组件对象上 --------------------------------------------------------------------------------