公司要开发自己的输入法,找了很多例子,都不是自己想要的。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));
}

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


更多相关文章

  1. Kotlin Android(安卓)UI利器之Anko Layouts
  2. Android之服务Service和它的CP们(BroadcastReceiver、Messenger
  3. android 混淆规则详解
  4. Android中进行图像压缩和缩放
  5. Android(安卓)自定义数字键盘(一)
  6. Android相机开发实战
  7. [置顶] android 基础笔试题
  8. Android实现登陆界面动画
  9. Eclipse如何快速调试系统App(具有系统权限的Android(安卓)App)即Ec

随机推荐

  1. 图片切换
  2. Android SetWallpaper
  3. Android 设置边距总结
  4. Android(安卓)实现沉浸式体验
  5. 初识SeekBar
  6. 手机拨号器
  7. Android介绍如何生成keystore 文件并使用
  8. Android WIDGETS 下的Power control修改
  9. Android——按钮类控件
  10. 【Android Demo】让Android支持自定义的t