这个虚幻3项目很让人头疼,就这么简单的功能,在scaleform的输入框TextField中弹出android键盘,我做了很久才完成。大致方法是,actionscript调用一个ShowKeyboard函数,对应的uc脚本中的函数被调用,表示需要弹出android的虚拟键盘。然后,uc脚本去调用c++代码,c++又调用JNI函数,最终弹出虚拟键盘。并且,还要反过来调用,即用户在虚拟键盘上的输入要及时反馈给scaleform的UI层。

先贴android代码。要及时获取输入框的状态,包括光标位置和按键的捕获,需要实现一个自定义的输入框类。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class EditTextCursorWatcher extends EditText {

	public EditTextCursorWatcher(Context context) {
		super(context);
	}
	
	private OnKeyPreImeListener keyPreImelistener;
	
	public interface OnKeyPreImeListener {
		public void onKeyPreIme(int keyCode, KeyEvent event);
	}
	
	public void addOnKeyPreImeListener (OnKeyPreImeListener _listener){
		keyPreImelistener = _listener;
	}
	
	public void removeOnKeyPreImeListener () {
		keyPreImelistener = null;
	}
	
	private OnSelectionChangedListener selectionChangedListener;
	
	public interface OnSelectionChangedListener {
		public void onSelectionChanged(int selStart, int selEnd);
	}

	public void addOnSelectionChangedListener (OnSelectionChangedListener _listener) {
		selectionChangedListener = _listener;
	}
	
	public void removeOnSelectionChangedListener () {
		selectionChangedListener = null;
	}

	@Override
	protected void onSelectionChanged(int selStart, int selEnd) {
		if (selectionChangedListener != null) {
			selectionChangedListener.onSelectionChanged(selStart, selEnd);
		}
		super.onSelectionChanged(selStart, selEnd);
	}

	@Override
	public boolean onKeyPreIme(int keyCode, KeyEvent event) {
		if (keyPreImelistener != null) {
			keyPreImelistener.onKeyPreIme(keyCode, event);
	    }
		return super.onKeyPreIme(keyCode, event);
	}
	
}

下面是正式的代码了,我用来测试的。这里的输入框都是动态创建的,然后隐藏键盘时会销毁掉。在Scaleform中使用时,这个创建的输入框其实是需要隐藏的,这里只需要隐藏KeyboardTextLayout就行了。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
public class MainActivity extends Activity {

	protected Handler handler = null;
	private LinearLayout KeyboardTextLayout	= null;
	private EditTextCursorWatcher KeyboardText = null;
	
	private TextView clonedText;
	private TextView clonedTextCursor;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		handler = new Handler();
		
		Timer timer = new Timer();
		timer.schedule(new TimerTask()
		{
			public void run()
		    {
				final String str = "Test";
		    	ShowKeyBoard(str, 3, 100, 100, 200, 30, false);
		    }
		},
		500);
	}
	
	public void ShowKeyBoard(String InputText, int selStart, float posX, float posY,
		float sizeX, float sizeY, boolean IsPassword)
	{
		final MainActivity activity		= this;
		final String FinalText			= InputText;
		final int FinalSelStart			= selStart;
		final int FinalPosX				= (int)(posX);
		final int FinalPosY				= (int)(posY);
		final int FinalSizeX			= (int)(sizeX);
		final int FinalSizeY			= (int)(sizeY);
		
		handler.post(new Runnable()
        {
			public void run()
            {
				if( KeyboardTextLayout == null )
				{
					KeyboardTextLayout = new LinearLayout( activity );
					KeyboardTextLayout.setOrientation( LinearLayout.HORIZONTAL );
					KeyboardTextLayout.setPadding(FinalPosX, FinalPosY, 0, 0);
					
					KeyboardText = new EditTextCursorWatcher( activity );
					KeyboardText.setSingleLine(true);
					KeyboardText.setHeight(FinalSizeY);
					KeyboardText.setWidth(FinalSizeX);
					KeyboardText.setPadding(0, 0, 0, 0);
					KeyboardText.setText(FinalText);
					final int keyboardTextLength = KeyboardText.getText().length();
					KeyboardText.setSelection(FinalSelStart > keyboardTextLength ? 
						keyboardTextLength : FinalSelStart);
					
					clonedTextCursor = new TextView(activity);
					clonedTextCursor.setHeight(FinalSizeY);
					clonedTextCursor.setWidth(FinalSizeX);
					clonedTextCursor.setPadding(0, 0, 0, 0);
					clonedTextCursor.setText("" + KeyboardText.getSelectionStart());
					
					KeyboardText.addTextChangedListener(new TextWatcher() {

						@Override
						public void beforeTextChanged(CharSequence s, int start, int count,
							int after) {
							
						}

						@Override
						public void onTextChanged(CharSequence s, int start, int before,
							int count) {
							clonedText.setText(s);
						}

						@Override
						public void afterTextChanged(Editable s) {
							
						}
						
					});
					
					KeyboardText.addOnSelectionChangedListener(new OnSelectionChangedListener(){

						@Override
						public void onSelectionChanged(int selStart, int selEnd) {
							clonedTextCursor.setText("" + selStart);
						}
						
					});
					
					KeyboardText.addOnKeyPreImeListener(new OnKeyPreImeListener () {

						@Override
						public void onKeyPreIme(int keyCode, KeyEvent event) {
							if (keyCode == KeyEvent.KEYCODE_BUTTON_B)
							{
								HideKeyBoard(false);
							}
						}
						
					});
					
					
					KeyboardTextLayout.addView(KeyboardText);
					
					// clone
					clonedText = new TextView(activity);
					clonedText.setHeight(FinalSizeY);
					clonedText.setWidth(FinalSizeX);
					clonedText.setPadding(0, 0, 0, 0);
					clonedText.setText(KeyboardText.getText().toString());
					KeyboardTextLayout.addView(clonedText);

					KeyboardTextLayout.addView(clonedTextCursor);
					
					addContentView( KeyboardTextLayout, new LayoutParams( 
						LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT) );
					KeyboardTextLayout.setVisibility(View.VISIBLE);
				}
				
				
			}
		});
		
		handler.postDelayed(new Runnable()
		{
			public void run() {
				if( KeyboardTextLayout != null )
				{  	
					KeyboardText.requestFocus();
					InputMethodManager imm = (InputMethodManager)getSystemService(
						Context.INPUT_METHOD_SERVICE);
					imm.showSoftInput( KeyboardText, InputMethodManager.SHOW_FORCED );
				}
			}
		}, 100);
	}
	
	public void HideKeyBoard( boolean wasCancelled )
	{	
		final boolean finalwasCancelled = wasCancelled;
		
		handler.post(new Runnable()
        {
			public void run()
            {	    
				if( KeyboardTextLayout != null )
				{         
					if( KeyboardText != null && !finalwasCancelled )
					{
						
					}

					// in case its still up
					InputMethodManager mgr = (InputMethodManager)getSystemService(
						Context.INPUT_METHOD_SERVICE);
					mgr.hideSoftInputFromWindow(KeyboardText.getWindowToken(), 0);
					
					ViewGroup viewGroup = (ViewGroup)(KeyboardTextLayout.getParent());
					viewGroup.removeView(KeyboardTextLayout);
					KeyboardTextLayout = null;
					KeyboardText = null;
				}
				
				// some extra task
				DisplayMetrics dm = new DisplayMetrics();
				getWindowManager().getDefaultDisplay().getMetrics(dm);
				int screenWidth = dm.widthPixels;
				int screenHeigh = dm.heightPixels;
				Toast.makeText(getApplicationContext(), "" + screenWidth + "," + screenHeigh,
					Toast.LENGTH_LONG).show();
			}
		});
	}

}

Android这一层做完了,其它的应该也不难了,但是UE3中那些相互调用真的让人很烦。uc脚本也只会在虚幻3里面使用,基本上被淘汰了。下面记录一点吧,首先是actionscript调用uc脚本,文档里都有,假设actionscript里面有个函数名叫handleShowKeyBoard,那么uc脚本里可以这么写。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// Called from ActionScript
delegate HandleShowKeyBoardDelegate(string inputText, int selStart, float posX, float posY);
function HandleShowKeyBoard(string inputText, int selStart, float posX, float posY)
{
	`Log("---handleShowKeyBoard has been called from actionscript---" $inputText 
		@ "  " $selStart @ "  " $posX @ "  " $posY);

	class'R_Util'.Static.ShowKeyboard(inputText, selStart, posX, posY, 0, 0, false);
}

function SetDelegate_HandleShowKeyBoard(delegate<HandleShowKeyBoardDelegate> Delegated)
{
	if (DialogClassRef == none)
		`Warn("SetDelegate_HandleShowKeyBoard failed to get DialogClassRef");

	class'R_UIManager'.static.GetDialogManager().ActionScriptSetFunction(
		DialogClassRef, "handleShowKeyBoard");
}

actionscript的关键代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// StyledTextInput.as
public function handleInput(details:InputDetails, pathToFocus:Array):Boolean {
	if (details.navEquivalent == NavigationCode.ENTER and details.value == "keyDown")
	{
		LoginDialog(LoginDialog.dialog).setFocusedTextField(this);
		var myPoint:Object = { x:0, y:0 };
		localToGlobal(myPoint);
		LoginDialog.handleShowKeyBoard(text, this.textField.caretIndex, myPoint.x, myPoint.y);
	}
	
	return super.handleInput(details, pathToFocus);
}

// LoginDialog.as
public static function handleShowKeyBoard(inputText:String, selStart:Number, 
	posX:Number, posY:Number):Void
{
	trace("---unreal should open input method---" + inputText + ":" + selStart + 
		"---" + posX + "," + posY);
}

public function setFocusedTextField(textField:StyledTextInput):Void
{
	focusedTextField = textField;
}

public function showKeyBoardCallback(inputText:String, selStart:Number):Void
{
	if (focusedTextField != null)
	{
		focusedTextField.text = inputText;
		Selection.setSelection(selStart, selStart);
	}
}

反过来,下面是uc函数调用actionscript函数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
function ShowKeyBoardCallback(string InputText, int selStart)
{
	if(DialogRef != None)
	{
		`Log("---actionscript ShowKeyBoardCallback should be called ---");
		DialogRef.ActionScriptVoid("showKeyBoardCallback");
	}
	else
	{
		`Warn("ShowKeyBoardCallback failed to get DialogRef");
	}
}

UC脚本怎么调用C++了?在uc中用native。C++怎么调用UC了?在uc中用event。然后编译UC脚本时,会自动生成相应的C++头文件,我们去实现即可。

1
2
3
4
5
6
7
static native function ShowKeyboard(string InputText, int selStart, 
	float posX, float posY, float sizeX, float sizeY, bool IsPassword);
event OnKeyboardFinished(string InputText, int selStart)
{
	`Log("---OnKeyboardFinished---"$InputText $selStart);
	class'R_UIManager'.static.ShowKeyBoardCallback(InputText, selStart);
}

最后JNI方面的事就不用说了。