有个需求需要在全键盘时可以用代码设置时优先显示字母还是数字,其实inputtype:datetime时就是优先数字,就是这样设置输入的标点符号会被过滤,这就需要新增一个inputType了。
首先看TextView的源码
一:找到inputType 的来源
1.来自xml
case com.android.internal.R.styleable.TextView_inputType:
inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
break;
2.肯定是调用setInputType,其实1的最后还是调用了setInputType
再看看inputype在attr中的定义
<!-- The type of data being placed in a text field, used to help an
input method decide how to let the user enter text. The constants
here correspond to those defined by
{@link android.text.InputType}. Generally you can select
a single value, though some can be combined together as
indicated. Setting this attribute to anything besides
<var>none</var> also implies that the text is editable. -->
<attr name="inputType">
<!-- There is no content type. The text is not editable. -->
<flag name="none" value="0x00000000" />
<!-- Just plain old text. Corresponds to
{@link android.text.InputType#TYPE_CLASS_TEXT} |
{@link android.text.InputType#TYPE_TEXT_VARIATION_NORMAL}. -->
<flag name="text" value="0x00000001" />
<!-- Can be combined with <var>text</var> and its variations to
request capitalization of all characters. Corresponds to
{@link android.text.InputType#TYPE_TEXT_FLAG_CAP_CHARACTERS}. -->
<flag name="textCapCharacters" value="0x00001001" />
<!-- Can be combined with <var>text</var> and its variations to
request capitalization of the first character of every word. Corresponds to
{@link android.text.InputType#TYPE_TEXT_FLAG_CAP_WORDS}. -->
<flag name="textCapWords" value="0x00002001" />
<!-- Can be combined with <var>text</var> and its variations to
request capitalization of the first character of every sentence. Corresponds to
{@link android.text.InputType#TYPE_TEXT_FLAG_CAP_SENTENCES}. -->
<flag name="textCapSentences" value="0x00004001" />
<!-- Can be combined with <var>text</var> and its variations to
request auto-correction of text being input. Corresponds to
{@link android.text.InputType#TYPE_TEXT_FLAG_AUTO_CORRECT}. -->
<flag name="textAutoCorrect" value="0x00008001" />
<!-- Can be combined with <var>text</var> and its variations to
specify that this field will be doing its own auto-completion and
talking with the input method appropriately. Corresponds to
{@link android.text.InputType#TYPE_TEXT_FLAG_AUTO_COMPLETE}. -->
<flag name="textAutoComplete" value="0x00010001" />
<!-- Can be combined with <var>text</var> and its variations to
allow multiple lines of text in the field. If this flag is not set,
the text field will be constrained to a single line. Corresponds to
{@link android.text.InputType#TYPE_TEXT_FLAG_MULTI_LINE}. -->
<flag name="textMultiLine" value="0x00020001" />
<!-- Can be combined with <var>text</var> and its variations to
indicate that though the regular text view should not be multiple
lines, the IME should provide multiple lines if it can. Corresponds to
{@link android.text.InputType#TYPE_TEXT_FLAG_IME_MULTI_LINE}. -->
<flag name="textImeMultiLine" value="0x00040001" />
<!-- Can be combined with <var>text</var> and its variations to
indicate that the IME should not show any
dictionary-based word suggestions. Corresponds to
{@link android.text.InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS}. -->
<flag name="textNoSuggestions" value="0x00080001" />
<!-- Text that will be used as a URI. Corresponds to
{@link android.text.InputType#TYPE_CLASS_TEXT} |
{@link android.text.InputType#TYPE_TEXT_VARIATION_URI}. -->
<flag name="textUri" value="0x00000011" />
<!-- Text that will be used as an e-mail address. Corresponds to
{@link android.text.InputType#TYPE_CLASS_TEXT} |
{@link android.text.InputType#TYPE_TEXT_VARIATION_EMAIL_ADDRESS}. -->
<flag name="textEmailAddress" value="0x00000021" />
<!-- Text that is being supplied as the subject of an e-mail. Corresponds to
{@link android.text.InputType#TYPE_CLASS_TEXT} |
{@link android.text.InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT}. -->
<flag name="textEmailSubject" value="0x00000031" />
<!-- Text that is the content of a short message. Corresponds to
{@link android.text.InputType#TYPE_CLASS_TEXT} |
{@link android.text.InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE}. -->
<flag name="textShortMessage" value="0x00000041" />
<!-- Text that is the content of a long message. Corresponds to
{@link android.text.InputType#TYPE_CLASS_TEXT} |
{@link android.text.InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE}. -->
<flag name="textLongMessage" value="0x00000051" />
<!-- Text that is the name of a person. Corresponds to
{@link android.text.InputType#TYPE_CLASS_TEXT} |
{@link android.text.InputType#TYPE_TEXT_VARIATION_PERSON_NAME}. -->
<flag name="textPersonName" value="0x00000061" />
<!-- Text that is being supplied as a postal mailing address. Corresponds to
{@link android.text.InputType#TYPE_CLASS_TEXT} |
{@link android.text.InputType#TYPE_TEXT_VARIATION_POSTAL_ADDRESS}. -->
<flag name="textPostalAddress" value="0x00000071" />
<!-- Text that is a password. Corresponds to
{@link android.text.InputType#TYPE_CLASS_TEXT} |
{@link android.text.InputType#TYPE_TEXT_VARIATION_PASSWORD}. -->
<flag name="textPassword" value="0x00000081" />
<!-- Text that is a password that should be visible. Corresponds to
{@link android.text.InputType#TYPE_CLASS_TEXT} |
{@link android.text.InputType#TYPE_TEXT_VARIATION_VISIBLE_PASSWORD}. -->
<flag name="textVisiblePassword" value="0x00000091" />
<!-- Text that is being supplied as text in a web form. Corresponds to
{@link android.text.InputType#TYPE_CLASS_TEXT} |
{@link android.text.InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}. -->
<flag name="textWebEditText" value="0x000000a1" />
<!-- Text that is filtering some other data. Corresponds to
{@link android.text.InputType#TYPE_CLASS_TEXT} |
{@link android.text.InputType#TYPE_TEXT_VARIATION_FILTER}. -->
<flag name="textFilter" value="0x000000b1" />
<!-- Text that is for phonetic pronunciation, such as a phonetic name
field in a contact entry. Corresponds to
{@link android.text.InputType#TYPE_CLASS_TEXT} |
{@link android.text.InputType#TYPE_TEXT_VARIATION_PHONETIC}. -->
<flag name="textPhonetic" value="0x000000c1" />
<!-- Text that will be used as an e-mail address on a web form. Corresponds to
{@link android.text.InputType#TYPE_CLASS_TEXT} |
{@link android.text.InputType#TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS}. -->
<flag name="textWebEmailAddress" value="0x000000d1" />
<!-- Text that will be used as a password on a web form. Corresponds to
{@link android.text.InputType#TYPE_CLASS_TEXT} |
{@link android.text.InputType#TYPE_TEXT_VARIATION_WEB_PASSWORD}. -->
<flag name="textWebPassword" value="0x000000e1" />
<!-- A numeric only field. Corresponds to
{@link android.text.InputType#TYPE_CLASS_NUMBER} |
{@link android.text.InputType#TYPE_NUMBER_VARIATION_NORMAL}. -->
<flag name="number" value="0x00000002" />
<!-- Can be combined with <var>number</var> and its other options to
allow a signed number. Corresponds to
{@link android.text.InputType#TYPE_CLASS_NUMBER} |
{@link android.text.InputType#TYPE_NUMBER_FLAG_SIGNED}. -->
<flag name="numberSigned" value="0x00001002" />
<!-- Can be combined with <var>number</var> and its other options to
allow a decimal (fractional) number. Corresponds to
{@link android.text.InputType#TYPE_CLASS_NUMBER} |
{@link android.text.InputType#TYPE_NUMBER_FLAG_DECIMAL}. -->
<flag name="numberDecimal" value="0x00002002" />
<!-- A numeric password field. Corresponds to
{@link android.text.InputType#TYPE_CLASS_NUMBER} |
{@link android.text.InputType#TYPE_NUMBER_VARIATION_PASSWORD}. -->
<flag name="numberPassword" value="0x00000012" />
<!-- For entering a phone number. Corresponds to
{@link android.text.InputType#TYPE_CLASS_PHONE}. -->
<flag name="phone" value="0x00000003" />
<!-- For entering a date and time. Corresponds to
{@link android.text.InputType#TYPE_CLASS_DATETIME} |
{@link android.text.InputType#TYPE_DATETIME_VARIATION_NORMAL}. -->
<flag name="datetime" value="0x00000004" />
<!-- For entering a date. Corresponds to
{@link android.text.InputType#TYPE_CLASS_DATETIME} |
{@link android.text.InputType#TYPE_DATETIME_VARIATION_DATE}. -->
<flag name="date" value="0x00000014" />
<!-- For entering a time. Corresponds to
{@link android.text.InputType#TYPE_CLASS_DATETIME} |
{@link android.text.InputType#TYPE_DATETIME_VARIATION_TIME}. -->
<flag name="time" value="0x00000024" />
</attr>
注意注释
原来所有的inputype是由标志位自由合成的,分别是类型和以及变量(通俗说就是变种),以及flag(分不清和变种有什么区别)
再看TextView源码
final int variation =
inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
final boolean passwordInputType = variation
== (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
final boolean webPasswordInputType = variation
== (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
final boolean numberPasswordInputType = variation
== (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
这些标志位的作用就是为了辨别是那个类,那个变量以及那个flag用来控制软键盘的显示模式和editext,那我的需求是全键盘,可以优先数字或者字母,那么新增的标志位类是
<flag name="text" value="0x00000001" />
如何新增flag可以模仿嘛
在<flag name="textNoSuggestions" value="0x00080001" />后面新增一个
<flag name="XXXXXXXXXXXXXXXXXX" value="0x00100001" />即可完成flag
贴出标志位的范围
public static final int TYPE_MASK_CLASS = 0x0000000f;
public static final int TYPE_MASK_VARIATION = 0x00000ff0;
public static final int TYPE_MASK_FLAGS = 0x00fff000;
二:看setInputType是如何设置inptuType的
private void setInputType(int type, boolean direct) {
final int cls = type & EditorInfo.TYPE_MASK_CLASS;
KeyListener input;
if (cls == EditorInfo.TYPE_CLASS_TEXT) {
boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
TextKeyListener.Capitalize cap;
if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
cap = TextKeyListener.Capitalize.CHARACTERS;
} else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
cap = TextKeyListener.Capitalize.WORDS;
} else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
cap = TextKeyListener.Capitalize.SENTENCES;
} else {
cap = TextKeyListener.Capitalize.NONE;
}
input = TextKeyListener.getInstance(autotext, cap);
} else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
input = DigitsKeyListener.getInstance(
(type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
(type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
} else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
switch (type & EditorInfo.TYPE_MASK_VARIATION) {
case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
input = DateKeyListener.getInstance();
break;
case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
input = TimeKeyListener.getInstance();
break;
default:
input = DateTimeKeyListener.getInstance();
break;
}
} else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
input = DialerKeyListener.getInstance();
} else {
input = TextKeyListener.getInstance();
}
setRawInputType(type);
if (direct) {
createEditorIfNeeded();
mEditor.mKeyListener = input;
} else {
setKeyListenerOnly(input);
}
}
我们只看EditorInfo.TYPE_CLASS_TEXT的情况
出现了TextKeyListener,点进去发现只和硬件的键盘有关系,可以pass
再看setRawInputType(type);
public void setRawInputType(int type) {
if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
createEditorIfNeeded();
mEditor.mInputType = type;
}
貌似起作用的就是 mEditor.mInputType = type;我们看下mEditor做什么了。
三.Editor和EditorInfo
好吧,Editor这个类没有实现Parcelable接口,因为我们是要控制到软键盘的必须是跨进程,Editor只起到中间作用,我们再看下EditorInfo,
public class EditorInfo implements InputType, Parcelable
就是我们要的最终能控制软键盘的显示模式的类,通过outAttrs.inputType = getInputType();EditorInfo 获取了inputType 如下
public int getInputType() {
return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType;
}
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
if (onCheckIsTextEditor() && isEnabled()) {
mEditor.createInputMethodStateIfNeeded();
outAttrs.inputType = getInputType();
if (mEditor.mInputContentType != null) {
outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
outAttrs.actionId = mEditor.mInputContentType.imeActionId;
outAttrs.extras = mEditor.mInputContentType.extras;
} else {
outAttrs.imeOptions = EditorInfo.IME_NULL;
}
if (focusSearch(FOCUS_DOWN) != null) {
outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
}
if (focusSearch(FOCUS_UP) != null) {
outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
}
if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)
== EditorInfo.IME_ACTION_UNSPECIFIED) {
if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
// An action has not been set, but the enter key will move to
// the next focus, so set the action to that.
outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
} else {
// An action has not been set, and there is no focus to move
// to, so let's just supply a "done" action.
outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
}
if (!shouldAdvanceFocusOnEnter()) {
outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
}
}
if (isMultilineInputType(outAttrs.inputType)) {
// Multi-line text editors should always show an enter key.
outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
}
outAttrs.hintText = mHint;
if (mText instanceof Editable) {
InputConnection ic = new EditableInputConnection(this);
outAttrs.initialSelStart = getSelectionStart();
outAttrs.initialSelEnd = getSelectionEnd();
outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
return ic;
}
}
return null;
}
onCreateInputConnection这是重写了父类View的方法,返回类型是InputConnection 实现类是EditableInputConnection。我们先看下onCreateInputConnection
四。onCreateInputConnection
好吧,又出现新的类InputMethodManager.java:,该类的startInputInner方法调用了当前edittext的onCreateInputConnection,且传入对象tba:EditorInfo tba = new EditorInfo(),该对象重新被Edittext赋值
boolean startInputInner(IBinder windowGainingFocus, int controlFlags, int softInputMode,
int windowFlags) {
final View view;
synchronized (mH) {
view = mServedView;
// Make sure we have a window token for the served view.
if (DEBUG) Log.v(TAG, "Starting input: view=" + view);
if (view == null) {
if (DEBUG) Log.v(TAG, "ABORT input: no served view!");
return false;
}
}
// Now we need to get an input connection from the served view.
// This is complicated in a couple ways: we can't be holding our lock
// when calling out to the view, and we need to make sure we call into
// the view on the same thread that is driving its view hierarchy.
Handler vh = view.getHandler();
if (vh == null) {
// If the view doesn't have a handler, something has changed out
// from under us, so just close the current input.
// If we don't close the current input, the current input method can remain on the
// screen without a connection.
if (DEBUG) Log.v(TAG, "ABORT input: no handler for view! Close current input.");
closeCurrentInput();
return false;
}
if (vh.getLooper() != Looper.myLooper()) {
// The view is running on a different thread than our own, so
// we need to reschedule our work for over there.
if (DEBUG) Log.v(TAG, "Starting input: reschedule to view thread");
vh.post(new Runnable() {
@Override
public void run() {
startInputInner(null, 0, 0, 0);
}
});
return false;
}
// Okay we are now ready to call into the served view and have it
// do its stuff.
// Life is good: let's hook everything up!
EditorInfo tba = new EditorInfo();
tba.packageName = view.getContext().getPackageName();
tba.fieldId = view.getId();
InputConnection ic = view.onCreateInputConnection(tba);
if (DEBUG) Log.v(TAG, "Starting input: tba=" + tba + " ic=" + ic);
synchronized (mH) {
// Now that we are locked again, validate that our state hasn't
// changed.
if (mServedView != view || !mServedConnecting) {
// Something else happened, so abort.
if (DEBUG) Log.v(TAG,
"Starting input: finished by someone else (view="
+ mServedView + " conn=" + mServedConnecting + ")");
return false;
}
// If we already have a text box, then this view is already
// connected so we want to restart it.
if (mCurrentTextBoxAttribute == null) {
controlFlags |= CONTROL_START_INITIAL;
}
// Hook 'em up and let 'er rip.
mCurrentTextBoxAttribute = tba;
mServedConnecting = false;
// Notify the served view that its previous input connection is finished
notifyInputConnectionFinished();
mServedInputConnection = ic;
ControlledInputConnectionWrapper servedContext;
if (ic != null) {
mCursorSelStart = tba.initialSelStart;
mCursorSelEnd = tba.initialSelEnd;
mCursorCandStart = -1;
mCursorCandEnd = -1;
mCursorRect.setEmpty();
mCursorAnchorInfo = null;
servedContext = new ControlledInputConnectionWrapper(vh.getLooper(), ic, this);
} else {
servedContext = null;
}
if (mServedInputConnectionWrapper != null) {
mServedInputConnectionWrapper.deactivate();
}
mServedInputConnectionWrapper = servedContext;
try {
if (DEBUG) Log.v(TAG, "START INPUT: " + view + " ic="
+ ic + " tba=" + tba + " controlFlags=#"
+ Integer.toHexString(controlFlags));
InputBindResult res;
if (windowGainingFocus != null) {
res = mService.windowGainedFocus(mClient, windowGainingFocus,
controlFlags, softInputMode, windowFlags,
tba, servedContext);
} else {
res = mService.startInput(mClient,
servedContext, tba, controlFlags);
}
if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
if (res != null) {
if (res.id != null) {
setInputChannelLocked(res.channel);
mBindSequence = res.sequence;
mCurMethod = res.method;
mCurId = res.id;
mNextUserActionNotificationSequenceNumber =
res.userActionNotificationSequenceNumber;
} else {
if (res.channel != null && res.channel != mCurChannel) {
res.channel.dispose();
}
if (mCurMethod == null) {
// This means there is no input method available.
if (DEBUG) Log.v(TAG, "ABORT input: no input method!");
return true;
}
}
}
if (mCurMethod != null && mCompletions != null) {
try {
mCurMethod.displayCompletions(mCompletions);
} catch (RemoteException e) {
}
}
} catch (RemoteException e) {
Log.w(TAG, "IME died: " + mCurId, e);
}
}
return true;
}
最后通过调用服务InputMethodManagerService的方法如下,就可以通知到软键盘inputType。
if (windowGainingFocus != null) {
res = mService.windowGainedFocus(mClient, windowGainingFocus,
controlFlags, softInputMode, windowFlags,
tba, servedContext);
} else {
res = mService.startInput(mClient,
servedContext, tba, controlFlags);
}
只要软键盘收到inputType,最后就是修改软键盘的源码实现想要的效果