(Android,NDK,C ++,OpenGL ES)
我需要一种可靠地从(软)键盘接收文本输入的方法。 解决方案可以通过Java使用NativeActivity子类或任何有效的方法。 最后我需要输入任何文本,所以我可以用OpenGL自己渲染它
一些背景知识 到目前为止,我通过调用showSoftInput或hideSoftInputFromWindow来调用JNI来触发软键盘。这到目前为止从未失败过。 但是,问题是本机活动不会发送所有字符。特别是ASCII范围之外的一些unicode字符,或某些运动软键盘不起作用(AKeyEvent_getKeyCode)
以前可以获取一些其他unicode字符,以便检查KeyEvent.ACTION_MULTIPLE并读取一串字符。 但即使这样也不再可靠。
到目前为止,我没有找到替代方法。 我尝试以编程方式添加EditText,但从未让它工作。即使尝试添加一个简单的Button,也会导致OpenGL视图不再呈现。
在iOS上我通过隐藏编辑框来解决这个问题,我只是激活它以使键盘显示出来。然后我会读出编辑框并使用字符串在OpenGL中渲染自己。
答案 0 :(得分:4)
我有同样的问题,我使用'Character'事件解决了它,我将事件与InputEvent分开处理。
问题是:AKeyEvent_getKeyCode
没有返回KeyCode的某些软键事件,特别是按住键时扩展的“unicode / latin”字符。这可以防止方法@Shammi和@eozgonul工作,因为在Java端重建的KeyEvent
没有足够的信息来获取unicode字符。
另一个问题是在InputQueue
事件被触发之前,dispatchKeyEvent
在C ++ / Native端被耗尽。这意味着KEYDOWN / KEYUP事件在Java代码可以处理事件之前全部触发。 (它们不是交错的)。
我的解决方案是通过覆盖dispatchKeyEvent
并将字符发送到Queue<Integer> queueLastInputCharacter = new ConcurrentLinkedQueue<Integer>();
// [JAVA]
@Override
public boolean dispatchKeyEvent (KeyEvent event)
{
int metaState = event.getMetaState();
int unichar = event.getUnicodeChar(metaState);
// We are queuing the Unicode version of the characters for
// sending to the app during processEvents() call.
// We Queue the KeyDown and ActionMultiple Event UnicodeCharacters
if(event.getAction()==KeyEvent.ACTION_DOWN){
if(unichar != 0){
queueLastInputCharacter.offer(Integer.valueOf(unichar));
}
else{
unichar = event.getUnicodeChar();
if(unichar != 0){
queueLastInputCharacter.offer(Integer.valueOf(unichar));
}
else if (event.getDisplayLabel() != 0){
String aText = new String();
aText = "";
aText += event.getDisplayLabel();
queueLastInputCharacter.offer(Integer.valueOf(Character.codePointAt(aText, 0)));
}
else
queueLastInputCharacter.offer(Integer.valueOf(0));
}
}
else if(event.getAction()==KeyEvent.ACTION_MULTIPLE){
unichar = (Character.codePointAt(event.getCharacters(), 0));
queueLastInputCharacter.offer(Integer.valueOf(unichar));
}
return super.dispatchKeyEvent(event);
}
并发队列将让线程一起玩得很好。
我有一个Java方法返回最后一个输入字符:
// [JAVA]
public int getLastUnicodeChar(){
if(!queueLastInputCharacter.isEmpty())
return queueLastInputCharacter.poll().intValue();
return 0;
}
在我的looper代码结束时,我进行了额外的检查以查看队列是否保留了任何unicode字符:
// [C++]
int ident;
int events;
struct android_poll_source* source;
// If not rendering, we will block 250ms waiting for events.
// If animating, we loop until all events are read, then continue
// to draw the next frame of animation.
while ((ident = ALooper_pollAll(((nv_app_status_focused(_lpApp)) ? 1 : 250),
NULL,
&events,
(void**)&source)) >= 0)
{
// Process this event.
if (source != NULL)
source->process(_lpApp, source);
// Check if we are exiting. If so, dump out
if (!nv_app_status_running(_lpApp))
return;
}
static int modtime = 10; // let's not run on every call
if(--modtime == 0) {
long uniChar = androidUnicodeCharFromKeyEvent();
while (uniChar != 0) {
KEvent kCharEvent; // Game engine event
kCharEvent.ptkKey = K_VK_ERROR;
kCharEvent.unicodeChar = uniChar;
kCharEvent.character = uniChar;
/* Send unicode char */
kCharEvent.type = K_EVENT_UNICHAR;
_lpPortableHandler(&kCharEvent);
if (kCharEvent.character < 127) {
/* Send ascii char for source compatibility as well */
kCharEvent.type = K_EVENT_CHAR;
_lpPortableHandler(&kCharEvent);
}
uniChar = androidUnicodeCharFromKeyEvent();
}
modtime = 10;
}
androidUnicodeCharFromKeyEvent
函数与@Shammi的GetStringFromAInputEvent
方法非常相似,只使用CallIntMethod
返回jint
。
备注强>
这确实需要修改引擎以处理与Key事件分开的字符事件。 Android仍然具有AKEYCODE_BACK
或AKEYCODE_ENTER
等关键代码,这些代码不是字符事件,仍然需要处理(并且可以在主输入循环器上处理)。
编辑框,控制台等...可以修改期望用户输入的内容以接收构建字符串的单独字符事件。如果您在多个平台上工作,那么除了正常的键输入事件之外,您还需要生成这些新的字符事件。
答案 1 :(得分:3)
我希望这对你有用,到目前为止为我工作。
int GetUnicodeChar(struct android_app* app, int eventType, int keyCode, int metaState)
{
JavaVM* javaVM = app->activity->vm;
JNIEnv* jniEnv = app->activity->env;
JavaVMAttachArgs attachArgs;
attachArgs.version = JNI_VERSION_1_6;
attachArgs.name = "NativeThread";
attachArgs.group = NULL;
jint result = javaVM->AttachCurrentThread(&jniEnv, &attachArgs);
if(result == JNI_ERR)
{
return 0;
}
jclass class_key_event = jniEnv->FindClass("android/view/KeyEvent");
int unicodeKey;
if(metaState == 0)
{
jmethodID method_get_unicode_char = jniEnv->GetMethodID(class_key_event, "getUnicodeChar", "()I");
jmethodID eventConstructor = jniEnv->GetMethodID(class_key_event, "<init>", "(II)V");
jobject eventObj = jniEnv->NewObject(class_key_event, eventConstructor, eventType, keyCode);
unicodeKey = jniEnv->CallIntMethod(eventObj, method_get_unicode_char);
}
else
{
jmethodID method_get_unicode_char = jniEnv->GetMethodID(class_key_event, "getUnicodeChar", "(I)I");
jmethodID eventConstructor = jniEnv->GetMethodID(class_key_event, "<init>", "(II)V");
jobject eventObj = jniEnv->NewObject(class_key_event, eventConstructor, eventType, keyCode);
unicodeKey = jniEnv->CallIntMethod(eventObj, method_get_unicode_char, metaState);
}
javaVM->DetachCurrentThread();
LOGI("Unicode key is: %d", unicodeKey);
return unicodeKey;
}
只需从输入处理程序中调用它,我的结构大致如下:
switch (AInputEvent_getType(event))
{
case AINPUT_EVENT_TYPE_KEY:
switch (AKeyEvent_getAction(event))
{
case AKEY_EVENT_ACTION_DOWN:
int key = AKeyEvent_getKeyCode(event);
int metaState = AKeyEvent_getMetaState(event);
int uniValue;
if(metaState != 0)
uniValue = GetUnicodeChar(app, AKEY_EVENT_ACTION_DOWN, key, metaState);
else
uniValue = GetUnicodeChar(app, AKEY_EVENT_ACTION_DOWN, key, 0);
既然你说你已经打开了软键盘,我就不会进入那个部分,但代码很简单。我基本上使用具有GetUnicodeChar函数的KeyEvent类的Java函数。
答案 2 :(得分:1)
Eozgonul的解决方案为我工作。我采用它并对其进行修改以分割Java和本机端之间的工作。基本上我扩展NativeActivity来派生我自己的类,它允许我尽可能多地移动到Java。我也最终传递了输入事件中的所有数据。我想确保在创建的KeyEvent对象中尽可能多地捕获它。
package com.MyCompany.MyApp;
import android.os.Bundle;
import android.view.inputmethod.InputMethodManager;
import android.content.Context;
import android.view.KeyEvent;
public class MyNativeActivity extends android.app.NativeActivity
{
// Need this for screen rotation to send configuration changed callbacks to native
@Override
public void onConfigurationChanged( android.content.res.Configuration newConfig )
{
super.onConfigurationChanged( newConfig );
}
public void showKeyboard()
{
InputMethodManager imm = ( InputMethodManager )getSystemService( Context.INPUT_METHOD_SERVICE );
imm.showSoftInput( this.getWindow().getDecorView(), InputMethodManager.SHOW_FORCED );
}
public void hideKeyboard()
{
InputMethodManager imm = ( InputMethodManager )getSystemService( Context.INPUT_METHOD_SERVICE );
imm.hideSoftInputFromWindow( this.getWindow().getDecorView().getWindowToken(), 0 );
}
public String stringFromKeyCode( long downTime, long eventTime,
int eventAction, int keyCode, int repeatCount, int metaState,
int deviceId, int scanCode, int flags, int source )
{
String strReturn;
KeyEvent keyEvent = new KeyEvent( downTime, eventTime, eventAction, keyCode, repeatCount, metaState, deviceId, scanCode, flags, source );
if ( metaState == 0 )
{
int unicodeChar = keyEvent.getUnicodeChar();
if ( eventAction == KeyEvent.ACTION_MULTIPLE && unicodeChar == keyEvent.KEYCODE_UNKNOWN )
{
strReturn = keyEvent.getCharacters();
}
else
{
strReturn = Character.toString( ( char )unicodeChar );
}
}
else
{
strReturn = Character.toString( ( char )( keyEvent.getUnicodeChar( metaState ) ) );
}
return strReturn;
}
}
在原住民方面......
std::string GetStringFromAInputEvent( android_app* pApp, AInputEvent* pInputEvent )
{
std::string strReturn;
JavaVM* pJavaVM = pApp->activity->vm;
JNIEnv* pJNIEnv = pApp->activity->env;
JavaVMAttachArgs javaVMAttachArgs;
javaVMAttachArgs.version = JNI_VERSION_1_6;
javaVMAttachArgs.name = "NativeThread";
javaVMAttachArgs.group = NULL;
jint jResult;
jResult = pJavaVM->AttachCurrentThread( &pJNIEnv, &javaVMAttachArgs );
if ( jResult != JNI_ERR )
{
// Retrieves NativeActivity.
jobject nativeActivity = pNativeActivity->clazz;
jclass ClassNativeActivity = pJNIEnv->GetObjectClass( nativeActivity );
jmethodID MethodStringFromKeyCode = pJNIEnv->GetMethodID( ClassNativeActivity, "stringFromKeyCode", "(JJIIIIIIII)Ljava/lang/String;" );
jlong jDownTime = AKeyEvent_getDownTime( pInputEvent );
jlong jEventTime = AKeyEvent_getEventTime( pInputEvent );
jint jEventAction = AKeyEvent_getAction( pInputEvent );
jint jKeyCode = AKeyEvent_getKeyCode( pInputEvent );
jint jRepeatCount = AKeyEvent_getRepeatCount( pInputEvent );
jint jMetaState = AKeyEvent_getMetaState( pInputEvent );
jint jDeviceID = AInputEvent_getDeviceId( pInputEvent );
jint jScanCode = AKeyEvent_getScanCode( pInputEvent );
jint jFlags = AKeyEvent_getFlags( pInputEvent );
jint jSource = AInputEvent_getSource( pInputEvent );
jstring jKeyCodeString = ( jstring )pJNIEnv->CallObjectMethod( nativeActivity, MethodStringFromKeyCode,
jDownTime, jEventTime, jEventAction,
jKeyCode, jRepeatCount, jMetaState,
jDeviceID, jScanCode, jFlags, jSource );
const char* keyCodeString = pJNIEnv->GetStringUTFChars( keyCodeString, nullptr );
strReturn = std::string( keyCodeString );
pJNIEnv->ReleaseStringUTFChars( jKeyCodeString, keyCodeString );
// Finished with the JVM.
pJavaVM->DetachCurrentThread();
}
return strReturn;
}
我采用这种方法的两个原因..
通过将代码移动到java并且只让您在本机端调用一个jni包装器方法来降低代码语法的复杂性。
Java是首选的Android语言,这使我可以快速迭代基于java的解决方案。此外,大多数现有解决方案都在java。
答案 3 :(得分:0)
基本上这将解决问题 NativeActivity override onKeyDown()
但是你必须实现除NDK键输入以外的其他方式来将onKeyMultiple的event.getCharacters()
字符串放入代码中。