android 输入法(包括手写界面)

        公司要开发自己的输入法,找了很多例子,都不是自己想要的。android本身的例子不能满足特殊布局的要求,而且没有手写输入,虽然在例子上实现了手写输入但是布局仍然调不好。花了很长时间来分析代码,太累了,决定自己做。现在把小有成果的经验分享一下。

       其实做输入法挺简单的,不用继承和实现andorid本身的keyboard和keyboardiew。自己完全可以自己写一个,而且还比较简单,当然要想写的复杂一些,那涉及的东西就多了。但是最重要的,也是必须要实现的类是inputmethodServer。

       同时要在AndroidManifest里注册好。

       下面我来具体说一下,

首先从简单来说,即在AndroidManifest里的注册了:

               <application >
        <service
            android:name="com.example.test.MainActivity"
            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>

     关键几点:  android:permission="android.permission.BIND_INPUT_METHOD" 加上这个权限才能设置成输入法。

           <meta-data
                android:name="android.view.im"
                android:resource="@xml/method" />

二、method.xml

   在xml里新建method.xml文件,具体内容如下:

    <?xml version="1.0" encoding="utf-8"?>
<!-- The attributes in this XML file provide configuration information -->
<!-- for the Search Manager. -->


<input-method xmlns:android="http://schemas.android.com/apk/res/android"
        android:settingsActivity="com.example.android.softkeyboard.ImePreferences"
>
    <subtype
        android:label="@string/label_subtype_generic"
        android:icon="@drawable/icon_en_us"
        android:imeSubtypeLocale="en_US"
        android:imeSubtypeMode="keyboard" />
    <subtype
        android:label="@string/label_subtype_en_GB"
        android:icon="@drawable/icon_en_gb"
        android:imeSubtypeLocale="en_GB"
        android:imeSubtypeMode="keyboard" />
</input-method>

     这些内容主要是我们点击输入法后面有个设置,点击会打开设置界面,这里的设置界面是ImePreferences.ImePreFerences实际上就是个Preference,相信大家都会写。我就不罗嗦了!

<subtype/>是用来设置不同的语言以及输入法。

三、写个主要的类继承InputMethodServer

      具体实现哪些方法自己定。生命周期可以参考下图(来自Android官网)



重要的几个方法:

onInitializeInterface() // InputMethodService在启动时,系统会调用该方法,初始化方法。

onCreateInputView()//

onCreateCandidatesView()//

  实际上有后两种方法,既可以实现输入法了。具体的说明请参考:http://blog.csdn.net/dahuaishu2010_/article/details/8669311

四、现在需要有键盘布局界面和候选词界面,google给出的例子,是在xml文件夹下定义了很多个xml文件,通过API里的属性进行布局。那样也行,就是有些不灵活。而google拼音输入法自己去没怎么用这些,而是自己定义了些属性,自己解析xml文件,对布局重新定义。你也可以按照google拼音输入法来做。

    这里我提供用layout文件夹下布局,跟通常我们的布局一样的布局来实现键盘界面。

写个普通的xml文件,如main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"

android:id="+id/container"

    tools:context=".MainActivity" >


</RelativeLayout>

   这个布局用来放各种各样的输入法:英语、中文、数字、网址、符号、手写等等。定义好后,这个布局初始化就放在onCreateInputView()里,每次进入都要换不同的输入法。

同时我建议还是自定义个KeyboardView继承RelativeLayout。这样更灵活一些。

例如我定一个类:

public class KeyboardView extends RelativeLayout {
private View currentView;


public KeyboardView(Context context) {
super(context);
/**
* 设置view的位置,增加默认childview
**/
RelativeLayout.LayoutParams lpWhile = new RelativeLayout.LayoutParams(
100, 50);
lpWhile.addRule(RelativeLayout.BELOW, 1);
lpWhile.addRule(RelativeLayout.ALIGN_LEFT, 1);
LayoutInflater inflater = LayoutInflater.from(context);
View view = inflater.inflate(R.layout.activity_main, null);
addView(view, lpWhile);
}


public View getCurrentView() {
return currentView;
}


public void setCurrentView(View currentView) {
this.removeAllViews();
this.addView(currentView);
this.currentView = currentView;
}

}

这样我们换输入法的时候只需要用到setCurrentView()方法,可以定义各种不同的界面,来放到这个容器里。

下面是CandidateView的实现,我这里直接用的是例子给的,也可以自己定义一个。

public class CandidateView extends View {


private static final int OUT_OF_BOUNDS = -1;


private MainActivity mService;
private List<String> mSuggestions;
private int mSelectedIndex;
private int mTouchX = OUT_OF_BOUNDS;
private Drawable mSelectionHighlight;
private boolean mTypedWordValid;


private Rect mBgPadding;


private static final int MAX_SUGGESTIONS = 32;
private static final int SCROLL_PIXELS = 20;


private int[] mWordWidth = new int[MAX_SUGGESTIONS];
private int[] mWordX = new int[MAX_SUGGESTIONS];


private static final int X_GAP = 10;


private static final List<String> EMPTY_LIST = new ArrayList<String>();


private int mColorNormal;
private int mColorRecommended;
private int mColorOther;
private int mVerticalPadding;
private Paint mPaint;
private boolean mScrolled;
private int mTargetScrollX;


private int mTotalWidth;


private GestureDetector mGestureDetector;


/**
* Construct a CandidateView for showing suggested words for completion.

* @param context
* @param attrs
*/
public CandidateView(Context context) {
super(context);
mSelectionHighlight = context.getResources().getDrawable(
android.R.drawable.list_selector_background);
mSelectionHighlight.setState(new int[] { android.R.attr.state_enabled,
android.R.attr.state_focused,
android.R.attr.state_window_focused,
android.R.attr.state_pressed });


Resources r = context.getResources();


setBackgroundColor(r.getColor(R.color.candidate_background));


mColorNormal = r.getColor(R.color.candidate_normal);
mColorRecommended = r.getColor(R.color.candidate_recommended);
mColorOther = r.getColor(R.color.candidate_other);
mVerticalPadding = r
.getDimensionPixelSize(R.dimen.candidate_vertical_padding);


mPaint = new Paint();
mPaint.setColor(mColorNormal);
mPaint.setAntiAlias(true);
mPaint.setTextSize(r
.getDimensionPixelSize(R.dimen.candidate_font_height));
mPaint.setStrokeWidth(0);


mGestureDetector = new GestureDetector(
new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
mScrolled = true;
int sx = getScrollX();
sx += distanceX;
if (sx < 0) {
sx = 0;
}
if (sx + getWidth() > mTotalWidth) {
sx -= distanceX;
}
mTargetScrollX = sx;
scrollTo(sx, getScrollY());
invalidate();
return true;
}
});
setHorizontalFadingEdgeEnabled(true);
setWillNotDraw(false);
setHorizontalScrollBarEnabled(false);
setVerticalScrollBarEnabled(false);
}


/**
* A connection back to the service to communicate with the text field

* @param listener
*/
public void setService(MainActivity listener) {
mService = listener;
}


@Override
public int computeHorizontalScrollRange() {
return mTotalWidth;
}


@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int measuredWidth = resolveSize(50, widthMeasureSpec);


// Get the desired height of the icon menu view (last row of items does
// not have a divider below)
Rect padding = new Rect();
mSelectionHighlight.getPadding(padding);
final int desiredHeight = ((int) mPaint.getTextSize())
+ mVerticalPadding + padding.top + padding.bottom;


// Maximum possible width and desired height
setMeasuredDimension(measuredWidth,
resolveSize(desiredHeight, heightMeasureSpec));
}


/**
* If the canvas is null, then only touch calculations are performed to pick
* the target candidate.
*/
@Override
protected void onDraw(Canvas canvas) {
if (canvas != null) {
super.onDraw(canvas);
}
mTotalWidth = 0;
if (mSuggestions == null)
return;


if (mBgPadding == null) {
mBgPadding = new Rect(0, 0, 0, 0);
if (getBackground() != null) {
getBackground().getPadding(mBgPadding);
}
}
int x = 10;
final int count = mSuggestions.size();
final int height = getHeight();
final Rect bgPadding = mBgPadding;
final Paint paint = mPaint;
final int touchX = mTouchX;
final int scrollX = getScrollX();
final boolean scrolled = mScrolled;
final boolean typedWordValid = mTypedWordValid;
final int y = (int) (((height - mPaint.getTextSize()) / 2) - mPaint
.ascent());


for (int i = 0; i < count; i++) {
String suggestion = mSuggestions.get(i);
float textWidth = paint.measureText(suggestion);
final int wordWidth = (int) textWidth + X_GAP * 6;


mWordX[i] = x;
mWordWidth[i] = wordWidth;
paint.setColor(mColorNormal);
if (touchX + scrollX >= x && touchX + scrollX < x + wordWidth
&& !scrolled) {
if (canvas != null) {
canvas.translate(x, 0);
mSelectionHighlight.setBounds(0, bgPadding.top, wordWidth,
height);
mSelectionHighlight.draw(canvas);
canvas.translate(-x, 0);
}
mSelectedIndex = i;
}


if (canvas != null) {
if ((i == 1 && !typedWordValid) || (i == 0 && typedWordValid)) {
paint.setFakeBoldText(true);
paint.setColor(mColorRecommended);
} else if (i != 0) {
paint.setColor(mColorOther);
}


canvas.drawText(suggestion, x + X_GAP + 18, y, paint);
paint.setColor(mColorOther);
paint.setStrokeWidth(5);
canvas.drawLine(x + wordWidth + 0.5f, bgPadding.top, x
+ wordWidth + 0.5f, height + 1, paint);
paint.setFakeBoldText(false);
}
x += wordWidth;
}
mTotalWidth = x;
if (mTargetScrollX != getScrollX()) {
scrollToTarget();
}
}


private void scrollToTarget() {
int sx = getScrollX();
if (mTargetScrollX > sx) {
sx += SCROLL_PIXELS;
if (sx >= mTargetScrollX) {
sx = mTargetScrollX;
requestLayout();
}
} else {
sx -= SCROLL_PIXELS;
if (sx <= mTargetScrollX) {
sx = mTargetScrollX;
requestLayout();
}
}
scrollTo(sx, getScrollY());
invalidate();
}


public void setSuggestions(List<String> suggestions, 
boolean typedWordValid) {
clear();
if (suggestions != null) {
mSuggestions = new ArrayList<String>(suggestions);
}
mTypedWordValid = typedWordValid;
scrollTo(0, 0);
mTargetScrollX = 0;
// Compute the total width
onDraw(null);
invalidate();
requestLayout();
}


public void clear() {
mSuggestions = EMPTY_LIST;
mTouchX = OUT_OF_BOUNDS;
mSelectedIndex = -1;
invalidate();
}


@Override
public boolean onTouchEvent(MotionEvent me) {


if (mGestureDetector.onTouchEvent(me)) {
return true;
}


int action = me.getAction();
int x = (int) me.getX();
int y = (int) me.getY();
mTouchX = x;


switch (action) {
case MotionEvent.ACTION_DOWN:
mScrolled = false;
invalidate();
break;
case MotionEvent.ACTION_MOVE:
if (y <= 0) {
// Fling up!?
if (mSelectedIndex >= 0) {
mService.pickSuggestionManually(mSelectedIndex);
mSelectedIndex = -1;
}
}
invalidate();
break;
case MotionEvent.ACTION_UP:
if (!mScrolled) {
if (mSelectedIndex >= 0) {
mService.pickSuggestionManually(mSelectedIndex);
}
}
mSelectedIndex = -1;
removeHighlight();
requestLayout();
break;
}
return true;
}


/**
* For flick through from keyboard, call this method with the x coordinate
* of the flick gesture.

* @param x
*/
public void takeSuggestionAt(float x) {
mTouchX = (int) x;
// To detect candidate
onDraw(null);
if (mSelectedIndex >= 0) {
mService.pickSuggestionManually(mSelectedIndex);
}
invalidate();
}


private void removeHighlight() {
mTouchX = OUT_OF_BOUNDS;
invalidate();
}
}


这些里面我改了部分代码,适应我自己的需要,没有大改。


五、这些都准备好了,现在就差怎样交互了,在之前的文章中已经提到怎样进行交互。现在具体来说一下,其实非常非常简单。就只有一个方法:getCurrentInputConnection().commitText();

这个方法直接可以把你输入的东西放到输入框中。

   至于点击如何选字的那再简单不过了,跟以前一样在你的布局每个按钮上实现onClickListener就行了,点击是把内容放到getCurrentInputConnection().commitText()方法里就行了。但是要注意特殊的

按钮,如搜索、空格、退格还有enter等按钮,同时要注意不同类型。

      关于把自己的输入框定义了格式的,可以在下面的方法中实现。

@Override
public void onStartInput(EditorInfo attribute, boolean restarting) {
// TODO Auto-generated method stub
switch (attribute.inputType & InputType.TYPE_MASK_CLASS) {
case InputType.TYPE_CLASS_NUMBER:
    //这里换成数字键布局
break;
case InputType.TYPE_CLASS_DATETIME:

//日期
break;
case InputType.TYPE_CLASS_PHONE:
//电话

break;
case InputType.TYPE_CLASS_TEXT:
//一般文本

     里面包括password,email等特殊的,需要自己判断

break;


default:
break;
}
super.onStartInput(attribute, restarting);
}

关于Enter键等的处理:

可以通过下面方法处理

keyEventCode//是KeyEventCode的事件,如:KeyEvent.KEYCODE_ENTER

private void keyDownUp(int keyEventCode) {
getCurrentInputConnection().sendKeyEvent(
new KeyEvent(KeyEvent.ACTION_DOWN, keyEventCode));
getCurrentInputConnection().sendKeyEvent(
new KeyEvent(KeyEvent.ACTION_UP, keyEventCode));
}

至此,基本上都完了,这只是很简单的一些。方便大家理解。自己的一点小小总结,还有很多问题,希望大家提出来,同时欢迎拍砖。


  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值