虚拟软键盘与输入法

1,软键盘输入法简介:Android输入法框架原则是能够支持多种输入法编辑器(Input Method Editor,IME),输入法编辑器包括软键盘输入法、手写输入法和传统的硬键盘输入法。本章的重点是介绍SDK 1.5引入的软键盘输入法。
通常,用户通过点击文本编辑栏激活输入法。例如当用户点击Google Search栏准备输入文本时,SDK1.5的软键盘输入法自动就从屏幕下方平移弹出,并占据屏幕底部的空间。而原来的Home屏幕中应用程序的空间则自动卷起,由于绝大多数应用程序的布局都是可调整的,因此当输入法窗口平移显示后,应用程序的整体布局还是可见的。这种处理输入法显示的模式叫做平移扫描(pan and scan),他是Android中处理软键盘显示的默认模式。
另一个表现平移扫描模式的例子是编写电子邮件的场景。当输入法被激活时,电子邮件应用的窗口自动调整大小并向上卷起,以便让电子邮件应用中所有的组件不被屏幕底部的输入法窗口遮挡。这种平移扫描方法当然有局限性,他只适用于显示区域能够调整大小,并且调整之后手机屏幕仍然有足够空间容纳所有视图组件的应用程序。想一想当手机处于横屏(landscape)时,手机屏幕的垂直高度变短,这时候屏幕高度就不足以同时容纳应用程序显示区域和输入法窗口。
Android还有另一种处理输入法和应用程序显示的模式,叫做全屏-摘要(Fullscreen-Extract)模式,"全屏"是指输入法全屏显示,“摘要”是指应用程序的视图显示只选择性地显示一部分,而非显示所有的应用视图。当输入法窗口太大或者手机屏幕高度太小,手机屏幕不能同时显示输入法窗口和所有应用程序视图组件时,就使用这种模式。通常我们会在横屏显示的状态下遇到全屏-摘要模式,而应用程序除了输入窗口保留外,其他窗口则不显示在屏幕上。


2,创建软键盘输入法:
接下来我们将用一个实例项目SoftKeyboard,向大家介绍创建软键盘输入法的要点。SoftKeyboard的源代码可以在<SDK>/platforms/android-1.5/samples目录下找到,SoftKeyboard的主要目的是展示如何创建一个软键盘输入法,他比Android自带的输入法LatinIME简单。对输入法有兴趣的读者如果想了解更多更深入的实现细节,可以在Android源码中查看LatinIME项目。

1)软键盘输入法的AndroidManifest.xml:
首先,我们来看看SoftKeyboard的AndroidManifest.xml文件

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.example.android.softkeyboard">
  <application android:label="@string/ime_name">
     <service android:name="SoftKeyboard"
        android:permission="android.permission.BIND_INPUT_METHOD">
        <intent-filter>
           <action android:name="android.view.InputMethod" />
        </intent-filter>
        <meta-data android:name="android.view.im"
                   android:resource="@xml/method" />
     </service>
  </application>
</manifest>

从AndroidManifest.xml中我们可以看出输入法其实就是一个Service,同时必须声明Service的Permission是BIND_INPUT_METHOD,确保输入法和系统相绑定。除了特殊的Permission声明之外,输入法还应该声明其Intent Filter的Action为"android.view.InputMethod"。输入法的额外信息可以通过<meta-data>以名值对的形式发布。

需要提醒读者注意的是,输入法的<meta-data>所指向的值必须是一个包含<input-method>标签的XML资源文件。

除了将输入法声明为Service之外,如果你的输入法程序还包含一个单独的输入法属性配置应用程序,那么你需要为这个单独的应用声明一个Activity,例如:

<!-- Optional activities. A good idea to have some user settings. -->
<activity android:name=".SoftKeyboardSettings"
   android:label="@string/fast_input_settings">
   <intent-filter>
     <action android:name="android.intent.action.MAIN" />
   <intent-filter>
</activity>

这一步是可选的,因为输入法可以直接在输入法用户界面中提供了所有的配置选项,而不需要单独的配置程序。

2)输入法和软键盘的核心类
为了在Android SDK 1.5中实现输入法,需要对android.inputmethodservice.InputMethodService进行继承扩展。InputMethodService类提供了关于输入法的标准实现,她是整个输入法的核心类,基于他用户可以开发出新的输入法。

SoftKeyboard项目中的SoftKeyboard.java实现了一个InputMethodService的子类SoftKeyboard,SoftKeyboard同时实现了接口类KeyboardView.OnKeyboardActionListener。

public class SoftKeyboard extends InputMethodService implements KeyboardView.OnKeyboardActionListener {
  static final boolean DEBUG=false;
  static final boolean PROCESS_HARD_KEYS=true;

  private KeyboardView mInputView;
  private CandidateView mCandidateView;
  private CompletionInfo[] mCompletions;

  private StringBuilder mComposing=new StringBuilder();
  private boolean mPredictionOn;
  private boolean mCompletionOn;
  private int mLastDisplayWidth;
  private boolean mCapsLock;
  private long mLastShiftTime;
  private long mMetaState;

  private LatinKeyboard mSymbolsKeyboard;
  private LatinKeyboard mSymbolsShiftedKeyboard;
  private LatinKeyboard mQwertyKeyboard;

  private LatinKeyboard mCurKeyboard;

  private String mWordSeparators;

  . . .
}

与输入法软键盘相关的有两个非常重要的类:android.inputmethodservice.KeyboardView和android.inputmethodservice.Keyboard。简单的说,android.inputmethodservice.KeyboardView负责绘制软键盘视图,而软键盘的具体布局、按键名称等由android.inputmethodservice.Keyboard定义。

实例中的KeyboardView用mInputView表示。mInputView由Android框架在方法onCreateInputView()中完成初始化。

@Override
public View onCreateInputView() {
  mInputView=(KeyboardView)getLayoutInflater().inflate(R.layout.input,null);
  mInputView.setOnKeyboardActionListener(this);
  mInputView.setKeyboard(mQwertyKeyboard);
  return mInputView;
}

R.layout.input表示的文件res/layout/input.xml定义了一个LatinKeyboardView。

<com.example.android.softkeyboard.LatinKeyboardView
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:id="@+id/keyboard"
   android:layout_alignParentBottom="true"
   android:layout_width="fill_parent"
   android:layout_height="wrap_content"
/>

LatinKeyboardView继承自类android.inputmethodservice.KeyboardView,同时KeyboardView的子类KeyboardView.OnKeyboardActionListener还能处理软键盘点击事件。

android.inputmethodservice.Keyboard类则负责加载软键盘的定义和属性。软键盘的定义和属性是指软键盘每一行包含哪些按键、按键的名称以及相应的按键编号,按键用文字还是图标表示,每个按键占多大的空间等。这些定义和属性都写在一个XML文件中,让我们来看看res/xml/qwerty.xml,它定义了标准英文字母的键值。

<Keyboard xmlns:android="http://schems.android.com/apk/res/android"
  android:keyWidth="10%p"
  android:horizontalGap="0px"
  android:verticalGap="0px"
  android:keyHeight="@dimen/key_height"
>
  <Row>
    <Key android:codes="113" android:keyLabel="q" android:keyEdgeFlags="left" />
    <Key android:codes="119" android:keyLabel="w" />
    <Key android:codes="101" android:keyLabel="e" />
    <Key android:codes="114" android:keyLabel="r" />
    <Key android:codes="116" android:keyLabel="t" />
     .  . .
    <Key android:codes="112" android:keyLabel="p" android:keyEdgeFlags="right" />
  </Row>

  <Row>
    . . .
  </Row>

  . . .
 
  <Row android:rowEdgeFlags="bottom">
    <Key android:codes="-3"
       android:keyIcon="@drawable/sym_keyboard_done"
       android:keyWidth="20%p" android:keyEdgeFlags="left" />
    <Key android:codes="-2"
       android:keyLabel="123" android:keyWidth="15%p" />
    <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
       android:keyWidth="30%p" android:isRepeatable="true" />
    <Key android:codes="46,44" android:keyLabel=". ,"
       android:keyWidth="15%p" />
    <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return"
       android:keyWidth="20%p" android:keyEdgeFlags="right" />
  </Row>
</Keyboard>

键盘的定义以行<row>为单位,每个<row>标签定义了键盘一行中包括的按键及其属性(按键的名称、大小、对应的编码、对应的图标等)。
SoftKeyboard项目中定义的LatinKeyboard类继承字Keyboard,LatinKeyboard的对象mSymbolsKeyboard,mSymbolsShiftedKeyboard和mQwertyKeyboard分表表示3种不同的软键盘布局属性。
mSymbolsKeyboard (非英文字母)特殊字符的软键盘布局
mSymbolsShiftedKeyboard 按下shift键之后的特殊字符布局
mQwertyKeyboard 标准的英文字母软键盘布局

接下来我们看看一个InputMethodService对象典型的生命周期,
对象创建=》onCreate()=>onCreateInputView()=>onCreateCandidatesView()=>onStartInputView()=>开始输入文本=》onFinishInput()=>onDestroy()=》对象结束

如果在onFinishInput()输入结束后,转移到新的文本域进行输入,那么将转到onStartInputView()。

onCreateInputVIew()只在第一次弹出软键盘时调用,返回表示软键盘布局的View。onCreateCandidatesView()也只调用一次,即第一次显示候选输入法时。软键盘显示之后,当用户开始输入时,onStartInputView()被调用。当用户完成对当前输入文字域的输入,进入到下一个文本编辑域时,onFinishInput()被调用。当输入法不再使用时,系统回调onDestroy()。后面内容中,我们会依次讲述onCreateInputView()、onCreateCandidatesView()、onStartInputView()等方法。

 

3)输入法
输入法一般有2个主要的可视元素:输入View和输入候选View。
输入View是用户可以和输入法进行交互的区域。对于SoftKeyboard来说,他就是软键盘区域。当输入法首次被激活后,InputMethodService.onCreateInputView()会被调用,他将创建并返回软键盘布局的View。

@Override public View onCreateInputView() {
 mInputView=(KeyboardView)getLayoutInflater().inflate(R.layout.input,null);
 mInputView.setOnKeyboardActionListener(this);
 mInputView.setKeyboard(mQwertyKeyboard);
 return mInputView;
}

当然,输入View是可选的,也就是说,你可以不显示该区域,方法就是使InputMethodService.onCreateInputView()返回为null。

输入候选View是为用户提供输入候选的区域,这部分区域也是可以省略的。跟输入View类似,你只需要在InputMethodService.onCreateCandidatesView()中返回null即可。下面实例中设置了输入候选View

@Override public View onCreateCandidatesView() {
 mCandidateView=new CandidateView(this);
 mCandidateView.setService(this);
 return mCandidateView;
}


4)根据目标输入类型调整输入法布局
由于应用程序文本域希望接受的输入类型可能不同(拨号应用希望接受的输入内容只是数字,而短信内容编辑域则可以接受任意类型的输入),输入法的布局会有所差别。Android SDK 1.5中自带的输入法LatinIME就为不同的应用程序提供了不同的输入法布局。
实现一个新的输入法时,我们也需要考虑为不同的目标输入类型准备不同的输入法布局,如何实现呢?回调方法InputMethodService.onStartInputView()传入的EditorInfo对象可以帮助我们完成对目标输入类型的判断,EditorInfo中包含有目标输入(通常是EditText)的文本属性。将EditorInfo.inputtype和EditorInfo.TYPE_CLASS_MASK做"与"操作,我们可以得到目标输入类型,常见的输入内容类型有TYPE_CLASS_NUMBER、TYPE_CLASS_DATETIME、TYPE_CLASS_PHONE、TYPE_CLASS_TEXT等。SoftKeyboard项目中根据输入域内容属性选择不同输入法布局的代码大体如下:

@Override public void onStartInput(EditorInfo attribute,boolean restarting) {
  super.onStartInput(attribute,restarting);
  ... ...
  //根据当前输入域的属性对MCurKeyboard进行初始化
  switch (attribute.inputType&EditorInfo.TYPE_MASK_CLASS) {
    case EditorInfo.TYPE_CLASS_NUMBER:
    case EditorInfo.TYPE_CLASS_DATETIME:
      //输入域为数字和日期时,使用默认的符号键盘
      mCurKeyboard=mSymbolsKeyboard;
      break;
    case EditorInfo.TYPE_CLASS_PHONE:
      //输入域为电话号码时,也使用默认的符号键盘
      //但通我们会使用专用的电话号码键盘
      mCurKeyboard=mSymbolKeyboard;
      break;
    case EditorInfo.TYPE_CLASS_TEXT:
      //输入域为普通文字时,默认使用英文字母键盘并设定开发输入联想
      mCurKeyboard=mQwertyKeyboard;
      mPredicationOn=true;
      ... ...
      break;
    default:
      //对其他所有未知的输入域类型,默认使用英文字母键盘
      mCurKeyboard=mQwertyKeyboard;
      updateShiftKeyState(attribute);
  }
  ... ...
}

从这段代码可以看到,根据目标输入类型,输入法选择了不通的输入法布局:当目标输入类型为数字、日期、电话号码时,输入法使用非英文字母的符号布局了当目标输入类型为普通文本时,输入法使用了英文字母布局。


5)获取输入内容:
有两种方法能够将输入的文本传给应用程序:要么以按键事件的形式,将每次按键输入单独传给应用;要么先对文本输入进行编辑,然后再将文本整体传递给应用。无论使用哪种方法,都需要使用android.view.inputmethod.InputConnection,他是输入法与应用程序交互的接口,他不仅能将单次的键盘点击事件发送给应用,而且还能读取输入法光标周围的字符串,将字符串提交给应用程序。可以通过方法InputMethodService()获得InputConnection对象。
为了发送一个单独的按键事件,需要构造KeyEvent对象,并调用InputConnection.SendKeyEvent()发送KeyEvent.

InputConnection ic=getCUrrentInputConnection();
long eventTime=SystemClock.uptimeMillis();
ic.sendKeyEvent(new KeyEvent(eventTime,eventTime,
     KeyEvent.ACTION_DOWN,keyEventCode,0,0,0,0,
     KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE));
ic.sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(),eventTime,
     KeyEvent.ACTION_UP,keyEventCode,0,0,0,0,
     KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE));

或者可以用简单的
InputMethodService.sendDownUpKeyEvents(keyEventCode);

Android推荐在诸如电话号码输入的场景中使用发送单个按键事件的方式,因为应用程序可以及时地根据输入内容进行逐一验证。和前面根据输入域期待的内容类型准备的输入法布局一样,我们可以通过EditorInfo对象(可以用getCurrentInputEditorInfo方法得到)获取输入域期待的数据类型,如果EditorInfo.inputType的值为TYPE_NULL,这表示输入域只支持单个按键事件。
若要将编辑后的文本发送给应用,需要用到android.view.inputmethod.InputConnection中的方法。
a)getTextBeforeCursor()  获取光标之前的文本
b)getTextAfterCursor()  获取光标之后的文本
c) deleteSurroundingText() 删除光标周围的文本
d)commitText()  将文本传递给应用

例如,目前输入法的光标左侧已经有Fell 4个字母,你打算用Hello!替换它,可以这么做:
InputConnection ic=getCurrenInputConnection();
ic.deleteSurroundingText(4,0);
ic.commitText("Hello",1);
ic.commitText("!",1);


6)截获硬键盘按键事件
当输入法的软键盘弹出后,你再试图通过硬键盘进行输入,会有怎么样的效果?输入法会接收到硬键盘的键盘事件,然后选择是否利用收到的硬键盘事件,或者将该硬键盘事件进一步转发给应用程序。例如,在你的输入过程中有多个候选输入时,可以使用硬键盘的方向键进行选择,这是输入法会利用硬键盘的输入事件,而没有直接把方向键转发给应用程序。
如果你的输入法要利用硬键盘事件,那么需要重载方法InputMethodService.onKeyDown()和InputMethodService.onKeyUp()。否则,那么记得一定要调用基类方法super.onKeyDown()和super.onKeyUp()。


7)输入候选View
无论是中文输入法还是英文输入法,都可以提供输入联想功能:当用户正在输入时,输入法根据已有的输入,将各种可能的联想输入结果显示在候选区域,方便用户选择。产生的联想结果通过输入候选View表示,回调方法onCreateCandidatesView()用来返回输入候选View,实例中的onCreateCandidatesView()为:

@Override public onCreateCandidatesView() {
 mCandidateView=new CandidateView(this);
 mCandidateView.setService(this);
 return mCandidateView;
}

CandidateView类为自定义View。输入候选View只需要尚存在可能的候选时才显示。随着用户输入的进展,输入候选View可能会消失,因此我们需要利用setCandidatesViewShown(boolean)方法控制输入候选View的显示状态(当boolean值为false时,不显示输入候选View)。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值