├── 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原生的按键事件就会分配到激活的组件对象上
--------------------------------------------------------------------------------